Revision be116d22
b/.gitignore | ||
---|---|---|
1 |
.DS_Store |
b/asi-http-request-with-pithos/.gitignore | ||
---|---|---|
1 |
build |
|
2 |
*xcodeproj/*mode* |
|
3 |
*xcodeproj/*pbxuser |
|
4 |
*xcodeproj/*per* |
|
5 |
*xcodeproj/project.xcworkspace |
|
6 |
*xcodeproj/xcuserdata |
|
7 |
*tmproj |
|
8 |
.DS_Store |
|
9 |
profile |
|
10 |
*.pbxuser |
|
11 |
*.mode1v3 |
|
12 |
External/GHUnit/* |
b/asi-http-request-with-pithos/Build Scripts/fetch_ios_ghunit.rb | ||
---|---|---|
1 |
#!/usr/bin/env ruby |
|
2 |
|
|
3 |
# This script fetches a pre-compiled copy of the iOS GHUnit.framework, if one isn't already in the External/GHUnit folder |
|
4 |
# This replaces the old system, where GHUnit was included as a git submodule, because: |
|
5 |
# a) git submodules confuse people (including me) |
|
6 |
# b) GHUnit seems to be tricky to build without warnings |
|
7 |
# The pre-compiled frameworks on allseeing-i.com were taken directly from those on the GHUnit downloads page on GitHub |
|
8 |
# If you'd rather build GHUnit yourself, simply grab a copy from http://github.com/gabriel/gh-unit and drop your built framework into External/GHUnit |
|
9 |
|
|
10 |
require 'net/http' |
|
11 |
if (!File.exists?('External/GHUnit/GHUnitIOS.framework')) |
|
12 |
`curl -s http://allseeing-i.com/ASIHTTPRequest/GHUnit/GHUnit-IOS.zip > External/GHUnit/GHUnit-IOS.zip` |
|
13 |
`unzip External/GHUnit/GHUnit-IOS.zip -d External/GHUnit/ & rm External/GHUnit/GHUnit-IOS.zip` |
|
14 |
end |
b/asi-http-request-with-pithos/Build Scripts/fetch_mac_ghunit.rb | ||
---|---|---|
1 |
#!/usr/bin/env ruby |
|
2 |
|
|
3 |
# This script fetches a pre-compiled copy of the Mac GHUnit.framework, if one isn't already in the External/GHUnit folder |
|
4 |
# This replaces the old system, where GHUnit was included as a git submodule, because: |
|
5 |
# a) git submodules confuse people (including me) |
|
6 |
# b) GHUnit seems to be tricky to build without warnings |
|
7 |
# The pre-compiled frameworks on allseeing-i.com were taken directly from those on the GHUnit downloads page on GitHub |
|
8 |
# If you'd rather build GHUnit yourself, simply grab a copy from http://github.com/gabriel/gh-unit and drop your built framework into External/GHUnit |
|
9 |
|
|
10 |
require 'net/http' |
|
11 |
if (!File.exists?('External/GHUnit/GHUnit.framework')) |
|
12 |
`curl -s http://allseeing-i.com/ASIHTTPRequest/GHUnit/GHUnit-Mac.zip > External/GHUnit/GHUnit-Mac.zip` |
|
13 |
`unzip External/GHUnit/GHUnit-Mac.zip -d External/GHUnit/ & rm External/GHUnit/GHUnit-Mac.zip` |
|
14 |
end |
b/asi-http-request-with-pithos/Build Scripts/set_version_number.rb | ||
---|---|---|
1 |
#!/usr/bin/env ruby |
|
2 |
|
|
3 |
# This script sets a version number for ASIHTTPRequest based on the last commit, and is run when you build one of the targets in the Xcode projects that come with ASIHTTPRequest |
|
4 |
# It only really needs to run on my computer, not on yours... :) |
|
5 |
require 'find' |
|
6 |
if (File.exists?('.git') && File.directory?('.git') && File.exists?('/usr/local/bin/git')) |
|
7 |
newversion = `/usr/local/bin/git describe --tags`.match(/(v([0-9]+)(\.([0-9]+)){1,}-([0-9]+))/).to_s.gsub(/[0-9]+$/){|commit| (commit.to_i + 1).to_s}+Time.now.strftime(" %Y-%m-%d") |
|
8 |
buffer = File.new('Classes/ASIHTTPRequest.m','r').read |
|
9 |
if !buffer.match(/#{Regexp.quote(newversion)}/) |
|
10 |
buffer = buffer.sub(/(NSString \*ASIHTTPRequestVersion = @\")(.*)(";)/,'\1'+newversion+'\3'); |
|
11 |
File.open('Classes/ASIHTTPRequest.m','w') {|fw| fw.write(buffer)} |
|
12 |
end |
|
13 |
end |
b/asi-http-request-with-pithos/Classes/ASIAuthenticationDialog.h | ||
---|---|---|
1 |
// |
|
2 |
// ASIAuthenticationDialog.h |
|
3 |
// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest |
|
4 |
// |
|
5 |
// Created by Ben Copsey on 21/08/2009. |
|
6 |
// Copyright 2009 All-Seeing Interactive. All rights reserved. |
|
7 |
// |
|
8 |
|
|
9 |
#import <Foundation/Foundation.h> |
|
10 |
#import <UIKit/UIKit.h> |
|
11 |
@class ASIHTTPRequest; |
|
12 |
|
|
13 |
typedef enum _ASIAuthenticationType { |
|
14 |
ASIStandardAuthenticationType = 0, |
|
15 |
ASIProxyAuthenticationType = 1 |
|
16 |
} ASIAuthenticationType; |
|
17 |
|
|
18 |
@interface ASIAutorotatingViewController : UIViewController |
|
19 |
@end |
|
20 |
|
|
21 |
@interface ASIAuthenticationDialog : ASIAutorotatingViewController <UIActionSheetDelegate, UITableViewDelegate, UITableViewDataSource> { |
|
22 |
ASIHTTPRequest *request; |
|
23 |
ASIAuthenticationType type; |
|
24 |
UITableView *tableView; |
|
25 |
UIViewController *presentingController; |
|
26 |
BOOL didEnableRotationNotifications; |
|
27 |
} |
|
28 |
+ (void)presentAuthenticationDialogForRequest:(ASIHTTPRequest *)request; |
|
29 |
+ (void)dismiss; |
|
30 |
|
|
31 |
@property (retain) ASIHTTPRequest *request; |
|
32 |
@property (assign) ASIAuthenticationType type; |
|
33 |
@property (assign) BOOL didEnableRotationNotifications; |
|
34 |
@property (retain, nonatomic) UIViewController *presentingController; |
|
35 |
@end |
b/asi-http-request-with-pithos/Classes/ASIAuthenticationDialog.m | ||
---|---|---|
1 |
// |
|
2 |
// ASIAuthenticationDialog.m |
|
3 |
// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest |
|
4 |
// |
|
5 |
// Created by Ben Copsey on 21/08/2009. |
|
6 |
// Copyright 2009 All-Seeing Interactive. All rights reserved. |
|
7 |
// |
|
8 |
|
|
9 |
#import "ASIAuthenticationDialog.h" |
|
10 |
#import "ASIHTTPRequest.h" |
|
11 |
#import <QuartzCore/QuartzCore.h> |
|
12 |
|
|
13 |
static ASIAuthenticationDialog *sharedDialog = nil; |
|
14 |
BOOL isDismissing = NO; |
|
15 |
static NSMutableArray *requestsNeedingAuthentication = nil; |
|
16 |
|
|
17 |
static const NSUInteger kUsernameRow = 0; |
|
18 |
static const NSUInteger kUsernameSection = 0; |
|
19 |
static const NSUInteger kPasswordRow = 1; |
|
20 |
static const NSUInteger kPasswordSection = 0; |
|
21 |
static const NSUInteger kDomainRow = 0; |
|
22 |
static const NSUInteger kDomainSection = 1; |
|
23 |
|
|
24 |
|
|
25 |
@implementation ASIAutorotatingViewController |
|
26 |
|
|
27 |
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation |
|
28 |
{ |
|
29 |
return YES; |
|
30 |
} |
|
31 |
|
|
32 |
@end |
|
33 |
|
|
34 |
|
|
35 |
@interface ASIAuthenticationDialog () |
|
36 |
- (void)showTitle; |
|
37 |
- (void)show; |
|
38 |
- (NSArray *)requestsRequiringTheseCredentials; |
|
39 |
- (void)presentNextDialog; |
|
40 |
- (void)keyboardWillShow:(NSNotification *)notification; |
|
41 |
- (void)orientationChanged:(NSNotification *)notification; |
|
42 |
- (void)cancelAuthenticationFromDialog:(id)sender; |
|
43 |
- (void)loginWithCredentialsFromDialog:(id)sender; |
|
44 |
@property (retain) UITableView *tableView; |
|
45 |
@end |
|
46 |
|
|
47 |
@implementation ASIAuthenticationDialog |
|
48 |
|
|
49 |
#pragma mark init / dealloc |
|
50 |
|
|
51 |
+ (void)initialize |
|
52 |
{ |
|
53 |
if (self == [ASIAuthenticationDialog class]) { |
|
54 |
requestsNeedingAuthentication = [[NSMutableArray array] retain]; |
|
55 |
} |
|
56 |
} |
|
57 |
|
|
58 |
+ (void)presentAuthenticationDialogForRequest:(ASIHTTPRequest *)theRequest |
|
59 |
{ |
|
60 |
// No need for a lock here, this will always be called on the main thread |
|
61 |
if (!sharedDialog) { |
|
62 |
sharedDialog = [[self alloc] init]; |
|
63 |
[sharedDialog setRequest:theRequest]; |
|
64 |
if ([theRequest authenticationNeeded] == ASIProxyAuthenticationNeeded) { |
|
65 |
[sharedDialog setType:ASIProxyAuthenticationType]; |
|
66 |
} else { |
|
67 |
[sharedDialog setType:ASIStandardAuthenticationType]; |
|
68 |
} |
|
69 |
[sharedDialog show]; |
|
70 |
} else { |
|
71 |
[requestsNeedingAuthentication addObject:theRequest]; |
|
72 |
} |
|
73 |
} |
|
74 |
|
|
75 |
- (id)init |
|
76 |
{ |
|
77 |
if ((self = [self initWithNibName:nil bundle:nil])) { |
|
78 |
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil]; |
|
79 |
|
|
80 |
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_3_2 |
|
81 |
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) { |
|
82 |
#endif |
|
83 |
if (![UIDevice currentDevice].generatesDeviceOrientationNotifications) { |
|
84 |
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications]; |
|
85 |
[self setDidEnableRotationNotifications:YES]; |
|
86 |
} |
|
87 |
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(orientationChanged:) name:UIDeviceOrientationDidChangeNotification object:nil]; |
|
88 |
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_3_2 |
|
89 |
} |
|
90 |
#endif |
|
91 |
} |
|
92 |
return self; |
|
93 |
} |
|
94 |
|
|
95 |
- (void)dealloc |
|
96 |
{ |
|
97 |
if ([self didEnableRotationNotifications]) { |
|
98 |
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIDeviceOrientationDidChangeNotification object:nil]; |
|
99 |
} |
|
100 |
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillShowNotification object:nil]; |
|
101 |
|
|
102 |
[request release]; |
|
103 |
[tableView release]; |
|
104 |
[presentingController.view removeFromSuperview]; |
|
105 |
[presentingController release]; |
|
106 |
[super dealloc]; |
|
107 |
} |
|
108 |
|
|
109 |
#pragma mark keyboard notifications |
|
110 |
|
|
111 |
- (void)keyboardWillShow:(NSNotification *)notification |
|
112 |
{ |
|
113 |
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_3_2 |
|
114 |
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) { |
|
115 |
#endif |
|
116 |
#if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_3_2 |
|
117 |
NSValue *keyboardBoundsValue = [[notification userInfo] objectForKey:UIKeyboardFrameEndUserInfoKey]; |
|
118 |
#else |
|
119 |
NSValue *keyboardBoundsValue = [[notification userInfo] objectForKey:UIKeyboardBoundsUserInfoKey]; |
|
120 |
#endif |
|
121 |
CGRect keyboardBounds; |
|
122 |
[keyboardBoundsValue getValue:&keyboardBounds]; |
|
123 |
UIEdgeInsets e = UIEdgeInsetsMake(0, 0, keyboardBounds.size.height, 0); |
|
124 |
[[self tableView] setScrollIndicatorInsets:e]; |
|
125 |
[[self tableView] setContentInset:e]; |
|
126 |
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_3_2 |
|
127 |
} |
|
128 |
#endif |
|
129 |
} |
|
130 |
|
|
131 |
// Manually handles orientation changes on iPhone |
|
132 |
- (void)orientationChanged:(NSNotification *)notification |
|
133 |
{ |
|
134 |
[self showTitle]; |
|
135 |
|
|
136 |
UIInterfaceOrientation o = [[UIApplication sharedApplication] statusBarOrientation]; |
|
137 |
CGFloat angle = 0; |
|
138 |
switch (o) { |
|
139 |
case UIDeviceOrientationLandscapeLeft: angle = 90; break; |
|
140 |
case UIDeviceOrientationLandscapeRight: angle = -90; break; |
|
141 |
case UIDeviceOrientationPortraitUpsideDown: angle = 180; break; |
|
142 |
default: break; |
|
143 |
} |
|
144 |
|
|
145 |
CGRect f = [[UIScreen mainScreen] applicationFrame]; |
|
146 |
|
|
147 |
// Swap the frame height and width if necessary |
|
148 |
if (UIDeviceOrientationIsLandscape(o)) { |
|
149 |
CGFloat t; |
|
150 |
t = f.size.width; |
|
151 |
f.size.width = f.size.height; |
|
152 |
f.size.height = t; |
|
153 |
} |
|
154 |
|
|
155 |
CGAffineTransform previousTransform = self.view.layer.affineTransform; |
|
156 |
CGAffineTransform newTransform = CGAffineTransformMakeRotation((CGFloat)(angle * M_PI / 180.0)); |
|
157 |
|
|
158 |
// Reset the transform so we can set the size |
|
159 |
self.view.layer.affineTransform = CGAffineTransformIdentity; |
|
160 |
self.view.frame = (CGRect){ { 0, 0 }, f.size}; |
|
161 |
|
|
162 |
// Revert to the previous transform for correct animation |
|
163 |
self.view.layer.affineTransform = previousTransform; |
|
164 |
|
|
165 |
[UIView beginAnimations:nil context:NULL]; |
|
166 |
[UIView setAnimationDuration:0.3]; |
|
167 |
|
|
168 |
// Set the new transform |
|
169 |
self.view.layer.affineTransform = newTransform; |
|
170 |
|
|
171 |
// Fix the view origin |
|
172 |
self.view.frame = (CGRect){ { f.origin.x, f.origin.y },self.view.frame.size}; |
|
173 |
[UIView commitAnimations]; |
|
174 |
} |
|
175 |
|
|
176 |
#pragma mark utilities |
|
177 |
|
|
178 |
- (UIViewController *)presentingController |
|
179 |
{ |
|
180 |
if (!presentingController) { |
|
181 |
presentingController = [[ASIAutorotatingViewController alloc] initWithNibName:nil bundle:nil]; |
|
182 |
|
|
183 |
// Attach to the window, but don't interfere. |
|
184 |
UIWindow *window = [[[UIApplication sharedApplication] windows] objectAtIndex:0]; |
|
185 |
[window addSubview:[presentingController view]]; |
|
186 |
[[presentingController view] setFrame:CGRectZero]; |
|
187 |
[[presentingController view] setUserInteractionEnabled:NO]; |
|
188 |
} |
|
189 |
|
|
190 |
return presentingController; |
|
191 |
} |
|
192 |
|
|
193 |
- (UITextField *)textFieldInRow:(NSUInteger)row section:(NSUInteger)section |
|
194 |
{ |
|
195 |
return [[[[[self tableView] cellForRowAtIndexPath: |
|
196 |
[NSIndexPath indexPathForRow:row inSection:section]] |
|
197 |
contentView] subviews] objectAtIndex:0]; |
|
198 |
} |
|
199 |
|
|
200 |
- (UITextField *)usernameField |
|
201 |
{ |
|
202 |
return [self textFieldInRow:kUsernameRow section:kUsernameSection]; |
|
203 |
} |
|
204 |
|
|
205 |
- (UITextField *)passwordField |
|
206 |
{ |
|
207 |
return [self textFieldInRow:kPasswordRow section:kPasswordSection]; |
|
208 |
} |
|
209 |
|
|
210 |
- (UITextField *)domainField |
|
211 |
{ |
|
212 |
return [self textFieldInRow:kDomainRow section:kDomainSection]; |
|
213 |
} |
|
214 |
|
|
215 |
#pragma mark show / dismiss |
|
216 |
|
|
217 |
+ (void)dismiss |
|
218 |
{ |
|
219 |
[[sharedDialog parentViewController] dismissModalViewControllerAnimated:YES]; |
|
220 |
} |
|
221 |
|
|
222 |
- (void)viewDidDisappear:(BOOL)animated |
|
223 |
{ |
|
224 |
[self retain]; |
|
225 |
[sharedDialog release]; |
|
226 |
sharedDialog = nil; |
|
227 |
[self performSelector:@selector(presentNextDialog) withObject:nil afterDelay:0]; |
|
228 |
[self release]; |
|
229 |
} |
|
230 |
|
|
231 |
- (void)dismiss |
|
232 |
{ |
|
233 |
if (self == sharedDialog) { |
|
234 |
[[self class] dismiss]; |
|
235 |
} else { |
|
236 |
[[self parentViewController] dismissModalViewControllerAnimated:YES]; |
|
237 |
} |
|
238 |
} |
|
239 |
|
|
240 |
- (void)showTitle |
|
241 |
{ |
|
242 |
UINavigationBar *navigationBar = [[[self view] subviews] objectAtIndex:0]; |
|
243 |
UINavigationItem *navItem = [[navigationBar items] objectAtIndex:0]; |
|
244 |
if (UIInterfaceOrientationIsPortrait([[UIDevice currentDevice] orientation])) { |
|
245 |
// Setup the title |
|
246 |
if ([self type] == ASIProxyAuthenticationType) { |
|
247 |
[navItem setPrompt:@"Login to this secure proxy server."]; |
|
248 |
} else { |
|
249 |
[navItem setPrompt:@"Login to this secure server."]; |
|
250 |
} |
|
251 |
} else { |
|
252 |
[navItem setPrompt:nil]; |
|
253 |
} |
|
254 |
[navigationBar sizeToFit]; |
|
255 |
CGRect f = [[self view] bounds]; |
|
256 |
f.origin.y = [navigationBar frame].size.height; |
|
257 |
f.size.height -= f.origin.y; |
|
258 |
[[self tableView] setFrame:f]; |
|
259 |
} |
|
260 |
|
|
261 |
- (void)show |
|
262 |
{ |
|
263 |
// Remove all subviews |
|
264 |
UIView *v; |
|
265 |
while ((v = [[[self view] subviews] lastObject])) { |
|
266 |
[v removeFromSuperview]; |
|
267 |
} |
|
268 |
|
|
269 |
// Setup toolbar |
|
270 |
UINavigationBar *bar = [[[UINavigationBar alloc] init] autorelease]; |
|
271 |
[bar setAutoresizingMask:UIViewAutoresizingFlexibleWidth]; |
|
272 |
|
|
273 |
UINavigationItem *navItem = [[[UINavigationItem alloc] init] autorelease]; |
|
274 |
bar.items = [NSArray arrayWithObject:navItem]; |
|
275 |
|
|
276 |
[[self view] addSubview:bar]; |
|
277 |
|
|
278 |
[self showTitle]; |
|
279 |
|
|
280 |
// Setup toolbar buttons |
|
281 |
if ([self type] == ASIProxyAuthenticationType) { |
|
282 |
[navItem setTitle:[[self request] proxyHost]]; |
|
283 |
} else { |
|
284 |
[navItem setTitle:[[[self request] url] host]]; |
|
285 |
} |
|
286 |
|
|
287 |
[navItem setLeftBarButtonItem:[[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel target:self action:@selector(cancelAuthenticationFromDialog:)] autorelease]]; |
|
288 |
[navItem setRightBarButtonItem:[[[UIBarButtonItem alloc] initWithTitle:@"Login" style:UIBarButtonItemStyleDone target:self action:@selector(loginWithCredentialsFromDialog:)] autorelease]]; |
|
289 |
|
|
290 |
// We show the login form in a table view, similar to Safari's authentication dialog |
|
291 |
[bar sizeToFit]; |
|
292 |
CGRect f = [[self view] bounds]; |
|
293 |
f.origin.y = [bar frame].size.height; |
|
294 |
f.size.height -= f.origin.y; |
|
295 |
|
|
296 |
[self setTableView:[[[UITableView alloc] initWithFrame:f style:UITableViewStyleGrouped] autorelease]]; |
|
297 |
[[self tableView] setDelegate:self]; |
|
298 |
[[self tableView] setDataSource:self]; |
|
299 |
[[self tableView] setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight]; |
|
300 |
[[self view] addSubview:[self tableView]]; |
|
301 |
|
|
302 |
// Force reload the table content, and focus the first field to show the keyboard |
|
303 |
[[self tableView] reloadData]; |
|
304 |
[[[[[self tableView] cellForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]].contentView subviews] objectAtIndex:0] becomeFirstResponder]; |
|
305 |
|
|
306 |
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_3_2 |
|
307 |
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { |
|
308 |
[self setModalPresentationStyle:UIModalPresentationFormSheet]; |
|
309 |
} |
|
310 |
#endif |
|
311 |
|
|
312 |
[[self presentingController] presentModalViewController:self animated:YES]; |
|
313 |
} |
|
314 |
|
|
315 |
#pragma mark button callbacks |
|
316 |
|
|
317 |
- (void)cancelAuthenticationFromDialog:(id)sender |
|
318 |
{ |
|
319 |
for (ASIHTTPRequest *theRequest in [self requestsRequiringTheseCredentials]) { |
|
320 |
[theRequest cancelAuthentication]; |
|
321 |
[requestsNeedingAuthentication removeObject:theRequest]; |
|
322 |
} |
|
323 |
[self dismiss]; |
|
324 |
} |
|
325 |
|
|
326 |
- (NSArray *)requestsRequiringTheseCredentials |
|
327 |
{ |
|
328 |
NSMutableArray *requestsRequiringTheseCredentials = [NSMutableArray array]; |
|
329 |
NSURL *requestURL = [[self request] url]; |
|
330 |
for (ASIHTTPRequest *otherRequest in requestsNeedingAuthentication) { |
|
331 |
NSURL *theURL = [otherRequest url]; |
|
332 |
if (([otherRequest authenticationNeeded] == [[self request] authenticationNeeded]) && [[theURL host] isEqualToString:[requestURL host]] && ([theURL port] == [requestURL port] || ([requestURL port] && [[theURL port] isEqualToNumber:[requestURL port]])) && [[theURL scheme] isEqualToString:[requestURL scheme]] && ((![otherRequest authenticationRealm] && ![[self request] authenticationRealm]) || ([otherRequest authenticationRealm] && [[self request] authenticationRealm] && [[[self request] authenticationRealm] isEqualToString:[otherRequest authenticationRealm]]))) { |
|
333 |
[requestsRequiringTheseCredentials addObject:otherRequest]; |
|
334 |
} |
|
335 |
} |
|
336 |
[requestsRequiringTheseCredentials addObject:[self request]]; |
|
337 |
return requestsRequiringTheseCredentials; |
|
338 |
} |
|
339 |
|
|
340 |
- (void)presentNextDialog |
|
341 |
{ |
|
342 |
if ([requestsNeedingAuthentication count]) { |
|
343 |
ASIHTTPRequest *nextRequest = [requestsNeedingAuthentication objectAtIndex:0]; |
|
344 |
[requestsNeedingAuthentication removeObjectAtIndex:0]; |
|
345 |
[[self class] presentAuthenticationDialogForRequest:nextRequest]; |
|
346 |
} |
|
347 |
} |
|
348 |
|
|
349 |
|
|
350 |
- (void)loginWithCredentialsFromDialog:(id)sender |
|
351 |
{ |
|
352 |
for (ASIHTTPRequest *theRequest in [self requestsRequiringTheseCredentials]) { |
|
353 |
|
|
354 |
NSString *username = [[self usernameField] text]; |
|
355 |
NSString *password = [[self passwordField] text]; |
|
356 |
|
|
357 |
if (username == nil) { username = @""; } |
|
358 |
if (password == nil) { password = @""; } |
|
359 |
|
|
360 |
if ([self type] == ASIProxyAuthenticationType) { |
|
361 |
[theRequest setProxyUsername:username]; |
|
362 |
[theRequest setProxyPassword:password]; |
|
363 |
} else { |
|
364 |
[theRequest setUsername:username]; |
|
365 |
[theRequest setPassword:password]; |
|
366 |
} |
|
367 |
|
|
368 |
// Handle NTLM domains |
|
369 |
NSString *scheme = ([self type] == ASIStandardAuthenticationType) ? [[self request] authenticationScheme] : [[self request] proxyAuthenticationScheme]; |
|
370 |
if ([scheme isEqualToString:(NSString *)kCFHTTPAuthenticationSchemeNTLM]) { |
|
371 |
NSString *domain = [[self domainField] text]; |
|
372 |
if ([self type] == ASIProxyAuthenticationType) { |
|
373 |
[theRequest setProxyDomain:domain]; |
|
374 |
} else { |
|
375 |
[theRequest setDomain:domain]; |
|
376 |
} |
|
377 |
} |
|
378 |
|
|
379 |
[theRequest retryUsingSuppliedCredentials]; |
|
380 |
[requestsNeedingAuthentication removeObject:theRequest]; |
|
381 |
} |
|
382 |
[self dismiss]; |
|
383 |
} |
|
384 |
|
|
385 |
#pragma mark table view data source |
|
386 |
|
|
387 |
- (NSInteger)numberOfSectionsInTableView:(UITableView *)aTableView |
|
388 |
{ |
|
389 |
NSString *scheme = ([self type] == ASIStandardAuthenticationType) ? [[self request] authenticationScheme] : [[self request] proxyAuthenticationScheme]; |
|
390 |
if ([scheme isEqualToString:(NSString *)kCFHTTPAuthenticationSchemeNTLM]) { |
|
391 |
return 2; |
|
392 |
} |
|
393 |
return 1; |
|
394 |
} |
|
395 |
|
|
396 |
- (CGFloat)tableView:(UITableView *)aTableView heightForFooterInSection:(NSInteger)section |
|
397 |
{ |
|
398 |
if (section == [self numberOfSectionsInTableView:aTableView]-1) { |
|
399 |
return 30; |
|
400 |
} |
|
401 |
return 0; |
|
402 |
} |
|
403 |
|
|
404 |
- (CGFloat)tableView:(UITableView *)aTableView heightForHeaderInSection:(NSInteger)section |
|
405 |
{ |
|
406 |
if (section == 0) { |
|
407 |
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_3_2 |
|
408 |
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { |
|
409 |
return 54; |
|
410 |
} |
|
411 |
#endif |
|
412 |
return 30; |
|
413 |
} |
|
414 |
return 0; |
|
415 |
} |
|
416 |
|
|
417 |
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section |
|
418 |
{ |
|
419 |
if (section == 0) { |
|
420 |
return [[self request] authenticationRealm]; |
|
421 |
} |
|
422 |
return nil; |
|
423 |
} |
|
424 |
|
|
425 |
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath |
|
426 |
{ |
|
427 |
#if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_3_0 |
|
428 |
UITableViewCell *cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil] autorelease]; |
|
429 |
#else |
|
430 |
UITableViewCell *cell = [[[UITableViewCell alloc] initWithFrame:CGRectMake(0,0,0,0) reuseIdentifier:nil] autorelease]; |
|
431 |
#endif |
|
432 |
|
|
433 |
[cell setSelectionStyle:UITableViewCellSelectionStyleNone]; |
|
434 |
|
|
435 |
CGRect f = CGRectInset([cell bounds], 10, 10); |
|
436 |
UITextField *textField = [[[UITextField alloc] initWithFrame:f] autorelease]; |
|
437 |
[textField setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight]; |
|
438 |
[textField setAutocapitalizationType:UITextAutocapitalizationTypeNone]; |
|
439 |
[textField setAutocorrectionType:UITextAutocorrectionTypeNo]; |
|
440 |
|
|
441 |
NSUInteger s = [indexPath section]; |
|
442 |
NSUInteger r = [indexPath row]; |
|
443 |
|
|
444 |
if (s == kUsernameSection && r == kUsernameRow) { |
|
445 |
[textField setPlaceholder:@"User"]; |
|
446 |
} else if (s == kPasswordSection && r == kPasswordRow) { |
|
447 |
[textField setPlaceholder:@"Password"]; |
|
448 |
[textField setSecureTextEntry:YES]; |
|
449 |
} else if (s == kDomainSection && r == kDomainRow) { |
|
450 |
[textField setPlaceholder:@"Domain"]; |
|
451 |
} |
|
452 |
[cell.contentView addSubview:textField]; |
|
453 |
|
|
454 |
return cell; |
|
455 |
} |
|
456 |
|
|
457 |
- (NSInteger)tableView:(UITableView *)aTableView numberOfRowsInSection:(NSInteger)section |
|
458 |
{ |
|
459 |
if (section == 0) { |
|
460 |
return 2; |
|
461 |
} else { |
|
462 |
return 1; |
|
463 |
} |
|
464 |
} |
|
465 |
|
|
466 |
- (NSString *)tableView:(UITableView *)aTableView titleForFooterInSection:(NSInteger)section |
|
467 |
{ |
|
468 |
if (section == [self numberOfSectionsInTableView:aTableView]-1) { |
|
469 |
// If we're using Basic authentication and the connection is not using SSL, we'll show the plain text message |
|
470 |
if ([[[self request] authenticationScheme] isEqualToString:(NSString *)kCFHTTPAuthenticationSchemeBasic] && ![[[[self request] url] scheme] isEqualToString:@"https"]) { |
|
471 |
return @"Password will be sent in the clear."; |
|
472 |
// We are using Digest, NTLM, or any scheme over SSL |
|
473 |
} else { |
|
474 |
return @"Password will be sent securely."; |
|
475 |
} |
|
476 |
} |
|
477 |
return nil; |
|
478 |
} |
|
479 |
|
|
480 |
#pragma mark - |
|
481 |
|
|
482 |
@synthesize request; |
|
483 |
@synthesize type; |
|
484 |
@synthesize tableView; |
|
485 |
@synthesize didEnableRotationNotifications; |
|
486 |
@synthesize presentingController; |
|
487 |
@end |
b/asi-http-request-with-pithos/Classes/ASICacheDelegate.h | ||
---|---|---|
1 |
// |
|
2 |
// ASICacheDelegate.h |
|
3 |
// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest |
|
4 |
// |
|
5 |
// Created by Ben Copsey on 01/05/2010. |
|
6 |
// Copyright 2010 All-Seeing Interactive. All rights reserved. |
|
7 |
// |
|
8 |
|
|
9 |
#import <Foundation/Foundation.h> |
|
10 |
@class ASIHTTPRequest; |
|
11 |
|
|
12 |
// Cache policies control the behaviour of a cache and how requests use the cache |
|
13 |
// When setting a cache policy, you can use a combination of these values as a bitmask |
|
14 |
// For example: [request setCachePolicy:ASIAskServerIfModifiedCachePolicy|ASIFallbackToCacheIfLoadFailsCachePolicy|ASIDoNotWriteToCacheCachePolicy]; |
|
15 |
// Note that some of the behaviours below are mutally exclusive - you cannot combine ASIAskServerIfModifiedWhenStaleCachePolicy and ASIAskServerIfModifiedCachePolicy, for example. |
|
16 |
typedef enum _ASICachePolicy { |
|
17 |
|
|
18 |
// The default cache policy. When you set a request to use this, it will use the cache's defaultCachePolicy |
|
19 |
// ASIDownloadCache's default cache policy is 'ASIAskServerIfModifiedWhenStaleCachePolicy' |
|
20 |
ASIUseDefaultCachePolicy = 0, |
|
21 |
|
|
22 |
// Tell the request not to read from the cache |
|
23 |
ASIDoNotReadFromCacheCachePolicy = 1, |
|
24 |
|
|
25 |
// The the request not to write to the cache |
|
26 |
ASIDoNotWriteToCacheCachePolicy = 2, |
|
27 |
|
|
28 |
// Ask the server if there is an updated version of this resource (using a conditional GET) ONLY when the cached data is stale |
|
29 |
ASIAskServerIfModifiedWhenStaleCachePolicy = 4, |
|
30 |
|
|
31 |
// Always ask the server if there is an updated version of this resource (using a conditional GET) |
|
32 |
ASIAskServerIfModifiedCachePolicy = 8, |
|
33 |
|
|
34 |
// If cached data exists, use it even if it is stale. This means requests will not talk to the server unless the resource they are requesting is not in the cache |
|
35 |
ASIOnlyLoadIfNotCachedCachePolicy = 16, |
|
36 |
|
|
37 |
// If cached data exists, use it even if it is stale. If cached data does not exist, stop (will not set an error on the request) |
|
38 |
ASIDontLoadCachePolicy = 32, |
|
39 |
|
|
40 |
// Specifies that cached data may be used if the request fails. If cached data is used, the request will succeed without error. Usually used in combination with other options above. |
|
41 |
ASIFallbackToCacheIfLoadFailsCachePolicy = 64 |
|
42 |
} ASICachePolicy; |
|
43 |
|
|
44 |
// Cache storage policies control whether cached data persists between application launches (ASICachePermanentlyCacheStoragePolicy) or not (ASICacheForSessionDurationCacheStoragePolicy) |
|
45 |
// Calling [ASIHTTPRequest clearSession] will remove any data stored using ASICacheForSessionDurationCacheStoragePolicy |
|
46 |
typedef enum _ASICacheStoragePolicy { |
|
47 |
ASICacheForSessionDurationCacheStoragePolicy = 0, |
|
48 |
ASICachePermanentlyCacheStoragePolicy = 1 |
|
49 |
} ASICacheStoragePolicy; |
|
50 |
|
|
51 |
|
|
52 |
@protocol ASICacheDelegate <NSObject> |
|
53 |
|
|
54 |
@required |
|
55 |
|
|
56 |
// Should return the cache policy that will be used when requests have their cache policy set to ASIUseDefaultCachePolicy |
|
57 |
- (ASICachePolicy)defaultCachePolicy; |
|
58 |
|
|
59 |
// Returns the date a cached response should expire on. Pass a non-zero max age to specify a custom date. |
|
60 |
- (NSDate *)expiryDateForRequest:(ASIHTTPRequest *)request maxAge:(NSTimeInterval)maxAge; |
|
61 |
|
|
62 |
// Updates cached response headers with a new expiry date. Pass a non-zero max age to specify a custom date. |
|
63 |
- (void)updateExpiryForRequest:(ASIHTTPRequest *)request maxAge:(NSTimeInterval)maxAge; |
|
64 |
|
|
65 |
// Looks at the request's cache policy and any cached headers to determine if the cache data is still valid |
|
66 |
- (BOOL)canUseCachedDataForRequest:(ASIHTTPRequest *)request; |
|
67 |
|
|
68 |
// Removes cached data for a particular request |
|
69 |
- (void)removeCachedDataForRequest:(ASIHTTPRequest *)request; |
|
70 |
|
|
71 |
// Should return YES if the cache considers its cached response current for the request |
|
72 |
// Should return NO is the data is not cached, or (for example) if the cached headers state the request should have expired |
|
73 |
- (BOOL)isCachedDataCurrentForRequest:(ASIHTTPRequest *)request; |
|
74 |
|
|
75 |
// Should store the response for the passed request in the cache |
|
76 |
// When a non-zero maxAge is passed, it should be used as the expiry time for the cached response |
|
77 |
- (void)storeResponseForRequest:(ASIHTTPRequest *)request maxAge:(NSTimeInterval)maxAge; |
|
78 |
|
|
79 |
// Removes cached data for a particular url |
|
80 |
- (void)removeCachedDataForURL:(NSURL *)url; |
|
81 |
|
|
82 |
// Should return an NSDictionary of cached headers for the passed URL, if it is stored in the cache |
|
83 |
- (NSDictionary *)cachedResponseHeadersForURL:(NSURL *)url; |
|
84 |
|
|
85 |
// Should return the cached body of a response for the passed URL, if it is stored in the cache |
|
86 |
- (NSData *)cachedResponseDataForURL:(NSURL *)url; |
|
87 |
|
|
88 |
// Returns a path to the cached response data, if it exists |
|
89 |
- (NSString *)pathToCachedResponseDataForURL:(NSURL *)url; |
|
90 |
|
|
91 |
// Returns a path to the cached response headers, if they url |
|
92 |
- (NSString *)pathToCachedResponseHeadersForURL:(NSURL *)url; |
|
93 |
|
|
94 |
// Returns the location to use to store cached response headers for a particular request |
|
95 |
- (NSString *)pathToStoreCachedResponseHeadersForRequest:(ASIHTTPRequest *)request; |
|
96 |
|
|
97 |
// Returns the location to use to store a cached response body for a particular request |
|
98 |
- (NSString *)pathToStoreCachedResponseDataForRequest:(ASIHTTPRequest *)request; |
|
99 |
|
|
100 |
// Clear cached data stored for the passed storage policy |
|
101 |
- (void)clearCachedResponsesForStoragePolicy:(ASICacheStoragePolicy)cachePolicy; |
|
102 |
|
|
103 |
@end |
b/asi-http-request-with-pithos/Classes/ASIDataCompressor.h | ||
---|---|---|
1 |
// |
|
2 |
// ASIDataCompressor.h |
|
3 |
// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest |
|
4 |
// |
|
5 |
// Created by Ben Copsey on 17/08/2010. |
|
6 |
// Copyright 2010 All-Seeing Interactive. All rights reserved. |
|
7 |
// |
|
8 |
|
|
9 |
// This is a helper class used by ASIHTTPRequest to handle deflating (compressing) data in memory and on disk |
|
10 |
// You may also find it helpful if you need to deflate data and files yourself - see the class methods below |
|
11 |
// Most of the zlib stuff is based on the sample code by Mark Adler available at http://zlib.net |
|
12 |
|
|
13 |
#import <Foundation/Foundation.h> |
|
14 |
#import <zlib.h> |
|
15 |
|
|
16 |
@interface ASIDataCompressor : NSObject { |
|
17 |
BOOL streamReady; |
|
18 |
z_stream zStream; |
|
19 |
} |
|
20 |
|
|
21 |
// Convenience constructor will call setupStream for you |
|
22 |
+ (id)compressor; |
|
23 |
|
|
24 |
// Compress the passed chunk of data |
|
25 |
// Passing YES for shouldFinish will finalize the deflated data - you must pass YES when you are on the last chunk of data |
|
26 |
- (NSData *)compressBytes:(Bytef *)bytes length:(NSUInteger)length error:(NSError **)err shouldFinish:(BOOL)shouldFinish; |
|
27 |
|
|
28 |
// Convenience method - pass it some data, and you'll get deflated data back |
|
29 |
+ (NSData *)compressData:(NSData*)uncompressedData error:(NSError **)err; |
|
30 |
|
|
31 |
// Convenience method - pass it a file containing the data to compress in sourcePath, and it will write deflated data to destinationPath |
|
32 |
+ (BOOL)compressDataFromFile:(NSString *)sourcePath toFile:(NSString *)destinationPath error:(NSError **)err; |
|
33 |
|
|
34 |
// Sets up zlib to handle the inflating. You only need to call this yourself if you aren't using the convenience constructor 'compressor' |
|
35 |
- (NSError *)setupStream; |
|
36 |
|
|
37 |
// Tells zlib to clean up. You need to call this if you need to cancel deflating part way through |
|
38 |
// If deflating finishes or fails, this method will be called automatically |
|
39 |
- (NSError *)closeStream; |
|
40 |
|
|
41 |
@property (assign, readonly) BOOL streamReady; |
|
42 |
@end |
b/asi-http-request-with-pithos/Classes/ASIDataCompressor.m | ||
---|---|---|
1 |
// |
|
2 |
// ASIDataCompressor.m |
|
3 |
// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest |
|
4 |
// |
|
5 |
// Created by Ben Copsey on 17/08/2010. |
|
6 |
// Copyright 2010 All-Seeing Interactive. All rights reserved. |
|
7 |
// |
|
8 |
|
|
9 |
#import "ASIDataCompressor.h" |
|
10 |
#import "ASIHTTPRequest.h" |
|
11 |
|
|
12 |
#define DATA_CHUNK_SIZE 262144 // Deal with gzipped data in 256KB chunks |
|
13 |
#define COMPRESSION_AMOUNT Z_DEFAULT_COMPRESSION |
|
14 |
|
|
15 |
@interface ASIDataCompressor () |
|
16 |
+ (NSError *)deflateErrorWithCode:(int)code; |
|
17 |
@end |
|
18 |
|
|
19 |
@implementation ASIDataCompressor |
|
20 |
|
|
21 |
+ (id)compressor |
|
22 |
{ |
|
23 |
ASIDataCompressor *compressor = [[[self alloc] init] autorelease]; |
|
24 |
[compressor setupStream]; |
|
25 |
return compressor; |
|
26 |
} |
|
27 |
|
|
28 |
- (void)dealloc |
|
29 |
{ |
|
30 |
if (streamReady) { |
|
31 |
[self closeStream]; |
|
32 |
} |
|
33 |
[super dealloc]; |
|
34 |
} |
|
35 |
|
|
36 |
- (NSError *)setupStream |
|
37 |
{ |
|
38 |
if (streamReady) { |
|
39 |
return nil; |
|
40 |
} |
|
41 |
// Setup the inflate stream |
|
42 |
zStream.zalloc = Z_NULL; |
|
43 |
zStream.zfree = Z_NULL; |
|
44 |
zStream.opaque = Z_NULL; |
|
45 |
zStream.avail_in = 0; |
|
46 |
zStream.next_in = 0; |
|
47 |
int status = deflateInit2(&zStream, COMPRESSION_AMOUNT, Z_DEFLATED, (15+16), 8, Z_DEFAULT_STRATEGY); |
|
48 |
if (status != Z_OK) { |
|
49 |
return [[self class] deflateErrorWithCode:status]; |
|
50 |
} |
|
51 |
streamReady = YES; |
|
52 |
return nil; |
|
53 |
} |
|
54 |
|
|
55 |
- (NSError *)closeStream |
|
56 |
{ |
|
57 |
if (!streamReady) { |
|
58 |
return nil; |
|
59 |
} |
|
60 |
// Close the deflate stream |
|
61 |
streamReady = NO; |
|
62 |
int status = deflateEnd(&zStream); |
|
63 |
if (status != Z_OK) { |
|
64 |
return [[self class] deflateErrorWithCode:status]; |
|
65 |
} |
|
66 |
return nil; |
|
67 |
} |
|
68 |
|
|
69 |
- (NSData *)compressBytes:(Bytef *)bytes length:(NSUInteger)length error:(NSError **)err shouldFinish:(BOOL)shouldFinish |
|
70 |
{ |
|
71 |
if (length == 0) return nil; |
|
72 |
|
|
73 |
NSUInteger halfLength = length/2; |
|
74 |
|
|
75 |
// We'll take a guess that the compressed data will fit in half the size of the original (ie the max to compress at once is half DATA_CHUNK_SIZE), if not, we'll increase it below |
|
76 |
NSMutableData *outputData = [NSMutableData dataWithLength:length/2]; |
|
77 |
|
|
78 |
int status; |
|
79 |
|
|
80 |
zStream.next_in = bytes; |
|
81 |
zStream.avail_in = (unsigned int)length; |
|
82 |
zStream.avail_out = 0; |
|
83 |
|
|
84 |
NSInteger bytesProcessedAlready = zStream.total_out; |
|
85 |
while (zStream.avail_out == 0) { |
|
86 |
|
|
87 |
if (zStream.total_out-bytesProcessedAlready >= [outputData length]) { |
|
88 |
[outputData increaseLengthBy:halfLength]; |
|
89 |
} |
|
90 |
|
|
91 |
zStream.next_out = [outputData mutableBytes] + zStream.total_out-bytesProcessedAlready; |
|
92 |
zStream.avail_out = (unsigned int)([outputData length] - (zStream.total_out-bytesProcessedAlready)); |
|
93 |
status = deflate(&zStream, shouldFinish ? Z_FINISH : Z_NO_FLUSH); |
|
94 |
|
|
95 |
if (status == Z_STREAM_END) { |
|
96 |
break; |
|
97 |
} else if (status != Z_OK) { |
|
98 |
if (err) { |
|
99 |
*err = [[self class] deflateErrorWithCode:status]; |
|
100 |
} |
|
101 |
return NO; |
|
102 |
} |
|
103 |
} |
|
104 |
|
|
105 |
// Set real length |
|
106 |
[outputData setLength: zStream.total_out-bytesProcessedAlready]; |
|
107 |
return outputData; |
|
108 |
} |
|
109 |
|
|
110 |
|
|
111 |
+ (NSData *)compressData:(NSData*)uncompressedData error:(NSError **)err |
|
112 |
{ |
|
113 |
NSError *theError = nil; |
|
114 |
NSData *outputData = [[ASIDataCompressor compressor] compressBytes:(Bytef *)[uncompressedData bytes] length:[uncompressedData length] error:&theError shouldFinish:YES]; |
|
115 |
if (theError) { |
|
116 |
if (err) { |
|
117 |
*err = theError; |
|
118 |
} |
|
119 |
return nil; |
|
120 |
} |
|
121 |
return outputData; |
|
122 |
} |
|
123 |
|
|
124 |
|
|
125 |
|
|
126 |
+ (BOOL)compressDataFromFile:(NSString *)sourcePath toFile:(NSString *)destinationPath error:(NSError **)err |
|
127 |
{ |
|
128 |
NSFileManager *fileManager = [[[NSFileManager alloc] init] autorelease]; |
|
129 |
|
|
130 |
// Create an empty file at the destination path |
|
131 |
if (![fileManager createFileAtPath:destinationPath contents:[NSData data] attributes:nil]) { |
|
132 |
if (err) { |
|
133 |
*err = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Compression of %@ failed because we were to create a file at %@",sourcePath,destinationPath],NSLocalizedDescriptionKey,nil]]; |
|
134 |
} |
|
135 |
return NO; |
|
136 |
} |
|
137 |
|
|
138 |
// Ensure the source file exists |
|
139 |
if (![fileManager fileExistsAtPath:sourcePath]) { |
|
140 |
if (err) { |
|
141 |
*err = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Compression of %@ failed the file does not exist",sourcePath],NSLocalizedDescriptionKey,nil]]; |
|
142 |
} |
|
143 |
return NO; |
|
144 |
} |
|
145 |
|
|
146 |
UInt8 inputData[DATA_CHUNK_SIZE]; |
|
147 |
NSData *outputData; |
|
148 |
NSInteger readLength; |
|
149 |
NSError *theError = nil; |
|
150 |
|
|
151 |
ASIDataCompressor *compressor = [ASIDataCompressor compressor]; |
|
152 |
|
|
153 |
NSInputStream *inputStream = [NSInputStream inputStreamWithFileAtPath:sourcePath]; |
|
154 |
[inputStream open]; |
|
155 |
NSOutputStream *outputStream = [NSOutputStream outputStreamToFileAtPath:destinationPath append:NO]; |
|
156 |
[outputStream open]; |
|
157 |
|
|
158 |
while ([compressor streamReady]) { |
|
159 |
|
|
160 |
// Read some data from the file |
|
161 |
readLength = [inputStream read:inputData maxLength:DATA_CHUNK_SIZE]; |
|
162 |
|
|
163 |
// Make sure nothing went wrong |
|
164 |
if ([inputStream streamStatus] == NSStreamEventErrorOccurred) { |
|
165 |
if (err) { |
|
166 |
*err = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Compression of %@ failed because we were unable to read from the source data file",sourcePath],NSLocalizedDescriptionKey,[inputStream streamError],NSUnderlyingErrorKey,nil]]; |
|
167 |
} |
|
168 |
[compressor closeStream]; |
|
169 |
return NO; |
|
170 |
} |
|
171 |
// Have we reached the end of the input data? |
|
172 |
if (!readLength) { |
|
173 |
break; |
|
174 |
} |
|
175 |
|
|
176 |
// Attempt to deflate the chunk of data |
|
177 |
outputData = [compressor compressBytes:inputData length:readLength error:&theError shouldFinish:readLength < DATA_CHUNK_SIZE ]; |
|
178 |
if (theError) { |
|
179 |
if (err) { |
|
180 |
*err = theError; |
|
181 |
} |
|
182 |
[compressor closeStream]; |
|
183 |
return NO; |
|
184 |
} |
|
185 |
|
|
186 |
// Write the deflated data out to the destination file |
|
187 |
[outputStream write:[outputData bytes] maxLength:[outputData length]]; |
|
188 |
|
|
189 |
// Make sure nothing went wrong |
|
190 |
if ([inputStream streamStatus] == NSStreamEventErrorOccurred) { |
|
191 |
if (err) { |
|
192 |
*err = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Compression of %@ failed because we were unable to write to the destination data file at &@",sourcePath,destinationPath],NSLocalizedDescriptionKey,[outputStream streamError],NSUnderlyingErrorKey,nil]]; |
|
193 |
} |
|
194 |
[compressor closeStream]; |
|
195 |
return NO; |
|
196 |
} |
|
197 |
|
|
198 |
} |
|
199 |
[inputStream close]; |
|
200 |
[outputStream close]; |
|
201 |
|
|
202 |
NSError *error = [compressor closeStream]; |
|
203 |
if (error) { |
|
204 |
if (err) { |
|
205 |
*err = error; |
|
206 |
} |
|
207 |
return NO; |
|
208 |
} |
|
209 |
|
|
210 |
return YES; |
|
211 |
} |
|
212 |
|
|
213 |
+ (NSError *)deflateErrorWithCode:(int)code |
|
214 |
{ |
|
215 |
return [NSError errorWithDomain:NetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Compression of data failed with code %hi",code],NSLocalizedDescriptionKey,nil]]; |
|
216 |
} |
|
217 |
|
|
218 |
@synthesize streamReady; |
|
219 |
@end |
b/asi-http-request-with-pithos/Classes/ASIDataDecompressor.h | ||
---|---|---|
1 |
// |
|
2 |
// ASIDataDecompressor.h |
|
3 |
// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest |
|
4 |
// |
|
5 |
// Created by Ben Copsey on 17/08/2010. |
|
6 |
// Copyright 2010 All-Seeing Interactive. All rights reserved. |
|
7 |
// |
|
8 |
|
|
9 |
// This is a helper class used by ASIHTTPRequest to handle inflating (decompressing) data in memory and on disk |
|
10 |
// You may also find it helpful if you need to inflate data and files yourself - see the class methods below |
|
11 |
// Most of the zlib stuff is based on the sample code by Mark Adler available at http://zlib.net |
|
12 |
|
|
13 |
#import <Foundation/Foundation.h> |
|
14 |
#import <zlib.h> |
|
15 |
|
|
16 |
@interface ASIDataDecompressor : NSObject { |
|
17 |
BOOL streamReady; |
|
18 |
z_stream zStream; |
|
19 |
} |
|
20 |
|
|
21 |
// Convenience constructor will call setupStream for you |
|
22 |
+ (id)decompressor; |
|
23 |
|
|
24 |
// Uncompress the passed chunk of data |
|
25 |
- (NSData *)uncompressBytes:(Bytef *)bytes length:(NSUInteger)length error:(NSError **)err; |
|
26 |
|
|
27 |
// Convenience method - pass it some deflated data, and you'll get inflated data back |
|
28 |
+ (NSData *)uncompressData:(NSData*)compressedData error:(NSError **)err; |
|
29 |
|
|
30 |
// Convenience method - pass it a file containing deflated data in sourcePath, and it will write inflated data to destinationPath |
|
31 |
+ (BOOL)uncompressDataFromFile:(NSString *)sourcePath toFile:(NSString *)destinationPath error:(NSError **)err; |
|
32 |
|
|
33 |
// Sets up zlib to handle the inflating. You only need to call this yourself if you aren't using the convenience constructor 'decompressor' |
|
34 |
- (NSError *)setupStream; |
|
35 |
|
|
36 |
// Tells zlib to clean up. You need to call this if you need to cancel inflating part way through |
|
37 |
// If inflating finishes or fails, this method will be called automatically |
|
38 |
- (NSError *)closeStream; |
|
39 |
|
|
40 |
@property (assign, readonly) BOOL streamReady; |
|
41 |
@end |
b/asi-http-request-with-pithos/Classes/ASIDataDecompressor.m | ||
---|---|---|
1 |
// |
|
2 |
// ASIDataDecompressor.m |
|
3 |
// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest |
|
4 |
// |
|
5 |
// Created by Ben Copsey on 17/08/2010. |
|
6 |
// Copyright 2010 All-Seeing Interactive. All rights reserved. |
|
7 |
// |
|
8 |
|
|
9 |
#import "ASIDataDecompressor.h" |
|
10 |
#import "ASIHTTPRequest.h" |
|
11 |
|
|
12 |
#define DATA_CHUNK_SIZE 262144 // Deal with gzipped data in 256KB chunks |
|
13 |
|
|
14 |
@interface ASIDataDecompressor () |
|
15 |
+ (NSError *)inflateErrorWithCode:(int)code; |
|
16 |
@end; |
|
17 |
|
|
18 |
@implementation ASIDataDecompressor |
|
19 |
|
|
20 |
+ (id)decompressor |
|
21 |
{ |
|
22 |
ASIDataDecompressor *decompressor = [[[self alloc] init] autorelease]; |
|
23 |
[decompressor setupStream]; |
|
24 |
return decompressor; |
|
25 |
} |
|
26 |
|
|
27 |
- (void)dealloc |
|
28 |
{ |
|
29 |
if (streamReady) { |
|
30 |
[self closeStream]; |
|
31 |
} |
|
32 |
[super dealloc]; |
|
33 |
} |
|
34 |
|
|
35 |
- (NSError *)setupStream |
|
36 |
{ |
|
37 |
if (streamReady) { |
|
38 |
return nil; |
|
39 |
} |
|
40 |
// Setup the inflate stream |
|
41 |
zStream.zalloc = Z_NULL; |
|
42 |
zStream.zfree = Z_NULL; |
|
43 |
zStream.opaque = Z_NULL; |
|
44 |
zStream.avail_in = 0; |
|
45 |
zStream.next_in = 0; |
|
46 |
int status = inflateInit2(&zStream, (15+32)); |
|
47 |
if (status != Z_OK) { |
|
48 |
return [[self class] inflateErrorWithCode:status]; |
|
49 |
} |
|
50 |
streamReady = YES; |
|
51 |
return nil; |
|
52 |
} |
|
53 |
|
|
54 |
- (NSError *)closeStream |
|
55 |
{ |
|
56 |
if (!streamReady) { |
|
57 |
return nil; |
|
58 |
} |
|
59 |
// Close the inflate stream |
|
60 |
streamReady = NO; |
|
61 |
int status = inflateEnd(&zStream); |
|
62 |
if (status != Z_OK) { |
|
63 |
return [[self class] inflateErrorWithCode:status]; |
|
64 |
} |
|
65 |
return nil; |
|
66 |
} |
|
67 |
|
|
68 |
- (NSData *)uncompressBytes:(Bytef *)bytes length:(NSUInteger)length error:(NSError **)err |
|
69 |
{ |
|
70 |
if (length == 0) return nil; |
|
71 |
|
|
72 |
NSUInteger halfLength = length/2; |
|
73 |
NSMutableData *outputData = [NSMutableData dataWithLength:length+halfLength]; |
|
74 |
|
|
75 |
int status; |
|
76 |
|
|
77 |
zStream.next_in = bytes; |
|
78 |
zStream.avail_in = (unsigned int)length; |
|
79 |
zStream.avail_out = 0; |
|
80 |
|
|
81 |
NSInteger bytesProcessedAlready = zStream.total_out; |
|
82 |
while (zStream.avail_in != 0) { |
|
83 |
|
|
84 |
if (zStream.total_out-bytesProcessedAlready >= [outputData length]) { |
|
85 |
[outputData increaseLengthBy:halfLength]; |
|
86 |
} |
|
87 |
|
|
88 |
zStream.next_out = [outputData mutableBytes] + zStream.total_out-bytesProcessedAlready; |
|
89 |
zStream.avail_out = (unsigned int)([outputData length] - (zStream.total_out-bytesProcessedAlready)); |
|
90 |
|
|
91 |
status = inflate(&zStream, Z_NO_FLUSH); |
|
92 |
|
|
93 |
if (status == Z_STREAM_END) { |
|
94 |
break; |
|
95 |
} else if (status != Z_OK) { |
|
96 |
if (err) { |
|
97 |
*err = [[self class] inflateErrorWithCode:status]; |
|
98 |
} |
|
99 |
return nil; |
|
100 |
} |
|
101 |
} |
|
102 |
|
|
103 |
// Set real length |
|
104 |
[outputData setLength: zStream.total_out-bytesProcessedAlready]; |
|
105 |
return outputData; |
|
106 |
} |
|
107 |
|
|
108 |
|
|
109 |
+ (NSData *)uncompressData:(NSData*)compressedData error:(NSError **)err |
|
110 |
{ |
|
111 |
NSError *theError = nil; |
|
112 |
NSData *outputData = [[ASIDataDecompressor decompressor] uncompressBytes:(Bytef *)[compressedData bytes] length:[compressedData length] error:&theError]; |
|
113 |
if (theError) { |
|
114 |
if (err) { |
|
115 |
*err = theError; |
|
116 |
} |
|
117 |
return nil; |
|
118 |
} |
|
119 |
return outputData; |
|
120 |
} |
|
121 |
|
|
122 |
+ (BOOL)uncompressDataFromFile:(NSString *)sourcePath toFile:(NSString *)destinationPath error:(NSError **)err |
|
123 |
{ |
|
124 |
NSFileManager *fileManager = [[[NSFileManager alloc] init] autorelease]; |
|
125 |
|
|
126 |
// Create an empty file at the destination path |
|
127 |
if (![fileManager createFileAtPath:destinationPath contents:[NSData data] attributes:nil]) { |
|
128 |
if (err) { |
|
129 |
*err = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Decompression of %@ failed because we were to create a file at %@",sourcePath,destinationPath],NSLocalizedDescriptionKey,nil]]; |
|
130 |
} |
|
131 |
return NO; |
|
132 |
} |
|
133 |
|
|
134 |
// Ensure the source file exists |
|
135 |
if (![fileManager fileExistsAtPath:sourcePath]) { |
|
136 |
if (err) { |
|
137 |
*err = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Decompression of %@ failed the file does not exist",sourcePath],NSLocalizedDescriptionKey,nil]]; |
|
138 |
} |
|
139 |
return NO; |
|
140 |
} |
|
141 |
|
|
142 |
UInt8 inputData[DATA_CHUNK_SIZE]; |
|
143 |
NSData *outputData; |
|
144 |
NSInteger readLength; |
|
145 |
NSError *theError = nil; |
|
146 |
|
|
147 |
|
|
148 |
ASIDataDecompressor *decompressor = [ASIDataDecompressor decompressor]; |
|
149 |
|
|
150 |
NSInputStream *inputStream = [NSInputStream inputStreamWithFileAtPath:sourcePath]; |
|
151 |
[inputStream open]; |
|
152 |
NSOutputStream *outputStream = [NSOutputStream outputStreamToFileAtPath:destinationPath append:NO]; |
|
153 |
[outputStream open]; |
|
154 |
|
|
155 |
while ([decompressor streamReady]) { |
|
156 |
|
|
157 |
// Read some data from the file |
|
158 |
readLength = [inputStream read:inputData maxLength:DATA_CHUNK_SIZE]; |
|
159 |
|
|
160 |
// Make sure nothing went wrong |
|
161 |
if ([inputStream streamStatus] == NSStreamEventErrorOccurred) { |
|
162 |
if (err) { |
|
163 |
*err = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Decompression of %@ failed because we were unable to read from the source data file",sourcePath],NSLocalizedDescriptionKey,[inputStream streamError],NSUnderlyingErrorKey,nil]]; |
|
164 |
} |
|
165 |
[decompressor closeStream]; |
|
166 |
return NO; |
|
167 |
} |
|
168 |
// Have we reached the end of the input data? |
|
169 |
if (!readLength) { |
|
170 |
break; |
|
171 |
} |
|
172 |
|
|
173 |
// Attempt to inflate the chunk of data |
|
174 |
outputData = [decompressor uncompressBytes:inputData length:readLength error:&theError]; |
|
175 |
if (theError) { |
|
176 |
if (err) { |
|
177 |
*err = theError; |
|
178 |
} |
|
179 |
[decompressor closeStream]; |
|
180 |
return NO; |
|
181 |
} |
|
182 |
|
|
183 |
// Write the inflated data out to the destination file |
|
184 |
[outputStream write:[outputData bytes] maxLength:[outputData length]]; |
|
185 |
|
|
186 |
// Make sure nothing went wrong |
|
187 |
if ([inputStream streamStatus] == NSStreamEventErrorOccurred) { |
|
188 |
if (err) { |
|
189 |
*err = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Decompression of %@ failed because we were unable to write to the destination data file at &@",sourcePath,destinationPath],NSLocalizedDescriptionKey,[outputStream streamError],NSUnderlyingErrorKey,nil]]; |
|
190 |
} |
|
191 |
[decompressor closeStream]; |
|
192 |
return NO; |
|
193 |
} |
|
194 |
|
|
195 |
} |
|
196 |
|
|
197 |
[inputStream close]; |
|
198 |
[outputStream close]; |
|
199 |
|
|
200 |
NSError *error = [decompressor closeStream]; |
|
201 |
if (error) { |
|
202 |
if (err) { |
|
203 |
*err = error; |
|
204 |
} |
|
205 |
return NO; |
|
206 |
} |
|
207 |
|
|
208 |
return YES; |
|
209 |
} |
|
210 |
|
|
211 |
|
|
212 |
+ (NSError *)inflateErrorWithCode:(int)code |
|
213 |
{ |
|
214 |
return [NSError errorWithDomain:NetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Decompression of data failed with code %hi",code],NSLocalizedDescriptionKey,nil]]; |
|
215 |
} |
|
216 |
|
|
217 |
@synthesize streamReady; |
|
218 |
@end |
b/asi-http-request-with-pithos/Classes/ASIDownloadCache.h | ||
---|---|---|
1 |
// |
|
2 |
// ASIDownloadCache.h |
|
3 |
// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest |
|
4 |
// |
|
5 |
// Created by Ben Copsey on 01/05/2010. |
|
6 |
// Copyright 2010 All-Seeing Interactive. All rights reserved. |
|
7 |
// |
|
8 |
|
|
9 |
#import <Foundation/Foundation.h> |
|
10 |
#import "ASICacheDelegate.h" |
|
11 |
|
|
12 |
@interface ASIDownloadCache : NSObject <ASICacheDelegate> { |
|
13 |
|
|
14 |
// The default cache policy for this cache |
|
15 |
// Requests that store data in the cache will use this cache policy if their cache policy is set to ASIUseDefaultCachePolicy |
|
16 |
// Defaults to ASIAskServerIfModifiedWhenStaleCachePolicy |
|
17 |
ASICachePolicy defaultCachePolicy; |
|
18 |
|
|
19 |
// The directory in which cached data will be stored |
|
20 |
// Defaults to a directory called 'ASIHTTPRequestCache' in the temporary directory |
|
21 |
NSString *storagePath; |
|
22 |
|
|
23 |
// Mediates access to the cache |
|
24 |
NSRecursiveLock *accessLock; |
|
25 |
|
|
26 |
// When YES, the cache will look for cache-control / pragma: no-cache headers, and won't reuse store responses if it finds them |
|
27 |
BOOL shouldRespectCacheControlHeaders; |
|
28 |
} |
|
29 |
|
|
30 |
// Returns a static instance of an ASIDownloadCache |
|
31 |
// In most circumstances, it will make sense to use this as a global cache, rather than creating your own cache |
|
32 |
// To make ASIHTTPRequests use it automatically, use [ASIHTTPRequest setDefaultCache:[ASIDownloadCache sharedCache]]; |
|
33 |
+ (id)sharedCache; |
|
34 |
|
|
35 |
// A helper function that determines if the server has requested data should not be cached by looking at the request's response headers |
|
36 |
+ (BOOL)serverAllowsResponseCachingForRequest:(ASIHTTPRequest *)request; |
|
37 |
|
|
38 |
@property (assign, nonatomic) ASICachePolicy defaultCachePolicy; |
|
39 |
@property (retain, nonatomic) NSString *storagePath; |
|
40 |
@property (retain) NSRecursiveLock *accessLock; |
|
41 |
@property (assign) BOOL shouldRespectCacheControlHeaders; |
|
42 |
@end |
b/asi-http-request-with-pithos/Classes/ASIDownloadCache.m | ||
---|---|---|
1 |
// |
|
2 |
// ASIDownloadCache.m |
|
3 |
// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest |
|
4 |
// |
|
5 |
// Created by Ben Copsey on 01/05/2010. |
|
6 |
// Copyright 2010 All-Seeing Interactive. All rights reserved. |
|
7 |
// |
|
8 |
|
|
9 |
#import "ASIDownloadCache.h" |
|
10 |
#import "ASIHTTPRequest.h" |
|
11 |
#import <CommonCrypto/CommonHMAC.h> |
|
12 |
|
|
13 |
static ASIDownloadCache *sharedCache = nil; |
|
14 |
|
|
15 |
static NSString *sessionCacheFolder = @"SessionStore"; |
|
16 |
static NSString *permanentCacheFolder = @"PermanentStore"; |
|
17 |
|
|
18 |
@interface ASIDownloadCache () |
|
19 |
+ (NSString *)keyForURL:(NSURL *)url; |
|
20 |
- (NSString *)pathToFile:(NSString *)file; |
|
21 |
@end |
|
22 |
|
|
23 |
@implementation ASIDownloadCache |
|
24 |
|
|
25 |
- (id)init |
|
26 |
{ |
|
27 |
self = [super init]; |
|
28 |
[self setShouldRespectCacheControlHeaders:YES]; |
|
29 |
[self setDefaultCachePolicy:ASIUseDefaultCachePolicy]; |
|
30 |
[self setAccessLock:[[[NSRecursiveLock alloc] init] autorelease]]; |
|
31 |
return self; |
|
32 |
} |
|
33 |
|
|
34 |
+ (id)sharedCache |
|
35 |
{ |
|
36 |
if (!sharedCache) { |
|
37 |
@synchronized(self) { |
|
38 |
if (!sharedCache) { |
|
39 |
sharedCache = [[self alloc] init]; |
|
40 |
[sharedCache setStoragePath:[[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:@"ASIHTTPRequestCache"]]; |
|
41 |
} |
|
42 |
} |
|
43 |
} |
|
44 |
return sharedCache; |
|
45 |
} |
|
46 |
|
|
47 |
- (void)dealloc |
|
48 |
{ |
|
49 |
[storagePath release]; |
|
50 |
[accessLock release]; |
|
51 |
[super dealloc]; |
|
52 |
} |
|
53 |
|
|
54 |
- (NSString *)storagePath |
|
55 |
{ |
|
56 |
[[self accessLock] lock]; |
|
57 |
NSString *p = [[storagePath retain] autorelease]; |
|
58 |
[[self accessLock] unlock]; |
|
59 |
return p; |
|
60 |
} |
|
61 |
|
|
62 |
|
|
63 |
- (void)setStoragePath:(NSString *)path |
|
64 |
{ |
|
65 |
[[self accessLock] lock]; |
|
66 |
[self clearCachedResponsesForStoragePolicy:ASICacheForSessionDurationCacheStoragePolicy]; |
|
67 |
[storagePath release]; |
|
68 |
storagePath = [path retain]; |
|
69 |
|
|
70 |
NSFileManager *fileManager = [[[NSFileManager alloc] init] autorelease]; |
|
71 |
|
|
72 |
BOOL isDirectory = NO; |
|
73 |
NSArray *directories = [NSArray arrayWithObjects:path,[path stringByAppendingPathComponent:sessionCacheFolder],[path stringByAppendingPathComponent:permanentCacheFolder],nil]; |
|
74 |
for (NSString *directory in directories) { |
|
75 |
BOOL exists = [fileManager fileExistsAtPath:directory isDirectory:&isDirectory]; |
|
76 |
if (exists && !isDirectory) { |
|
77 |
[[self accessLock] unlock]; |
|
78 |
[NSException raise:@"FileExistsAtCachePath" format:@"Cannot create a directory for the cache at '%@', because a file already exists",directory]; |
|
79 |
} else if (!exists) { |
|
80 |
[fileManager createDirectoryAtPath:directory withIntermediateDirectories:NO attributes:nil error:nil]; |
|
81 |
if (![fileManager fileExistsAtPath:directory]) { |
|
82 |
[[self accessLock] unlock]; |
|
83 |
[NSException raise:@"FailedToCreateCacheDirectory" format:@"Failed to create a directory for the cache at '%@'",directory]; |
|
84 |
} |
|
85 |
} |
|
86 |
} |
|
87 |
[self clearCachedResponsesForStoragePolicy:ASICacheForSessionDurationCacheStoragePolicy]; |
|
88 |
[[self accessLock] unlock]; |
|
89 |
} |
|
90 |
|
|
91 |
- (void)updateExpiryForRequest:(ASIHTTPRequest *)request maxAge:(NSTimeInterval)maxAge |
|
92 |
{ |
|
93 |
NSString *headerPath = [self pathToStoreCachedResponseHeadersForRequest:request]; |
|
94 |
NSMutableDictionary *cachedHeaders = [NSMutableDictionary dictionaryWithContentsOfFile:headerPath]; |
|
95 |
if (!cachedHeaders) { |
|
96 |
return; |
|
97 |
} |
|
98 |
NSDate *expires = [self expiryDateForRequest:request maxAge:maxAge]; |
|
99 |
if (!expires) { |
|
100 |
return; |
|
101 |
} |
|
102 |
[cachedHeaders setObject:[NSNumber numberWithDouble:[expires timeIntervalSince1970]] forKey:@"X-ASIHTTPRequest-Expires"]; |
|
103 |
[cachedHeaders writeToFile:headerPath atomically:NO]; |
|
104 |
} |
|
105 |
|
|
106 |
- (NSDate *)expiryDateForRequest:(ASIHTTPRequest *)request maxAge:(NSTimeInterval)maxAge |
|
107 |
{ |
|
108 |
NSMutableDictionary *responseHeaders = [NSMutableDictionary dictionaryWithDictionary:[request responseHeaders]]; |
|
109 |
|
|
110 |
// If we weren't given a custom max-age, lets look for one in the response headers |
|
111 |
if (!maxAge) { |
|
112 |
NSString *cacheControl = [[responseHeaders objectForKey:@"Cache-Control"] lowercaseString]; |
|
113 |
if (cacheControl) { |
|
114 |
NSScanner *scanner = [NSScanner scannerWithString:cacheControl]; |
|
115 |
[scanner scanUpToString:@"max-age" intoString:NULL]; |
|
116 |
if ([scanner scanString:@"max-age" intoString:NULL]) { |
|
117 |
[scanner scanString:@"=" intoString:NULL]; |
|
118 |
[scanner scanDouble:&maxAge]; |
|
119 |
} |
|
120 |
} |
|
121 |
} |
|
122 |
|
|
123 |
// RFC 2612 says max-age must override any Expires header |
|
124 |
if (maxAge) { |
|
125 |
return [[NSDate date] addTimeInterval:maxAge]; |
|
126 |
} else { |
|
127 |
NSString *expires = [responseHeaders objectForKey:@"Expires"]; |
|
128 |
if (expires) { |
|
129 |
return [ASIHTTPRequest dateFromRFC1123String:expires]; |
|
130 |
} |
|
131 |
} |
|
132 |
return nil; |
|
133 |
} |
|
134 |
|
|
135 |
- (void)storeResponseForRequest:(ASIHTTPRequest *)request maxAge:(NSTimeInterval)maxAge |
|
136 |
{ |
|
137 |
[[self accessLock] lock]; |
|
138 |
|
|
139 |
if ([request error] || ![request responseHeaders] || ([request cachePolicy] & ASIDoNotWriteToCacheCachePolicy)) { |
|
140 |
[[self accessLock] unlock]; |
|
141 |
return; |
|
142 |
} |
|
143 |
|
|
144 |
// We only cache 200/OK or redirect reponses (redirect responses are cached so the cache works better with no internet connection) |
|
145 |
int responseCode = [request responseStatusCode]; |
|
146 |
if (responseCode != 200 && responseCode != 301 && responseCode != 302 && responseCode != 303 && responseCode != 307) { |
|
147 |
[[self accessLock] unlock]; |
|
148 |
return; |
|
149 |
} |
|
150 |
|
|
151 |
if ([self shouldRespectCacheControlHeaders] && ![[self class] serverAllowsResponseCachingForRequest:request]) { |
|
152 |
[[self accessLock] unlock]; |
|
153 |
return; |
|
154 |
} |
|
155 |
|
|
156 |
NSString *headerPath = [self pathToStoreCachedResponseHeadersForRequest:request]; |
|
157 |
NSString *dataPath = [self pathToStoreCachedResponseDataForRequest:request]; |
|
158 |
|
|
159 |
NSMutableDictionary *responseHeaders = [NSMutableDictionary dictionaryWithDictionary:[request responseHeaders]]; |
|
160 |
if ([request isResponseCompressed]) { |
|
161 |
[responseHeaders removeObjectForKey:@"Content-Encoding"]; |
|
162 |
} |
|
163 |
|
|
164 |
// Create a special 'X-ASIHTTPRequest-Expires' header |
|
165 |
// This is what we use for deciding if cached data is current, rather than parsing the expires / max-age headers individually each time |
|
166 |
// We store this as a timestamp to make reading it easier as NSDateFormatter is quite expensive |
Also available in: Unified diff