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
... This diff was truncated because it exceeds the maximum size that can be displayed.

Also available in: Unified diff