1.1 Binary file .DS_Store has changed
2.1 Binary file ASI-HTTP/.DS_Store has changed
3.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
3.2 +++ b/ASI-HTTP/ASIAuthenticationDialog.h Thu Dec 24 00:14:02 2009 +0000
3.3 @@ -0,0 +1,28 @@
3.4 +//
3.5 +// ASIAuthenticationDialog.h
3.6 +// iPhone
3.7 +//
3.8 +// Created by Ben Copsey on 21/08/2009.
3.9 +// Copyright 2009 All-Seeing Interactive. All rights reserved.
3.10 +//
3.11 +
3.12 +#import <Foundation/Foundation.h>
3.13 +@class ASIHTTPRequest;
3.14 +
3.15 +typedef enum _ASIAuthenticationType {
3.16 + ASIStandardAuthenticationType = 0,
3.17 + ASIProxyAuthenticationType = 1
3.18 +} ASIAuthenticationType;
3.19 +
3.20 +@interface ASIAuthenticationDialog : NSObject <UIActionSheetDelegate, UITableViewDelegate, UITableViewDataSource> {
3.21 + ASIHTTPRequest *request;
3.22 + UIActionSheet *loginDialog;
3.23 + ASIAuthenticationType type;
3.24 +}
3.25 ++ (void)presentAuthenticationDialogForRequest:(ASIHTTPRequest *)request;
3.26 ++ (void)presentProxyAuthenticationDialogForRequest:(ASIHTTPRequest *)request;
3.27 +
3.28 +@property (retain) ASIHTTPRequest *request;
3.29 +@property (retain) UIActionSheet *loginDialog;
3.30 +@property (assign) ASIAuthenticationType type;
3.31 +@end
4.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
4.2 +++ b/ASI-HTTP/ASIAuthenticationDialog.m Thu Dec 24 00:14:02 2009 +0000
4.3 @@ -0,0 +1,236 @@
4.4 +//
4.5 +// ASIAuthenticationDialog.m
4.6 +// iPhone
4.7 +//
4.8 +// Created by Ben Copsey on 21/08/2009.
4.9 +// Copyright 2009 All-Seeing Interactive. All rights reserved.
4.10 +//
4.11 +
4.12 +#import "ASIAuthenticationDialog.h"
4.13 +#import "ASIHTTPRequest.h"
4.14 +
4.15 +ASIAuthenticationDialog *sharedDialog = nil;
4.16 +NSLock *dialogLock = nil;
4.17 +
4.18 +@interface ASIAuthenticationDialog ()
4.19 +- (void)show;
4.20 +@end
4.21 +
4.22 +@implementation ASIAuthenticationDialog
4.23 +
4.24 ++ (void)initialize
4.25 +{
4.26 + if (self == [ASIAuthenticationDialog class]) {
4.27 + dialogLock = [[NSLock alloc] init];
4.28 + }
4.29 +}
4.30 +
4.31 ++ (void)presentProxyAuthenticationDialogForRequest:(ASIHTTPRequest *)request
4.32 +{
4.33 + [dialogLock lock];
4.34 + [sharedDialog release];
4.35 + sharedDialog = [[self alloc] init];
4.36 + [sharedDialog setRequest:request];
4.37 + [sharedDialog setType:ASIProxyAuthenticationType];
4.38 + [sharedDialog show];
4.39 + [dialogLock unlock];
4.40 +}
4.41 +
4.42 ++ (void)presentAuthenticationDialogForRequest:(ASIHTTPRequest *)request
4.43 +{
4.44 + [dialogLock lock];
4.45 + [sharedDialog release];
4.46 + sharedDialog = [[self alloc] init];
4.47 + [sharedDialog setRequest:request];
4.48 + [sharedDialog show];
4.49 + [dialogLock unlock];
4.50 +
4.51 +}
4.52 +
4.53 +- (void)show
4.54 +{
4.55 + // Create an action sheet to show the login dialog
4.56 + [self setLoginDialog:[[[UIActionSheet alloc] init] autorelease]];
4.57 + [[self loginDialog] setActionSheetStyle:UIActionSheetStyleBlackOpaque];
4.58 + [[self loginDialog] setDelegate:self];
4.59 +
4.60 + // We show the login form in a table view, similar to Safari's authentication dialog
4.61 + UITableView *table = [[[UITableView alloc] initWithFrame:CGRectMake(0,80,320,480) style:UITableViewStyleGrouped] autorelease];
4.62 + [table setDelegate:self];
4.63 + [table setDataSource:self];
4.64 + [[self loginDialog] addSubview:table];
4.65 + [[self loginDialog] showInView:[[[UIApplication sharedApplication] windows] objectAtIndex:0]];
4.66 + [[self loginDialog] setFrame:CGRectMake(0,0,320,480)];
4.67 +
4.68 + // Setup the title (Couldn't figure out how to put this in the same toolbar as the buttons)
4.69 + UIToolbar *titleBar = [[[UIToolbar alloc] initWithFrame:CGRectMake(0,0,320,30)] autorelease];
4.70 + UILabel *label = [[[UILabel alloc] initWithFrame:CGRectMake(10,0,300,30)] autorelease];
4.71 + if ([self type] == ASIProxyAuthenticationType) {
4.72 + [label setText:@"Login to this secure proxy server."];
4.73 + } else {
4.74 + [label setText:@"Login to this secure server."];
4.75 + }
4.76 + [label setTextColor:[UIColor blackColor]];
4.77 + [label setFont:[UIFont systemFontOfSize:13.0]];
4.78 + [label setShadowColor:[UIColor colorWithRed:1 green:1 blue:1 alpha:0.5]];
4.79 + [label setShadowOffset:CGSizeMake(0, 1.0)];
4.80 + [label setOpaque:NO];
4.81 + [label setBackgroundColor:nil];
4.82 + [label setTextAlignment:UITextAlignmentCenter];
4.83 +
4.84 + [titleBar addSubview:label];
4.85 + [[self loginDialog] addSubview:titleBar];
4.86 +
4.87 + // Setup the toolbar
4.88 + UIToolbar *toolbar = [[[UIToolbar alloc] initWithFrame:CGRectMake(0,30,320,50)] autorelease];
4.89 +
4.90 + NSMutableArray *items = [[[NSMutableArray alloc] init] autorelease];
4.91 + UIBarButtonItem *backButton = [[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel target:self action:@selector(cancelAuthenticationFromDialog:)] autorelease];
4.92 + [items addObject:backButton];
4.93 +
4.94 + label = [[[UILabel alloc] initWithFrame:CGRectMake(0,0,170,50)] autorelease];
4.95 + if ([self type] == ASIProxyAuthenticationType) {
4.96 + [label setText:[[self request] proxyHost]];
4.97 + } else {
4.98 + [label setText:[[[self request] url] host]];
4.99 + }
4.100 + [label setTextColor:[UIColor whiteColor]];
4.101 + [label setFont:[UIFont boldSystemFontOfSize:22.0]];
4.102 + [label setShadowColor:[UIColor colorWithRed:0 green:0 blue:0 alpha:0.5]];
4.103 + [label setShadowOffset:CGSizeMake(0, -1.0)];
4.104 + [label setOpaque:NO];
4.105 + [label setBackgroundColor:nil];
4.106 + [label setTextAlignment:UITextAlignmentCenter];
4.107 +
4.108 + [toolbar addSubview:label];
4.109 +
4.110 + UIBarButtonItem *labelButton = [[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel target:nil action:nil] autorelease];
4.111 + [labelButton setCustomView:label];
4.112 + [items addObject:labelButton];
4.113 + [items addObject:[[[UIBarButtonItem alloc] initWithTitle:@"Login" style:UIBarButtonItemStyleDone target:self action:@selector(loginWithCredentialsFromDialog:)] autorelease]];
4.114 + [toolbar setItems:items];
4.115 +
4.116 + [[self loginDialog] addSubview:toolbar];
4.117 +
4.118 + // Force reload the table content, and focus the first field to show the keyboard
4.119 + [table reloadData];
4.120 + [[[[table cellForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]] subviews] objectAtIndex:2] becomeFirstResponder];
4.121 +
4.122 +}
4.123 +
4.124 +- (void)cancelAuthenticationFromDialog:(id)sender
4.125 +{
4.126 + [[self request] cancelAuthentication];
4.127 + [[self loginDialog] dismissWithClickedButtonIndex:0 animated:YES];
4.128 +}
4.129 +
4.130 +- (void)loginWithCredentialsFromDialog:(id)sender
4.131 +{
4.132 + NSString *username = [[[[[[[self loginDialog] subviews] objectAtIndex:0] cellForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]] subviews] objectAtIndex:2] text];
4.133 + NSString *password = [[[[[[[self loginDialog] subviews] objectAtIndex:0] cellForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:1]] subviews] objectAtIndex:2] text];
4.134 +
4.135 + if ([self type] == ASIProxyAuthenticationType) {
4.136 + [[self request] setProxyUsername:username];
4.137 + [[self request] setProxyPassword:password];
4.138 + } else {
4.139 + [[self request] setUsername:username];
4.140 + [[self request] setPassword:password];
4.141 + }
4.142 +
4.143 + // Handle NTLM domains
4.144 + NSString *scheme = ([self type] == ASIStandardAuthenticationType) ? [[self request] authenticationScheme] : [[self request] proxyAuthenticationScheme];
4.145 + if ([scheme isEqualToString:(NSString *)kCFHTTPAuthenticationSchemeNTLM]) {
4.146 + NSString *domain = [[[[[[[self loginDialog] subviews] objectAtIndex:0] cellForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:2]] subviews] objectAtIndex:2] text];
4.147 + if ([self type] == ASIProxyAuthenticationType) {
4.148 + [[self request] setProxyDomain:domain];
4.149 + } else {
4.150 + [[self request] setDomain:domain];
4.151 + }
4.152 + }
4.153 +
4.154 + [[self loginDialog] dismissWithClickedButtonIndex:1 animated:YES];
4.155 + [[self request] retryUsingSuppliedCredentials];
4.156 +}
4.157 +
4.158 +
4.159 +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
4.160 +{
4.161 + NSString *scheme = ([self type] == ASIStandardAuthenticationType) ? [[self request] authenticationScheme] : [[self request] proxyAuthenticationScheme];
4.162 + if ([scheme isEqualToString:(NSString *)kCFHTTPAuthenticationSchemeNTLM]) {
4.163 + return 3;
4.164 + }
4.165 + return 2;
4.166 +}
4.167 +
4.168 +- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section
4.169 +{
4.170 + if (section == [self numberOfSectionsInTableView:tableView]-1) {
4.171 + return 30;
4.172 + }
4.173 + return 0;
4.174 +}
4.175 +
4.176 +- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
4.177 +{
4.178 + if (section == 0) {
4.179 + return 30;
4.180 + }
4.181 + return 0;
4.182 +}
4.183 +
4.184 +- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
4.185 +{
4.186 + if (section == 0) {
4.187 + return [[self request] authenticationRealm];
4.188 + }
4.189 + return nil;
4.190 +}
4.191 +
4.192 +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
4.193 +{
4.194 +#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_3_0
4.195 + UITableViewCell *cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:nil] autorelease];
4.196 +#else
4.197 + UITableViewCell *cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil] autorelease];
4.198 +#endif
4.199 +
4.200 + [cell setSelectionStyle:UITableViewCellSelectionStyleNone];
4.201 + UITextField *textField = [[[UITextField alloc] initWithFrame:CGRectMake(20,12,260,25)] autorelease];
4.202 + [textField setAutocapitalizationType:UITextAutocapitalizationTypeNone];
4.203 + if ([indexPath section] == 0) {
4.204 + [textField setPlaceholder:@"User"];
4.205 + } else if ([indexPath section] == 1) {
4.206 + [textField setPlaceholder:@"Password"];
4.207 + [textField setSecureTextEntry:YES];
4.208 + } else if ([indexPath section] == 2) {
4.209 + [textField setPlaceholder:@"Domain"];
4.210 + }
4.211 + [cell addSubview:textField];
4.212 +
4.213 + return cell;
4.214 +}
4.215 +
4.216 +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
4.217 +{
4.218 + return 1;
4.219 +}
4.220 +
4.221 +
4.222 +- (NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section
4.223 +{
4.224 + if (section == [self numberOfSectionsInTableView:tableView]-1) {
4.225 + // If we're using Basic authentication and the connection is not using SSL, we'll show the plain text message
4.226 + if ([[[self request] authenticationScheme] isEqualToString:(NSString *)kCFHTTPAuthenticationSchemeBasic] && ![[[[self request] url] scheme] isEqualToString:@"https"]) {
4.227 + return @"Password will be sent in the clear.";
4.228 + // We are using Digest, NTLM, or any scheme over SSL
4.229 + } else {
4.230 + return @"Password will be sent securely.";
4.231 + }
4.232 + }
4.233 + return nil;
4.234 +}
4.235 +
4.236 +@synthesize request;
4.237 +@synthesize loginDialog;
4.238 +@synthesize type;
4.239 +@end
5.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
5.2 +++ b/ASI-HTTP/ASIFormDataRequest.h Thu Dec 24 00:14:02 2009 +0000
5.3 @@ -0,0 +1,54 @@
5.4 +//
5.5 +// ASIFormDataRequest.h
5.6 +// asi-http-request
5.7 +//
5.8 +// Created by Ben Copsey on 07/11/2008.
5.9 +// Copyright 2008-2009 All-Seeing Interactive. All rights reserved.
5.10 +//
5.11 +
5.12 +#import <Foundation/Foundation.h>
5.13 +#import "ASIHTTPRequest.h"
5.14 +
5.15 +typedef enum _ASIPostFormat {
5.16 + ASIMultipartFormDataPostFormat = 0,
5.17 + ASIURLEncodedPostFormat = 1
5.18 +
5.19 +} ASIPostFormat;
5.20 +
5.21 +@interface ASIFormDataRequest : ASIHTTPRequest {
5.22 +
5.23 + // Parameters that will be POSTed to the url
5.24 + NSMutableDictionary *postData;
5.25 +
5.26 + // Files that will be POSTed to the url
5.27 + NSMutableDictionary *fileData;
5.28 +
5.29 + ASIPostFormat postFormat;
5.30 +
5.31 + NSStringEncoding stringEncoding;
5.32 +}
5.33 +
5.34 +#pragma mark utilities
5.35 +- (NSString*)encodeURL:(NSString *)string;
5.36 +
5.37 +#pragma mark setup request
5.38 +
5.39 +// Add a POST variable to the request
5.40 +- (void)setPostValue:(id <NSObject>)value forKey:(NSString *)key;
5.41 +
5.42 +// Add the contents of a local file to the request
5.43 +- (void)setFile:(NSString *)filePath forKey:(NSString *)key;
5.44 +
5.45 +// Same as above, but you can specify the content-type and file name
5.46 +- (void)setFile:(id)data withFileName:(NSString *)fileName andContentType:(NSString *)contentType forKey:(NSString *)key;
5.47 +
5.48 +// Add the contents of an NSData object to the request
5.49 +- (void)setData:(NSData *)data forKey:(NSString *)key;
5.50 +
5.51 +// Same as above, but you can specify the content-type and file name
5.52 +- (void)setData:(id)data withFileName:(NSString *)fileName andContentType:(NSString *)contentType forKey:(NSString *)key;
5.53 +
5.54 +
5.55 +@property (assign) ASIPostFormat postFormat;
5.56 +@property (assign) NSStringEncoding stringEncoding;
5.57 +@end
6.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
6.2 +++ b/ASI-HTTP/ASIFormDataRequest.m Thu Dec 24 00:14:02 2009 +0000
6.3 @@ -0,0 +1,221 @@
6.4 +//
6.5 +// ASIFormDataRequest.m
6.6 +// asi-http-request
6.7 +//
6.8 +// Created by Ben Copsey on 07/11/2008.
6.9 +// Copyright 2008-2009 All-Seeing Interactive. All rights reserved.
6.10 +//
6.11 +
6.12 +#import "ASIFormDataRequest.h"
6.13 +
6.14 +
6.15 +// Private stuff
6.16 +@interface ASIFormDataRequest ()
6.17 +- (void)buildMultipartFormDataPostBody;
6.18 +- (void)buildURLEncodedPostBody;
6.19 +@property (retain) NSMutableDictionary *postData;
6.20 +@property (retain) NSMutableDictionary *fileData;
6.21 +@end
6.22 +
6.23 +@implementation ASIFormDataRequest
6.24 +
6.25 +#pragma mark utilities
6.26 +- (NSString*)encodeURL:(NSString *)string
6.27 +{
6.28 + NSString *newString = [(NSString *)CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)string, NULL, CFSTR(":/?#[]@!$ &'()*+,;=\"<>%{}|\\^~`"), CFStringConvertNSStringEncodingToEncoding([self stringEncoding])) autorelease];
6.29 + if (newString) {
6.30 + return newString;
6.31 + }
6.32 + return @"";
6.33 +}
6.34 +
6.35 +#pragma mark init / dealloc
6.36 +
6.37 ++ (id)requestWithURL:(NSURL *)newURL
6.38 +{
6.39 + return [[[self alloc] initWithURL:newURL] autorelease];
6.40 +}
6.41 +
6.42 +- (id)initWithURL:(NSURL *)newURL
6.43 +{
6.44 + self = [super initWithURL:newURL];
6.45 + [self setPostFormat:ASIURLEncodedPostFormat];
6.46 + [self setStringEncoding:NSUTF8StringEncoding];
6.47 + return self;
6.48 +}
6.49 +
6.50 +- (void)dealloc
6.51 +{
6.52 + [postData release];
6.53 + [fileData release];
6.54 + [super dealloc];
6.55 +}
6.56 +
6.57 +#pragma mark setup request
6.58 +
6.59 +- (void)setPostValue:(id <NSObject>)value forKey:(NSString *)key
6.60 +{
6.61 + if (![self postData]) {
6.62 + [self setPostData:[NSMutableDictionary dictionary]];
6.63 + }
6.64 + [[self postData] setValue:[value description] forKey:key];
6.65 + [self setRequestMethod:@"POST"];
6.66 +}
6.67 +
6.68 +- (void)setFile:(NSString *)filePath forKey:(NSString *)key
6.69 +{
6.70 + [self setFile:filePath withFileName:nil andContentType:nil forKey:key];
6.71 +}
6.72 +
6.73 +- (void)setFile:(id)data withFileName:(NSString *)fileName andContentType:(NSString *)contentType forKey:(NSString *)key
6.74 +{
6.75 + if (![self fileData]) {
6.76 + [self setFileData:[NSMutableDictionary dictionary]];
6.77 + }
6.78 +
6.79 + // If data is a path to a local file
6.80 + if ([data isKindOfClass:[NSString class]]) {
6.81 + BOOL isDirectory = NO;
6.82 + BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:(NSString *)data isDirectory:&isDirectory];
6.83 + if (!fileExists || isDirectory) {
6.84 + [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIInternalErrorWhileBuildingRequestType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"No file exists at %@",data],NSLocalizedDescriptionKey,nil]]];
6.85 + }
6.86 +
6.87 + // If the caller didn't specify a custom file name, we'll use the file name of the file we were passed
6.88 + if (!fileName) {
6.89 + fileName = [(NSString *)data lastPathComponent];
6.90 + }
6.91 +
6.92 + // If we were given the path to a file, and the user didn't specify a mime type, we can detect it (currently only on Mac OS)
6.93 + // Will return 'application/octet-stream' on iPhone, or if the mime type cannot be determined
6.94 + if (!contentType) {
6.95 + contentType = [ASIHTTPRequest mimeTypeForFileAtPath:data];
6.96 + }
6.97 + }
6.98 +
6.99 + NSDictionary *fileInfo = [NSDictionary dictionaryWithObjectsAndKeys:data, @"data", contentType, @"contentType", fileName, @"fileName", nil];
6.100 + [[self fileData] setObject:fileInfo forKey:key];
6.101 + [self setRequestMethod: @"POST"];
6.102 +}
6.103 +
6.104 +- (void)setData:(NSData *)data forKey:(NSString *)key
6.105 +{
6.106 + [self setData:data withFileName:@"file" andContentType:nil forKey:key];
6.107 +}
6.108 +
6.109 +- (void)setData:(id)data withFileName:(NSString *)fileName andContentType:(NSString *)contentType forKey:(NSString *)key
6.110 +{
6.111 + if (![self fileData]) {
6.112 + [self setFileData:[NSMutableDictionary dictionary]];
6.113 + }
6.114 + if (!contentType) {
6.115 + contentType = @"application/octet-stream";
6.116 + }
6.117 +
6.118 + NSDictionary *fileInfo = [NSDictionary dictionaryWithObjectsAndKeys:data, @"data", contentType, @"contentType", fileName, @"fileName", nil];
6.119 + [[self fileData] setObject:fileInfo forKey:key];
6.120 + [self setRequestMethod: @"POST"];
6.121 +}
6.122 +
6.123 +- (void)buildPostBody
6.124 +{
6.125 + if (![self postData] && ![self fileData]) {
6.126 + [super buildPostBody];
6.127 + return;
6.128 + }
6.129 + if ([[self fileData] count] > 0) {
6.130 + [self setShouldStreamPostDataFromDisk:YES];
6.131 + }
6.132 +
6.133 + if ([self postFormat] == ASIURLEncodedPostFormat) {
6.134 + [self buildURLEncodedPostBody];
6.135 + } else {
6.136 + [self buildMultipartFormDataPostBody];
6.137 + }
6.138 +
6.139 + [super buildPostBody];
6.140 +}
6.141 +
6.142 +
6.143 +- (void)buildMultipartFormDataPostBody
6.144 +{
6.145 + NSString *charset = (NSString *)CFStringConvertEncodingToIANACharSetName(CFStringConvertNSStringEncodingToEncoding([self stringEncoding]));
6.146 +
6.147 + // Set your own boundary string only if really obsessive. We don't bother to check if post data contains the boundary, since it's pretty unlikely that it does.
6.148 + NSString *stringBoundary = @"0xKhTmLbOuNdArY";
6.149 +
6.150 + [self addRequestHeader:@"Content-Type" value:[NSString stringWithFormat:@"multipart/form-data; charset=%@; boundary=%@", charset, stringBoundary]];
6.151 +
6.152 + [self appendPostData:[[NSString stringWithFormat:@"--%@\r\n",stringBoundary] dataUsingEncoding:[self stringEncoding]]];
6.153 +
6.154 + // Adds post data
6.155 + NSData *endItemBoundary = [[NSString stringWithFormat:@"\r\n--%@\r\n",stringBoundary] dataUsingEncoding:[self stringEncoding]];
6.156 + NSEnumerator *e = [[self postData] keyEnumerator];
6.157 + NSString *key;
6.158 + int i=0;
6.159 + while (key = [e nextObject]) {
6.160 + [self appendPostData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n\r\n",key] dataUsingEncoding:[self stringEncoding]]];
6.161 + [self appendPostData:[[[self postData] objectForKey:key] dataUsingEncoding:[self stringEncoding]]];
6.162 + i++;
6.163 + if (i != [[self postData] count] || [[self fileData] count] > 0) { //Only add the boundary if this is not the last item in the post body
6.164 + [self appendPostData:endItemBoundary];
6.165 + }
6.166 + }
6.167 +
6.168 + // Adds files to upload
6.169 + e = [fileData keyEnumerator];
6.170 + i=0;
6.171 + while (key = [e nextObject]) {
6.172 + NSDictionary *fileInfo = [[self fileData] objectForKey:key];
6.173 + id file = [fileInfo objectForKey:@"data"];
6.174 + NSString *contentType = [fileInfo objectForKey:@"contentType"];
6.175 + NSString *fileName = [fileInfo objectForKey:@"fileName"];
6.176 +
6.177 + [self appendPostData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\"%@\"\r\n", key, fileName] dataUsingEncoding:[self stringEncoding]]];
6.178 + [self appendPostData:[[NSString stringWithFormat:@"Content-Type: %@; charset=%@\r\n\r\n", contentType, charset] dataUsingEncoding:[self stringEncoding]]];
6.179 +
6.180 + if ([file isKindOfClass:[NSString class]]) {
6.181 + [self appendPostDataFromFile:file];
6.182 + } else {
6.183 + [self appendPostData:file];
6.184 + }
6.185 + i++;
6.186 + // Only add the boundary if this is not the last item in the post body
6.187 + if (i != [[self fileData] count]) {
6.188 + [self appendPostData:endItemBoundary];
6.189 + }
6.190 + }
6.191 +
6.192 + [self appendPostData:[[NSString stringWithFormat:@"\r\n--%@--\r\n",stringBoundary] dataUsingEncoding:[self stringEncoding]]];
6.193 +
6.194 +}
6.195 +
6.196 +- (void)buildURLEncodedPostBody
6.197 +{
6.198 + // We can't post binary data using application/x-www-form-urlencoded
6.199 + if ([[self fileData] count] > 0) {
6.200 + [self setPostFormat:ASIMultipartFormDataPostFormat];
6.201 + [self buildMultipartFormDataPostBody];
6.202 + return;
6.203 + }
6.204 + NSString *charset = (NSString *)CFStringConvertEncodingToIANACharSetName(CFStringConvertNSStringEncodingToEncoding([self stringEncoding]));
6.205 +
6.206 + [self addRequestHeader:@"Content-Type" value:[NSString stringWithFormat:@"application/x-www-form-urlencoded; charset=%@",charset]];
6.207 +
6.208 +
6.209 + NSEnumerator *e = [[self postData] keyEnumerator];
6.210 + NSString *key;
6.211 + int i=0;
6.212 + int count = [[self postData] count]-1;
6.213 + while (key = [e nextObject]) {
6.214 + NSString *data = [NSString stringWithFormat:@"%@=%@%@", [self encodeURL:key], [self encodeURL:[[self postData] objectForKey:key]],(i<count ? @"&" : @"")];
6.215 + [self appendPostData:[data dataUsingEncoding:[self stringEncoding]]];
6.216 + i++;
6.217 + }
6.218 +}
6.219 +
6.220 +@synthesize postData;
6.221 +@synthesize fileData;
6.222 +@synthesize postFormat;
6.223 +@synthesize stringEncoding;
6.224 +@end
7.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
7.2 +++ b/ASI-HTTP/ASIHTTPRequest.h Thu Dec 24 00:14:02 2009 +0000
7.3 @@ -0,0 +1,611 @@
7.4 +//
7.5 +// ASIHTTPRequest.h
7.6 +//
7.7 +// Created by Ben Copsey on 04/10/2007.
7.8 +// Copyright 2007-2009 All-Seeing Interactive. All rights reserved.
7.9 +//
7.10 +// A guide to the main features is available at:
7.11 +// http://allseeing-i.com/ASIHTTPRequest
7.12 +//
7.13 +// Portions are based on the ImageClient example from Apple:
7.14 +// See: http://developer.apple.com/samplecode/ImageClient/listing37.html
7.15 +
7.16 +#import <Foundation/Foundation.h>
7.17 +#if TARGET_OS_IPHONE
7.18 + #import <CFNetwork/CFNetwork.h>
7.19 +#endif
7.20 +#import <stdio.h>
7.21 +
7.22 +// Make targeting 2.2.1 more reliable
7.23 +// See: http://www.blumtnwerx.com/blog/2009/06/cross-sdk-code-hygiene-in-xcode/
7.24 +#ifndef __IPHONE_3_0
7.25 + #define __IPHONE_3_0 30000
7.26 +#endif
7.27 +
7.28 +
7.29 +typedef enum _ASINetworkErrorType {
7.30 + ASIConnectionFailureErrorType = 1,
7.31 + ASIRequestTimedOutErrorType = 2,
7.32 + ASIAuthenticationErrorType = 3,
7.33 + ASIRequestCancelledErrorType = 4,
7.34 + ASIUnableToCreateRequestErrorType = 5,
7.35 + ASIInternalErrorWhileBuildingRequestType = 6,
7.36 + ASIInternalErrorWhileApplyingCredentialsType = 7,
7.37 + ASIFileManagementError = 8,
7.38 + ASITooMuchRedirectionErrorType = 9
7.39 +
7.40 +} ASINetworkErrorType;
7.41 +
7.42 +// The error domain that all errors generated by ASIHTTPRequest use
7.43 +extern NSString* const NetworkRequestErrorDomain;
7.44 +
7.45 +// You can use this number to throttle upload and download bandwidth in iPhone OS apps send or receive a large amount of data
7.46 +// This may help apps that might otherwise be rejected for inclusion into the app store for using excessive bandwidth
7.47 +// This number is not official, as far as I know there is no officially documented bandwidth limit
7.48 +extern unsigned long const ASIWWANBandwidthThrottleAmount;
7.49 +
7.50 +@interface ASIHTTPRequest : NSOperation {
7.51 +
7.52 + // The url for this operation, should include GET params in the query string where appropriate
7.53 + NSURL *url;
7.54 +
7.55 + // The delegate, you need to manage setting and talking to your delegate in your subclasses
7.56 + id delegate;
7.57 +
7.58 + // A queue delegate that should *ALSO* be notified of delegate message (used by ASINetworkQueue)
7.59 + id queue;
7.60 +
7.61 + // HTTP method to use (GET / POST / PUT / DELETE / HEAD). Defaults to GET
7.62 + NSString *requestMethod;
7.63 +
7.64 + // Request body - only used when the whole body is stored in memory (shouldStreamPostDataFromDisk is false)
7.65 + NSMutableData *postBody;
7.66 +
7.67 + // gzipped request body used when shouldCompressRequestBody is YES
7.68 + NSData *compressedPostBody;
7.69 +
7.70 + // When true, post body will be streamed from a file on disk, rather than loaded into memory at once (useful for large uploads)
7.71 + // Automatically set to true in ASIFormDataRequests when using setFile:forKey:
7.72 + BOOL shouldStreamPostDataFromDisk;
7.73 +
7.74 + // Path to file used to store post body (when shouldStreamPostDataFromDisk is true)
7.75 + // You can set this yourself - useful if you want to PUT a file from local disk
7.76 + NSString *postBodyFilePath;
7.77 +
7.78 + // Path to a temporary file used to store a deflated post body (when shouldCompressPostBody is YES)
7.79 + NSString *compressedPostBodyFilePath;
7.80 +
7.81 + // Set to true when ASIHTTPRequest automatically created a temporary file containing the request body (when true, the file at postBodyFilePath will be deleted at the end of the request)
7.82 + BOOL didCreateTemporaryPostDataFile;
7.83 +
7.84 + // Used when writing to the post body when shouldStreamPostDataFromDisk is true (via appendPostData: or appendPostDataFromFile:)
7.85 + NSOutputStream *postBodyWriteStream;
7.86 +
7.87 + // Used for reading from the post body when sending the request
7.88 + NSInputStream *postBodyReadStream;
7.89 +
7.90 + // Dictionary for custom HTTP request headers
7.91 + NSMutableDictionary *requestHeaders;
7.92 +
7.93 + // Will be populated with HTTP response headers from the server
7.94 + NSDictionary *responseHeaders;
7.95 +
7.96 + // Can be used to manually insert cookie headers to a request, but it's more likely that sessionCookies will do this for you
7.97 + NSMutableArray *requestCookies;
7.98 +
7.99 + // Will be populated with cookies
7.100 + NSArray *responseCookies;
7.101 +
7.102 + // If use useCookiePersistance is true, network requests will present valid cookies from previous requests
7.103 + BOOL useCookiePersistance;
7.104 +
7.105 + // If useKeychainPersistance is true, network requests will attempt to read credentials from the keychain, and will save them in the keychain when they are successfully presented
7.106 + BOOL useKeychainPersistance;
7.107 +
7.108 + // If useSessionPersistance is true, network requests will save credentials and reuse for the duration of the session (until clearSession is called)
7.109 + BOOL useSessionPersistance;
7.110 +
7.111 + // If allowCompressedResponse is true, requests will inform the server they can accept compressed data, and will automatically decompress gzipped responses. Default is true.
7.112 + BOOL allowCompressedResponse;
7.113 +
7.114 + // If shouldCompressRequestBody is true, the request body will be gzipped. Default is false.
7.115 + // You will probably need to enable this feature on your webserver to make this work. Tested with apache only.
7.116 + BOOL shouldCompressRequestBody;
7.117 +
7.118 + // When downloadDestinationPath is set, the result of this request will be downloaded to the file at this location
7.119 + // If downloadDestinationPath is not set, download data will be stored in memory
7.120 + NSString *downloadDestinationPath;
7.121 +
7.122 + //The location that files will be downloaded to. Once a download is complete, files will be decompressed (if necessary) and moved to downloadDestinationPath
7.123 + NSString *temporaryFileDownloadPath;
7.124 +
7.125 + // Used for writing data to a file when downloadDestinationPath is set
7.126 + NSOutputStream *fileDownloadOutputStream;
7.127 +
7.128 + // When the request fails or completes successfully, complete will be true
7.129 + BOOL complete;
7.130 +
7.131 + // If an error occurs, error will contain an NSError
7.132 + // If error code is = ASIConnectionFailureErrorType (1, Connection failure occurred) - inspect [[error userInfo] objectForKey:NSUnderlyingErrorKey] for more information
7.133 + NSError *error;
7.134 +
7.135 + // Username and password used for authentication
7.136 + NSString *username;
7.137 + NSString *password;
7.138 +
7.139 + // Domain used for NTLM authentication
7.140 + NSString *domain;
7.141 +
7.142 + // Username and password used for proxy authentication
7.143 + NSString *proxyUsername;
7.144 + NSString *proxyPassword;
7.145 +
7.146 + // Domain used for NTLM proxy authentication
7.147 + NSString *proxyDomain;
7.148 +
7.149 + // Delegate for displaying upload progress (usually an NSProgressIndicator, but you can supply a different object and handle this yourself)
7.150 + id uploadProgressDelegate;
7.151 +
7.152 + // Delegate for displaying download progress (usually an NSProgressIndicator, but you can supply a different object and handle this yourself)
7.153 + id downloadProgressDelegate;
7.154 +
7.155 + // Whether we've seen the headers of the response yet
7.156 + BOOL haveExaminedHeaders;
7.157 +
7.158 + // Data we receive will be stored here. Data may be compressed unless allowCompressedResponse is false - you should use [request responseData] instead in most cases
7.159 + NSMutableData *rawResponseData;
7.160 +
7.161 + // Used for sending and receiving data
7.162 + CFHTTPMessageRef request;
7.163 + CFReadStreamRef readStream;
7.164 +
7.165 + // Used for authentication
7.166 + CFHTTPAuthenticationRef requestAuthentication;
7.167 + NSMutableDictionary *requestCredentials;
7.168 +
7.169 + // Used during NTLM authentication
7.170 + int authenticationRetryCount;
7.171 +
7.172 + // Authentication scheme (Basic, Digest, NTLM)
7.173 + NSString *authenticationScheme;
7.174 +
7.175 + // Realm for authentication when credentials are required
7.176 + NSString *authenticationRealm;
7.177 +
7.178 + // And now, the same thing, but for authenticating proxies
7.179 + BOOL needsProxyAuthentication;
7.180 +
7.181 + // When YES, ASIHTTPRequest will present a dialog allowing users to enter credentials when no-matching credentials were found for a server that requires authentication
7.182 + // The dialog will not be shown if your delegate responds to authenticationNeededForRequest:
7.183 + // Default is NO.
7.184 + BOOL shouldPresentAuthenticationDialog;
7.185 +
7.186 + // When YES, ASIHTTPRequest will present a dialog allowing users to enter credentials when no-matching credentials were found for a proxy server that requires authentication
7.187 + // The dialog will not be shown if your delegate responds to proxyAuthenticationNeededForRequest:
7.188 + // Default is YES (basically, because most people won't want the hassle of adding support for authenticating proxies to their apps)
7.189 + BOOL shouldPresentProxyAuthenticationDialog;
7.190 +
7.191 + // Used for proxy authentication
7.192 + CFHTTPAuthenticationRef proxyAuthentication;
7.193 + NSMutableDictionary *proxyCredentials;
7.194 +
7.195 + // Used during authentication with an NTLM proxy
7.196 + int proxyAuthenticationRetryCount;
7.197 +
7.198 + // Authentication scheme for the proxy (Basic, Digest, NTLM)
7.199 + NSString *proxyAuthenticationScheme;
7.200 +
7.201 + // Realm for proxy authentication when credentials are required
7.202 + NSString *proxyAuthenticationRealm;
7.203 +
7.204 + // HTTP status code, eg: 200 = OK, 404 = Not found etc
7.205 + int responseStatusCode;
7.206 +
7.207 + NSString *responseStatusMessage;
7.208 +
7.209 + // Size of the response
7.210 + unsigned long long contentLength;
7.211 +
7.212 + // Size of the partially downloaded content
7.213 + unsigned long long partialDownloadSize;
7.214 +
7.215 + // Size of the POST payload
7.216 + unsigned long long postLength;
7.217 +
7.218 + // The total amount of downloaded data
7.219 + unsigned long long totalBytesRead;
7.220 +
7.221 + // The total amount of uploaded data
7.222 + unsigned long long totalBytesSent;
7.223 +
7.224 + // Last amount of data read (used for incrementing progress)
7.225 + unsigned long long lastBytesRead;
7.226 +
7.227 + // Last amount of data sent (used for incrementing progress)
7.228 + unsigned long long lastBytesSent;
7.229 +
7.230 + // This lock will block the request until the delegate supplies authentication info
7.231 + NSConditionLock *authenticationLock;
7.232 +
7.233 + // This lock prevents the operation from being cancelled at an inopportune moment
7.234 + NSLock *cancelledLock;
7.235 +
7.236 + // Called on the delegate when the request completes successfully
7.237 + SEL didFinishSelector;
7.238 +
7.239 + // Called on the delegate when the request fails
7.240 + SEL didFailSelector;
7.241 +
7.242 + // Used for recording when something last happened during the request, we will compare this value with the current date to time out requests when appropriate
7.243 + NSDate *lastActivityTime;
7.244 +
7.245 + // Number of seconds to wait before timing out - default is 10
7.246 + NSTimeInterval timeOutSeconds;
7.247 +
7.248 + // Autorelease pool for the main loop, since it's highly likely that this operation will run in a thread
7.249 + NSAutoreleasePool *pool;
7.250 +
7.251 + // Will be YES when a HEAD request will handle the content-length before this request starts
7.252 + BOOL shouldResetProgressIndicators;
7.253 +
7.254 + // Used by HEAD requests when showAccurateProgress is YES to preset the content-length for this request
7.255 + ASIHTTPRequest *mainRequest;
7.256 +
7.257 + // When NO, this request will only update the progress indicator when it completes
7.258 + // When YES, this request will update the progress indicator according to how much data it has received so far
7.259 + // The default for requests is YES
7.260 + // Also see the comments in ASINetworkQueue.h
7.261 + BOOL showAccurateProgress;
7.262 +
7.263 + // Used to ensure the progress indicator is only incremented once when showAccurateProgress = NO
7.264 + BOOL updatedProgress;
7.265 +
7.266 + // Prevents the body of the post being built more than once (largely for subclasses)
7.267 + BOOL haveBuiltPostBody;
7.268 +
7.269 + // Used internally, may reflect the size of the internal buffer used by CFNetwork
7.270 + // POST / PUT operations with body sizes greater than uploadBufferSize will not timeout unless more than uploadBufferSize bytes have been sent
7.271 + // Likely to be 32KB on iPhone 3.0, 128KB on Mac OS X Leopard and iPhone 2.2.x
7.272 + unsigned long long uploadBufferSize;
7.273 +
7.274 + // Text encoding for responses that do not send a Content-Type with a charset value. Defaults to NSISOLatin1StringEncoding
7.275 + NSStringEncoding defaultResponseEncoding;
7.276 +
7.277 + // The text encoding of the response, will be defaultResponseEncoding if the server didn't specify. Can't be set.
7.278 + NSStringEncoding responseEncoding;
7.279 +
7.280 + // Tells ASIHTTPRequest not to delete partial downloads, and allows it to use an existing file to resume a download. Defaults to NO.
7.281 + BOOL allowResumeForFileDownloads;
7.282 +
7.283 + // Custom user information associated with the request
7.284 + NSDictionary *userInfo;
7.285 +
7.286 + // Use HTTP 1.0 rather than 1.1 (defaults to false)
7.287 + BOOL useHTTPVersionOne;
7.288 +
7.289 + // When YES, requests will automatically redirect when they get a HTTP 30x header (defaults to YES)
7.290 + BOOL shouldRedirect;
7.291 +
7.292 + // Used internally to tell the main loop we need to stop and retry with a new url
7.293 + BOOL needsRedirect;
7.294 +
7.295 + // Incremented every time this request redirects. When it reaches 5, we give up
7.296 + int redirectCount;
7.297 +
7.298 + // When NO, requests will not check the secure certificate is valid (use for self-signed cerficates during development, DO NOT USE IN PRODUCTION) Default is YES
7.299 + BOOL validatesSecureCertificate;
7.300 +
7.301 + // Details on the proxy to use - you could set these yourself, but it's probably best to let ASIHTTPRequest detect the system proxy settings
7.302 + NSString *proxyHost;
7.303 + int proxyPort;
7.304 +
7.305 + // URL for a PAC (Proxy Auto Configuration) file. If you want to set this yourself, it's probably best if you use a local file
7.306 + NSURL *PACurl;
7.307 +
7.308 + // True when request is attempting to handle an authentication challenge
7.309 + BOOL authenticationChallengeInProgress;
7.310 +
7.311 + // When YES, ASIHTTPRequests will present credentials from the session store for requests to the same server before being asked for them
7.312 + // This avoids an extra round trip for requests after authentication has succeeded, which is much for efficient for authenticated requests with large bodies, or on slower connections
7.313 + // Set to NO to only present credentials when explictly asked for them
7.314 + // This only affects credentials stored in the session cache when useSessionPersistance is YES. Credentials from the keychain are never presented unless the server asks for them
7.315 + // Default is YES
7.316 + BOOL shouldPresentCredentialsBeforeChallenge;
7.317 +}
7.318 +
7.319 +#pragma mark init / dealloc
7.320 +
7.321 +// Should be an HTTP or HTTPS url, may include username and password if appropriate
7.322 +- (id)initWithURL:(NSURL *)newURL;
7.323 +
7.324 +// Convenience constructor
7.325 ++ (id)requestWithURL:(NSURL *)newURL;
7.326 +
7.327 +#pragma mark setup request
7.328 +
7.329 +// Add a custom header to the request
7.330 +- (void)addRequestHeader:(NSString *)header value:(NSString *)value;
7.331 +
7.332 +- (void)buildPostBody;
7.333 +
7.334 +// Called to add data to the post body. Will append to postBody when shouldStreamPostDataFromDisk is false, or write to postBodyWriteStream when true
7.335 +- (void)appendPostData:(NSData *)data;
7.336 +- (void)appendPostDataFromFile:(NSString *)file;
7.337 +
7.338 +#pragma mark get information about this
7.339 +
7.340 +// Returns the contents of the result as an NSString (not appropriate for binary data - used responseData instead)
7.341 +- (NSString *)responseString;
7.342 +
7.343 +// Response data, automatically uncompressed where appropriate
7.344 +- (NSData *)responseData;
7.345 +
7.346 +// Returns true if the response was gzip compressed
7.347 +- (BOOL)isResponseCompressed;
7.348 +
7.349 +#pragma mark running a request
7.350 +
7.351 +// Run a request asynchronously by adding it to the global queue
7.352 +// (Use [request start] for a synchronous request)
7.353 +- (void)startAsynchronous;
7.354 +
7.355 +#pragma mark request logic
7.356 +
7.357 +// Main request loop is in here
7.358 +- (void)loadRequest;
7.359 +
7.360 +// Start the read stream. Called by loadRequest, and again to restart the request when authentication is needed
7.361 +- (void)startRequest;
7.362 +
7.363 +// Call to delete the temporary file used during a file download (if it exists)
7.364 +// No need to call this if the request succeeds - it is removed automatically
7.365 +- (void)removeTemporaryDownloadFile;
7.366 +
7.367 +// Call to remove the file used as the request body
7.368 +// No need to call this if the request succeeds and you didn't specify postBodyFilePath manually - it is removed automatically
7.369 +- (void)removePostDataFile;
7.370 +
7.371 +#pragma mark upload/download progress
7.372 +
7.373 +// Called on main thread to update progress delegates
7.374 +- (void)updateProgressIndicators;
7.375 +- (void)resetUploadProgress:(unsigned long long)value;
7.376 +- (void)updateUploadProgress;
7.377 +- (void)resetDownloadProgress:(unsigned long long)value;
7.378 +- (void)updateDownloadProgress;
7.379 +
7.380 +// Called when authorisation is needed, as we only find out we don't have permission to something when the upload is complete
7.381 +- (void)removeUploadProgressSoFar;
7.382 +
7.383 +// Helper method for interacting with progress indicators to abstract the details of different APIS (NSProgressIndicator and UIProgressView)
7.384 ++ (void)setProgress:(double)progress forProgressIndicator:(id)indicator;
7.385 +
7.386 +#pragma mark handling request complete / failure
7.387 +
7.388 +// Called when a request completes successfully, lets the delegate now via didFinishSelector
7.389 +- (void)requestFinished;
7.390 +
7.391 +// Called when a request fails, and lets the delegate now via didFailSelector
7.392 +- (void)failWithError:(NSError *)theError;
7.393 +
7.394 +#pragma mark parsing HTTP response headers
7.395 +
7.396 +// Reads the response headers to find the content length, encoding, cookies for the session
7.397 +// Also initiates request redirection when shouldRedirect is true
7.398 +// Returns true if the request needs a username and password (or if those supplied were incorrect)
7.399 +- (BOOL)readResponseHeadersReturningAuthenticationFailure;
7.400 +
7.401 +#pragma mark http authentication stuff
7.402 +
7.403 +// Apply credentials to this request
7.404 +- (BOOL)applyCredentials:(NSDictionary *)newCredentials;
7.405 +- (BOOL)applyProxyCredentials:(NSDictionary *)newCredentials;
7.406 +
7.407 +// Attempt to obtain credentials for this request from the URL, username and password or keychain
7.408 +- (NSMutableDictionary *)findCredentials;
7.409 +- (NSMutableDictionary *)findProxyCredentials;
7.410 +
7.411 +// Unlock (unpause) the request thread so it can resume the request
7.412 +// Should be called by delegates when they have populated the authentication information after an authentication challenge
7.413 +- (void)retryUsingSuppliedCredentials;
7.414 +
7.415 +// Should be called by delegates when they wish to cancel authentication and stop
7.416 +- (void)cancelAuthentication;
7.417 +
7.418 +// Apply authentication information and resume the request after an authentication challenge
7.419 +- (void)attemptToApplyCredentialsAndResume;
7.420 +- (void)attemptToApplyProxyCredentialsAndResume;
7.421 +
7.422 +#pragma mark stream status handlers
7.423 +
7.424 +// CFnetwork event handlers
7.425 +- (void)handleNetworkEvent:(CFStreamEventType)type;
7.426 +- (void)handleBytesAvailable;
7.427 +- (void)handleStreamComplete;
7.428 +- (void)handleStreamError;
7.429 +
7.430 +#pragma mark global queue
7.431 +
7.432 ++ (NSOperationQueue *)sharedRequestQueue;
7.433 +
7.434 +#pragma mark session credentials
7.435 +
7.436 ++ (NSMutableArray *)sessionProxyCredentialsStore;
7.437 ++ (NSMutableArray *)sessionCredentialsStore;
7.438 +
7.439 ++ (void)storeProxyAuthenticationCredentialsInSessionStore:(NSDictionary *)credentials;
7.440 ++ (void)storeAuthenticationCredentialsInSessionStore:(NSDictionary *)credentials;
7.441 +
7.442 ++ (void)removeProxyAuthenticationCredentialsFromSessionStore:(NSDictionary *)credentials;
7.443 ++ (void)removeAuthenticationCredentialsFromSessionStore:(NSDictionary *)credentials;
7.444 +
7.445 +- (NSDictionary *)findSessionProxyAuthenticationCredentials;
7.446 +- (NSDictionary *)findSessionAuthenticationCredentials;
7.447 +
7.448 +
7.449 +#pragma mark keychain storage
7.450 +
7.451 +// Save credentials for this request to the keychain
7.452 +- (void)saveCredentialsToKeychain:(NSDictionary *)newCredentials;
7.453 +
7.454 +// Save credentials to the keychain
7.455 ++ (void)saveCredentials:(NSURLCredential *)credentials forHost:(NSString *)host port:(int)port protocol:(NSString *)protocol realm:(NSString *)realm;
7.456 ++ (void)saveCredentials:(NSURLCredential *)credentials forProxy:(NSString *)host port:(int)port realm:(NSString *)realm;
7.457 +
7.458 +// Return credentials from the keychain
7.459 ++ (NSURLCredential *)savedCredentialsForHost:(NSString *)host port:(int)port protocol:(NSString *)protocol realm:(NSString *)realm;
7.460 ++ (NSURLCredential *)savedCredentialsForProxy:(NSString *)host port:(int)port protocol:(NSString *)protocol realm:(NSString *)realm;
7.461 +
7.462 +// Remove credentials from the keychain
7.463 ++ (void)removeCredentialsForHost:(NSString *)host port:(int)port protocol:(NSString *)protocol realm:(NSString *)realm;
7.464 ++ (void)removeCredentialsForProxy:(NSString *)host port:(int)port realm:(NSString *)realm;
7.465 +
7.466 +// We keep track of any cookies we accept, so that we can remove them from the persistent store later
7.467 ++ (void)setSessionCookies:(NSMutableArray *)newSessionCookies;
7.468 ++ (NSMutableArray *)sessionCookies;
7.469 +
7.470 +// Adds a cookie to our list of cookies we've accepted, checking first for an old version of the same cookie and removing that
7.471 ++ (void)addSessionCookie:(NSHTTPCookie *)newCookie;
7.472 +
7.473 +// Dump all session data (authentication and cookies)
7.474 ++ (void)clearSession;
7.475 +
7.476 +#pragma mark gzip decompression
7.477 +
7.478 +// Uncompress gzipped data with zlib
7.479 ++ (NSData *)uncompressZippedData:(NSData*)compressedData;
7.480 +
7.481 +// Uncompress gzipped data from a file into another file, used when downloading to a file
7.482 ++ (int)uncompressZippedDataFromFile:(NSString *)sourcePath toFile:(NSString *)destinationPath;
7.483 ++ (int)uncompressZippedDataFromSource:(FILE *)source toDestination:(FILE *)dest;
7.484 +
7.485 +#pragma mark gzip compression
7.486 +
7.487 +// Compress data with gzip using zlib
7.488 ++ (NSData *)compressData:(NSData*)uncompressedData;
7.489 +
7.490 +// gzip compress data from a file, saving to another file, used for uploading when shouldCompressRequestBody is true
7.491 ++ (int)compressDataFromFile:(NSString *)sourcePath toFile:(NSString *)destinationPath;
7.492 ++ (int)compressDataFromSource:(FILE *)source toDestination:(FILE *)dest;
7.493 +
7.494 +#pragma mark get user agent
7.495 +
7.496 +// Will be used as a user agent if requests do not specify a custom user agent
7.497 +// Is only used when you have specified a Bundle Display Name (CFDisplayBundleName) or Bundle Name (CFBundleName) in your plist
7.498 ++ (NSString *)defaultUserAgentString;
7.499 +
7.500 +#pragma mark proxy autoconfiguration
7.501 +
7.502 +// Returns an array of proxies to use for a particular url, given the url of a PAC script
7.503 ++ (NSArray *)proxiesForURL:(NSURL *)theURL fromPAC:(NSURL *)pacScriptURL;
7.504 +
7.505 +#pragma mark mime-type detection
7.506 +
7.507 +// Only works on Mac OS, will always return 'application/octet-stream' on iPhone
7.508 ++ (NSString *)mimeTypeForFileAtPath:(NSString *)path;
7.509 +
7.510 +#pragma mark bandwidth measurement / throttling
7.511 +
7.512 +// The maximum number of bytes ALL requests can send / receive in a second
7.513 +// This is a rough figure. The actual amount used will be slightly more, this does not include HTTP headers
7.514 ++ (unsigned long)maxBandwidthPerSecond;
7.515 ++ (void)setMaxBandwidthPerSecond:(unsigned long)bytes;
7.516 +
7.517 +// Get a rough average (for the last 5 seconds) of how much bandwidth is being used, in bytes
7.518 ++ (unsigned long)averageBandwidthUsedPerSecond;
7.519 +
7.520 +// Will return YES is bandwidth throttling is currently in use
7.521 ++ (BOOL)isBandwidthThrottled;
7.522 +
7.523 +// Used internally to record bandwidth use, and by ASIInputStreams when uploading. It's probably best if you don't mess with this.
7.524 ++ (void)incrementBandwidthUsedInLastSecond:(unsigned long)bytes;
7.525 +
7.526 +// On iPhone, ASIHTTPRequest can automatically turn throttling on and off as the connection type changes between WWAN and WiFi
7.527 +
7.528 +#if TARGET_OS_IPHONE
7.529 +// Set to YES to automatically turn on throttling when WWAN is connected, and automatically turn it off when it isn't
7.530 ++ (void)setShouldThrottleBandwidthForWWAN:(BOOL)throttle;
7.531 +
7.532 +// Turns on throttling automatically when WWAN is connected using a custom limit, and turns it off automatically when it isn't
7.533 ++ (void)throttleBandwidthForWWANUsingLimit:(unsigned long)limit;
7.534 +
7.535 +// Called when the status of the network changes
7.536 ++ (void)reachabilityChanged:(NSNotification *)note;
7.537 +#endif
7.538 +
7.539 +
7.540 +- (BOOL)showProxyAuthenticationDialog;
7.541 +- (BOOL)showAuthenticationDialog;
7.542 +
7.543 ++ (unsigned long)maxUploadReadLength;
7.544 +
7.545 +@property (retain) NSString *username;
7.546 +@property (retain) NSString *password;
7.547 +@property (retain) NSString *domain;
7.548 +
7.549 +@property (retain) NSString *proxyUsername;
7.550 +@property (retain) NSString *proxyPassword;
7.551 +@property (retain) NSString *proxyDomain;
7.552 +
7.553 +@property (retain) NSString *proxyHost;
7.554 +@property (assign) int proxyPort;
7.555 +
7.556 +@property (retain,setter=setURL:) NSURL *url;
7.557 +@property (assign) id delegate;
7.558 +@property (assign) id queue;
7.559 +@property (assign) id uploadProgressDelegate;
7.560 +@property (assign) id downloadProgressDelegate;
7.561 +@property (assign) BOOL useKeychainPersistance;
7.562 +@property (assign) BOOL useSessionPersistance;
7.563 +@property (retain) NSString *downloadDestinationPath;
7.564 +@property (retain) NSString *temporaryFileDownloadPath;
7.565 +@property (assign) SEL didFinishSelector;
7.566 +@property (assign) SEL didFailSelector;
7.567 +@property (retain,readonly) NSString *authenticationRealm;
7.568 +@property (retain,readonly) NSString *proxyAuthenticationRealm;
7.569 +@property (retain) NSError *error;
7.570 +@property (assign,readonly) BOOL complete;
7.571 +@property (retain,readonly) NSDictionary *responseHeaders;
7.572 +@property (retain) NSMutableDictionary *requestHeaders;
7.573 +@property (retain) NSMutableArray *requestCookies;
7.574 +@property (retain,readonly) NSArray *responseCookies;
7.575 +@property (assign) BOOL useCookiePersistance;
7.576 +@property (retain) NSDictionary *requestCredentials;
7.577 +@property (retain) NSDictionary *proxyCredentials;
7.578 +@property (assign,readonly) int responseStatusCode;
7.579 +@property (retain,readonly) NSString *responseStatusMessage;
7.580 +@property (retain,readonly) NSMutableData *rawResponseData;
7.581 +@property (assign) NSTimeInterval timeOutSeconds;
7.582 +@property (retain) NSString *requestMethod;
7.583 +@property (retain) NSMutableData *postBody;
7.584 +@property (assign,readonly) unsigned long long contentLength;
7.585 +@property (assign) unsigned long long postLength;
7.586 +@property (assign) BOOL shouldResetProgressIndicators;
7.587 +@property (retain) ASIHTTPRequest *mainRequest;
7.588 +@property (assign) BOOL showAccurateProgress;
7.589 +@property (assign,readonly) unsigned long long totalBytesRead;
7.590 +@property (assign,readonly) unsigned long long totalBytesSent;
7.591 +@property (assign) NSStringEncoding defaultResponseEncoding;
7.592 +@property (assign,readonly) NSStringEncoding responseEncoding;
7.593 +@property (assign) BOOL allowCompressedResponse;
7.594 +@property (assign) BOOL allowResumeForFileDownloads;
7.595 +@property (retain) NSDictionary *userInfo;
7.596 +@property (retain) NSString *postBodyFilePath;
7.597 +@property (assign) BOOL shouldStreamPostDataFromDisk;
7.598 +@property (assign) BOOL didCreateTemporaryPostDataFile;
7.599 +@property (assign) BOOL useHTTPVersionOne;
7.600 +@property (assign, readonly) unsigned long long partialDownloadSize;
7.601 +@property (assign) BOOL shouldRedirect;
7.602 +@property (assign) BOOL validatesSecureCertificate;
7.603 +@property (assign) BOOL shouldCompressRequestBody;
7.604 +@property (assign) BOOL needsProxyAuthentication;
7.605 +@property (retain) NSURL *PACurl;
7.606 +@property (retain) NSString *authenticationScheme;
7.607 +@property (retain) NSString *proxyAuthenticationScheme;
7.608 +@property (assign) BOOL shouldPresentAuthenticationDialog;
7.609 +@property (assign) BOOL shouldPresentProxyAuthenticationDialog;
7.610 +@property (assign) BOOL authenticationChallengeInProgress;
7.611 +@property (assign) BOOL shouldPresentCredentialsBeforeChallenge;
7.612 +@property (assign, readonly) int authenticationRetryCount;
7.613 +@property (assign, readonly) int proxyAuthenticationRetryCount;
7.614 +@end
8.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
8.2 +++ b/ASI-HTTP/ASIHTTPRequest.m Thu Dec 24 00:14:02 2009 +0000
8.3 @@ -0,0 +1,2949 @@
8.4 +//
8.5 +// ASIHTTPRequest.m
8.6 +//
8.7 +// Created by Ben Copsey on 04/10/2007.
8.8 +// Copyright 2007-2009 All-Seeing Interactive. All rights reserved.
8.9 +//
8.10 +// A guide to the main features is available at:
8.11 +// http://allseeing-i.com/ASIHTTPRequest
8.12 +//
8.13 +// Portions are based on the ImageClient example from Apple:
8.14 +// See: http://developer.apple.com/samplecode/ImageClient/listing37.html
8.15 +
8.16 +#import "ASIHTTPRequest.h"
8.17 +#import <zlib.h>
8.18 +#if TARGET_OS_IPHONE
8.19 +#import "Reachability.h"
8.20 +#import "ASIAuthenticationDialog.h"
8.21 +#else
8.22 +#import <SystemConfiguration/SystemConfiguration.h>
8.23 +#endif
8.24 +#import "ASIInputStream.h"
8.25 +
8.26 +
8.27 +// We use our own custom run loop mode as CoreAnimation seems to want to hijack our threads otherwise
8.28 +static CFStringRef ASIHTTPRequestRunMode = CFSTR("ASIHTTPRequest");
8.29 +
8.30 +NSString* const NetworkRequestErrorDomain = @"ASIHTTPRequestErrorDomain";
8.31 +
8.32 +static const CFOptionFlags kNetworkEvents = kCFStreamEventOpenCompleted | kCFStreamEventHasBytesAvailable | kCFStreamEventEndEncountered | kCFStreamEventErrorOccurred;
8.33 +
8.34 +// In memory caches of credentials, used on when useSessionPersistance is YES
8.35 +static NSMutableArray *sessionCredentialsStore = nil;
8.36 +static NSMutableArray *sessionProxyCredentialsStore = nil;
8.37 +
8.38 +// This lock mediates access to session credentials
8.39 +static NSRecursiveLock *sessionCredentialsLock = nil;
8.40 +
8.41 +// We keep track of cookies we have received here so we can remove them from the sharedHTTPCookieStorage later
8.42 +static NSMutableArray *sessionCookies = nil;
8.43 +
8.44 +// The number of times we will allow requests to redirect before we fail with a redirection error
8.45 +const int RedirectionLimit = 5;
8.46 +
8.47 +static void ReadStreamClientCallBack(CFReadStreamRef readStream, CFStreamEventType type, void *clientCallBackInfo) {
8.48 + [((ASIHTTPRequest*)clientCallBackInfo) handleNetworkEvent: type];
8.49 +}
8.50 +
8.51 +// This lock prevents the operation from being cancelled while it is trying to update the progress, and vice versa
8.52 +static NSRecursiveLock *progressLock;
8.53 +
8.54 +static NSError *ASIRequestCancelledError;
8.55 +static NSError *ASIRequestTimedOutError;
8.56 +static NSError *ASIAuthenticationError;
8.57 +static NSError *ASIUnableToCreateRequestError;
8.58 +static NSError *ASITooMuchRedirectionError;
8.59 +
8.60 +static NSMutableArray *bandwidthUsageTracker = nil;
8.61 +static unsigned long averageBandwidthUsedPerSecond = 0;
8.62 +
8.63 +// Records how much bandwidth all requests combined have used in the last second
8.64 +static unsigned long bandwidthUsedInLastSecond = 0;
8.65 +
8.66 +// A date one second in the future from the time it was created
8.67 +static NSDate *bandwidthMeasurementDate = nil;
8.68 +
8.69 +// Since throttling variables are shared among all requests, we'll use a lock to mediate access
8.70 +static NSLock *bandwidthThrottlingLock = nil;
8.71 +
8.72 +// the maximum number of bytes that can be transmitted in one second
8.73 +static unsigned long maxBandwidthPerSecond = 0;
8.74 +
8.75 +// A default figure for throttling bandwidth on mobile devices
8.76 +unsigned long const ASIWWANBandwidthThrottleAmount = 14800;
8.77 +
8.78 +#if TARGET_OS_IPHONE
8.79 +// YES when bandwidth throttling is active
8.80 +// This flag does not denote whether throttling is turned on - rather whether it is currently in use
8.81 +// It will be set to NO when throttling was turned on with setShouldThrottleBandwidthForWWAN, but a WI-FI connection is active
8.82 +static BOOL isBandwidthThrottled = NO;
8.83 +
8.84 +// When YES, bandwidth will be automatically throttled when using WWAN (3G/Edge/GPRS)
8.85 +// Wifi will not be throttled
8.86 +static BOOL shouldThrottleBandwithForWWANOnly = NO;
8.87 +#endif
8.88 +
8.89 +// Mediates access to the session cookies so requests
8.90 +static NSRecursiveLock *sessionCookiesLock = nil;
8.91 +
8.92 +// This lock ensures delegates only receive one notification that authentication is required at once
8.93 +// When using ASIAuthenticationDialogs, it also ensures only one dialog is shown at once
8.94 +// If a request can't aquire the lock immediately, it means a dialog is being shown or a delegate is handling the authentication challenge
8.95 +// Once it gets the lock, it will try to look for existing credentials again rather than showing the dialog / notifying the delegate
8.96 +// This is so it can make use of any credentials supplied for the other request, if they are appropriate
8.97 +static NSRecursiveLock *delegateAuthenticationLock = nil;
8.98 +
8.99 +static NSOperationQueue *sharedRequestQueue = nil;
8.100 +
8.101 +// Private stuff
8.102 +@interface ASIHTTPRequest ()
8.103 +
8.104 +- (void)cancelLoad;
8.105 +- (BOOL)askDelegateForCredentials;
8.106 +- (BOOL)askDelegateForProxyCredentials;
8.107 ++ (void)measureBandwidthUsage;
8.108 ++ (void)recordBandwidthUsage;
8.109 +
8.110 +@property (assign) BOOL complete;
8.111 +@property (retain) NSDictionary *responseHeaders;
8.112 +@property (retain) NSArray *responseCookies;
8.113 +@property (assign) int responseStatusCode;
8.114 +@property (retain) NSMutableData *rawResponseData;
8.115 +@property (retain, nonatomic) NSDate *lastActivityTime;
8.116 +@property (assign) unsigned long long contentLength;
8.117 +@property (assign) unsigned long long partialDownloadSize;
8.118 +@property (assign, nonatomic) unsigned long long uploadBufferSize;
8.119 +@property (assign) NSStringEncoding responseEncoding;
8.120 +@property (retain, nonatomic) NSOutputStream *postBodyWriteStream;
8.121 +@property (retain, nonatomic) NSInputStream *postBodyReadStream;
8.122 +@property (assign) unsigned long long totalBytesRead;
8.123 +@property (assign) unsigned long long totalBytesSent;
8.124 +@property (assign, nonatomic) unsigned long long lastBytesRead;
8.125 +@property (assign, nonatomic) unsigned long long lastBytesSent;
8.126 +@property (retain) NSLock *cancelledLock;
8.127 +@property (assign, nonatomic) BOOL haveBuiltPostBody;
8.128 +@property (retain, nonatomic) NSOutputStream *fileDownloadOutputStream;
8.129 +@property (assign) int authenticationRetryCount;
8.130 +@property (assign) int proxyAuthenticationRetryCount;
8.131 +@property (assign, nonatomic) BOOL updatedProgress;
8.132 +@property (assign, nonatomic) BOOL needsRedirect;
8.133 +@property (assign, nonatomic) int redirectCount;
8.134 +@property (retain, nonatomic) NSData *compressedPostBody;
8.135 +@property (retain, nonatomic) NSString *compressedPostBodyFilePath;
8.136 +@property (retain) NSConditionLock *authenticationLock;
8.137 +@property (retain) NSString *authenticationRealm;
8.138 +@property (retain) NSString *proxyAuthenticationRealm;
8.139 +@property (retain) NSString *responseStatusMessage;
8.140 +@end
8.141 +
8.142 +
8.143 +@implementation ASIHTTPRequest
8.144 +
8.145 +
8.146 +
8.147 +#pragma mark init / dealloc
8.148 +
8.149 ++ (void)initialize
8.150 +{
8.151 + if (self == [ASIHTTPRequest class]) {
8.152 + progressLock = [[NSRecursiveLock alloc] init];
8.153 + bandwidthThrottlingLock = [[NSLock alloc] init];
8.154 + sessionCookiesLock = [[NSRecursiveLock alloc] init];
8.155 + sessionCredentialsLock = [[NSRecursiveLock alloc] init];
8.156 + delegateAuthenticationLock = [[NSRecursiveLock alloc] init];
8.157 + bandwidthUsageTracker = [[NSMutableArray alloc] initWithCapacity:5];
8.158 + ASIRequestTimedOutError = [[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIRequestTimedOutErrorType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"The request timed out",NSLocalizedDescriptionKey,nil]] retain];
8.159 + ASIAuthenticationError = [[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIAuthenticationErrorType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Authentication needed",NSLocalizedDescriptionKey,nil]] retain];
8.160 + ASIRequestCancelledError = [[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIRequestCancelledErrorType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"The request was cancelled",NSLocalizedDescriptionKey,nil]] retain];
8.161 + ASIUnableToCreateRequestError = [[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIUnableToCreateRequestErrorType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Unable to create request (bad url?)",NSLocalizedDescriptionKey,nil]] retain];
8.162 + ASITooMuchRedirectionError = [[NSError errorWithDomain:NetworkRequestErrorDomain code:ASITooMuchRedirectionErrorType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"The request failed because it redirected too many times",NSLocalizedDescriptionKey,nil]] retain];
8.163 + }
8.164 + [super initialize];
8.165 +}
8.166 +
8.167 +
8.168 +- (id)initWithURL:(NSURL *)newURL
8.169 +{
8.170 + self = [super init];
8.171 + [self setRequestMethod:@"GET"];
8.172 +
8.173 + [self setShouldPresentCredentialsBeforeChallenge:YES];
8.174 + [self setShouldRedirect:YES];
8.175 + [self setShowAccurateProgress:YES];
8.176 + [self setShouldResetProgressIndicators:YES];
8.177 + [self setAllowCompressedResponse:YES];
8.178 + [self setDefaultResponseEncoding:NSISOLatin1StringEncoding];
8.179 + [self setShouldPresentProxyAuthenticationDialog:YES];
8.180 +
8.181 + [self setTimeOutSeconds:10];
8.182 + [self setUseSessionPersistance:YES];
8.183 + [self setUseCookiePersistance:YES];
8.184 + [self setValidatesSecureCertificate:YES];
8.185 + [self setRequestCookies:[[[NSMutableArray alloc] init] autorelease]];
8.186 + [self setDidFinishSelector:@selector(requestFinished:)];
8.187 + [self setDidFailSelector:@selector(requestFailed:)];
8.188 + [self setURL:newURL];
8.189 + [self setCancelledLock:[[[NSLock alloc] init] autorelease]];
8.190 + return self;
8.191 +}
8.192 +
8.193 ++ (id)requestWithURL:(NSURL *)newURL
8.194 +{
8.195 + return [[[self alloc] initWithURL:newURL] autorelease];
8.196 +}
8.197 +
8.198 +- (void)dealloc
8.199 +{
8.200 + [self setAuthenticationChallengeInProgress:NO];
8.201 + if (requestAuthentication) {
8.202 + CFRelease(requestAuthentication);
8.203 + }
8.204 + if (proxyAuthentication) {
8.205 + CFRelease(proxyAuthentication);
8.206 + }
8.207 + if (request) {
8.208 + CFRelease(request);
8.209 + }
8.210 + [self cancelLoad];
8.211 + [userInfo release];
8.212 + [mainRequest release];
8.213 + [postBody release];
8.214 + [compressedPostBody release];
8.215 + [error release];
8.216 + [requestHeaders release];
8.217 + [requestCookies release];
8.218 + [downloadDestinationPath release];
8.219 + [temporaryFileDownloadPath release];
8.220 + [fileDownloadOutputStream release];
8.221 + [username release];
8.222 + [password release];
8.223 + [domain release];
8.224 + [authenticationRealm release];
8.225 + [authenticationScheme release];
8.226 + [requestCredentials release];
8.227 + [proxyHost release];
8.228 + [proxyUsername release];
8.229 + [proxyPassword release];
8.230 + [proxyDomain release];
8.231 + [proxyAuthenticationRealm release];
8.232 + [proxyAuthenticationScheme release];
8.233 + [proxyCredentials release];
8.234 + [url release];
8.235 + [authenticationLock release];
8.236 + [lastActivityTime release];
8.237 + [responseCookies release];
8.238 + [rawResponseData release];
8.239 + [responseHeaders release];
8.240 + [requestMethod release];
8.241 + [cancelledLock release];
8.242 + [postBodyFilePath release];
8.243 + [compressedPostBodyFilePath release];
8.244 + [postBodyWriteStream release];
8.245 + [postBodyReadStream release];
8.246 + [PACurl release];
8.247 + [responseStatusMessage release];
8.248 + [super dealloc];
8.249 +}
8.250 +
8.251 +
8.252 +#pragma mark setup request
8.253 +
8.254 +- (void)addRequestHeader:(NSString *)header value:(NSString *)value
8.255 +{
8.256 + if (!requestHeaders) {
8.257 + [self setRequestHeaders:[NSMutableDictionary dictionaryWithCapacity:1]];
8.258 + }
8.259 + [requestHeaders setObject:value forKey:header];
8.260 +}
8.261 +
8.262 +// This function will be called either just before a request starts, or when postLength is needed, whichever comes first
8.263 +// postLength must be set by the time this function is complete
8.264 +- (void)buildPostBody
8.265 +{
8.266 + // Are we submitting the request body from a file on disk
8.267 + if ([self postBodyFilePath]) {
8.268 +
8.269 + // If we were writing to the post body via appendPostData or appendPostDataFromFile, close the write stream
8.270 + if ([self postBodyWriteStream]) {
8.271 + [[self postBodyWriteStream] close];
8.272 + [self setPostBodyWriteStream:nil];
8.273 + }
8.274 +
8.275 + NSError *err = nil;
8.276 + NSString *path;
8.277 + if ([self shouldCompressRequestBody]) {
8.278 + [self setCompressedPostBodyFilePath:[NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]]];
8.279 + [ASIHTTPRequest compressDataFromFile:[self postBodyFilePath] toFile:[self compressedPostBodyFilePath]];
8.280 + path = [self compressedPostBodyFilePath];
8.281 + } else {
8.282 + path = [self postBodyFilePath];
8.283 + }
8.284 + [self setPostLength:[[[NSFileManager defaultManager] attributesOfItemAtPath:path error:&err] fileSize]];
8.285 + if (err) {
8.286 + [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIFileManagementError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Failed to get attributes for file at path '@%'",path],NSLocalizedDescriptionKey,error,NSUnderlyingErrorKey,nil]]];
8.287 + return;
8.288 + }
8.289 +
8.290 + // Otherwise, we have an in-memory request body
8.291 + } else {
8.292 + if ([self shouldCompressRequestBody]) {
8.293 + [self setCompressedPostBody:[ASIHTTPRequest compressData:[self postBody]]];
8.294 + [self setPostLength:[[self compressedPostBody] length]];
8.295 + } else {
8.296 + [self setPostLength:[[self postBody] length]];
8.297 + }
8.298 + }
8.299 +
8.300 + if ([self postLength] > 0) {
8.301 + if (![requestMethod isEqualToString:@"POST"] && ![requestMethod isEqualToString:@"PUT"]) {
8.302 + [self setRequestMethod:@"POST"];
8.303 + }
8.304 + [self addRequestHeader:@"Content-Length" value:[NSString stringWithFormat:@"%llu",[self postLength]]];
8.305 + }
8.306 + [self setHaveBuiltPostBody:YES];
8.307 +}
8.308 +
8.309 +// Sets up storage for the post body
8.310 +- (void)setupPostBody
8.311 +{
8.312 + if ([self shouldStreamPostDataFromDisk]) {
8.313 + if (![self postBodyFilePath]) {
8.314 + [self setPostBodyFilePath:[NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]]];
8.315 + [self setDidCreateTemporaryPostDataFile:YES];
8.316 + }
8.317 + if (![self postBodyWriteStream]) {
8.318 + [self setPostBodyWriteStream:[[[NSOutputStream alloc] initToFileAtPath:[self postBodyFilePath] append:NO] autorelease]];
8.319 + [[self postBodyWriteStream] open];
8.320 + }
8.321 + } else {
8.322 + if (![self postBody]) {
8.323 + [self setPostBody:[[[NSMutableData alloc] init] autorelease]];
8.324 + }
8.325 + }
8.326 +}
8.327 +
8.328 +- (void)appendPostData:(NSData *)data
8.329 +{
8.330 + [self setupPostBody];
8.331 + if ([data length] == 0) {
8.332 + return;
8.333 + }
8.334 + if ([self shouldStreamPostDataFromDisk]) {
8.335 + [[self postBodyWriteStream] write:[data bytes] maxLength:[data length]];
8.336 + } else {
8.337 + [[self postBody] appendData:data];
8.338 + }
8.339 +}
8.340 +
8.341 +- (void)appendPostDataFromFile:(NSString *)file
8.342 +{
8.343 + [self setupPostBody];
8.344 + NSInputStream *stream = [[[NSInputStream alloc] initWithFileAtPath:file] autorelease];
8.345 + [stream open];
8.346 + int bytesRead;
8.347 + while ([stream hasBytesAvailable]) {
8.348 +
8.349 + unsigned char buffer[1024*256];
8.350 + bytesRead = [stream read:buffer maxLength:sizeof(buffer)];
8.351 + if (bytesRead == 0) {
8.352 + break;
8.353 + }
8.354 + if ([self shouldStreamPostDataFromDisk]) {
8.355 + [[self postBodyWriteStream] write:buffer maxLength:bytesRead];
8.356 + } else {
8.357 + [[self postBody] appendData:[NSData dataWithBytes:buffer length:bytesRead]];
8.358 + }
8.359 + }
8.360 + [stream close];
8.361 +}
8.362 +
8.363 +#pragma mark get information about this request
8.364 +
8.365 +- (BOOL)isFinished
8.366 +{
8.367 + return [self complete];
8.368 +}
8.369 +
8.370 +
8.371 +- (void)cancel
8.372 +{
8.373 + // Request may already be complete
8.374 + if ([self complete] || [self isCancelled]) {
8.375 + return;
8.376 + }
8.377 + [self failWithError:ASIRequestCancelledError];
8.378 + [super cancel];
8.379 + [self cancelLoad];
8.380 + [self setComplete:YES];
8.381 +
8.382 +}
8.383 +
8.384 +
8.385 +// Call this method to get the received data as an NSString. Don't use for binary data!
8.386 +- (NSString *)responseString
8.387 +{
8.388 + NSData *data = [self responseData];
8.389 + if (!data) {
8.390 + return nil;
8.391 + }
8.392 +
8.393 + return [[[NSString alloc] initWithBytes:[data bytes] length:[data length] encoding:[self responseEncoding]] autorelease];
8.394 +}
8.395 +
8.396 +- (BOOL)isResponseCompressed
8.397 +{
8.398 + NSString *encoding = [[self responseHeaders] objectForKey:@"Content-Encoding"];
8.399 + return encoding && [encoding rangeOfString:@"gzip"].location != NSNotFound;
8.400 +}
8.401 +
8.402 +- (NSData *)responseData
8.403 +{
8.404 + if ([self isResponseCompressed]) {
8.405 + return [ASIHTTPRequest uncompressZippedData:[self rawResponseData]];
8.406 + } else {
8.407 + return [self rawResponseData];
8.408 + }
8.409 +}
8.410 +
8.411 +#pragma mark running a request
8.412 +
8.413 +// Run a request asynchronously by adding it to the global queue
8.414 +// (Use [request start] for a synchronous request)
8.415 +- (void)startAsynchronous
8.416 +{
8.417 + [[ASIHTTPRequest sharedRequestQueue] addOperation:self];
8.418 +}
8.419 +
8.420 +
8.421 +#pragma mark request logic
8.422 +
8.423 +// Create the request
8.424 +- (void)main
8.425 +{
8.426 +
8.427 + [pool release];
8.428 + pool = [[NSAutoreleasePool alloc] init];
8.429 +
8.430 + [self setComplete:NO];
8.431 +
8.432 + if (![self url]) {
8.433 + [self failWithError:ASIUnableToCreateRequestError];
8.434 + return;
8.435 + }
8.436 +
8.437 + if (![self haveBuiltPostBody]) {
8.438 + [self buildPostBody];
8.439 + }
8.440 +
8.441 + // If we're redirecting, we'll already have a CFHTTPMessageRef
8.442 + if (request) {
8.443 + CFRelease(request);
8.444 + }
8.445 +
8.446 + // Create a new HTTP request.
8.447 + request = CFHTTPMessageCreateRequest(kCFAllocatorDefault, (CFStringRef)[self requestMethod], (CFURLRef)[self url], [self useHTTPVersionOne] ? kCFHTTPVersion1_0 : kCFHTTPVersion1_1);
8.448 + if (!request) {
8.449 + [self failWithError:ASIUnableToCreateRequestError];
8.450 + return;
8.451 + }
8.452 +
8.453 +
8.454 + // If we've already talked to this server and have valid credentials, let's apply them to the request
8.455 + if ([self shouldPresentCredentialsBeforeChallenge]) {
8.456 + if ([self useSessionPersistance]) {
8.457 + NSDictionary *credentials = [self findSessionAuthenticationCredentials];
8.458 + if (credentials) {
8.459 + if (!CFHTTPMessageApplyCredentialDictionary(request, (CFHTTPAuthenticationRef)[credentials objectForKey:@"Authentication"], (CFDictionaryRef)[credentials objectForKey:@"Credentials"], NULL)) {
8.460 + [[self class] removeAuthenticationCredentialsFromSessionStore:[credentials objectForKey:@"Credentials"]];
8.461 + }
8.462 + }
8.463 + credentials = [self findSessionProxyAuthenticationCredentials];
8.464 + if (credentials) {
8.465 + if (!CFHTTPMessageApplyCredentialDictionary(request, (CFHTTPAuthenticationRef)[credentials objectForKey:@"Authentication"], (CFDictionaryRef)[credentials objectForKey:@"Credentials"], NULL)) {
8.466 + [[self class] removeProxyAuthenticationCredentialsFromSessionStore:[credentials objectForKey:@"Credentials"]];
8.467 + }
8.468 + }
8.469 + }
8.470 + }
8.471 +
8.472 + // Add cookies from the persistant (mac os global) store
8.473 + if ([self useCookiePersistance] ) {
8.474 + NSArray *cookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookiesForURL:[self url]];
8.475 + if (cookies) {
8.476 + [[self requestCookies] addObjectsFromArray:cookies];
8.477 + }
8.478 + }
8.479 +
8.480 + // Apply request cookies
8.481 + NSArray *cookies;
8.482 + if ([self mainRequest]) {
8.483 + cookies = [[self mainRequest] requestCookies];
8.484 + } else {
8.485 + cookies = [self requestCookies];
8.486 + }
8.487 + if ([cookies count] > 0) {
8.488 + NSHTTPCookie *cookie;
8.489 + NSString *cookieHeader = nil;
8.490 + for (cookie in cookies) {
8.491 + if (!cookieHeader) {
8.492 + cookieHeader = [NSString stringWithFormat: @"%@=%@",[cookie name],[cookie value]];
8.493 + } else {
8.494 + cookieHeader = [NSString stringWithFormat: @"%@; %@=%@",cookieHeader,[cookie name],[cookie value]];
8.495 + }
8.496 + }
8.497 + if (cookieHeader) {
8.498 + [self addRequestHeader:@"Cookie" value:cookieHeader];
8.499 + }
8.500 + }
8.501 +
8.502 + // Build and set the user agent string if the request does not already have a custom user agent specified
8.503 + if (![[self requestHeaders] objectForKey:@"User-Agent"]) {
8.504 + NSString *userAgentString = [ASIHTTPRequest defaultUserAgentString];
8.505 + if (userAgentString) {
8.506 + [self addRequestHeader:@"User-Agent" value:userAgentString];
8.507 + }
8.508 + }
8.509 +
8.510 +
8.511 + // Accept a compressed response
8.512 + if ([self allowCompressedResponse]) {
8.513 + [self addRequestHeader:@"Accept-Encoding" value:@"gzip"];
8.514 + }
8.515 +
8.516 + // Configure a compressed request body
8.517 + if ([self shouldCompressRequestBody]) {
8.518 + [self addRequestHeader:@"Content-Encoding" value:@"gzip"];
8.519 + }
8.520 +
8.521 + // Should this request resume an existing download?
8.522 + if ([self allowResumeForFileDownloads] && [self downloadDestinationPath] && [self temporaryFileDownloadPath] && [[NSFileManager defaultManager] fileExistsAtPath:[self temporaryFileDownloadPath]]) {
8.523 + NSError *err = nil;
8.524 + [self setPartialDownloadSize:[[[NSFileManager defaultManager] attributesOfItemAtPath:[self temporaryFileDownloadPath] error:&err] fileSize]];
8.525 + if (err) {
8.526 + [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIFileManagementError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Failed to get attributes for file at path '@%'",[self temporaryFileDownloadPath]],NSLocalizedDescriptionKey,error,NSUnderlyingErrorKey,nil]]];
8.527 + return;
8.528 + }
8.529 + [self addRequestHeader:@"Range" value:[NSString stringWithFormat:@"bytes=%llu-",[self partialDownloadSize]]];
8.530 + }
8.531 +
8.532 + // Add custom headers
8.533 + NSDictionary *headers;
8.534 +
8.535 + //Add headers from the main request if this is a HEAD request generated by an ASINetworkQueue
8.536 + if ([self mainRequest]) {
8.537 + headers = [mainRequest requestHeaders];
8.538 + } else {
8.539 + headers = [self requestHeaders];
8.540 + }
8.541 + NSString *header;
8.542 + for (header in headers) {
8.543 + CFHTTPMessageSetHeaderFieldValue(request, (CFStringRef)header, (CFStringRef)[[self requestHeaders] objectForKey:header]);
8.544 + }
8.545 +
8.546 + [self loadRequest];
8.547 +
8.548 +}
8.549 +
8.550 +- (void)startRequest
8.551 +{
8.552 + [[self cancelledLock] lock];
8.553 +
8.554 + if ([self isCancelled]) {
8.555 + [[self cancelledLock] unlock];
8.556 + return;
8.557 + }
8.558 +
8.559 + [self setAuthenticationLock:[[[NSConditionLock alloc] initWithCondition:1] autorelease]];
8.560 +
8.561 + [self setComplete:NO];
8.562 + [self setTotalBytesRead:0];
8.563 + [self setLastBytesRead:0];
8.564 +
8.565 + // If we're retrying a request after an authentication failure, let's remove any progress we made
8.566 + if ([self lastBytesSent] > 0) {
8.567 + [self removeUploadProgressSoFar];
8.568 + }
8.569 +
8.570 + [self setLastBytesSent:0];
8.571 + if ([self shouldResetProgressIndicators]) {
8.572 + [self setContentLength:0];
8.573 + [self resetDownloadProgress:0];
8.574 + }
8.575 + [self setResponseHeaders:nil];
8.576 + if (![self downloadDestinationPath]) {
8.577 + [self setRawResponseData:[[[NSMutableData alloc] init] autorelease]];
8.578 + }
8.579 + // Create the stream for the request
8.580 +
8.581 + // Do we need to stream the request body from disk
8.582 + if ([self shouldStreamPostDataFromDisk] && [self postBodyFilePath] && [[NSFileManager defaultManager] fileExistsAtPath:[self postBodyFilePath]]) {
8.583 +
8.584 + // Are we gzipping the request body?
8.585 + if ([self compressedPostBodyFilePath] && [[NSFileManager defaultManager] fileExistsAtPath:[self compressedPostBodyFilePath]]) {
8.586 + [self setPostBodyReadStream:[ASIInputStream inputStreamWithFileAtPath:[self compressedPostBodyFilePath]]];
8.587 + } else {
8.588 + [self setPostBodyReadStream:[ASIInputStream inputStreamWithFileAtPath:[self postBodyFilePath]]];
8.589 + }
8.590 + readStream = CFReadStreamCreateForStreamedHTTPRequest(kCFAllocatorDefault, request,(CFReadStreamRef)[self postBodyReadStream]);
8.591 + } else {
8.592 +
8.593 + // If we have a request body, we'll stream it from memory using our custom stream, so that we can measure bandwidth use and it can be bandwidth-throttled if nescessary
8.594 + if ([self postBody]) {
8.595 + if ([self shouldCompressRequestBody] && [self compressedPostBody]) {
8.596 + [self setPostBodyReadStream:[ASIInputStream inputStreamWithData:[self compressedPostBody]]];
8.597 + } else if ([self postBody]) {
8.598 + [self setPostBodyReadStream:[ASIInputStream inputStreamWithData:[self postBody]]];
8.599 + }
8.600 + readStream = CFReadStreamCreateForStreamedHTTPRequest(kCFAllocatorDefault, request,(CFReadStreamRef)[self postBodyReadStream]);
8.601 +
8.602 + } else {
8.603 + readStream = CFReadStreamCreateForHTTPRequest(kCFAllocatorDefault, request);
8.604 + }
8.605 + }
8.606 + if (!readStream) {
8.607 + [[self cancelledLock] unlock];
8.608 + [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIInternalErrorWhileBuildingRequestType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Unable to create read stream",NSLocalizedDescriptionKey,nil]]];
8.609 + return;
8.610 + }
8.611 +
8.612 + // Tell CFNetwork not to validate SSL certificates
8.613 + if (!validatesSecureCertificate) {
8.614 + CFReadStreamSetProperty(readStream, kCFStreamPropertySSLSettings, [NSMutableDictionary dictionaryWithObject:(NSString *)kCFBooleanFalse forKey:(NSString *)kCFStreamSSLValidatesCertificateChain]);
8.615 + }
8.616 +
8.617 +
8.618 + // Handle proxy settings
8.619 +
8.620 + // Have details of the proxy been set on this request
8.621 + if (![self proxyHost] && ![self proxyPort]) {
8.622 +
8.623 + // If not, we need to figure out what they'll be
8.624 +
8.625 + NSArray *proxies = nil;
8.626 +
8.627 + // Have we been given a proxy auto config file?
8.628 + if ([self PACurl]) {
8.629 +
8.630 + proxies = [ASIHTTPRequest proxiesForURL:[self url] fromPAC:[self PACurl]];
8.631 +
8.632 + // Detect proxy settings and apply them
8.633 + } else {
8.634 +
8.635 + #if TARGET_OS_IPHONE
8.636 + #if TARGET_IPHONE_SIMULATOR && __IPHONE_OS_VERSION_MIN_REQUIRED < IPHONE_3_0
8.637 + // Can't detect proxies in 2.2.1 Simulator
8.638 + NSDictionary *proxySettings = [NSMutableDictionary dictionary];
8.639 + #else
8.640 + NSDictionary *proxySettings = [(NSDictionary *)CFNetworkCopySystemProxySettings() autorelease];
8.641 + #endif
8.642 + #else
8.643 + NSDictionary *proxySettings = [(NSDictionary *)SCDynamicStoreCopyProxies(NULL) autorelease];
8.644 + #endif
8.645 +
8.646 + proxies = [(NSArray *)CFNetworkCopyProxiesForURL((CFURLRef)[self url], (CFDictionaryRef)proxySettings) autorelease];
8.647 +
8.648 + // Now check to see if the proxy settings contained a PAC url, we need to run the script to get the real list of proxies if so
8.649 + NSDictionary *settings = [proxies objectAtIndex:0];
8.650 + if ([settings objectForKey:(NSString *)kCFProxyAutoConfigurationURLKey]) {
8.651 + proxies = [ASIHTTPRequest proxiesForURL:[self url] fromPAC:[settings objectForKey:(NSString *)kCFProxyAutoConfigurationURLKey]];
8.652 + }
8.653 + }
8.654 +
8.655 + if (!proxies) {
8.656 + CFRelease(readStream);
8.657 + readStream = NULL;
8.658 + [[self cancelledLock] unlock];
8.659 + [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIInternalErrorWhileBuildingRequestType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Unable to obtain information on proxy servers needed for request",NSLocalizedDescriptionKey,nil]]];
8.660 + return;
8.661 + }
8.662 + // I don't really understand why the dictionary returned by CFNetworkCopyProxiesForURL uses different key names from CFNetworkCopySystemProxySettings/SCDynamicStoreCopyProxies
8.663 + // and why its key names are documented while those we actually need to use don't seem to be (passing the kCF* keys doesn't seem to work)
8.664 + if ([proxies count] > 0) {
8.665 + NSDictionary *settings = [proxies objectAtIndex:0];
8.666 + [self setProxyHost:[settings objectForKey:(NSString *)kCFProxyHostNameKey]];
8.667 + [self setProxyPort:[[settings objectForKey:(NSString *)kCFProxyPortNumberKey] intValue]];
8.668 + }
8.669 + }
8.670 + if ([self proxyHost] && [self proxyPort]) {
8.671 + NSMutableDictionary *proxyToUse = [NSMutableDictionary dictionaryWithObjectsAndKeys:[self proxyHost],kCFStreamPropertyHTTPProxyHost,[NSNumber numberWithInt:[self proxyPort]],kCFStreamPropertyHTTPProxyPort,nil];
8.672 + CFReadStreamSetProperty(readStream, kCFStreamPropertyHTTPProxy, proxyToUse);
8.673 + }
8.674 +
8.675 + // Set the client
8.676 + CFStreamClientContext ctxt = {0, self, NULL, NULL, NULL};
8.677 + if (!CFReadStreamSetClient(readStream, kNetworkEvents, ReadStreamClientCallBack, &ctxt)) {
8.678 + CFRelease(readStream);
8.679 + readStream = NULL;
8.680 + [[self cancelledLock] unlock];
8.681 + [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIInternalErrorWhileBuildingRequestType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Unable to setup read stream",NSLocalizedDescriptionKey,nil]]];
8.682 + return;
8.683 + }
8.684 +
8.685 + // Schedule the stream
8.686 + CFReadStreamScheduleWithRunLoop(readStream, CFRunLoopGetCurrent(), ASIHTTPRequestRunMode);
8.687 +
8.688 + // Start the HTTP connection
8.689 + if (!CFReadStreamOpen(readStream)) {
8.690 + CFReadStreamSetClient(readStream, 0, NULL, NULL);
8.691 + CFReadStreamUnscheduleFromRunLoop(readStream, CFRunLoopGetCurrent(), ASIHTTPRequestRunMode);
8.692 + CFRelease(readStream);
8.693 + readStream = NULL;
8.694 + [[self cancelledLock] unlock];
8.695 + [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIInternalErrorWhileBuildingRequestType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Unable to start HTTP connection",NSLocalizedDescriptionKey,nil]]];
8.696 + return;
8.697 + }
8.698 + [[self cancelledLock] unlock];
8.699 +
8.700 +
8.701 + if (shouldResetProgressIndicators) {
8.702 + double amount = 1;
8.703 + if (showAccurateProgress) {
8.704 + amount = postLength;
8.705 + }
8.706 + [self resetUploadProgress:amount];
8.707 + }
8.708 + // Record when the request started, so we can timeout if nothing happens
8.709 + [self setLastActivityTime:[NSDate date]];
8.710 +}
8.711 +
8.712 +// This is the 'main loop' for the request. Basically, it runs the runloop that our network stuff is attached to, and checks to see if we should cancel or timeout
8.713 +- (void)loadRequest
8.714 +{
8.715 + [self startRequest];
8.716 +
8.717 +
8.718 + // Wait for the request to finish
8.719 + while (!complete) {
8.720 +
8.721 + // This may take a while, so we'll release the pool each cycle to stop a giant backlog of autoreleased objects building up
8.722 + [pool release];
8.723 + pool = [[NSAutoreleasePool alloc] init];
8.724 +
8.725 + NSDate *now = [NSDate date];
8.726 +
8.727 + // We won't let the request cancel until we're done with this cycle of the loop
8.728 + [[self cancelledLock] lock];
8.729 +
8.730 + // See if we need to timeout
8.731 + if (lastActivityTime && timeOutSeconds > 0 && [now timeIntervalSinceDate:lastActivityTime] > timeOutSeconds) {
8.732 +
8.733 + // Prevent timeouts before 128KB* has been sent when the size of data to upload is greater than 128KB* (*32KB on iPhone 3.0 SDK)
8.734 + // This is to workaround the fact that kCFStreamPropertyHTTPRequestBytesWrittenCount is the amount written to the buffer, not the amount actually sent
8.735 + // This workaround prevents erroneous timeouts in low bandwidth situations (eg iPhone)
8.736 + if (totalBytesSent || postLength <= uploadBufferSize || (uploadBufferSize > 0 && totalBytesSent > uploadBufferSize)) {
8.737 + [self failWithError:ASIRequestTimedOutError];
8.738 + [[self cancelledLock] unlock];
8.739 + [self cancelLoad];
8.740 + [self setComplete:YES];
8.741 + break;
8.742 + }
8.743 + }
8.744 +
8.745 + // Do we need to redirect?
8.746 + if ([self needsRedirect]) {
8.747 + [[self cancelledLock] unlock];
8.748 + [self cancelLoad];
8.749 + [self setNeedsRedirect:NO];
8.750 + [self setRedirectCount:[self redirectCount]+1];
8.751 + if ([self redirectCount] > RedirectionLimit) {
8.752 + // Some naughty / badly coded website is trying to force us into a redirection loop. This is not cool.
8.753 + [self failWithError:ASITooMuchRedirectionError];
8.754 + [self setComplete:YES];
8.755 + } else {
8.756 + // Go all the way back to the beginning and build the request again, so that we can apply any new cookies
8.757 + [self main];
8.758 + }
8.759 + break;
8.760 + }
8.761 +
8.762 + // See if our NSOperationQueue told us to cancel
8.763 + if ([self isCancelled] || [self complete]) {
8.764 + [[self cancelledLock] unlock];
8.765 + break;
8.766 + }
8.767 +
8.768 + // Find out if we've sent any more data than last time, and reset the timeout if so
8.769 + if (totalBytesSent > lastBytesSent) {
8.770 + [self setLastActivityTime:[NSDate date]];
8.771 + [self setLastBytesSent:totalBytesSent];
8.772 + }
8.773 +
8.774 + // Find out how much data we've uploaded so far
8.775 + [self setTotalBytesSent:[[(NSNumber *)CFReadStreamCopyProperty(readStream, kCFStreamPropertyHTTPRequestBytesWrittenCount) autorelease] unsignedLongLongValue]];
8.776 +
8.777 + // Updating the progress indicators will attempt to aquire the lock again when needed
8.778 + [[self cancelledLock] unlock];
8.779 +
8.780 + [self updateProgressIndicators];
8.781 +
8.782 + // Measure bandwidth used, and throttle if nescessary
8.783 + [ASIHTTPRequest measureBandwidthUsage];
8.784 +
8.785 + // This thread should wait for 1/4 second for the stream to do something. We'll stop early if it does.
8.786 + CFRunLoopRunInMode(ASIHTTPRequestRunMode,0.25,YES);
8.787 + }
8.788 +
8.789 + [pool release];
8.790 + pool = nil;
8.791 +}
8.792 +
8.793 +// Cancel loading and clean up. NEVER CALL THIS FROM ANOTHER THREAD!
8.794 +- (void)cancelLoad
8.795 +{
8.796 + [[self cancelledLock] lock];
8.797 + if (readStream) {
8.798 + CFReadStreamSetClient(readStream, kCFStreamEventNone, NULL, NULL);
8.799 + CFReadStreamUnscheduleFromRunLoop(readStream, CFRunLoopGetCurrent(), ASIHTTPRequestRunMode);
8.800 + CFReadStreamClose(readStream);
8.801 + CFRelease(readStream);
8.802 + readStream = NULL;
8.803 + }
8.804 +
8.805 + [[self postBodyReadStream] close];
8.806 +
8.807 + if ([self rawResponseData]) {
8.808 + [self setRawResponseData:nil];
8.809 +
8.810 + // If we were downloading to a file
8.811 + } else if ([self temporaryFileDownloadPath]) {
8.812 + [[self fileDownloadOutputStream] close];
8.813 +
8.814 + // If we haven't said we might want to resume, let's remove the temporary file too
8.815 + if (![self allowResumeForFileDownloads]) {
8.816 + [self removeTemporaryDownloadFile];
8.817 + }
8.818 + }
8.819 +
8.820 + // Clean up any temporary file used to store request body for streaming
8.821 + if (![self authenticationChallengeInProgress] && [self didCreateTemporaryPostDataFile]) {
8.822 + [self removePostDataFile];
8.823 + [self setDidCreateTemporaryPostDataFile:NO];
8.824 + }
8.825 +
8.826 + [self setResponseHeaders:nil];
8.827 + [[self cancelledLock] unlock];
8.828 +}
8.829 +
8.830 +
8.831 +- (void)removeTemporaryDownloadFile
8.832 +{
8.833 + if ([self temporaryFileDownloadPath]) {
8.834 + if ([[NSFileManager defaultManager] fileExistsAtPath:[self temporaryFileDownloadPath]]) {
8.835 + NSError *removeError = nil;
8.836 + [[NSFileManager defaultManager] removeItemAtPath:[self temporaryFileDownloadPath] error:&removeError];
8.837 + if (removeError) {
8.838 + [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIFileManagementError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Failed to delete file at path '%@'",[self temporaryFileDownloadPath]],NSLocalizedDescriptionKey,removeError,NSUnderlyingErrorKey,nil]]];
8.839 + }
8.840 + }
8.841 + [self setTemporaryFileDownloadPath:nil];
8.842 + }
8.843 +}
8.844 +
8.845 +- (void)removePostDataFile
8.846 +{
8.847 + if ([self postBodyFilePath]) {
8.848 + NSError *removeError = nil;
8.849 + [[NSFileManager defaultManager] removeItemAtPath:[self postBodyFilePath] error:&removeError];
8.850 + if (removeError) {
8.851 + [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIFileManagementError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Failed to delete file at path '%@'",[self postBodyFilePath]],NSLocalizedDescriptionKey,removeError,NSUnderlyingErrorKey,nil]]];
8.852 + }
8.853 + [self setPostBodyFilePath:nil];
8.854 + }
8.855 + if ([self compressedPostBodyFilePath]) {
8.856 + NSError *removeError = nil;
8.857 + [[NSFileManager defaultManager] removeItemAtPath:[self compressedPostBodyFilePath] error:&removeError];
8.858 + if (removeError) {
8.859 + [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIFileManagementError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Failed to delete file at path '%@'",[self compressedPostBodyFilePath]],NSLocalizedDescriptionKey,removeError,NSUnderlyingErrorKey,nil]]];
8.860 + }
8.861 + [self setCompressedPostBodyFilePath:nil];
8.862 + }
8.863 +}
8.864 +
8.865 +
8.866 +#pragma mark upload/download progress
8.867 +
8.868 +
8.869 +- (void)updateProgressIndicators
8.870 +{
8.871 +
8.872 + //Only update progress if this isn't a HEAD request used to preset the content-length
8.873 + if (!mainRequest) {
8.874 + if ([self showAccurateProgress] || ([self complete] && ![self updatedProgress])) {
8.875 + [self updateUploadProgress];
8.876 + [self updateDownloadProgress];
8.877 + }
8.878 + }
8.879 +
8.880 +}
8.881 +
8.882 +
8.883 +- (void)setUploadProgressDelegate:(id)newDelegate
8.884 +{
8.885 + uploadProgressDelegate = newDelegate;
8.886 +
8.887 + // If the uploadProgressDelegate is an NSProgressIndicator, we set it's MaxValue to 1.0 so we can treat it similarly to UIProgressViews
8.888 + SEL selector = @selector(setMaxValue:);
8.889 + if ([uploadProgressDelegate respondsToSelector:selector]) {
8.890 + double max = 1.0;
8.891 + NSMethodSignature *signature = [[uploadProgressDelegate class] instanceMethodSignatureForSelector:selector];
8.892 + NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
8.893 + [invocation setTarget:uploadProgressDelegate];
8.894 + [invocation setSelector:selector];
8.895 + [invocation setArgument:&max atIndex:2];
8.896 + [invocation invoke];
8.897 +
8.898 + }
8.899 +}
8.900 +
8.901 +- (void)setDownloadProgressDelegate:(id)newDelegate
8.902 +{
8.903 + downloadProgressDelegate = newDelegate;
8.904 +
8.905 + // If the downloadProgressDelegate is an NSProgressIndicator, we set it's MaxValue to 1.0 so we can treat it similarly to UIProgressViews
8.906 + SEL selector = @selector(setMaxValue:);
8.907 + if ([downloadProgressDelegate respondsToSelector:selector]) {
8.908 + double max = 1.0;
8.909 + NSMethodSignature *signature = [[downloadProgressDelegate class] instanceMethodSignatureForSelector:selector];
8.910 + NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
8.911 + [invocation setSelector:@selector(setMaxValue:)];
8.912 + [invocation setArgument:&max atIndex:2];
8.913 + [invocation invokeWithTarget:downloadProgressDelegate];
8.914 + }
8.915 +}
8.916 +
8.917 +
8.918 +- (void)resetUploadProgress:(unsigned long long)value
8.919 +{
8.920 + [progressLock lock];
8.921 +
8.922 + // Request this request's own upload progress delegate
8.923 + if (uploadProgressDelegate) {
8.924 + [ASIHTTPRequest setProgress:0 forProgressIndicator:uploadProgressDelegate];
8.925 + }
8.926 + [progressLock unlock];
8.927 +}
8.928 +
8.929 +- (void)updateUploadProgress
8.930 +{
8.931 + [[self cancelledLock] lock];
8.932 + if ([self isCancelled]) {
8.933 + [[self cancelledLock] unlock];
8.934 + return;
8.935 + }
8.936 +
8.937 + // If this is the first time we've written to the buffer, byteCount will be the size of the buffer (currently seems to be 128KB on both Leopard and iPhone 2.2.1, 32KB on iPhone 3.0)
8.938 + // If request body is less than the buffer size, byteCount will be the total size of the request body
8.939 + // We will remove this from any progress display, as kCFStreamPropertyHTTPRequestBytesWrittenCount does not tell us how much data has actually be written
8.940 + if (totalBytesSent > 0 && uploadBufferSize == 0 && totalBytesSent != postLength) {
8.941 + [self setUploadBufferSize:totalBytesSent];
8.942 + SEL selector = @selector(setUploadBufferSize:);
8.943 + if ([queue respondsToSelector:selector]) {
8.944 + NSMethodSignature *signature = nil;
8.945 + signature = [[queue class] instanceMethodSignatureForSelector:selector];
8.946 + NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
8.947 + [invocation setTarget:queue];
8.948 + [invocation setSelector:selector];
8.949 + [invocation setArgument:&totalBytesSent atIndex:2];
8.950 + [invocation invoke];
8.951 + }
8.952 + }
8.953 +
8.954 +
8.955 +
8.956 + [[self cancelledLock] unlock];
8.957 +
8.958 + if (totalBytesSent == 0) {
8.959 + return;
8.960 + }
8.961 +
8.962 +
8.963 + // Update the progress queue, if we have one
8.964 + SEL selector = @selector(incrementUploadProgressBy:);
8.965 + if ([queue respondsToSelector:selector]) {
8.966 + unsigned long long value = 0;
8.967 + if (showAccurateProgress) {
8.968 + if (totalBytesSent == postLength || lastBytesSent > 0) {
8.969 + value = totalBytesSent-lastBytesSent;
8.970 + } else {
8.971 + value = 0;
8.972 + }
8.973 + } else {
8.974 + value = 1;
8.975 + [self setUpdatedProgress:YES];
8.976 + }
8.977 +
8.978 + NSMethodSignature *signature = nil;
8.979 + signature = [[queue class] instanceMethodSignatureForSelector:selector];
8.980 + NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
8.981 + [invocation setTarget:queue];
8.982 + [invocation setSelector:selector];
8.983 + [invocation setArgument:&value atIndex:2];
8.984 + [invocation invoke];
8.985 + }
8.986 +
8.987 + // Update this request's own upload progress delegate
8.988 + if (uploadProgressDelegate) {
8.989 + [ASIHTTPRequest setProgress:(double)(1.0*(totalBytesSent-uploadBufferSize)/(postLength-uploadBufferSize)) forProgressIndicator:uploadProgressDelegate];
8.990 +
8.991 + }
8.992 +
8.993 +}
8.994 +
8.995 +
8.996 +- (void)resetDownloadProgress:(unsigned long long)value
8.997 +{
8.998 + [progressLock lock];
8.999 +
8.1000 + // Reset download progress for this request in the queue
8.1001 + SEL selector = @selector(incrementDownloadSizeBy:);
8.1002 + if ([queue respondsToSelector:selector]) {
8.1003 + NSMethodSignature *signature = [[queue class] instanceMethodSignatureForSelector:selector];
8.1004 + NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
8.1005 + [invocation setTarget:queue];
8.1006 + [invocation setSelector:selector];
8.1007 + [invocation setArgument:&value atIndex:2];
8.1008 + [invocation invoke];
8.1009 + }
8.1010 +
8.1011 + // Request this request's own download progress delegate
8.1012 + if (downloadProgressDelegate) {
8.1013 + [ASIHTTPRequest setProgress:0 forProgressIndicator:downloadProgressDelegate];
8.1014 + }
8.1015 + [progressLock unlock];
8.1016 +}
8.1017 +
8.1018 +- (void)updateDownloadProgress
8.1019 +{
8.1020 +
8.1021 +
8.1022 + // We won't update download progress until we've examined the headers, since we might need to authenticate
8.1023 + if (responseHeaders) {
8.1024 +
8.1025 + unsigned long long bytesReadSoFar = totalBytesRead+partialDownloadSize;
8.1026 +
8.1027 + // We're using a progress queue or compatible controller to handle progress
8.1028 + SEL selector = @selector(incrementDownloadProgressBy:);
8.1029 + if ([queue respondsToSelector:@selector(incrementDownloadProgressBy:)]) {
8.1030 +
8.1031 + NSAutoreleasePool *thePool = [[NSAutoreleasePool alloc] init];
8.1032 +
8.1033 + unsigned long long value = 0;
8.1034 + if ([self showAccurateProgress]) {
8.1035 + value = bytesReadSoFar-[self lastBytesRead];
8.1036 + } else {
8.1037 + value = 1;
8.1038 + [self setUpdatedProgress:YES];
8.1039 + }
8.1040 +
8.1041 +
8.1042 + NSMethodSignature *signature = [[queue class] instanceMethodSignatureForSelector:selector];
8.1043 + NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
8.1044 + [invocation setTarget:queue];
8.1045 + [invocation setSelector:selector];
8.1046 + [invocation setArgument:&value atIndex:2];
8.1047 + [invocation invoke];
8.1048 +
8.1049 + [thePool release];
8.1050 + }
8.1051 +
8.1052 + if (downloadProgressDelegate && contentLength > 0) {
8.1053 + [ASIHTTPRequest setProgress:(double)(1.0*bytesReadSoFar/(contentLength+partialDownloadSize)) forProgressIndicator:downloadProgressDelegate];
8.1054 + }
8.1055 +
8.1056 + [self setLastBytesRead:bytesReadSoFar];
8.1057 + }
8.1058 +
8.1059 +}
8.1060 +
8.1061 +-(void)removeUploadProgressSoFar
8.1062 +{
8.1063 +
8.1064 + // We're using a progress queue or compatible controller to handle progress
8.1065 + SEL selector = @selector(decrementUploadProgressBy:);
8.1066 + if ([queue respondsToSelector:selector]) {
8.1067 + unsigned long long value = 0-lastBytesSent;
8.1068 +
8.1069 + NSMethodSignature *signature = [[queue class] instanceMethodSignatureForSelector:selector];
8.1070 + NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
8.1071 + [invocation setTarget:queue];
8.1072 + [invocation setSelector:selector];
8.1073 + [invocation setArgument:&value atIndex:2];
8.1074 + [invocation invoke];
8.1075 +
8.1076 + }
8.1077 +
8.1078 + if (uploadProgressDelegate) {
8.1079 + [ASIHTTPRequest setProgress:0 forProgressIndicator:uploadProgressDelegate];
8.1080 + }
8.1081 +}
8.1082 +
8.1083 +
8.1084 ++ (void)setProgress:(double)progress forProgressIndicator:(id)indicator
8.1085 +{
8.1086 +
8.1087 + SEL selector;
8.1088 + [progressLock lock];
8.1089 +
8.1090 + // Cocoa Touch: UIProgressView
8.1091 + if ([indicator respondsToSelector:@selector(setProgress:)]) {
8.1092 + selector = @selector(setProgress:);
8.1093 + NSMethodSignature *signature = [[indicator class] instanceMethodSignatureForSelector:selector];
8.1094 + NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
8.1095 + [invocation setSelector:selector];
8.1096 + float progressFloat = (float)progress; // UIProgressView wants a float for the progress parameter
8.1097 + [invocation setArgument:&progressFloat atIndex:2];
8.1098 +
8.1099 + // If we're running in the main thread, update the progress straight away. Otherwise, it's not that urgent
8.1100 + [invocation performSelectorOnMainThread:@selector(invokeWithTarget:) withObject:indicator waitUntilDone:[NSThread isMainThread]];
8.1101 +
8.1102 +
8.1103 + // Cocoa: NSProgressIndicator
8.1104 + } else if ([indicator respondsToSelector:@selector(setDoubleValue:)]) {
8.1105 + selector = @selector(setDoubleValue:);
8.1106 + NSMethodSignature *signature = [[indicator class] instanceMethodSignatureForSelector:selector];
8.1107 + NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
8.1108 + [invocation setSelector:selector];
8.1109 + [invocation setArgument:&progress atIndex:2];
8.1110 +
8.1111 +
8.1112 + [invocation performSelectorOnMainThread:@selector(invokeWithTarget:) withObject:indicator waitUntilDone:[NSThread isMainThread]];
8.1113 +
8.1114 + }
8.1115 + [progressLock unlock];
8.1116 +}
8.1117 +
8.1118 +
8.1119 +#pragma mark handling request complete / failure
8.1120 +
8.1121 +// Subclasses might override this method to process the result in the same thread
8.1122 +// If you do this, don't forget to call [super requestFinished] to let the queue / delegate know we're done
8.1123 +- (id)requestFinished
8.1124 +{
8.1125 + if ([self error] || [self mainRequest]) {
8.1126 + return;
8.1127 + }
8.1128 + // Let the queue know we are done
8.1129 + if ([queue respondsToSelector:@selector(requestDidFinish:)]) {
8.1130 + [queue performSelectorOnMainThread:@selector(requestDidFinish:) withObject:self waitUntilDone:[NSThread isMainThread]];
8.1131 + }
8.1132 +
8.1133 + // Let the delegate know we are done
8.1134 + if (didFinishSelector && [delegate respondsToSelector:didFinishSelector]) {
8.1135 + [delegate performSelectorOnMainThread:didFinishSelector withObject:self waitUntilDone:[NSThread isMainThread]];
8.1136 + }
8.1137 +}
8.1138 +
8.1139 +// Subclasses might override this method to perform error handling in the same thread
8.1140 +// If you do this, don't forget to call [super failWithError:] to let the queue / delegate know we're done
8.1141 +- (void)failWithError:(NSError *)theError
8.1142 +{
8.1143 + [self setComplete:YES];
8.1144 +
8.1145 + if ([self isCancelled] || [self error]) {
8.1146 + return;
8.1147 + }
8.1148 +
8.1149 + // If this is a HEAD request created by an ASINetworkQueue or compatible queue delegate, make the main request fail
8.1150 + if ([self mainRequest]) {
8.1151 + ASIHTTPRequest *mRequest = [self mainRequest];
8.1152 + [mRequest setError:theError];
8.1153 +
8.1154 + // Let the queue know something went wrong
8.1155 + if ([queue respondsToSelector:@selector(requestDidFail:)]) {
8.1156 + [queue performSelectorOnMainThread:@selector(requestDidFail:) withObject:mRequest waitUntilDone:[NSThread isMainThread]];
8.1157 + }
8.1158 +
8.1159 + } else {
8.1160 + [self setError:theError];
8.1161 +
8.1162 + // Let the queue know something went wrong
8.1163 + if ([queue respondsToSelector:@selector(requestDidFail:)]) {
8.1164 + [queue performSelectorOnMainThread:@selector(requestDidFail:) withObject:self waitUntilDone:[NSThread isMainThread]];
8.1165 + }
8.1166 +
8.1167 + // Let the delegate know something went wrong
8.1168 + if (didFailSelector && [delegate respondsToSelector:didFailSelector]) {
8.1169 + [delegate performSelectorOnMainThread:didFailSelector withObject:self waitUntilDone:[NSThread isMainThread]];
8.1170 + }
8.1171 + }
8.1172 +}
8.1173 +
8.1174 +#pragma mark parsing HTTP response headers
8.1175 +
8.1176 +- (BOOL)readResponseHeadersReturningAuthenticationFailure
8.1177 +{
8.1178 + [self setAuthenticationChallengeInProgress:NO];
8.1179 + [self setNeedsProxyAuthentication:NO];
8.1180 + BOOL isAuthenticationChallenge = NO;
8.1181 + CFHTTPMessageRef headers = (CFHTTPMessageRef)CFReadStreamCopyProperty(readStream, kCFStreamPropertyHTTPResponseHeader);
8.1182 + if (CFHTTPMessageIsHeaderComplete(headers)) {
8.1183 +
8.1184 + CFDictionaryRef headerFields = CFHTTPMessageCopyAllHeaderFields(headers);
8.1185 + [self setResponseHeaders:(NSDictionary *)headerFields];
8.1186 +
8.1187 + CFRelease(headerFields);
8.1188 + [self setResponseStatusCode:CFHTTPMessageGetResponseStatusCode(headers)];
8.1189 + [self setResponseStatusMessage:[(NSString *)CFHTTPMessageCopyResponseStatusLine(headers) autorelease]];
8.1190 +
8.1191 + // Is the server response a challenge for credentials?
8.1192 + isAuthenticationChallenge = ([self responseStatusCode] == 401);
8.1193 + if ([self responseStatusCode] == 407) {
8.1194 + isAuthenticationChallenge = YES;
8.1195 + [self setNeedsProxyAuthentication:YES];
8.1196 + }
8.1197 + [self setAuthenticationChallengeInProgress:isAuthenticationChallenge];
8.1198 +
8.1199 + // We won't reset the download progress delegate if we got an authentication challenge
8.1200 + if (!isAuthenticationChallenge) {
8.1201 +
8.1202 + // See if we got a Content-length header
8.1203 + NSString *cLength = [responseHeaders valueForKey:@"Content-Length"];
8.1204 + if (cLength) {
8.1205 + [self setContentLength:CFStringGetIntValue((CFStringRef)cLength)];
8.1206 + if ([self mainRequest]) {
8.1207 + [[self mainRequest] setContentLength:contentLength];
8.1208 + }
8.1209 + if ([self showAccurateProgress] && [self shouldResetProgressIndicators]) {
8.1210 + [self resetDownloadProgress:[self contentLength]+[self partialDownloadSize]];
8.1211 + }
8.1212 + }
8.1213 +
8.1214 + // Handle response text encoding
8.1215 + // If the Content-Type header specified an encoding, we'll use that, otherwise we use defaultStringEncoding (which defaults to NSISOLatin1StringEncoding)
8.1216 + NSString *contentType = [[self responseHeaders] objectForKey:@"Content-Type"];
8.1217 + NSStringEncoding encoding = [self defaultResponseEncoding];
8.1218 + if (contentType) {
8.1219 +
8.1220 + NSString *charsetSeparator = @"charset=";
8.1221 + NSScanner *charsetScanner = [NSScanner scannerWithString: contentType];
8.1222 + NSString *IANAEncoding = nil;
8.1223 +
8.1224 + if ([charsetScanner scanUpToString: charsetSeparator intoString: NULL] && [charsetScanner scanLocation] < [contentType length])
8.1225 + {
8.1226 + [charsetScanner setScanLocation: [charsetScanner scanLocation] + [charsetSeparator length]];
8.1227 + [charsetScanner scanUpToString: @";" intoString: &IANAEncoding];
8.1228 + }
8.1229 +
8.1230 + if (IANAEncoding) {
8.1231 + CFStringEncoding cfEncoding = CFStringConvertIANACharSetNameToEncoding((CFStringRef)IANAEncoding);
8.1232 + if (cfEncoding != kCFStringEncodingInvalidId) {
8.1233 + encoding = CFStringConvertEncodingToNSStringEncoding(cfEncoding);
8.1234 + }
8.1235 + }
8.1236 + }
8.1237 + [self setResponseEncoding:encoding];
8.1238 +
8.1239 + // Handle cookies
8.1240 + NSArray *newCookies = [NSHTTPCookie cookiesWithResponseHeaderFields:responseHeaders forURL:url];
8.1241 + [self setResponseCookies:newCookies];
8.1242 +
8.1243 + if ([self useCookiePersistance]) {
8.1244 +
8.1245 + // Store cookies in global persistent store
8.1246 + [[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookies:newCookies forURL:url mainDocumentURL:nil];
8.1247 +
8.1248 + // We also keep any cookies in the sessionCookies array, so that we have a reference to them if we need to remove them later
8.1249 + NSHTTPCookie *cookie;
8.1250 + for (cookie in newCookies) {
8.1251 + [ASIHTTPRequest addSessionCookie:cookie];
8.1252 + }
8.1253 + }
8.1254 + // Do we need to redirect?
8.1255 + if ([self shouldRedirect] && [responseHeaders valueForKey:@"Location"]) {
8.1256 + if ([self responseStatusCode] > 300 && [self responseStatusCode] < 308 && [self responseStatusCode] != 304) {
8.1257 + if ([self responseStatusCode] == 303) {
8.1258 + [self setRequestMethod:@"GET"];
8.1259 + [self setPostBody:nil];
8.1260 + [self setPostLength:0];
8.1261 + [self setRequestHeaders:nil];
8.1262 + }
8.1263 + [self setURL:[[NSURL URLWithString:[responseHeaders valueForKey:@"Location"] relativeToURL:[self url]] absoluteURL]];
8.1264 + [self setNeedsRedirect:YES];
8.1265 +
8.1266 + // Clear the request cookies
8.1267 + // This means manually added cookies will not be added to the redirect request - only those stored in the global persistent store
8.1268 + // But, this is probably the safest option - we might be redirecting to a different domain
8.1269 + [self setRequestCookies:[NSMutableArray array]];
8.1270 + }
8.1271 + }
8.1272 +
8.1273 + }
8.1274 +
8.1275 + }
8.1276 + CFRelease(headers);
8.1277 + return isAuthenticationChallenge;
8.1278 +}
8.1279 +
8.1280 +
8.1281 +#pragma mark http authentication
8.1282 +
8.1283 +- (void)saveProxyCredentialsToKeychain:(NSDictionary *)newCredentials
8.1284 +{
8.1285 + NSURLCredential *authenticationCredentials = [NSURLCredential credentialWithUser:[newCredentials objectForKey:(NSString *)kCFHTTPAuthenticationUsername] password:[newCredentials objectForKey:(NSString *)kCFHTTPAuthenticationPassword] persistence:NSURLCredentialPersistencePermanent];
8.1286 + if (authenticationCredentials) {
8.1287 + [ASIHTTPRequest saveCredentials:authenticationCredentials forProxy:[self proxyHost] port:[self proxyPort] realm:[self proxyAuthenticationRealm]];
8.1288 + }
8.1289 +}
8.1290 +
8.1291 +
8.1292 +- (void)saveCredentialsToKeychain:(NSDictionary *)newCredentials
8.1293 +{
8.1294 + NSURLCredential *authenticationCredentials = [NSURLCredential credentialWithUser:[newCredentials objectForKey:(NSString *)kCFHTTPAuthenticationUsername] password:[newCredentials objectForKey:(NSString *)kCFHTTPAuthenticationPassword] persistence:NSURLCredentialPersistencePermanent];
8.1295 +
8.1296 + if (authenticationCredentials) {
8.1297 + [ASIHTTPRequest saveCredentials:authenticationCredentials forHost:[[self url] host] port:[[[self url] port] intValue] protocol:[[self url] scheme] realm:[self authenticationRealm]];
8.1298 + }
8.1299 +}
8.1300 +
8.1301 +- (BOOL)applyProxyCredentials:(NSDictionary *)newCredentials
8.1302 +{
8.1303 + [self setProxyAuthenticationRetryCount:[self proxyAuthenticationRetryCount]+1];
8.1304 +
8.1305 + if (newCredentials && proxyAuthentication && request) {
8.1306 +
8.1307 + // Apply whatever credentials we've built up to the old request
8.1308 + if (CFHTTPMessageApplyCredentialDictionary(request, proxyAuthentication, (CFMutableDictionaryRef)newCredentials, NULL)) {
8.1309 +
8.1310 + //If we have credentials and they're ok, let's save them to the keychain
8.1311 + if (useKeychainPersistance) {
8.1312 + [self saveProxyCredentialsToKeychain:newCredentials];
8.1313 + }
8.1314 + if (useSessionPersistance) {
8.1315 + NSMutableDictionary *sessionProxyCredentials = [NSMutableDictionary dictionary];
8.1316 + [sessionProxyCredentials setObject:(id)proxyAuthentication forKey:@"Authentication"];
8.1317 + [sessionProxyCredentials setObject:newCredentials forKey:@"Credentials"];
8.1318 + [sessionProxyCredentials setObject:[self proxyHost] forKey:@"Host"];
8.1319 + [sessionProxyCredentials setObject:[NSNumber numberWithInt:[self proxyPort]] forKey:@"Port"];
8.1320 + [sessionProxyCredentials setObject:[self proxyAuthenticationScheme] forKey:@"AuthenticationScheme"];
8.1321 + [[self class] storeProxyAuthenticationCredentialsInSessionStore:sessionProxyCredentials];
8.1322 + }
8.1323 + [self setProxyCredentials:newCredentials];
8.1324 + return YES;
8.1325 + } else {
8.1326 + [[self class] removeProxyAuthenticationCredentialsFromSessionStore:newCredentials];
8.1327 + }
8.1328 + }
8.1329 + return NO;
8.1330 +}
8.1331 +
8.1332 +- (BOOL)applyCredentials:(NSDictionary *)newCredentials
8.1333 +{
8.1334 + [self setAuthenticationRetryCount:[self authenticationRetryCount]+1];
8.1335 +
8.1336 + if (newCredentials && requestAuthentication && request) {
8.1337 + // Apply whatever credentials we've built up to the old request
8.1338 + if (CFHTTPMessageApplyCredentialDictionary(request, requestAuthentication, (CFMutableDictionaryRef)newCredentials, NULL)) {
8.1339 +
8.1340 + //If we have credentials and they're ok, let's save them to the keychain
8.1341 + if (useKeychainPersistance) {
8.1342 + [self saveCredentialsToKeychain:newCredentials];
8.1343 + }
8.1344 + if (useSessionPersistance) {
8.1345 +
8.1346 + NSMutableDictionary *sessionCredentials = [NSMutableDictionary dictionary];
8.1347 + [sessionCredentials setObject:(id)requestAuthentication forKey:@"Authentication"];
8.1348 + [sessionCredentials setObject:newCredentials forKey:@"Credentials"];
8.1349 + [sessionCredentials setObject:[self url] forKey:@"URL"];
8.1350 + [sessionCredentials setObject:[self authenticationScheme] forKey:@"AuthenticationScheme"];
8.1351 + if ([self authenticationRealm]) {
8.1352 + [sessionCredentials setObject:[self authenticationRealm] forKey:@"AuthenticationRealm"];
8.1353 + }
8.1354 + [[self class] storeAuthenticationCredentialsInSessionStore:sessionCredentials];
8.1355 +
8.1356 + }
8.1357 + [self setRequestCredentials:newCredentials];
8.1358 + return YES;
8.1359 + } else {
8.1360 + [[self class] removeAuthenticationCredentialsFromSessionStore:newCredentials];
8.1361 + }
8.1362 + }
8.1363 + return NO;
8.1364 +}
8.1365 +
8.1366 +- (NSMutableDictionary *)findProxyCredentials
8.1367 +{
8.1368 + NSMutableDictionary *newCredentials = [[[NSMutableDictionary alloc] init] autorelease];
8.1369 +
8.1370 + // Is an account domain needed? (used currently for NTLM only)
8.1371 + if (CFHTTPAuthenticationRequiresAccountDomain(proxyAuthentication)) {
8.1372 + if (![self proxyDomain]) {
8.1373 + [self setProxyDomain:@""];
8.1374 + }
8.1375 + [newCredentials setObject:[self proxyDomain] forKey:(NSString *)kCFHTTPAuthenticationAccountDomain];
8.1376 + }
8.1377 +
8.1378 + NSString *user = nil;
8.1379 + NSString *pass = nil;
8.1380 +
8.1381 +
8.1382 + // If this is a HEAD request generated by an ASINetworkQueue, we'll try to use the details from the main request
8.1383 + if ([self mainRequest] && [[self mainRequest] proxyUsername] && [[self mainRequest] proxyPassword]) {
8.1384 + user = [[self mainRequest] proxyUsername];
8.1385 + pass = [[self mainRequest] proxyPassword];
8.1386 +
8.1387 + // Let's try to use the ones set in this object
8.1388 + } else if ([self proxyUsername] && [self proxyPassword]) {
8.1389 + user = [self proxyUsername];
8.1390 + pass = [self proxyPassword];
8.1391 + }
8.1392 +
8.1393 +
8.1394 + // Ok, that didn't work, let's try the keychain
8.1395 + // For authenticating proxies, we'll look in the keychain regardless of the value of useKeychainPersistance
8.1396 + if ((!user || !pass)) {
8.1397 + NSURLCredential *authenticationCredentials = [ASIHTTPRequest savedCredentialsForProxy:[self proxyHost] port:[self proxyPort] protocol:[[self url] scheme] realm:[self proxyAuthenticationRealm]];
8.1398 + if (authenticationCredentials) {
8.1399 + user = [authenticationCredentials user];
8.1400 + pass = [authenticationCredentials password];
8.1401 + }
8.1402 +
8.1403 + }
8.1404 +
8.1405 + // If we have a username and password, let's apply them to the request and continue
8.1406 + if (user && pass) {
8.1407 +
8.1408 + [newCredentials setObject:user forKey:(NSString *)kCFHTTPAuthenticationUsername];
8.1409 + [newCredentials setObject:pass forKey:(NSString *)kCFHTTPAuthenticationPassword];
8.1410 + return newCredentials;
8.1411 + }
8.1412 + return nil;
8.1413 +}
8.1414 +
8.1415 +
8.1416 +- (NSMutableDictionary *)findCredentials
8.1417 +{
8.1418 + NSMutableDictionary *newCredentials = [[[NSMutableDictionary alloc] init] autorelease];
8.1419 +
8.1420 + // Is an account domain needed? (used currently for NTLM only)
8.1421 + if (CFHTTPAuthenticationRequiresAccountDomain(requestAuthentication)) {
8.1422 + if (!domain) {
8.1423 + [self setDomain:@""];
8.1424 + }
8.1425 + [newCredentials setObject:domain forKey:(NSString *)kCFHTTPAuthenticationAccountDomain];
8.1426 + }
8.1427 +
8.1428 + // First, let's look at the url to see if the username and password were included
8.1429 + NSString *user = [[self url] user];
8.1430 + NSString *pass = [[self url] password];
8.1431 +
8.1432 + // If the username and password weren't in the url
8.1433 + if (!user || !pass) {
8.1434 +
8.1435 + // If this is a HEAD request generated by an ASINetworkQueue, we'll try to use the details from the main request
8.1436 + if ([self mainRequest] && [[self mainRequest] username] && [[self mainRequest] password]) {
8.1437 + user = [[self mainRequest] username];
8.1438 + pass = [[self mainRequest] password];
8.1439 +
8.1440 + // Let's try to use the ones set in this object
8.1441 + } else if ([self username] && [self password]) {
8.1442 + user = [self username];
8.1443 + pass = [self password];
8.1444 + }
8.1445 +
8.1446 + }
8.1447 +
8.1448 + // Ok, that didn't work, let's try the keychain
8.1449 + if ((!user || !pass) && useKeychainPersistance) {
8.1450 + NSURLCredential *authenticationCredentials = [ASIHTTPRequest savedCredentialsForHost:[[self url] host] port:[[[self url] port] intValue] protocol:[[self url] scheme] realm:[self authenticationRealm]];
8.1451 + if (authenticationCredentials) {
8.1452 + user = [authenticationCredentials user];
8.1453 + pass = [authenticationCredentials password];
8.1454 + }
8.1455 +
8.1456 + }
8.1457 +
8.1458 + // If we have a username and password, let's apply them to the request and continue
8.1459 + if (user && pass) {
8.1460 +
8.1461 + [newCredentials setObject:user forKey:(NSString *)kCFHTTPAuthenticationUsername];
8.1462 + [newCredentials setObject:pass forKey:(NSString *)kCFHTTPAuthenticationPassword];
8.1463 + return newCredentials;
8.1464 + }
8.1465 + return nil;
8.1466 +}
8.1467 +
8.1468 +// Called by delegate or authentication dialog to resume loading once authentication info has been populated
8.1469 +- (void)retryUsingSuppliedCredentials
8.1470 +{
8.1471 + [[self authenticationLock] lockWhenCondition:1];
8.1472 + [[self authenticationLock] unlockWithCondition:2];
8.1473 +}
8.1474 +
8.1475 +// Called by delegate or authentication dialog to cancel authentication
8.1476 +- (void)cancelAuthentication
8.1477 +{
8.1478 + [self failWithError:ASIAuthenticationError];
8.1479 + [[self authenticationLock] lockWhenCondition:1];
8.1480 + [[self authenticationLock] unlockWithCondition:2];
8.1481 +}
8.1482 +
8.1483 +- (BOOL)showProxyAuthenticationDialog
8.1484 +{
8.1485 +// Mac authentication dialog coming soon!
8.1486 +#if TARGET_OS_IPHONE
8.1487 + // Cannot show the dialog when we are running on the main thread, as the locks will cause the app to hang
8.1488 + if ([self shouldPresentProxyAuthenticationDialog] && ![NSThread isMainThread]) {
8.1489 + [ASIAuthenticationDialog performSelectorOnMainThread:@selector(presentProxyAuthenticationDialogForRequest:) withObject:self waitUntilDone:[NSThread isMainThread]];
8.1490 + [[self authenticationLock] lockWhenCondition:2];
8.1491 + [[self authenticationLock] unlockWithCondition:1];
8.1492 + if ([self error]) {
8.1493 + return NO;
8.1494 + }
8.1495 + return YES;
8.1496 + }
8.1497 + return NO;
8.1498 +#else
8.1499 + return NO;
8.1500 +#endif
8.1501 +}
8.1502 +
8.1503 +
8.1504 +- (BOOL)askDelegateForProxyCredentials
8.1505 +{
8.1506 + // Can't use delegate authentication when running on the main thread
8.1507 + if ([NSThread isMainThread]) {
8.1508 + return NO;
8.1509 + }
8.1510 +
8.1511 + // If we have a delegate, we'll see if it can handle proxyAuthenticationNeededForRequest:.
8.1512 + // Otherwise, we'll try the queue (if this request is part of one) and it will pass the message on to its own delegate
8.1513 + id authenticationDelegate = [self delegate];
8.1514 + if (!authenticationDelegate) {
8.1515 + authenticationDelegate = [self queue];
8.1516 + }
8.1517 +
8.1518 + if ([authenticationDelegate respondsToSelector:@selector(proxyAuthenticationNeededForRequest:)]) {
8.1519 + [authenticationDelegate performSelectorOnMainThread:@selector(proxyAuthenticationNeededForRequest:) withObject:self waitUntilDone:[NSThread isMainThread]];
8.1520 + [[self authenticationLock] lockWhenCondition:2];
8.1521 + [[self authenticationLock] unlockWithCondition:1];
8.1522 +
8.1523 + // Was the request cancelled?
8.1524 + if ([self error] || [[self mainRequest] error]) {
8.1525 + return NO;
8.1526 + }
8.1527 + return YES;
8.1528 + }
8.1529 + return NO;
8.1530 +}
8.1531 +
8.1532 +- (void)attemptToApplyProxyCredentialsAndResume
8.1533 +{
8.1534 +
8.1535 + if ([self error] || [self isCancelled]) {
8.1536 + return;
8.1537 + }
8.1538 +
8.1539 + // Read authentication data
8.1540 + if (!proxyAuthentication) {
8.1541 + CFHTTPMessageRef responseHeader = (CFHTTPMessageRef) CFReadStreamCopyProperty(readStream,kCFStreamPropertyHTTPResponseHeader);
8.1542 + proxyAuthentication = CFHTTPAuthenticationCreateFromResponse(NULL, responseHeader);
8.1543 + CFRelease(responseHeader);
8.1544 + [self setProxyAuthenticationScheme:[(NSString *)CFHTTPAuthenticationCopyMethod(proxyAuthentication) autorelease]];
8.1545 + }
8.1546 +
8.1547 + // If we haven't got a CFHTTPAuthenticationRef by now, something is badly wrong, so we'll have to give up
8.1548 + if (!proxyAuthentication) {
8.1549 + [self cancelLoad];
8.1550 + [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIInternalErrorWhileApplyingCredentialsType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Failed to get authentication object from response headers",NSLocalizedDescriptionKey,nil]]];
8.1551 + return;
8.1552 + }
8.1553 +
8.1554 + // Get the authentication realm
8.1555 + [self setProxyAuthenticationRealm:nil];
8.1556 + if (!CFHTTPAuthenticationRequiresAccountDomain(proxyAuthentication)) {
8.1557 + [self setProxyAuthenticationRealm:[(NSString *)CFHTTPAuthenticationCopyRealm(proxyAuthentication) autorelease]];
8.1558 + }
8.1559 +
8.1560 + // See if authentication is valid
8.1561 + CFStreamError err;
8.1562 + if (!CFHTTPAuthenticationIsValid(proxyAuthentication, &err)) {
8.1563 +
8.1564 + CFRelease(proxyAuthentication);
8.1565 + proxyAuthentication = NULL;
8.1566 +
8.1567 + // check for bad credentials, so we can give the delegate a chance to replace them
8.1568 + if (err.domain == kCFStreamErrorDomainHTTP && (err.error == kCFStreamErrorHTTPAuthenticationBadUserName || err.error == kCFStreamErrorHTTPAuthenticationBadPassword)) {
8.1569 +
8.1570 + // Prevent more than one request from asking for credentials at once
8.1571 + [delegateAuthenticationLock lock];
8.1572 +
8.1573 + // We know the credentials we just presented are bad, we should remove them from the session store too
8.1574 + [[self class] removeProxyAuthenticationCredentialsFromSessionStore:proxyCredentials];
8.1575 + [self setProxyCredentials:nil];
8.1576 +
8.1577 +
8.1578 + // If the user cancelled authentication via a dialog presented by another request, our queue may have cancelled us
8.1579 + if ([self error] || [self isCancelled]) {
8.1580 + [delegateAuthenticationLock unlock];
8.1581 + return;
8.1582 + }
8.1583 +
8.1584 +
8.1585 + // Now we've acquired the lock, it may be that the session contains credentials we can re-use for this request
8.1586 + if ([self useSessionPersistance]) {
8.1587 + NSDictionary *credentials = [self findSessionProxyAuthenticationCredentials];
8.1588 + if (credentials && [self applyProxyCredentials:[credentials objectForKey:@"Credentials"]]) {
8.1589 + [delegateAuthenticationLock unlock];
8.1590 + [self startRequest];
8.1591 + return;
8.1592 + }
8.1593 + }
8.1594 +
8.1595 + [self setLastActivityTime:nil];
8.1596 +
8.1597 + if ([self askDelegateForProxyCredentials]) {
8.1598 + [self attemptToApplyProxyCredentialsAndResume];
8.1599 + [delegateAuthenticationLock unlock];
8.1600 + return;
8.1601 + }
8.1602 + if ([self showProxyAuthenticationDialog]) {
8.1603 + [self attemptToApplyProxyCredentialsAndResume];
8.1604 + [delegateAuthenticationLock unlock];
8.1605 + return;
8.1606 + }
8.1607 + [delegateAuthenticationLock unlock];
8.1608 + }
8.1609 + [self cancelLoad];
8.1610 + [self failWithError:ASIAuthenticationError];
8.1611 + return;
8.1612 + }
8.1613 +
8.1614 + [self cancelLoad];
8.1615 +
8.1616 + if (proxyCredentials) {
8.1617 +
8.1618 + // We use startRequest rather than starting all over again in load request because NTLM requires we reuse the request
8.1619 + if ((([self proxyAuthenticationScheme] != (NSString *)kCFHTTPAuthenticationSchemeNTLM) || [self proxyAuthenticationRetryCount] < 2) && [self applyCredentials:proxyCredentials]) {
8.1620 + [self startRequest];
8.1621 +
8.1622 + // We've failed NTLM authentication twice, we should assume our credentials are wrong
8.1623 + } else if ([self proxyAuthenticationScheme] == (NSString *)kCFHTTPAuthenticationSchemeNTLM && [self proxyAuthenticationRetryCount] == 2) {
8.1624 + [self failWithError:ASIAuthenticationError];
8.1625 +
8.1626 + // Something went wrong, we'll have to give up
8.1627 + } else {
8.1628 + [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIInternalErrorWhileApplyingCredentialsType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Failed to apply proxy credentials to request",NSLocalizedDescriptionKey,nil]]];
8.1629 + }
8.1630 +
8.1631 + // Are a user name & password needed?
8.1632 + } else if (CFHTTPAuthenticationRequiresUserNameAndPassword(proxyAuthentication)) {
8.1633 +
8.1634 + // Prevent more than one request from asking for credentials at once
8.1635 + [delegateAuthenticationLock lock];
8.1636 +
8.1637 + // If the user cancelled authentication via a dialog presented by another request, our queue may have cancelled us
8.1638 + if ([self error] || [self isCancelled]) {
8.1639 + [delegateAuthenticationLock unlock];
8.1640 + return;
8.1641 + }
8.1642 +
8.1643 + // Now we've acquired the lock, it may be that the session contains credentials we can re-use for this request
8.1644 + if ([self useSessionPersistance]) {
8.1645 + NSDictionary *credentials = [self findSessionProxyAuthenticationCredentials];
8.1646 + if (credentials && [self applyProxyCredentials:[credentials objectForKey:@"Credentials"]]) {
8.1647 + [delegateAuthenticationLock unlock];
8.1648 + [self startRequest];
8.1649 + return;
8.1650 + }
8.1651 + }
8.1652 +
8.1653 + NSMutableDictionary *newCredentials = [self findProxyCredentials];
8.1654 +
8.1655 + //If we have some credentials to use let's apply them to the request and continue
8.1656 + if (newCredentials) {
8.1657 +
8.1658 + if ([self applyProxyCredentials:newCredentials]) {
8.1659 + [delegateAuthenticationLock unlock];
8.1660 + [self startRequest];
8.1661 + } else {
8.1662 + [delegateAuthenticationLock unlock];
8.1663 + [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIInternalErrorWhileApplyingCredentialsType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Failed to apply proxy credentials to request",NSLocalizedDescriptionKey,nil]]];
8.1664 + }
8.1665 +
8.1666 + return;
8.1667 + }
8.1668 +
8.1669 + if ([self askDelegateForProxyCredentials]) {
8.1670 + [self attemptToApplyProxyCredentialsAndResume];
8.1671 + [delegateAuthenticationLock unlock];
8.1672 + return;
8.1673 + }
8.1674 +
8.1675 + if ([self showProxyAuthenticationDialog]) {
8.1676 + [self attemptToApplyProxyCredentialsAndResume];
8.1677 + [delegateAuthenticationLock unlock];
8.1678 + return;
8.1679 + }
8.1680 + [delegateAuthenticationLock unlock];
8.1681 +
8.1682 + // The delegate isn't interested and we aren't showing the authentication dialog, we'll have to give up
8.1683 + [self failWithError:ASIAuthenticationError];
8.1684 + return;
8.1685 + }
8.1686 +
8.1687 +}
8.1688 +
8.1689 +- (BOOL)showAuthenticationDialog
8.1690 +{
8.1691 +// Mac authentication dialog coming soon!
8.1692 +#if TARGET_OS_IPHONE
8.1693 + // Cannot show the dialog when we are running on the main thread, as the locks will cause the app to hang
8.1694 + if ([self shouldPresentAuthenticationDialog] && ![NSThread isMainThread]) {
8.1695 + [ASIAuthenticationDialog performSelectorOnMainThread:@selector(presentAuthenticationDialogForRequest:) withObject:self waitUntilDone:[NSThread isMainThread]];
8.1696 + [[self authenticationLock] lockWhenCondition:2];
8.1697 + [[self authenticationLock] unlockWithCondition:1];
8.1698 + if ([self error]) {
8.1699 + return NO;
8.1700 + }
8.1701 + return YES;
8.1702 + }
8.1703 + return NO;
8.1704 +#else
8.1705 + return NO;
8.1706 +#endif
8.1707 +}
8.1708 +
8.1709 +- (BOOL)askDelegateForCredentials
8.1710 +{
8.1711 + // Can't use delegate authentication when running on the main thread
8.1712 + if ([NSThread isMainThread]) {
8.1713 + return NO;
8.1714 + }
8.1715 +
8.1716 + // If we have a delegate, we'll see if it can handle proxyAuthenticationNeededForRequest:.
8.1717 + // Otherwise, we'll try the queue (if this request is part of one) and it will pass the message on to its own delegate
8.1718 + id authenticationDelegate = [self delegate];
8.1719 + if (!authenticationDelegate) {
8.1720 + authenticationDelegate = [self queue];
8.1721 + }
8.1722 +
8.1723 + if ([authenticationDelegate respondsToSelector:@selector(authenticationNeededForRequest:)]) {
8.1724 + [authenticationDelegate performSelectorOnMainThread:@selector(authenticationNeededForRequest:) withObject:self waitUntilDone:[NSThread isMainThread]];
8.1725 + [[self authenticationLock] lockWhenCondition:2];
8.1726 + [[self authenticationLock] unlockWithCondition:1];
8.1727 +
8.1728 + // Was the request cancelled?
8.1729 + if ([self error] || [[self mainRequest] error]) {
8.1730 + return NO;
8.1731 + }
8.1732 + return YES;
8.1733 + }
8.1734 + return NO;
8.1735 +}
8.1736 +
8.1737 +- (void)attemptToApplyCredentialsAndResume
8.1738 +{
8.1739 + if ([self error] || [self isCancelled]) {
8.1740 + return;
8.1741 + }
8.1742 +
8.1743 + if ([self needsProxyAuthentication]) {
8.1744 + [self attemptToApplyProxyCredentialsAndResume];
8.1745 + return;
8.1746 + }
8.1747 +
8.1748 + // Read authentication data
8.1749 + if (!requestAuthentication) {
8.1750 + CFHTTPMessageRef responseHeader = (CFHTTPMessageRef) CFReadStreamCopyProperty(readStream,kCFStreamPropertyHTTPResponseHeader);
8.1751 + requestAuthentication = CFHTTPAuthenticationCreateFromResponse(NULL, responseHeader);
8.1752 + CFRelease(responseHeader);
8.1753 + [self setAuthenticationScheme:[(NSString *)CFHTTPAuthenticationCopyMethod(requestAuthentication) autorelease]];
8.1754 + }
8.1755 +
8.1756 + if (!requestAuthentication) {
8.1757 + [self cancelLoad];
8.1758 + [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIInternalErrorWhileApplyingCredentialsType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Failed to get authentication object from response headers",NSLocalizedDescriptionKey,nil]]];
8.1759 + return;
8.1760 + }
8.1761 +
8.1762 + // Get the authentication realm
8.1763 + [self setAuthenticationRealm:nil];
8.1764 + if (!CFHTTPAuthenticationRequiresAccountDomain(requestAuthentication)) {
8.1765 + [self setAuthenticationRealm:[(NSString *)CFHTTPAuthenticationCopyRealm(requestAuthentication) autorelease]];
8.1766 + }
8.1767 +
8.1768 + // See if authentication is valid
8.1769 + CFStreamError err;
8.1770 + if (!CFHTTPAuthenticationIsValid(requestAuthentication, &err)) {
8.1771 +
8.1772 + CFRelease(requestAuthentication);
8.1773 + requestAuthentication = NULL;
8.1774 +
8.1775 + // check for bad credentials, so we can give the delegate a chance to replace them
8.1776 + if (err.domain == kCFStreamErrorDomainHTTP && (err.error == kCFStreamErrorHTTPAuthenticationBadUserName || err.error == kCFStreamErrorHTTPAuthenticationBadPassword)) {
8.1777 +
8.1778 + // Prevent more than one request from asking for credentials at once
8.1779 + [delegateAuthenticationLock lock];
8.1780 +
8.1781 + // We know the credentials we just presented are bad, we should remove them from the session store too
8.1782 + [[self class] removeAuthenticationCredentialsFromSessionStore:requestCredentials];
8.1783 + [self setRequestCredentials:nil];
8.1784 +
8.1785 + // If the user cancelled authentication via a dialog presented by another request, our queue may have cancelled us
8.1786 + if ([self error] || [self isCancelled]) {
8.1787 + [delegateAuthenticationLock unlock];
8.1788 + return;
8.1789 + }
8.1790 +
8.1791 + // Now we've acquired the lock, it may be that the session contains credentials we can re-use for this request
8.1792 + if ([self useSessionPersistance]) {
8.1793 + NSDictionary *credentials = [self findSessionAuthenticationCredentials];
8.1794 + if (credentials && [self applyCredentials:[credentials objectForKey:@"Credentials"]]) {
8.1795 + [delegateAuthenticationLock unlock];
8.1796 + [self startRequest];
8.1797 + return;
8.1798 + }
8.1799 + }
8.1800 +
8.1801 +
8.1802 +
8.1803 + [self setLastActivityTime:nil];
8.1804 +
8.1805 + if ([self askDelegateForCredentials]) {
8.1806 + [self attemptToApplyCredentialsAndResume];
8.1807 + [delegateAuthenticationLock unlock];
8.1808 + return;
8.1809 + }
8.1810 + if ([self showAuthenticationDialog]) {
8.1811 + [self attemptToApplyCredentialsAndResume];
8.1812 + [delegateAuthenticationLock unlock];
8.1813 + return;
8.1814 + }
8.1815 + [delegateAuthenticationLock unlock];
8.1816 + }
8.1817 + [self cancelLoad];
8.1818 + [self failWithError:ASIAuthenticationError];
8.1819 + return;
8.1820 + }
8.1821 +
8.1822 + [self cancelLoad];
8.1823 +
8.1824 + if (requestCredentials) {
8.1825 +
8.1826 + if ((([self authenticationScheme] != (NSString *)kCFHTTPAuthenticationSchemeNTLM) || [self authenticationRetryCount] < 2) && [self applyCredentials:requestCredentials]) {
8.1827 + [self startRequest];
8.1828 +
8.1829 + // We've failed NTLM authentication twice, we should assume our credentials are wrong
8.1830 + } else if ([self authenticationScheme] == (NSString *)kCFHTTPAuthenticationSchemeNTLM && [self authenticationRetryCount ] == 2) {
8.1831 + [self failWithError:ASIAuthenticationError];
8.1832 +
8.1833 + } else {
8.1834 + [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIInternalErrorWhileApplyingCredentialsType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Failed to apply credentials to request",NSLocalizedDescriptionKey,nil]]];
8.1835 + }
8.1836 +
8.1837 + // Are a user name & password needed?
8.1838 + } else if (CFHTTPAuthenticationRequiresUserNameAndPassword(requestAuthentication)) {
8.1839 +
8.1840 + // Prevent more than one request from asking for credentials at once
8.1841 + [delegateAuthenticationLock lock];
8.1842 +
8.1843 + // If the user cancelled authentication via a dialog presented by another request, our queue may have cancelled us
8.1844 + if ([self error] || [self isCancelled]) {
8.1845 + [delegateAuthenticationLock unlock];
8.1846 + return;
8.1847 + }
8.1848 +
8.1849 + // Now we've acquired the lock, it may be that the session contains credentials we can re-use for this request
8.1850 + if ([self useSessionPersistance]) {
8.1851 + NSDictionary *credentials = [self findSessionAuthenticationCredentials];
8.1852 + if (credentials && [self applyCredentials:[credentials objectForKey:@"Credentials"]]) {
8.1853 + [delegateAuthenticationLock unlock];
8.1854 + [self startRequest];
8.1855 + return;
8.1856 + }
8.1857 + }
8.1858 +
8.1859 +
8.1860 + NSMutableDictionary *newCredentials = [self findCredentials];
8.1861 +
8.1862 + //If we have some credentials to use let's apply them to the request and continue
8.1863 + if (newCredentials) {
8.1864 +
8.1865 + if ([self applyCredentials:newCredentials]) {
8.1866 + [delegateAuthenticationLock unlock];
8.1867 + [self startRequest];
8.1868 + } else {
8.1869 + [delegateAuthenticationLock unlock];
8.1870 + [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIInternalErrorWhileApplyingCredentialsType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Failed to apply credentials to request",NSLocalizedDescriptionKey,nil]]];
8.1871 + }
8.1872 + return;
8.1873 + }
8.1874 + if ([self askDelegateForCredentials]) {
8.1875 + [self attemptToApplyCredentialsAndResume];
8.1876 + [delegateAuthenticationLock unlock];
8.1877 + return;
8.1878 + }
8.1879 +
8.1880 + if ([self showAuthenticationDialog]) {
8.1881 + [self attemptToApplyCredentialsAndResume];
8.1882 + [delegateAuthenticationLock unlock];
8.1883 + return;
8.1884 + }
8.1885 + [delegateAuthenticationLock unlock];
8.1886 +
8.1887 + [self failWithError:ASIAuthenticationError];
8.1888 +
8.1889 + return;
8.1890 + }
8.1891 +
8.1892 +}
8.1893 +
8.1894 +
8.1895 +#pragma mark stream status handlers
8.1896 +
8.1897 +
8.1898 +- (void)handleNetworkEvent:(CFStreamEventType)type
8.1899 +{
8.1900 + // Dispatch the stream events.
8.1901 + switch (type) {
8.1902 + case kCFStreamEventHasBytesAvailable:
8.1903 + [self handleBytesAvailable];
8.1904 + break;
8.1905 +
8.1906 + case kCFStreamEventEndEncountered:
8.1907 + [self handleStreamComplete];
8.1908 + break;
8.1909 +
8.1910 + case kCFStreamEventErrorOccurred:
8.1911 + [self handleStreamError];
8.1912 + break;
8.1913 +
8.1914 + default:
8.1915 + break;
8.1916 + }
8.1917 +}
8.1918 +
8.1919 +
8.1920 +- (void)handleBytesAvailable
8.1921 +{
8.1922 +
8.1923 + if (![self responseHeaders]) {
8.1924 + if ([self readResponseHeadersReturningAuthenticationFailure]) {
8.1925 + [self attemptToApplyCredentialsAndResume];
8.1926 + return;
8.1927 + }
8.1928 + }
8.1929 + if ([self needsRedirect]) {
8.1930 + return;
8.1931 + }
8.1932 + int bufferSize = 2048;
8.1933 + if (contentLength > 262144) {
8.1934 + bufferSize = 65536;
8.1935 + } else if (contentLength > 65536) {
8.1936 + bufferSize = 16384;
8.1937 + }
8.1938 +
8.1939 + // Reduce the buffer size if we're receiving data too quickly when bandwidth throttling is active
8.1940 + // This just augments the throttling done in measureBandwidthUsage to reduce the amount we go over the limit
8.1941 +
8.1942 + if ([[self class] isBandwidthThrottled]) {
8.1943 + [bandwidthThrottlingLock lock];
8.1944 + if (maxBandwidthPerSecond > 0) {
8.1945 + long long maxSize = (long long)maxBandwidthPerSecond-(long long)bandwidthUsedInLastSecond;
8.1946 + if (maxSize < 0) {
8.1947 + // We aren't supposed to read any more data right now, but we'll read a single byte anyway so the CFNetwork's buffer isn't full
8.1948 + bufferSize = 1;
8.1949 + } else if (maxSize/4 < bufferSize) {
8.1950 + // We were going to fetch more data that we should be allowed, so we'll reduce the size of our read
8.1951 + bufferSize = maxSize/4;
8.1952 + }
8.1953 + }
8.1954 + if (bufferSize < 1) {
8.1955 + bufferSize = 1;
8.1956 + }
8.1957 + [bandwidthThrottlingLock unlock];
8.1958 + }
8.1959 +
8.1960 +
8.1961 + UInt8 buffer[bufferSize];
8.1962 + CFIndex bytesRead = CFReadStreamRead(readStream, buffer, sizeof(buffer));
8.1963 +
8.1964 +
8.1965 + // Less than zero is an error
8.1966 + if (bytesRead < 0) {
8.1967 + [self handleStreamError];
8.1968 +
8.1969 + // If zero bytes were read, wait for the EOF to come.
8.1970 + } else if (bytesRead) {
8.1971 +
8.1972 + [self setTotalBytesRead:[self totalBytesRead]+bytesRead];
8.1973 + [self setLastActivityTime:[NSDate date]];
8.1974 +
8.1975 + // For bandwidth measurement / throttling
8.1976 + [ASIHTTPRequest incrementBandwidthUsedInLastSecond:bytesRead];
8.1977 +
8.1978 + // Are we downloading to a file?
8.1979 + if ([self downloadDestinationPath]) {
8.1980 + if (![self fileDownloadOutputStream]) {
8.1981 + BOOL append = NO;
8.1982 + if (![self temporaryFileDownloadPath]) {
8.1983 + [self setTemporaryFileDownloadPath:[NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]]];
8.1984 + } else if ([self allowResumeForFileDownloads]) {
8.1985 + append = YES;
8.1986 + }
8.1987 +
8.1988 + [self setFileDownloadOutputStream:[[[NSOutputStream alloc] initToFileAtPath:[self temporaryFileDownloadPath] append:append] autorelease]];
8.1989 + [[self fileDownloadOutputStream] open];
8.1990 + }
8.1991 + [[self fileDownloadOutputStream] write:buffer maxLength:bytesRead];
8.1992 +
8.1993 + //Otherwise, let's add the data to our in-memory store
8.1994 + } else {
8.1995 + [rawResponseData appendBytes:buffer length:bytesRead];
8.1996 + }
8.1997 + }
8.1998 +}
8.1999 +
8.2000 +- (void)handleStreamComplete
8.2001 +{
8.2002 + //Try to read the headers (if this is a HEAD request handleBytesAvailable may not be called)
8.2003 + if (![self responseHeaders]) {
8.2004 + if ([self readResponseHeadersReturningAuthenticationFailure]) {
8.2005 + [self attemptToApplyCredentialsAndResume];
8.2006 + return;
8.2007 + }
8.2008 + }
8.2009 + if ([self needsRedirect]) {
8.2010 + return;
8.2011 + }
8.2012 + [progressLock lock];
8.2013 +
8.2014 + [self setComplete:YES];
8.2015 + [self updateProgressIndicators];
8.2016 +
8.2017 + [[self cancelledLock] lock];
8.2018 + if (readStream) {
8.2019 + CFReadStreamSetClient(readStream, kCFStreamEventNone, NULL, NULL);
8.2020 + CFReadStreamUnscheduleFromRunLoop(readStream, CFRunLoopGetCurrent(), ASIHTTPRequestRunMode);
8.2021 + CFReadStreamClose(readStream);
8.2022 + CFRelease(readStream);
8.2023 + readStream = NULL;
8.2024 + }
8.2025 +
8.2026 + [[self postBodyReadStream] close];
8.2027 +
8.2028 + NSError *fileError = nil;
8.2029 +
8.2030 + // Delete up the request body temporary file, if it exists
8.2031 + if ([self didCreateTemporaryPostDataFile]) {
8.2032 + [self removePostDataFile];
8.2033 + }
8.2034 +
8.2035 + // Close the output stream as we're done writing to the file
8.2036 + if ([self temporaryFileDownloadPath]) {
8.2037 + [[self fileDownloadOutputStream] close];
8.2038 +
8.2039 + // Decompress the file (if necessary) directly to the destination path
8.2040 + if ([self isResponseCompressed]) {
8.2041 + int decompressionStatus = [ASIHTTPRequest uncompressZippedDataFromFile:[self temporaryFileDownloadPath] toFile:[self downloadDestinationPath]];
8.2042 + if (decompressionStatus != Z_OK) {
8.2043 + fileError = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASIFileManagementError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Decompression of %@ failed with code %hi",[self temporaryFileDownloadPath],decompressionStatus],NSLocalizedDescriptionKey,nil]];
8.2044 + }
8.2045 +
8.2046 + [self removeTemporaryDownloadFile];
8.2047 + } else {
8.2048 +
8.2049 + //Remove any file at the destination path
8.2050 + NSError *moveError = nil;
8.2051 + if ([[NSFileManager defaultManager] fileExistsAtPath:[self downloadDestinationPath]]) {
8.2052 + [[NSFileManager defaultManager] removeItemAtPath:[self downloadDestinationPath] error:&moveError];
8.2053 + if (moveError) {
8.2054 + fileError = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASIFileManagementError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Unable to remove file at path '%@'",[self downloadDestinationPath]],NSLocalizedDescriptionKey,moveError,NSUnderlyingErrorKey,nil]];
8.2055 + }
8.2056 + }
8.2057 +
8.2058 + //Move the temporary file to the destination path
8.2059 + if (!fileError) {
8.2060 + [[NSFileManager defaultManager] moveItemAtPath:[self temporaryFileDownloadPath] toPath:[self downloadDestinationPath] error:&moveError];
8.2061 + if (moveError) {
8.2062 + fileError = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASIFileManagementError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Failed to move file from '%@' to '%@'",[self temporaryFileDownloadPath],[self downloadDestinationPath]],NSLocalizedDescriptionKey,moveError,NSUnderlyingErrorKey,nil]];
8.2063 + }
8.2064 + [self setTemporaryFileDownloadPath:nil];
8.2065 + }
8.2066 + }
8.2067 + }
8.2068 + [[self cancelledLock] unlock];
8.2069 + [progressLock unlock];
8.2070 +
8.2071 + if (fileError) {
8.2072 + [self failWithError:fileError];
8.2073 + } else {
8.2074 + [self requestFinished];
8.2075 + }
8.2076 +}
8.2077 +
8.2078 +
8.2079 +- (void)handleStreamError
8.2080 +{
8.2081 + NSError *underlyingError = [(NSError *)CFReadStreamCopyError(readStream) autorelease];
8.2082 +
8.2083 + [self cancelLoad];
8.2084 + [self setComplete:YES];
8.2085 +
8.2086 + if (![self error]) { // We may already have handled this error
8.2087 +
8.2088 +
8.2089 + NSString *reason = @"A connection failure occurred";
8.2090 +
8.2091 + // We'll use a custom error message for SSL errors, but you should always check underlying error if you want more details
8.2092 + // For some reason SecureTransport.h doesn't seem to be available on iphone, so error codes hard-coded
8.2093 + // Also, iPhone seems to handle errors differently from Mac OS X - a self-signed certificate returns a different error code on each platform, so we'll just provide a general error
8.2094 + if ([[underlyingError domain] isEqualToString:NSOSStatusErrorDomain]) {
8.2095 + if ([underlyingError code] <= -9800 && [underlyingError code] >= -9818) {
8.2096 + reason = [NSString stringWithFormat:@"%@: SSL problem (possibily a bad/expired/self-signed certificate)",reason];
8.2097 + }
8.2098 + }
8.2099 +
8.2100 + [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIConnectionFailureErrorType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:reason,NSLocalizedDescriptionKey,underlyingError,NSUnderlyingErrorKey,nil]]];
8.2101 + }
8.2102 + [super cancel];
8.2103 +}
8.2104 +
8.2105 +#pragma mark global queue
8.2106 +
8.2107 ++ (NSOperationQueue *)sharedRequestQueue
8.2108 +{
8.2109 + if (!sharedRequestQueue) {
8.2110 + sharedRequestQueue = [[NSOperationQueue alloc] init];
8.2111 + [sharedRequestQueue setMaxConcurrentOperationCount:YES];
8.2112 + }
8.2113 + return sharedRequestQueue;
8.2114 +}
8.2115 +
8.2116 +# pragma mark session credentials
8.2117 +
8.2118 ++ (NSMutableArray *)sessionProxyCredentialsStore
8.2119 +{
8.2120 + if (!sessionProxyCredentialsStore) {
8.2121 + sessionProxyCredentialsStore = [[NSMutableArray alloc] init];
8.2122 + }
8.2123 + return sessionProxyCredentialsStore;
8.2124 +}
8.2125 +
8.2126 ++ (NSMutableArray *)sessionCredentialsStore
8.2127 +{
8.2128 + if (!sessionCredentialsStore) {
8.2129 + sessionCredentialsStore = [[NSMutableArray alloc] init];
8.2130 + }
8.2131 + return sessionCredentialsStore;
8.2132 +}
8.2133 +
8.2134 ++ (void)storeProxyAuthenticationCredentialsInSessionStore:(NSDictionary *)credentials
8.2135 +{
8.2136 + [sessionCredentialsLock lock];
8.2137 + [self removeProxyAuthenticationCredentialsFromSessionStore:[credentials objectForKey:@"Credentials"]];
8.2138 + [[[self class] sessionProxyCredentialsStore] addObject:credentials];
8.2139 + [sessionCredentialsLock unlock];
8.2140 +}
8.2141 +
8.2142 ++ (void)storeAuthenticationCredentialsInSessionStore:(NSDictionary *)credentials
8.2143 +{
8.2144 + [sessionCredentialsLock lock];
8.2145 + [self removeAuthenticationCredentialsFromSessionStore:[credentials objectForKey:@"Credentials"]];
8.2146 + [[[self class] sessionCredentialsStore] addObject:credentials];
8.2147 + [sessionCredentialsLock unlock];
8.2148 +}
8.2149 +
8.2150 ++ (void)removeProxyAuthenticationCredentialsFromSessionStore:(NSDictionary *)credentials
8.2151 +{
8.2152 + [sessionCredentialsLock lock];
8.2153 + NSMutableArray *sessionCredentialsList = [[self class] sessionProxyCredentialsStore];
8.2154 + int i;
8.2155 + for (i=0; i<[sessionCredentialsList count]; i++) {
8.2156 + NSDictionary *theCredentials = [sessionCredentialsList objectAtIndex:i];
8.2157 + if ([theCredentials objectForKey:@"Credentials"] == credentials) {
8.2158 + [sessionCredentialsList removeObjectAtIndex:i];
8.2159 + [sessionCredentialsLock unlock];
8.2160 + return;
8.2161 + }
8.2162 + }
8.2163 + [sessionCredentialsLock unlock];
8.2164 +}
8.2165 +
8.2166 ++ (void)removeAuthenticationCredentialsFromSessionStore:(NSDictionary *)credentials
8.2167 +{
8.2168 + [sessionCredentialsLock lock];
8.2169 + NSMutableArray *sessionCredentialsList = [[self class] sessionCredentialsStore];
8.2170 + int i;
8.2171 + for (i=0; i<[sessionCredentialsList count]; i++) {
8.2172 + NSDictionary *theCredentials = [sessionCredentialsList objectAtIndex:i];
8.2173 + if ([theCredentials objectForKey:@"Credentials"] == credentials) {
8.2174 + [sessionCredentialsList removeObjectAtIndex:i];
8.2175 + [sessionCredentialsLock unlock];
8.2176 + return;
8.2177 + }
8.2178 + }
8.2179 + [sessionCredentialsLock unlock];
8.2180 +}
8.2181 +
8.2182 +- (NSDictionary *)findSessionProxyAuthenticationCredentials
8.2183 +{
8.2184 + [sessionCredentialsLock lock];
8.2185 + NSMutableArray *sessionCredentialsList = [[self class] sessionProxyCredentialsStore];
8.2186 + for (NSDictionary *theCredentials in sessionCredentialsList) {
8.2187 + if ([[theCredentials objectForKey:@"Host"] isEqualToString:[self proxyHost]] && [[theCredentials objectForKey:@"Port"] intValue] == [self proxyPort]) {
8.2188 + [sessionCredentialsLock unlock];
8.2189 + return theCredentials;
8.2190 + }
8.2191 + }
8.2192 + [sessionCredentialsLock unlock];
8.2193 + return nil;
8.2194 +}
8.2195 +
8.2196 +
8.2197 +- (NSDictionary *)findSessionAuthenticationCredentials
8.2198 +{
8.2199 + [sessionCredentialsLock lock];
8.2200 + NSMutableArray *sessionCredentialsList = [[self class] sessionCredentialsStore];
8.2201 + // Find an exact match
8.2202 + for (NSDictionary *theCredentials in sessionCredentialsList) {
8.2203 + if ([[theCredentials objectForKey:@"URL"] isEqual:[self url]]) {
8.2204 + if (![self responseStatusCode] || [[theCredentials objectForKey:@"AuthenticationRealm"] isEqualToString:[self authenticationRealm]]) {
8.2205 + [sessionCredentialsLock unlock];
8.2206 + return theCredentials;
8.2207 + }
8.2208 + }
8.2209 + }
8.2210 + // Find a rough match
8.2211 + NSURL *requestURL = [self url];
8.2212 + for (NSDictionary *theCredentials in sessionCredentialsList) {
8.2213 + NSURL *theURL = [theCredentials objectForKey:@"URL"];
8.2214 + if ([[theURL host] isEqualToString:[requestURL host]] && [[theURL port] isEqualToNumber:[requestURL port]] && [[theURL scheme] isEqualToString:[requestURL scheme]]) {
8.2215 + if (![self responseStatusCode] || [[theCredentials objectForKey:@"AuthenticationRealm"] isEqualToString:[self authenticationRealm]]) {
8.2216 + [sessionCredentialsLock unlock];
8.2217 + return theCredentials;
8.2218 + }
8.2219 + }
8.2220 + }
8.2221 + [sessionCredentialsLock unlock];
8.2222 + return nil;
8.2223 +}
8.2224 +
8.2225 +#pragma mark keychain storage
8.2226 +
8.2227 ++ (void)saveCredentials:(NSURLCredential *)credentials forHost:(NSString *)host port:(int)port protocol:(NSString *)protocol realm:(NSString *)realm
8.2228 +{
8.2229 + NSURLProtectionSpace *protectionSpace = [[[NSURLProtectionSpace alloc] initWithHost:host port:port protocol:protocol realm:realm authenticationMethod:NSURLAuthenticationMethodDefault] autorelease];
8.2230 + [[NSURLCredentialStorage sharedCredentialStorage] setDefaultCredential:credentials forProtectionSpace:protectionSpace];
8.2231 +}
8.2232 +
8.2233 ++ (void)saveCredentials:(NSURLCredential *)credentials forProxy:(NSString *)host port:(int)port realm:(NSString *)realm
8.2234 +{
8.2235 + NSURLProtectionSpace *protectionSpace = [[[NSURLProtectionSpace alloc] initWithProxyHost:host port:port type:NSURLProtectionSpaceHTTPProxy realm:realm authenticationMethod:NSURLAuthenticationMethodDefault] autorelease];
8.2236 + [[NSURLCredentialStorage sharedCredentialStorage] setDefaultCredential:credentials forProtectionSpace:protectionSpace];
8.2237 +}
8.2238 +
8.2239 ++ (NSURLCredential *)savedCredentialsForHost:(NSString *)host port:(int)port protocol:(NSString *)protocol realm:(NSString *)realm
8.2240 +{
8.2241 + NSURLProtectionSpace *protectionSpace = [[[NSURLProtectionSpace alloc] initWithHost:host port:port protocol:protocol realm:realm authenticationMethod:NSURLAuthenticationMethodDefault] autorelease];
8.2242 + return [[NSURLCredentialStorage sharedCredentialStorage] defaultCredentialForProtectionSpace:protectionSpace];
8.2243 +}
8.2244 +
8.2245 ++ (NSURLCredential *)savedCredentialsForProxy:(NSString *)host port:(int)port protocol:(NSString *)protocol realm:(NSString *)realm
8.2246 +{
8.2247 + NSURLProtectionSpace *protectionSpace = [[[NSURLProtectionSpace alloc] initWithProxyHost:host port:port type:NSURLProtectionSpaceHTTPProxy realm:realm authenticationMethod:NSURLAuthenticationMethodDefault] autorelease];
8.2248 + return [[NSURLCredentialStorage sharedCredentialStorage] defaultCredentialForProtectionSpace:protectionSpace];
8.2249 +}
8.2250 +
8.2251 ++ (void)removeCredentialsForHost:(NSString *)host port:(int)port protocol:(NSString *)protocol realm:(NSString *)realm
8.2252 +{
8.2253 + NSURLProtectionSpace *protectionSpace = [[[NSURLProtectionSpace alloc] initWithHost:host port:port protocol:protocol realm:realm authenticationMethod:NSURLAuthenticationMethodDefault] autorelease];
8.2254 + NSURLCredentialStorage *storage = [NSURLCredentialStorage sharedCredentialStorage];
8.2255 + [storage removeCredential:[storage defaultCredentialForProtectionSpace:protectionSpace] forProtectionSpace:protectionSpace];
8.2256 +}
8.2257 +
8.2258 ++ (void)removeCredentialsForProxy:(NSString *)host port:(int)port realm:(NSString *)realm
8.2259 +{
8.2260 + NSURLProtectionSpace *protectionSpace = [[[NSURLProtectionSpace alloc] initWithProxyHost:host port:port type:NSURLProtectionSpaceHTTPProxy realm:realm authenticationMethod:NSURLAuthenticationMethodDefault] autorelease];
8.2261 + NSURLCredentialStorage *storage = [NSURLCredentialStorage sharedCredentialStorage];
8.2262 + [storage removeCredential:[storage defaultCredentialForProtectionSpace:protectionSpace] forProtectionSpace:protectionSpace];
8.2263 +}
8.2264 +
8.2265 +
8.2266 ++ (NSMutableArray *)sessionCookies
8.2267 +{
8.2268 + if (!sessionCookies) {
8.2269 + [ASIHTTPRequest setSessionCookies:[[[NSMutableArray alloc] init] autorelease]];
8.2270 + }
8.2271 + return sessionCookies;
8.2272 +}
8.2273 +
8.2274 ++ (void)setSessionCookies:(NSMutableArray *)newSessionCookies
8.2275 +{
8.2276 + [sessionCookiesLock lock];
8.2277 + // Remove existing cookies from the persistent store
8.2278 + for (NSHTTPCookie *cookie in sessionCookies) {
8.2279 + [[NSHTTPCookieStorage sharedHTTPCookieStorage] deleteCookie:cookie];
8.2280 + }
8.2281 + [sessionCookies release];
8.2282 + sessionCookies = [newSessionCookies retain];
8.2283 + [sessionCookiesLock unlock];
8.2284 +}
8.2285 +
8.2286 ++ (void)addSessionCookie:(NSHTTPCookie *)newCookie
8.2287 +{
8.2288 + [sessionCookiesLock lock];
8.2289 + NSHTTPCookie *cookie;
8.2290 + int i;
8.2291 + int max = [[ASIHTTPRequest sessionCookies] count];
8.2292 + for (i=0; i<max; i++) {
8.2293 + cookie = [[ASIHTTPRequest sessionCookies] objectAtIndex:i];
8.2294 + if ([[cookie domain] isEqualToString:[newCookie domain]] && [[cookie path] isEqualToString:[newCookie path]] && [[cookie name] isEqualToString:[newCookie name]]) {
8.2295 + [[ASIHTTPRequest sessionCookies] removeObjectAtIndex:i];
8.2296 + break;
8.2297 + }
8.2298 + }
8.2299 + [[ASIHTTPRequest sessionCookies] addObject:newCookie];
8.2300 + [sessionCookiesLock unlock];
8.2301 +}
8.2302 +
8.2303 +// Dump all session data (authentication and cookies)
8.2304 ++ (void)clearSession
8.2305 +{
8.2306 + [sessionCredentialsLock lock];
8.2307 + [[[self class] sessionCredentialsStore] removeAllObjects];
8.2308 + [sessionCredentialsLock unlock];
8.2309 + [[self class] setSessionCookies:nil];
8.2310 +}
8.2311 +
8.2312 +#pragma mark gzip decompression
8.2313 +
8.2314 +//
8.2315 +// Contributed by Shaun Harrison of Enormego, see: http://developers.enormego.com/view/asihttprequest_gzip
8.2316 +// Based on this: http://deusty.blogspot.com/2007/07/gzip-compressiondecompression.html
8.2317 +//
8.2318 ++ (NSData *)uncompressZippedData:(NSData*)compressedData
8.2319 +{
8.2320 + if ([compressedData length] == 0) return compressedData;
8.2321 +
8.2322 + unsigned full_length = [compressedData length];
8.2323 + unsigned half_length = [compressedData length] / 2;
8.2324 +
8.2325 + NSMutableData *decompressed = [NSMutableData dataWithLength: full_length + half_length];
8.2326 + BOOL done = NO;
8.2327 + int status;
8.2328 +
8.2329 + z_stream strm;
8.2330 + strm.next_in = (Bytef *)[compressedData bytes];
8.2331 + strm.avail_in = [compressedData length];
8.2332 + strm.total_out = 0;
8.2333 + strm.zalloc = Z_NULL;
8.2334 + strm.zfree = Z_NULL;
8.2335 +
8.2336 + if (inflateInit2(&strm, (15+32)) != Z_OK) return nil;
8.2337 +
8.2338 + while (!done) {
8.2339 + // Make sure we have enough room and reset the lengths.
8.2340 + if (strm.total_out >= [decompressed length]) {
8.2341 + [decompressed increaseLengthBy: half_length];
8.2342 + }
8.2343 + strm.next_out = [decompressed mutableBytes] + strm.total_out;
8.2344 + strm.avail_out = [decompressed length] - strm.total_out;
8.2345 +
8.2346 + // Inflate another chunk.
8.2347 + status = inflate (&strm, Z_SYNC_FLUSH);
8.2348 + if (status == Z_STREAM_END) {
8.2349 + done = YES;
8.2350 + } else if (status != Z_OK) {
8.2351 + break;
8.2352 + }
8.2353 + }
8.2354 + if (inflateEnd (&strm) != Z_OK) return nil;
8.2355 +
8.2356 + // Set real length.
8.2357 + if (done) {
8.2358 + [decompressed setLength: strm.total_out];
8.2359 + return [NSData dataWithData: decompressed];
8.2360 + } else {
8.2361 + return nil;
8.2362 + }
8.2363 +}
8.2364 +
8.2365 +// NOTE: To debug this method, turn off Data Formatters in Xcode or you'll crash on closeFile
8.2366 ++ (int)uncompressZippedDataFromFile:(NSString *)sourcePath toFile:(NSString *)destinationPath
8.2367 +{
8.2368 + // Create an empty file at the destination path
8.2369 + if (![[NSFileManager defaultManager] createFileAtPath:destinationPath contents:[NSData data] attributes:nil]) {
8.2370 + return 1;
8.2371 + }
8.2372 +
8.2373 + // Get a FILE struct for the source file
8.2374 + NSFileHandle *inputFileHandle = [NSFileHandle fileHandleForReadingAtPath:sourcePath];
8.2375 + FILE *source = fdopen([inputFileHandle fileDescriptor], "r");
8.2376 +
8.2377 + // Get a FILE struct for the destination path
8.2378 + NSFileHandle *outputFileHandle = [NSFileHandle fileHandleForWritingAtPath:destinationPath];
8.2379 + FILE *dest = fdopen([outputFileHandle fileDescriptor], "w");
8.2380 +
8.2381 +
8.2382 + // Uncompress data in source and save in destination
8.2383 + int status = [ASIHTTPRequest uncompressZippedDataFromSource:source toDestination:dest];
8.2384 +
8.2385 + // Close the files
8.2386 + fclose(dest);
8.2387 + fclose(source);
8.2388 + [inputFileHandle closeFile];
8.2389 + [outputFileHandle closeFile];
8.2390 + return status;
8.2391 +}
8.2392 +
8.2393 +//
8.2394 +// From the zlib sample code by Mark Adler, code here:
8.2395 +// http://www.zlib.net/zpipe.c
8.2396 +//
8.2397 +#define CHUNK 16384
8.2398 +
8.2399 ++ (int)uncompressZippedDataFromSource:(FILE *)source toDestination:(FILE *)dest
8.2400 +{
8.2401 + int ret;
8.2402 + unsigned have;
8.2403 + z_stream strm;
8.2404 + unsigned char in[CHUNK];
8.2405 + unsigned char out[CHUNK];
8.2406 +
8.2407 + /* allocate inflate state */
8.2408 + strm.zalloc = Z_NULL;
8.2409 + strm.zfree = Z_NULL;
8.2410 + strm.opaque = Z_NULL;
8.2411 + strm.avail_in = 0;
8.2412 + strm.next_in = Z_NULL;
8.2413 + ret = inflateInit2(&strm, (15+32));
8.2414 + if (ret != Z_OK)
8.2415 + return ret;
8.2416 +
8.2417 + /* decompress until deflate stream ends or end of file */
8.2418 + do {
8.2419 + strm.avail_in = fread(in, 1, CHUNK, source);
8.2420 + if (ferror(source)) {
8.2421 + (void)inflateEnd(&strm);
8.2422 + return Z_ERRNO;
8.2423 + }
8.2424 + if (strm.avail_in == 0)
8.2425 + break;
8.2426 + strm.next_in = in;
8.2427 +
8.2428 + /* run inflate() on input until output buffer not full */
8.2429 + do {
8.2430 + strm.avail_out = CHUNK;
8.2431 + strm.next_out = out;
8.2432 + ret = inflate(&strm, Z_NO_FLUSH);
8.2433 + assert(ret != Z_STREAM_ERROR); /* state not clobbered */
8.2434 + switch (ret) {
8.2435 + case Z_NEED_DICT:
8.2436 + ret = Z_DATA_ERROR; /* and fall through */
8.2437 + case Z_DATA_ERROR:
8.2438 + case Z_MEM_ERROR:
8.2439 + (void)inflateEnd(&strm);
8.2440 + return ret;
8.2441 + }
8.2442 + have = CHUNK - strm.avail_out;
8.2443 + if (fwrite(&out, 1, have, dest) != have || ferror(dest)) {
8.2444 + (void)inflateEnd(&strm);
8.2445 + return Z_ERRNO;
8.2446 + }
8.2447 + } while (strm.avail_out == 0);
8.2448 +
8.2449 + /* done when inflate() says it's done */
8.2450 + } while (ret != Z_STREAM_END);
8.2451 +
8.2452 + /* clean up and return */
8.2453 + (void)inflateEnd(&strm);
8.2454 + return ret == Z_STREAM_END ? Z_OK : Z_DATA_ERROR;
8.2455 +}
8.2456 +
8.2457 +
8.2458 +#pragma mark gzip compression
8.2459 +
8.2460 +// Based on this from Robbie Hanson: http://deusty.blogspot.com/2007/07/gzip-compressiondecompression.html
8.2461 +
8.2462 ++ (NSData *)compressData:(NSData*)uncompressedData
8.2463 +{
8.2464 + if ([uncompressedData length] == 0) return uncompressedData;
8.2465 +
8.2466 + z_stream strm;
8.2467 +
8.2468 + strm.zalloc = Z_NULL;
8.2469 + strm.zfree = Z_NULL;
8.2470 + strm.opaque = Z_NULL;
8.2471 + strm.total_out = 0;
8.2472 + strm.next_in=(Bytef *)[uncompressedData bytes];
8.2473 + strm.avail_in = [uncompressedData length];
8.2474 +
8.2475 + // Compresssion Levels:
8.2476 + // Z_NO_COMPRESSION
8.2477 + // Z_BEST_SPEED
8.2478 + // Z_BEST_COMPRESSION
8.2479 + // Z_DEFAULT_COMPRESSION
8.2480 +
8.2481 + if (deflateInit2(&strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED, (15+16), 8, Z_DEFAULT_STRATEGY) != Z_OK) return nil;
8.2482 +
8.2483 + NSMutableData *compressed = [NSMutableData dataWithLength:16384]; // 16K chunks for expansion
8.2484 +
8.2485 + do {
8.2486 +
8.2487 + if (strm.total_out >= [compressed length])
8.2488 + [compressed increaseLengthBy: 16384];
8.2489 +
8.2490 + strm.next_out = [compressed mutableBytes] + strm.total_out;
8.2491 + strm.avail_out = [compressed length] - strm.total_out;
8.2492 +
8.2493 + deflate(&strm, Z_FINISH);
8.2494 +
8.2495 + } while (strm.avail_out == 0);
8.2496 +
8.2497 + deflateEnd(&strm);
8.2498 +
8.2499 + [compressed setLength: strm.total_out];
8.2500 + return [NSData dataWithData:compressed];
8.2501 +}
8.2502 +
8.2503 +// NOTE: To debug this method, turn off Data Formatters in Xcode or you'll crash on closeFile
8.2504 ++ (int)compressDataFromFile:(NSString *)sourcePath toFile:(NSString *)destinationPath
8.2505 +{
8.2506 + // Create an empty file at the destination path
8.2507 + [[NSFileManager defaultManager] createFileAtPath:destinationPath contents:[NSData data] attributes:nil];
8.2508 +
8.2509 + // Get a FILE struct for the source file
8.2510 + NSFileHandle *inputFileHandle = [NSFileHandle fileHandleForReadingAtPath:sourcePath];
8.2511 + FILE *source = fdopen([inputFileHandle fileDescriptor], "r");
8.2512 +
8.2513 + // Get a FILE struct for the destination path
8.2514 + NSFileHandle *outputFileHandle = [NSFileHandle fileHandleForWritingAtPath:destinationPath];
8.2515 + FILE *dest = fdopen([outputFileHandle fileDescriptor], "w");
8.2516 +
8.2517 + // compress data in source and save in destination
8.2518 + int status = [ASIHTTPRequest compressDataFromSource:source toDestination:dest];
8.2519 +
8.2520 + // Close the files
8.2521 + fclose(dest);
8.2522 + fclose(source);
8.2523 +
8.2524 + // We have to close both of these explictly because CFReadStreamCreateForStreamedHTTPRequest() seems to go bonkers otherwise
8.2525 + [inputFileHandle closeFile];
8.2526 + [outputFileHandle closeFile];
8.2527 +
8.2528 + return status;
8.2529 +}
8.2530 +
8.2531 +//
8.2532 +// Also from the zlib sample code at http://www.zlib.net/zpipe.c
8.2533 +//
8.2534 ++ (int)compressDataFromSource:(FILE *)source toDestination:(FILE *)dest
8.2535 +{
8.2536 + int ret, flush;
8.2537 + unsigned have;
8.2538 + z_stream strm;
8.2539 + unsigned char in[CHUNK];
8.2540 + unsigned char out[CHUNK];
8.2541 +
8.2542 + /* allocate deflate state */
8.2543 + strm.zalloc = Z_NULL;
8.2544 + strm.zfree = Z_NULL;
8.2545 + strm.opaque = Z_NULL;
8.2546 + ret = deflateInit2(&strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED, (15+16), 8, Z_DEFAULT_STRATEGY);
8.2547 + if (ret != Z_OK)
8.2548 + return ret;
8.2549 +
8.2550 + /* compress until end of file */
8.2551 + do {
8.2552 + strm.avail_in = fread(in, 1, CHUNK, source);
8.2553 + if (ferror(source)) {
8.2554 + (void)deflateEnd(&strm);
8.2555 + return Z_ERRNO;
8.2556 + }
8.2557 + flush = feof(source) ? Z_FINISH : Z_NO_FLUSH;
8.2558 + strm.next_in = in;
8.2559 +
8.2560 + /* run deflate() on input until output buffer not full, finish
8.2561 + compression if all of source has been read in */
8.2562 + do {
8.2563 + strm.avail_out = CHUNK;
8.2564 + strm.next_out = out;
8.2565 + ret = deflate(&strm, flush); /* no bad return value */
8.2566 + assert(ret != Z_STREAM_ERROR); /* state not clobbered */
8.2567 + have = CHUNK - strm.avail_out;
8.2568 + if (fwrite(out, 1, have, dest) != have || ferror(dest)) {
8.2569 + (void)deflateEnd(&strm);
8.2570 + return Z_ERRNO;
8.2571 + }
8.2572 + } while (strm.avail_out == 0);
8.2573 + assert(strm.avail_in == 0); /* all input will be used */
8.2574 +
8.2575 + /* done when last data in file processed */
8.2576 + } while (flush != Z_FINISH);
8.2577 + assert(ret == Z_STREAM_END); /* stream will be complete */
8.2578 +
8.2579 + /* clean up and return */
8.2580 + (void)deflateEnd(&strm);
8.2581 + return Z_OK;
8.2582 +}
8.2583 +
8.2584 +#pragma mark get user agent
8.2585 +
8.2586 ++ (NSString *)defaultUserAgentString
8.2587 +{
8.2588 + NSBundle *bundle = [NSBundle mainBundle];
8.2589 +
8.2590 + // Attempt to find a name for this application
8.2591 + NSString *appName = [bundle objectForInfoDictionaryKey:@"CFBundleDisplayName"];
8.2592 + if (!appName) {
8.2593 + appName = [bundle objectForInfoDictionaryKey:@"CFBundleName"];
8.2594 + }
8.2595 + // If we couldn't find one, we'll give up (and ASIHTTPRequest will use the standard CFNetwork user agent)
8.2596 + if (!appName) {
8.2597 + return nil;
8.2598 + }
8.2599 + NSString *appVersion = nil;
8.2600 + NSString *marketingVersionNumber = [bundle objectForInfoDictionaryKey:@"CFBundleShortVersionString"];
8.2601 + NSString *developmentVersionNumber = [bundle objectForInfoDictionaryKey:@"CFBundleVersion"];
8.2602 + if (marketingVersionNumber && developmentVersionNumber) {
8.2603 + if ([marketingVersionNumber isEqualToString:developmentVersionNumber]) {
8.2604 + appVersion = marketingVersionNumber;
8.2605 + } else {
8.2606 + appVersion = [NSString stringWithFormat:@"%@ rv:%@",marketingVersionNumber,developmentVersionNumber];
8.2607 + }
8.2608 + } else {
8.2609 + appVersion = (marketingVersionNumber ? marketingVersionNumber : developmentVersionNumber);
8.2610 + }
8.2611 +
8.2612 +
8.2613 + NSString *deviceName;
8.2614 + NSString *OSName;
8.2615 + NSString *OSVersion;
8.2616 +
8.2617 + NSString *locale = [[NSLocale currentLocale] localeIdentifier];
8.2618 +
8.2619 +#if TARGET_OS_IPHONE
8.2620 + UIDevice *device = [UIDevice currentDevice];
8.2621 + deviceName = [device model];
8.2622 + OSName = [device systemName];
8.2623 + OSVersion = [device systemVersion];
8.2624 +
8.2625 +#else
8.2626 + deviceName = @"Macintosh";
8.2627 + OSName = @"Mac OS X";
8.2628 +
8.2629 + // From http://www.cocoadev.com/index.pl?DeterminingOSVersion
8.2630 + // We won't bother to check for systems prior to 10.4, since ASIHTTPRequest only works on 10.5+
8.2631 + OSErr err;
8.2632 + SInt32 versionMajor, versionMinor, versionBugFix;
8.2633 + if ((err = Gestalt(gestaltSystemVersionMajor, &versionMajor)) != noErr) return nil;
8.2634 + if ((err = Gestalt(gestaltSystemVersionMinor, &versionMinor)) != noErr) return nil;
8.2635 + if ((err = Gestalt(gestaltSystemVersionBugFix, &versionBugFix)) != noErr) return nil;
8.2636 + OSVersion = [NSString stringWithFormat:@"%u.%u.%u", versionMajor, versionMinor, versionBugFix];
8.2637 +
8.2638 +#endif
8.2639 + // Takes the form "My Application 1.0 (Macintosh; Mac OS X 10.5.7; en_GB)"
8.2640 + return [NSString stringWithFormat:@"%@ %@ (%@; %@ %@; %@)", appName, appVersion, deviceName, OSName, OSVersion, locale];
8.2641 +}
8.2642 +
8.2643 +#pragma mark proxy autoconfiguration
8.2644 +
8.2645 +// Returns an array of proxies to use for a particular url, given the url of a PAC script
8.2646 ++ (NSArray *)proxiesForURL:(NSURL *)theURL fromPAC:(NSURL *)pacScriptURL
8.2647 +{
8.2648 + // From: http://developer.apple.com/samplecode/CFProxySupportTool/listing1.html
8.2649 + // Work around <rdar://problem/5530166>. This dummy call to
8.2650 + // CFNetworkCopyProxiesForURL initialise some state within CFNetwork
8.2651 + // that is required by CFNetworkCopyProxiesForAutoConfigurationScript.
8.2652 + CFRelease(CFNetworkCopyProxiesForURL((CFURLRef)theURL, NULL));
8.2653 +
8.2654 + NSStringEncoding encoding;
8.2655 + NSError *err = nil;
8.2656 + NSString *script = [NSString stringWithContentsOfURL:pacScriptURL usedEncoding:&encoding error:&err];
8.2657 + if (err) {
8.2658 + return nil;
8.2659 + }
8.2660 + // Obtain the list of proxies by running the autoconfiguration script
8.2661 +#if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MIN_REQUIRED < IPHONE_3_0
8.2662 + NSArray *proxies = [(NSArray *)CFNetworkCopyProxiesForAutoConfigurationScript((CFStringRef)script,(CFURLRef)theURL) autorelease];
8.2663 +#else
8.2664 + CFErrorRef err2 = NULL;
8.2665 + NSArray *proxies = [(NSArray *)CFNetworkCopyProxiesForAutoConfigurationScript((CFStringRef)script,(CFURLRef)theURL, &err2) autorelease];
8.2666 + if (err2) {
8.2667 + return nil;
8.2668 + }
8.2669 +#endif
8.2670 + return proxies;
8.2671 +}
8.2672 +
8.2673 +#pragma mark mime-type detection
8.2674 +
8.2675 ++ (NSString *)mimeTypeForFileAtPath:(NSString *)path
8.2676 +{
8.2677 + // NSTask does seem to exist in the 2.2.1 SDK, though it's not in the 3.0 SDK. It's probably best if we just use a generic mime type on iPhone all the time.
8.2678 +#if TARGET_OS_IPHONE
8.2679 + return @"application/octet-stream";
8.2680 +
8.2681 + // Grab the mime type using an NSTask to run the 'file' program, with the Mac OS-specific parameters to grab the mime type
8.2682 + // Perhaps there is a better way to do this?
8.2683 +#else
8.2684 + NSTask *task = [[[NSTask alloc] init] autorelease];
8.2685 + [task setLaunchPath: @"/usr/bin/file"];
8.2686 + [task setArguments:[NSMutableArray arrayWithObjects:@"-Ib",path,nil]];
8.2687 +
8.2688 + NSPipe *outputPipe = [NSPipe pipe];
8.2689 + [task setStandardOutput:outputPipe];
8.2690 +
8.2691 + NSFileHandle *file = [outputPipe fileHandleForReading];
8.2692 +
8.2693 + [task launch];
8.2694 + [task waitUntilExit];
8.2695 +
8.2696 + if ([task terminationStatus] != 0) {
8.2697 + return @"application/octet-stream";
8.2698 + }
8.2699 +
8.2700 + NSString *mimeTypeString = [[[[NSString alloc] initWithData:[file readDataToEndOfFile] encoding: NSUTF8StringEncoding] autorelease] stringByReplacingOccurrencesOfString:@"\n" withString:@""];
8.2701 + return [[mimeTypeString componentsSeparatedByString:@";"] objectAtIndex:0];
8.2702 +#endif
8.2703 +}
8.2704 +
8.2705 +#pragma mark bandwidth measurement / throttling
8.2706 +
8.2707 ++ (BOOL)isBandwidthThrottled
8.2708 +{
8.2709 +#if TARGET_OS_IPHONE
8.2710 + [bandwidthThrottlingLock lock];
8.2711 +
8.2712 + BOOL throttle = isBandwidthThrottled || (!shouldThrottleBandwithForWWANOnly && (maxBandwidthPerSecond));
8.2713 + [bandwidthThrottlingLock unlock];
8.2714 + return throttle;
8.2715 +#else
8.2716 + [bandwidthThrottlingLock lock];
8.2717 + BOOL throttle = (maxBandwidthPerSecond);
8.2718 + [bandwidthThrottlingLock unlock];
8.2719 + return throttle;
8.2720 +#endif
8.2721 +}
8.2722 +
8.2723 ++ (unsigned long)maxBandwidthPerSecond
8.2724 +{
8.2725 + [bandwidthThrottlingLock lock];
8.2726 + unsigned long amount = maxBandwidthPerSecond;
8.2727 + [bandwidthThrottlingLock unlock];
8.2728 + return amount;
8.2729 +}
8.2730 +
8.2731 ++ (void)setMaxBandwidthPerSecond:(unsigned long)bytes
8.2732 +{
8.2733 + [bandwidthThrottlingLock lock];
8.2734 + maxBandwidthPerSecond = bytes;
8.2735 + [bandwidthThrottlingLock unlock];
8.2736 +}
8.2737 +
8.2738 ++ (void)incrementBandwidthUsedInLastSecond:(unsigned long)bytes
8.2739 +{
8.2740 + [bandwidthThrottlingLock lock];
8.2741 + bandwidthUsedInLastSecond += bytes;
8.2742 + //NSLog(@"used in last second: %lu",bandwidthUsedInLastSecond);
8.2743 + [bandwidthThrottlingLock unlock];
8.2744 +}
8.2745 +
8.2746 ++ (void)recordBandwidthUsage
8.2747 +{
8.2748 + if (bandwidthUsedInLastSecond == 0) {
8.2749 + [bandwidthUsageTracker removeAllObjects];
8.2750 + } else {
8.2751 + NSTimeInterval interval = [bandwidthMeasurementDate timeIntervalSinceNow];
8.2752 + while ((interval < 0 || [bandwidthUsageTracker count] > 5) && [bandwidthUsageTracker count] > 0) {
8.2753 + [bandwidthUsageTracker removeObjectAtIndex:0];
8.2754 + interval++;
8.2755 + }
8.2756 + }
8.2757 + //NSLog(@"Used: %qi",bandwidthUsedInLastSecond);
8.2758 + [bandwidthUsageTracker addObject:[NSNumber numberWithUnsignedLong:bandwidthUsedInLastSecond]];
8.2759 + [bandwidthMeasurementDate release];
8.2760 + bandwidthMeasurementDate = [[NSDate dateWithTimeIntervalSinceNow:1] retain];
8.2761 + bandwidthUsedInLastSecond = 0;
8.2762 +
8.2763 + int measurements = [bandwidthUsageTracker count];
8.2764 + unsigned long long totalBytes = 0;
8.2765 + for (NSNumber *bytes in bandwidthUsageTracker) {
8.2766 + totalBytes += [bytes unsignedLongValue];
8.2767 + }
8.2768 + averageBandwidthUsedPerSecond = totalBytes/measurements;
8.2769 +}
8.2770 +
8.2771 ++ (unsigned long)averageBandwidthUsedPerSecond
8.2772 +{
8.2773 + [bandwidthThrottlingLock lock];
8.2774 +
8.2775 + if (!bandwidthMeasurementDate || [bandwidthMeasurementDate timeIntervalSinceNow] < 0) {
8.2776 + [self recordBandwidthUsage];
8.2777 + }
8.2778 + unsigned long amount = averageBandwidthUsedPerSecond;
8.2779 + [bandwidthThrottlingLock unlock];
8.2780 + return amount;
8.2781 +}
8.2782 +
8.2783 ++ (void)measureBandwidthUsage
8.2784 +{
8.2785 + // Other requests may have to wait for this lock if we're sleeping, but this is fine, since in that case we already know they shouldn't be sending or receiving data
8.2786 + [bandwidthThrottlingLock lock];
8.2787 +
8.2788 + if (!bandwidthMeasurementDate || [bandwidthMeasurementDate timeIntervalSinceNow] < -0) {
8.2789 + [self recordBandwidthUsage];
8.2790 + }
8.2791 +
8.2792 + // Are we performing bandwidth throttling?
8.2793 + if (maxBandwidthPerSecond > 0) {
8.2794 + // How much data can we still send or receive this second?
8.2795 + long long bytesRemaining = (long long)maxBandwidthPerSecond - (long long)bandwidthUsedInLastSecond;
8.2796 +
8.2797 + // Have we used up our allowance?
8.2798 + if (bytesRemaining < 8) {
8.2799 +
8.2800 + // Yes, put this request to sleep until a second is up
8.2801 + [NSThread sleepUntilDate:bandwidthMeasurementDate];
8.2802 + [self recordBandwidthUsage];
8.2803 + }
8.2804 + }
8.2805 + [bandwidthThrottlingLock unlock];
8.2806 +}
8.2807 +
8.2808 +#if TARGET_OS_IPHONE
8.2809 ++ (void)setShouldThrottleBandwidthForWWAN:(BOOL)throttle
8.2810 +{
8.2811 + if (throttle) {
8.2812 + [ASIHTTPRequest throttleBandwidthForWWANUsingLimit:ASIWWANBandwidthThrottleAmount];
8.2813 + } else {
8.2814 + [[NSNotificationCenter defaultCenter] removeObserver:self name:@"kNetworkReachabilityChangedNotification" object:nil];
8.2815 + [ASIHTTPRequest setMaxBandwidthPerSecond:0];
8.2816 + [bandwidthThrottlingLock lock];
8.2817 + shouldThrottleBandwithForWWANOnly = NO;
8.2818 + [bandwidthThrottlingLock unlock];
8.2819 + }
8.2820 +}
8.2821 +
8.2822 ++ (void)throttleBandwidthForWWANUsingLimit:(unsigned long)limit
8.2823 +{
8.2824 + [bandwidthThrottlingLock lock];
8.2825 + shouldThrottleBandwithForWWANOnly = YES;
8.2826 + maxBandwidthPerSecond = limit;
8.2827 + [[Reachability sharedReachability] setNetworkStatusNotificationsEnabled:YES];
8.2828 + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(reachabilityChanged:) name:@"kNetworkReachabilityChangedNotification" object:nil];
8.2829 + [bandwidthThrottlingLock unlock];
8.2830 + [ASIHTTPRequest reachabilityChanged:nil];
8.2831 +}
8.2832 +
8.2833 ++ (void)reachabilityChanged:(NSNotification *)note
8.2834 +{
8.2835 + [bandwidthThrottlingLock lock];
8.2836 + if ([[Reachability sharedReachability] internetConnectionStatus] == ReachableViaCarrierDataNetwork) {
8.2837 + isBandwidthThrottled = YES;
8.2838 + } else {
8.2839 + isBandwidthThrottled = NO;
8.2840 + }
8.2841 + [bandwidthThrottlingLock unlock];
8.2842 +}
8.2843 +#endif
8.2844 +
8.2845 ++ (unsigned long)maxUploadReadLength
8.2846 +{
8.2847 +
8.2848 + [bandwidthThrottlingLock lock];
8.2849 +
8.2850 + // We'll split our bandwidth allowance into 4 (which is the default for an ASINetworkQueue's max concurrent operations count) to give all running requests a fighting chance of reading data this cycle
8.2851 + long long toRead = maxBandwidthPerSecond/4;
8.2852 + if (maxBandwidthPerSecond > 0 && (bandwidthUsedInLastSecond + toRead > maxBandwidthPerSecond)) {
8.2853 + toRead = maxBandwidthPerSecond-bandwidthUsedInLastSecond;
8.2854 + if (toRead < 0) {
8.2855 + toRead = 0;
8.2856 + }
8.2857 + }
8.2858 +
8.2859 + if (toRead == 0 || !bandwidthMeasurementDate || [bandwidthMeasurementDate timeIntervalSinceNow] < -0) {
8.2860 + //NSLog(@"sleep");
8.2861 + [NSThread sleepUntilDate:bandwidthMeasurementDate];
8.2862 + [self recordBandwidthUsage];
8.2863 + }
8.2864 + [bandwidthThrottlingLock unlock];
8.2865 + return toRead;
8.2866 +}
8.2867 +
8.2868 +
8.2869 +@synthesize username;
8.2870 +@synthesize password;
8.2871 +@synthesize domain;
8.2872 +@synthesize proxyUsername;
8.2873 +@synthesize proxyPassword;
8.2874 +@synthesize proxyDomain;
8.2875 +@synthesize url;
8.2876 +@synthesize delegate;
8.2877 +@synthesize queue;
8.2878 +@synthesize uploadProgressDelegate;
8.2879 +@synthesize downloadProgressDelegate;
8.2880 +@synthesize useKeychainPersistance;
8.2881 +@synthesize useSessionPersistance;
8.2882 +@synthesize useCookiePersistance;
8.2883 +@synthesize downloadDestinationPath;
8.2884 +@synthesize temporaryFileDownloadPath;
8.2885 +@synthesize didFinishSelector;
8.2886 +@synthesize didFailSelector;
8.2887 +@synthesize authenticationRealm;
8.2888 +@synthesize proxyAuthenticationRealm;
8.2889 +@synthesize error;
8.2890 +@synthesize complete;
8.2891 +@synthesize requestHeaders;
8.2892 +@synthesize responseHeaders;
8.2893 +@synthesize responseCookies;
8.2894 +@synthesize requestCookies;
8.2895 +@synthesize requestCredentials;
8.2896 +@synthesize responseStatusCode;
8.2897 +@synthesize rawResponseData;
8.2898 +@synthesize lastActivityTime;
8.2899 +@synthesize timeOutSeconds;
8.2900 +@synthesize requestMethod;
8.2901 +@synthesize postBody;
8.2902 +@synthesize compressedPostBody;
8.2903 +@synthesize contentLength;
8.2904 +@synthesize partialDownloadSize;
8.2905 +@synthesize postLength;
8.2906 +@synthesize shouldResetProgressIndicators;
8.2907 +@synthesize mainRequest;
8.2908 +@synthesize totalBytesRead;
8.2909 +@synthesize totalBytesSent;
8.2910 +@synthesize showAccurateProgress;
8.2911 +@synthesize uploadBufferSize;
8.2912 +@synthesize defaultResponseEncoding;
8.2913 +@synthesize responseEncoding;
8.2914 +@synthesize allowCompressedResponse;
8.2915 +@synthesize allowResumeForFileDownloads;
8.2916 +@synthesize userInfo;
8.2917 +@synthesize postBodyFilePath;
8.2918 +@synthesize compressedPostBodyFilePath;
8.2919 +@synthesize postBodyWriteStream;
8.2920 +@synthesize postBodyReadStream;
8.2921 +@synthesize shouldStreamPostDataFromDisk;
8.2922 +@synthesize didCreateTemporaryPostDataFile;
8.2923 +@synthesize useHTTPVersionOne;
8.2924 +@synthesize lastBytesRead;
8.2925 +@synthesize lastBytesSent;
8.2926 +@synthesize cancelledLock;
8.2927 +@synthesize haveBuiltPostBody;
8.2928 +@synthesize fileDownloadOutputStream;
8.2929 +@synthesize authenticationRetryCount;
8.2930 +@synthesize proxyAuthenticationRetryCount;
8.2931 +@synthesize updatedProgress;
8.2932 +@synthesize shouldRedirect;
8.2933 +@synthesize validatesSecureCertificate;
8.2934 +@synthesize needsRedirect;
8.2935 +@synthesize redirectCount;
8.2936 +@synthesize shouldCompressRequestBody;
8.2937 +@synthesize authenticationLock;
8.2938 +@synthesize needsProxyAuthentication;
8.2939 +@synthesize proxyCredentials;
8.2940 +@synthesize proxyHost;
8.2941 +@synthesize proxyPort;
8.2942 +@synthesize PACurl;
8.2943 +@synthesize authenticationScheme;
8.2944 +@synthesize proxyAuthenticationScheme;
8.2945 +@synthesize shouldPresentAuthenticationDialog;
8.2946 +@synthesize shouldPresentProxyAuthenticationDialog;
8.2947 +@synthesize authenticationChallengeInProgress;
8.2948 +@synthesize responseStatusMessage;
8.2949 +@synthesize shouldPresentCredentialsBeforeChallenge;
8.2950 +@end
8.2951 +
8.2952 +
9.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
9.2 +++ b/ASI-HTTP/ASIInputStream.h Thu Dec 24 00:14:02 2009 +0000
9.3 @@ -0,0 +1,22 @@
9.4 +//
9.5 +// ASIInputStream.h
9.6 +// asi-http-request
9.7 +//
9.8 +// Created by Ben Copsey on 10/08/2009.
9.9 +// Copyright 2009 All-Seeing Interactive. All rights reserved.
9.10 +//
9.11 +
9.12 +#import <Foundation/Foundation.h>
9.13 +
9.14 +// This is a wrapper for NSInputStream that pretends to be an NSInputStream itself
9.15 +// Subclassing NSInputStream seems to be tricky, and may involve overriding undocumented methods, so we'll cheat instead.
9.16 +// It is used by ASIHTTPRequest whenever we have a request body, and handles measuring and throttling the bandwidth used for uploading
9.17 +
9.18 +@interface ASIInputStream : NSObject {
9.19 + NSInputStream *stream;
9.20 +}
9.21 ++ (id)inputStreamWithFileAtPath:(NSString *)path;
9.22 ++ (id)inputStreamWithData:(NSData *)data;
9.23 +
9.24 +@property (retain) NSInputStream *stream;
9.25 +@end
10.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
10.2 +++ b/ASI-HTTP/ASIInputStream.m Thu Dec 24 00:14:02 2009 +0000
10.3 @@ -0,0 +1,103 @@
10.4 +//
10.5 +// ASIInputStream.m
10.6 +// asi-http-request
10.7 +//
10.8 +// Created by Ben Copsey on 10/08/2009.
10.9 +// Copyright 2009 All-Seeing Interactive. All rights reserved.
10.10 +//
10.11 +
10.12 +#import "ASIInputStream.h"
10.13 +#import "ASIHTTPRequest.h"
10.14 +
10.15 +// Used to ensure only one request can read data at once
10.16 +static NSLock *readLock = nil;
10.17 +
10.18 +@implementation ASIInputStream
10.19 +
10.20 ++ (void)initialize
10.21 +{
10.22 + if (self == [ASIInputStream class]) {
10.23 + readLock = [[NSLock alloc] init];
10.24 + }
10.25 +}
10.26 +
10.27 ++ (id)inputStreamWithFileAtPath:(NSString *)path
10.28 +{
10.29 + ASIInputStream *stream = [[[self alloc] init] autorelease];
10.30 + [stream setStream:[NSInputStream inputStreamWithFileAtPath:path]];
10.31 + return stream;
10.32 +}
10.33 +
10.34 ++ (id)inputStreamWithData:(NSData *)data
10.35 +{
10.36 + ASIInputStream *stream = [[[self alloc] init] autorelease];
10.37 + [stream setStream:[NSInputStream inputStreamWithData:data]];
10.38 + return stream;
10.39 +}
10.40 +
10.41 +- (void)dealloc
10.42 +{
10.43 + [stream release];
10.44 + [super dealloc];
10.45 +}
10.46 +
10.47 +
10.48 +// Ok, so this works, but I don't really understand why.
10.49 +// Ideally, we'd just return the stream's hasBytesAvailable, but CFNetwork seems to want to monopolise our run loop until (presumably) its buffer is full, which will cause timeouts if we're throttling the bandwidth
10.50 +// We return NO when we shouldn't be uploading any more data because our bandwidth limit has run out (for now)
10.51 +// The call to maxUploadReadLength will recognise that we've run out of our allotted bandwidth limit, and sleep this thread for the rest of the measurement period
10.52 +// This method will be called again, but we'll almost certainly return YES the next time around, because we'll have more limit to use up
10.53 +// The NO returns seem to snap CFNetwork out of its reverie, and return control to the main loop in loadRequest, so that we can manage timeouts and progress delegate updates
10.54 +- (BOOL)hasBytesAvailable
10.55 +{
10.56 +
10.57 + if ([ASIHTTPRequest isBandwidthThrottled]) {
10.58 + [readLock lock];
10.59 + if ([ASIHTTPRequest maxUploadReadLength] == 0) {
10.60 + [readLock unlock];
10.61 + return NO;
10.62 + }
10.63 + [readLock unlock];
10.64 + }
10.65 + return [[self stream] hasBytesAvailable];
10.66 +
10.67 +}
10.68 +
10.69 +// Called when CFNetwork wants to read more of our request body
10.70 +// When throttling is on, we ask ASIHTTPRequest for the maximum amount of data we can read
10.71 +- (NSInteger)read:(uint8_t *)buffer maxLength:(NSUInteger)len
10.72 +{
10.73 + [readLock lock];
10.74 + unsigned long toRead = len;
10.75 + if ([ASIHTTPRequest isBandwidthThrottled]) {
10.76 + toRead = [ASIHTTPRequest maxUploadReadLength];
10.77 + if (toRead > len) {
10.78 + toRead = len;
10.79 +
10.80 + // Hopefully this won't happen because hasBytesAvailable will have returned NO, but just in case - we need to read at least 1 byte, or bad things might happen
10.81 + } else if (toRead == 0) {
10.82 + toRead = 1;
10.83 + }
10.84 + //NSLog(@"Throttled read %u",toRead);
10.85 + } else {
10.86 + //NSLog(@"Unthrottled read %u",toRead);
10.87 + }
10.88 + [ASIHTTPRequest incrementBandwidthUsedInLastSecond:toRead];
10.89 + [readLock unlock];
10.90 + return [[self stream] read:buffer maxLength:toRead];
10.91 +}
10.92 +
10.93 +// If we get asked to perform a method we don't have (which is almost all of them), we'll just forward the message to our stream
10.94 +
10.95 +- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
10.96 +{
10.97 + return [[self stream] methodSignatureForSelector:aSelector];
10.98 +}
10.99 +
10.100 +- (void)forwardInvocation:(NSInvocation *)anInvocation
10.101 +{
10.102 + [anInvocation invokeWithTarget:[self stream]];
10.103 +}
10.104 +
10.105 +@synthesize stream;
10.106 +@end
11.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
11.2 +++ b/ASI-HTTP/ASINSStringAdditions.h Thu Dec 24 00:14:02 2009 +0000
11.3 @@ -0,0 +1,16 @@
11.4 +//
11.5 +// ASINSStringAdditions.h
11.6 +// asi-http-request
11.7 +//
11.8 +// Created by Ben Copsey on 12/09/2008.
11.9 +// Copyright 2008 All-Seeing Interactive. All rights reserved.
11.10 +//
11.11 +
11.12 +#import <Foundation/Foundation.h>
11.13 +
11.14 +@interface NSString (CookieValueEncodingAdditions)
11.15 +
11.16 +- (NSString *)encodedCookieValue;
11.17 +- (NSString *)decodedCookieValue;
11.18 +
11.19 +@end
12.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
12.2 +++ b/ASI-HTTP/ASINSStringAdditions.m Thu Dec 24 00:14:02 2009 +0000
12.3 @@ -0,0 +1,28 @@
12.4 +//
12.5 +// ASINSStringAdditions.m
12.6 +// asi-http-request
12.7 +//
12.8 +// Created by Ben Copsey on 12/09/2008.
12.9 +// Copyright 2008 All-Seeing Interactive. All rights reserved.
12.10 +//
12.11 +
12.12 +#import "ASINSStringAdditions.h"
12.13 +
12.14 +@implementation NSString (CookieValueEncodingAdditions)
12.15 +
12.16 +- (NSString *)decodedCookieValue
12.17 +{
12.18 + NSMutableString *s = [NSMutableString stringWithString:[self stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
12.19 + //Also swap plus signs for spaces
12.20 + [s replaceOccurrencesOfString:@"+" withString:@" " options:NSLiteralSearch range:NSMakeRange(0, [s length])];
12.21 + return [NSString stringWithString:s];
12.22 +}
12.23 +
12.24 +- (NSString *)encodedCookieValue
12.25 +{
12.26 + return [self stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
12.27 +}
12.28 +
12.29 +@end
12.30 +
12.31 +
13.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
13.2 +++ b/ASI-HTTP/ASINetworkQueue.h Thu Dec 24 00:14:02 2009 +0000
13.3 @@ -0,0 +1,108 @@
13.4 +//
13.5 +// ASINetworkQueue.h
13.6 +// asi-http-request
13.7 +//
13.8 +// Created by Ben Copsey on 07/11/2008.
13.9 +// Copyright 2008-2009 All-Seeing Interactive. All rights reserved.
13.10 +//
13.11 +
13.12 +#import <Foundation/Foundation.h>
13.13 +
13.14 +@interface ASINetworkQueue : NSOperationQueue {
13.15 +
13.16 + // Delegate will get didFail + didFinish messages (if set)
13.17 + id delegate;
13.18 +
13.19 + // Will be called when a request completes with the request as the argument
13.20 + SEL requestDidFinishSelector;
13.21 +
13.22 + // Will be called when a request fails with the request as the argument
13.23 + SEL requestDidFailSelector;
13.24 +
13.25 + // Will be called when the queue finishes with the queue as the argument
13.26 + SEL queueDidFinishSelector;
13.27 +
13.28 + // Upload progress indicator, probably an NSProgressIndicator or UIProgressView
13.29 + id uploadProgressDelegate;
13.30 +
13.31 + // Total amount uploaded so far for all requests in this queue
13.32 + unsigned long long uploadProgressBytes;
13.33 +
13.34 + // Total amount to be uploaded for all requests in this queue - requests add to this figure as they work out how much data they have to transmit
13.35 + unsigned long long uploadProgressTotalBytes;
13.36 +
13.37 + // Download progress indicator, probably an NSProgressIndicator or UIProgressView
13.38 + id downloadProgressDelegate;
13.39 +
13.40 + // Total amount downloaded so far for all requests in this queue
13.41 + unsigned long long downloadProgressBytes;
13.42 +
13.43 + // Total amount to be downloaded for all requests in this queue - requests add to this figure as they receive Content-Length headers
13.44 + unsigned long long downloadProgressTotalBytes;
13.45 +
13.46 + // When YES, the queue will cancel all requests when a request fails. Default is YES
13.47 + BOOL shouldCancelAllRequestsOnFailure;
13.48 +
13.49 + //Number of real requests (excludes HEAD requests created to manage showAccurateProgress)
13.50 + int requestsCount;
13.51 +
13.52 + // When NO, this request will only update the progress indicator when it completes
13.53 + // When YES, this request will update the progress indicator according to how much data it has recieved so far
13.54 + // When YES, the queue will first perform HEAD requests for all GET requests in the queue, so it can calculate the total download size before it starts
13.55 + // NO means better performance, because it skips this step for GET requests, and it won't waste time updating the progress indicator until a request completes
13.56 + // Set to YES if the size of a requests in the queue varies greatly for much more accurate results
13.57 + // Default for requests in the queue is NO
13.58 + BOOL showAccurateProgress;
13.59 +
13.60 +
13.61 +}
13.62 +
13.63 +// Convenience constructor
13.64 ++ (id)queue;
13.65 +
13.66 +// Used internally to manage HEAD requests when showAccurateProgress is YES, do not use!
13.67 +- (void)addHEADOperation:(NSOperation *)operation;
13.68 +
13.69 +// Called at the start of a request to add on the size of this upload to the total
13.70 +- (void)incrementUploadSizeBy:(unsigned long long)bytes;
13.71 +
13.72 +// Called during a request when data is written to the upload stream to increment the progress indicator
13.73 +- (void)incrementUploadProgressBy:(unsigned long long)bytes;
13.74 +
13.75 +// Called at the start of a request to add on the size of this download to the total
13.76 +- (void)incrementDownloadSizeBy:(unsigned long long)bytes;
13.77 +
13.78 +// Called during a request when data is received to increment the progress indicator
13.79 +- (void)incrementDownloadProgressBy:(unsigned long long)bytes;
13.80 +
13.81 +// Called during a request when authorisation fails to cancel any progress so far
13.82 +- (void)decrementUploadProgressBy:(unsigned long long)bytes;
13.83 +
13.84 +// Called when the first chunk of data is written to the upload buffer
13.85 +// We ignore the first part chunk when tracking upload progress, as kCFStreamPropertyHTTPRequestBytesWrittenCount reports the amount of data written to the buffer, not the amount sent
13.86 +// This is to workaround the first 128KB of data appearing in an upload progress delegate immediately
13.87 +- (void)setUploadBufferSize:(unsigned long long)bytes;
13.88 +
13.89 +// All ASINetworkQueues are paused when created so that total size can be calculated before the queue starts
13.90 +// This method will start the queue
13.91 +- (void)go;
13.92 +
13.93 +// Used on iPhone platform to show / hide the network activity indicator (in the status bar)
13.94 +// On mac, you could subclass to do something else
13.95 +- (void)updateNetworkActivityIndicator;
13.96 +
13.97 +// Returns YES if the queue is in progress
13.98 +- (BOOL)isNetworkActive;
13.99 +
13.100 +
13.101 +@property (assign,setter=setUploadProgressDelegate:) id uploadProgressDelegate;
13.102 +@property (assign,setter=setDownloadProgressDelegate:) id downloadProgressDelegate;
13.103 +
13.104 +@property (assign) SEL requestDidFinishSelector;
13.105 +@property (assign) SEL requestDidFailSelector;
13.106 +@property (assign) SEL queueDidFinishSelector;
13.107 +@property (assign) BOOL shouldCancelAllRequestsOnFailure;
13.108 +@property (assign) id delegate;
13.109 +@property (assign) BOOL showAccurateProgress;
13.110 +@property (assign, readonly) int requestsCount;
13.111 +@end
14.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
14.2 +++ b/ASI-HTTP/ASINetworkQueue.m Thu Dec 24 00:14:02 2009 +0000
14.3 @@ -0,0 +1,318 @@
14.4 +//
14.5 +// ASINetworkQueue.m
14.6 +// asi-http-request
14.7 +//
14.8 +// Created by Ben Copsey on 07/11/2008.
14.9 +// Copyright 2008-2009 All-Seeing Interactive. All rights reserved.
14.10 +//
14.11 +
14.12 +#import "ASINetworkQueue.h"
14.13 +#import "ASIHTTPRequest.h"
14.14 +
14.15 +// Private stuff
14.16 +@interface ASINetworkQueue ()
14.17 + @property (assign) int requestsCount;
14.18 + @property (assign) unsigned long long uploadProgressBytes;
14.19 + @property (assign) unsigned long long uploadProgressTotalBytes;
14.20 + @property (assign) unsigned long long downloadProgressBytes;
14.21 + @property (assign) unsigned long long downloadProgressTotalBytes;
14.22 +@end
14.23 +
14.24 +@implementation ASINetworkQueue
14.25 +
14.26 +- (id)init
14.27 +{
14.28 + self = [super init];
14.29 + [self setShouldCancelAllRequestsOnFailure:YES];
14.30 + [self setMaxConcurrentOperationCount:4];
14.31 + [self setSuspended:YES];
14.32 +
14.33 + return self;
14.34 +}
14.35 +
14.36 ++ (id)queue
14.37 +{
14.38 + return [[[self alloc] init] autorelease];
14.39 +}
14.40 +
14.41 +- (void)dealloc
14.42 +{
14.43 + //We need to clear the delegate on any requests that haven't got around to cleaning up yet, as otherwise they'll try to let us know if something goes wrong, and we'll be long gone by then
14.44 + for (ASIHTTPRequest *request in [self operations]) {
14.45 + [request setDelegate:nil];
14.46 + }
14.47 + [super dealloc];
14.48 +}
14.49 +
14.50 +- (BOOL)isNetworkActive
14.51 +{
14.52 + return ([self requestsCount] > 0 && ![self isSuspended]);
14.53 +}
14.54 +
14.55 +- (void)updateNetworkActivityIndicator
14.56 +{
14.57 +#if TARGET_OS_IPHONE
14.58 + [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:[self isNetworkActive]];
14.59 +#endif
14.60 +}
14.61 +
14.62 +- (void)setSuspended:(BOOL)suspend
14.63 +{
14.64 + [super setSuspended:suspend];
14.65 + [self updateNetworkActivityIndicator];
14.66 +}
14.67 +
14.68 +
14.69 +- (void)go
14.70 +{
14.71 + if (![self showAccurateProgress]) {
14.72 + if ([self downloadProgressDelegate]) {
14.73 + [self incrementDownloadSizeBy:[self requestsCount]];
14.74 + }
14.75 + if ([self uploadProgressDelegate]) {
14.76 + [self incrementUploadSizeBy:[self requestsCount]];
14.77 + }
14.78 + }
14.79 + [self setSuspended:NO];
14.80 +}
14.81 +
14.82 +- (void)cancelAllOperations
14.83 +{
14.84 + [self setRequestsCount:0];
14.85 + [self setUploadProgressBytes:0];
14.86 + [self setUploadProgressTotalBytes:0];
14.87 + [self setDownloadProgressBytes:0];
14.88 + [self setDownloadProgressTotalBytes:0];
14.89 + [super cancelAllOperations];
14.90 + [self updateNetworkActivityIndicator];
14.91 +}
14.92 +
14.93 +- (void)setUploadProgressDelegate:(id)newDelegate
14.94 +{
14.95 + uploadProgressDelegate = newDelegate;
14.96 +
14.97 + // If the uploadProgressDelegate is an NSProgressIndicator, we set it's MaxValue to 1.0 so we can treat it similarly to UIProgressViews
14.98 + SEL selector = @selector(setMaxValue:);
14.99 + if ([[self uploadProgressDelegate] respondsToSelector:selector]) {
14.100 + double max = 1.0;
14.101 + NSMethodSignature *signature = [[[self uploadProgressDelegate] class] instanceMethodSignatureForSelector:selector];
14.102 + NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
14.103 + [invocation setSelector:selector];
14.104 + [invocation setArgument:&max atIndex:2];
14.105 + [invocation invokeWithTarget:[self uploadProgressDelegate]];
14.106 + }
14.107 +}
14.108 +
14.109 +
14.110 +- (void)setDownloadProgressDelegate:(id)newDelegate
14.111 +{
14.112 + downloadProgressDelegate = newDelegate;
14.113 +
14.114 + // If the downloadProgressDelegate is an NSProgressIndicator, we set it's MaxValue to 1.0 so we can treat it similarly to UIProgressViews
14.115 + SEL selector = @selector(setMaxValue:);
14.116 + if ([[self downloadProgressDelegate] respondsToSelector:selector]) {
14.117 + double max = 1.0;
14.118 + NSMethodSignature *signature = [[[self downloadProgressDelegate] class] instanceMethodSignatureForSelector:selector];
14.119 + NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
14.120 + [invocation setSelector:@selector(setMaxValue:)];
14.121 + [invocation setArgument:&max atIndex:2];
14.122 + [invocation invokeWithTarget:[self downloadProgressDelegate]];
14.123 + }
14.124 +}
14.125 +
14.126 +- (void)addHEADOperation:(NSOperation *)operation
14.127 +{
14.128 + if ([operation isKindOfClass:[ASIHTTPRequest class]]) {
14.129 +
14.130 + ASIHTTPRequest *request = (ASIHTTPRequest *)operation;
14.131 + [request setRequestMethod:@"HEAD"];
14.132 + [request setQueuePriority:10];
14.133 + [request setShowAccurateProgress:YES];
14.134 + [request setQueue:self];
14.135 +
14.136 + // Important - we are calling NSOperation's add method - we don't want to add this as a normal request!
14.137 + [super addOperation:request];
14.138 + }
14.139 +}
14.140 +
14.141 +// Only add ASIHTTPRequests to this queue!!
14.142 +- (void)addOperation:(NSOperation *)operation
14.143 +{
14.144 + if (![operation isKindOfClass:[ASIHTTPRequest class]]) {
14.145 + [NSException raise:@"AttemptToAddInvalidRequest" format:@"Attempted to add an object that was not an ASIHTTPRequest to an ASINetworkQueue"];
14.146 + }
14.147 +
14.148 + [self setRequestsCount:[self requestsCount]+1];
14.149 +
14.150 + ASIHTTPRequest *request = (ASIHTTPRequest *)operation;
14.151 +
14.152 + if ([self showAccurateProgress]) {
14.153 +
14.154 + // If this is a GET request and we want accurate progress, perform a HEAD request first to get the content-length
14.155 + if ([[request requestMethod] isEqualToString:@"GET"]) {
14.156 + ASIHTTPRequest *HEADRequest = [[[ASIHTTPRequest alloc] initWithURL:[request url]] autorelease];
14.157 + [HEADRequest setMainRequest:request];
14.158 + [self addHEADOperation:HEADRequest];
14.159 +
14.160 + //Tell the request not to reset the progress indicator when it gets a content-length, as we will get the length from the HEAD request
14.161 + [request setShouldResetProgressIndicators:NO];
14.162 +
14.163 + [request addDependency:HEADRequest];
14.164 +
14.165 + // If we want to track uploading for this request accurately, we need to add the size of the post content to the total
14.166 + } else if (uploadProgressDelegate) {
14.167 + [request buildPostBody];
14.168 + [self setUploadProgressTotalBytes:[self uploadProgressTotalBytes]+[request postLength]];
14.169 + }
14.170 + }
14.171 + [request setShowAccurateProgress:[self showAccurateProgress]];
14.172 +
14.173 +
14.174 + [request setQueue:self];
14.175 + [super addOperation:request];
14.176 + [self updateNetworkActivityIndicator];
14.177 +
14.178 +}
14.179 +
14.180 +- (void)requestDidFail:(ASIHTTPRequest *)request
14.181 +{
14.182 + [self setRequestsCount:[self requestsCount]-1];
14.183 + [self updateNetworkActivityIndicator];
14.184 + if ([self requestDidFailSelector]) {
14.185 + [[self delegate] performSelector:[self requestDidFailSelector] withObject:request];
14.186 + }
14.187 + if ([self shouldCancelAllRequestsOnFailure] && [self requestsCount] > 0) {
14.188 + [self cancelAllOperations];
14.189 + }
14.190 + if ([self requestsCount] == 0) {
14.191 + if ([self queueDidFinishSelector]) {
14.192 + [[self delegate] performSelector:[self queueDidFinishSelector] withObject:self];
14.193 + }
14.194 + }
14.195 +}
14.196 +
14.197 +- (void)requestDidFinish:(ASIHTTPRequest *)request
14.198 +{
14.199 + [self setRequestsCount:[self requestsCount]-1];
14.200 + [self updateNetworkActivityIndicator];
14.201 + if ([self requestDidFinishSelector]) {
14.202 + [[self delegate] performSelector:[self requestDidFinishSelector] withObject:request];
14.203 + }
14.204 + if ([self requestsCount] == 0) {
14.205 + if ([self queueDidFinishSelector]) {
14.206 + [[self delegate] performSelector:[self queueDidFinishSelector] withObject:self];
14.207 + }
14.208 + }
14.209 +}
14.210 +
14.211 +
14.212 +- (void)setUploadBufferSize:(unsigned long long)bytes
14.213 +{
14.214 + if (![self uploadProgressDelegate]) {
14.215 + return;
14.216 + }
14.217 + [self setUploadProgressTotalBytes:[self uploadProgressTotalBytes] - bytes];
14.218 + [self incrementUploadProgressBy:0];
14.219 +}
14.220 +
14.221 +- (void)incrementUploadSizeBy:(unsigned long long)bytes
14.222 +{
14.223 + if (![self uploadProgressDelegate]) {
14.224 + return;
14.225 + }
14.226 + [self setUploadProgressTotalBytes:[self uploadProgressTotalBytes] + bytes];
14.227 + [self incrementUploadProgressBy:0];
14.228 +}
14.229 +
14.230 +- (void)decrementUploadProgressBy:(unsigned long long)bytes
14.231 +{
14.232 + if (![self uploadProgressDelegate] || [self uploadProgressTotalBytes] == 0) {
14.233 + return;
14.234 + }
14.235 + [self setUploadProgressBytes:[self uploadProgressBytes] - bytes];
14.236 +
14.237 + double progress = ([self uploadProgressBytes]*1.0)/([self uploadProgressTotalBytes]*1.0);
14.238 + [ASIHTTPRequest setProgress:progress forProgressIndicator:[self uploadProgressDelegate]];
14.239 +}
14.240 +
14.241 +
14.242 +- (void)incrementUploadProgressBy:(unsigned long long)bytes
14.243 +{
14.244 + if (![self uploadProgressDelegate] || [self uploadProgressTotalBytes] == 0) {
14.245 + return;
14.246 + }
14.247 + [self setUploadProgressBytes:[self uploadProgressBytes] + bytes];
14.248 +
14.249 + double progress = ([self uploadProgressBytes]*1.0)/([self uploadProgressTotalBytes]*1.0);
14.250 + [ASIHTTPRequest setProgress:progress forProgressIndicator:[self uploadProgressDelegate]];
14.251 +
14.252 +}
14.253 +
14.254 +- (void)incrementDownloadSizeBy:(unsigned long long)bytes
14.255 +{
14.256 + if (![self downloadProgressDelegate]) {
14.257 + return;
14.258 + }
14.259 + [self setDownloadProgressTotalBytes:[self downloadProgressTotalBytes] + bytes];
14.260 + [self incrementDownloadProgressBy:0];
14.261 +}
14.262 +
14.263 +- (void)incrementDownloadProgressBy:(unsigned long long)bytes
14.264 +{
14.265 + if (![self downloadProgressDelegate] || [self downloadProgressTotalBytes] == 0) {
14.266 + return;
14.267 + }
14.268 + [self setDownloadProgressBytes:[self downloadProgressBytes] + bytes];
14.269 + double progress = ([self downloadProgressBytes]*1.0)/([self downloadProgressTotalBytes]*1.0);
14.270 + [ASIHTTPRequest setProgress:progress forProgressIndicator:[self downloadProgressDelegate]];
14.271 +}
14.272 +
14.273 +
14.274 +// Since this queue takes over as the delegate for all requests it contains, it should forward authorisation requests to its own delegate
14.275 +- (void)authenticationNeededForRequest:(ASIHTTPRequest *)request
14.276 +{
14.277 + if ([[self delegate] respondsToSelector:@selector(authenticationNeededForRequest:)]) {
14.278 + [[self delegate] performSelector:@selector(authenticationNeededForRequest:) withObject:request];
14.279 + }
14.280 +}
14.281 +
14.282 +- (void)proxyAuthenticationNeededForRequest:(ASIHTTPRequest *)request
14.283 +{
14.284 + if ([[self delegate] respondsToSelector:@selector(proxyAuthenticationNeededForRequest:)]) {
14.285 + [[self delegate] performSelector:@selector(proxyAuthenticationNeededForRequest:) withObject:request];
14.286 + }
14.287 +}
14.288 +
14.289 +
14.290 +- (BOOL)respondsToSelector:(SEL)selector
14.291 +{
14.292 + if (selector == @selector(authenticationNeededForRequest:)) {
14.293 + if ([[self delegate] respondsToSelector:@selector(authenticationNeededForRequest:)]) {
14.294 + return YES;
14.295 + }
14.296 + return NO;
14.297 + } else if (selector == @selector(proxyAuthenticationNeededForRequest:)) {
14.298 + if ([[self delegate] respondsToSelector:@selector(proxyAuthenticationNeededForRequest:)]) {
14.299 + return YES;
14.300 + }
14.301 + return NO;
14.302 + }
14.303 + return [super respondsToSelector:selector];
14.304 +}
14.305 +
14.306 +
14.307 +@synthesize requestsCount;
14.308 +@synthesize uploadProgressBytes;
14.309 +@synthesize uploadProgressTotalBytes;
14.310 +@synthesize downloadProgressBytes;
14.311 +@synthesize downloadProgressTotalBytes;
14.312 +@synthesize shouldCancelAllRequestsOnFailure;
14.313 +@synthesize uploadProgressDelegate;
14.314 +@synthesize downloadProgressDelegate;
14.315 +@synthesize requestDidFinishSelector;
14.316 +@synthesize requestDidFailSelector;
14.317 +@synthesize queueDidFinishSelector;
14.318 +@synthesize delegate;
14.319 +@synthesize showAccurateProgress;
14.320 +
14.321 +@end
15.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
15.2 +++ b/ASI-HTTP/ASIS3BucketObject.h Thu Dec 24 00:14:02 2009 +0000
15.3 @@ -0,0 +1,52 @@
15.4 +//
15.5 +// ASIS3BucketObject.h
15.6 +// Mac
15.7 +//
15.8 +// Created by Ben Copsey on 13/07/2009.
15.9 +// Copyright 2009 All-Seeing Interactive. All rights reserved.
15.10 +//
15.11 +
15.12 +#import <Foundation/Foundation.h>
15.13 +@class ASIS3Request;
15.14 +
15.15 +@interface ASIS3BucketObject : NSObject {
15.16 +
15.17 + // The bucket this object belongs to
15.18 + NSString *bucket;
15.19 +
15.20 + // The key (path) of this object in the bucket
15.21 + NSString *key;
15.22 +
15.23 + // When this object was last modified
15.24 + NSDate *lastModified;
15.25 +
15.26 + // The ETag for this object's content
15.27 + NSString *ETag;
15.28 +
15.29 + // The size in bytes of this object
15.30 + unsigned long long size;
15.31 +
15.32 + // Info about the owner
15.33 + NSString *ownerID;
15.34 + NSString *ownerName;
15.35 +}
15.36 +
15.37 ++ (id)objectWithBucket:(NSString *)bucket;
15.38 +
15.39 +// Returns a request that will fetch this object when run
15.40 +- (ASIS3Request *)GETRequest;
15.41 +
15.42 +// Returns a request that will replace this object with the contents of the file at filePath when run
15.43 +- (ASIS3Request *)PUTRequestWithFile:(NSString *)filePath;
15.44 +
15.45 +// Returns a request that will delete this object when run
15.46 +- (ASIS3Request *)DELETERequest;
15.47 +
15.48 +@property (retain) NSString *bucket;
15.49 +@property (retain) NSString *key;
15.50 +@property (retain) NSDate *lastModified;
15.51 +@property (retain) NSString *ETag;
15.52 +@property (assign) unsigned long long size;
15.53 +@property (retain) NSString *ownerID;
15.54 +@property (retain) NSString *ownerName;
15.55 +@end
16.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
16.2 +++ b/ASI-HTTP/ASIS3BucketObject.m Thu Dec 24 00:14:02 2009 +0000
16.3 @@ -0,0 +1,61 @@
16.4 +//
16.5 +// ASIS3BucketObject.m
16.6 +// Mac
16.7 +//
16.8 +// Created by Ben Copsey on 13/07/2009.
16.9 +// Copyright 2009 All-Seeing Interactive. All rights reserved.
16.10 +//
16.11 +
16.12 +#import "ASIS3BucketObject.h"
16.13 +#import "ASIS3Request.h"
16.14 +
16.15 +@implementation ASIS3BucketObject
16.16 +
16.17 ++ (id)objectWithBucket:(NSString *)bucket
16.18 +{
16.19 + ASIS3BucketObject *object = [[[self alloc] init] autorelease];
16.20 + [object setBucket:bucket];
16.21 + return object;
16.22 +}
16.23 +
16.24 +- (void)dealloc
16.25 +{
16.26 + [key release];
16.27 + [lastModified release];
16.28 + [ETag release];
16.29 + [ownerID release];
16.30 + [ownerName release];
16.31 + [super dealloc];
16.32 +}
16.33 +
16.34 +- (ASIS3Request *)GETRequest
16.35 +{
16.36 + return [ASIS3Request requestWithBucket:[self bucket] path:[NSString stringWithFormat:@"/%@",[self key]]];
16.37 +}
16.38 +
16.39 +- (ASIS3Request *)PUTRequestWithFile:(NSString *)filePath
16.40 +{
16.41 + return [ASIS3Request PUTRequestForFile:filePath withBucket:[self bucket] path:[NSString stringWithFormat:@"/%@",[self key]]];
16.42 +}
16.43 +
16.44 +- (ASIS3Request *)DELETERequest
16.45 +{
16.46 + ASIS3Request *request = [ASIS3Request requestWithBucket:[self bucket] path:[NSString stringWithFormat:@"/%@",[self key]]];
16.47 + [request setRequestMethod:@"DELETE"];
16.48 + return request;
16.49 +}
16.50 +
16.51 +
16.52 +- (NSString *)description
16.53 +{
16.54 + return [NSString stringWithFormat:@"Key: %@ lastModified: %@ ETag: %@ size: %llu ownerID: %@ ownerName: %@",[self key],[self lastModified],[self ETag],[self size],[self ownerID],[self ownerName]];
16.55 +}
16.56 +
16.57 +@synthesize bucket;
16.58 +@synthesize key;
16.59 +@synthesize lastModified;
16.60 +@synthesize ETag;
16.61 +@synthesize size;
16.62 +@synthesize ownerID;
16.63 +@synthesize ownerName;
16.64 +@end
17.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
17.2 +++ b/ASI-HTTP/ASIS3ListRequest.h Thu Dec 24 00:14:02 2009 +0000
17.3 @@ -0,0 +1,47 @@
17.4 +//
17.5 +// ASIS3ListRequest.h
17.6 +// Mac
17.7 +//
17.8 +// Created by Ben Copsey on 13/07/2009.
17.9 +// Copyright 2009 All-Seeing Interactive. All rights reserved.
17.10 +//
17.11 +
17.12 +#import <Foundation/Foundation.h>
17.13 +#import "ASIS3Request.h"
17.14 +@class ASIS3BucketObject;
17.15 +
17.16 +@interface ASIS3ListRequest : ASIS3Request {
17.17 +
17.18 + NSString *prefix;
17.19 + NSString *marker;
17.20 + int maxResultCount;
17.21 + NSString *delimiter;
17.22 +
17.23 + // Internally used while parsing the response
17.24 + NSString *currentContent;
17.25 + NSString *currentElement;
17.26 + ASIS3BucketObject *currentObject;
17.27 + NSMutableArray *objects;
17.28 +
17.29 + // Options for filtering list requests
17.30 + // See http://docs.amazonwebservices.com/AmazonS3/2006-03-01/index.html?RESTBucketGET.html
17.31 + NSString *listPrefix;
17.32 + NSString *listMarker;
17.33 + int listMaxResults;
17.34 + NSString *listDelimiter;
17.35 +}
17.36 +// Create a list request
17.37 ++ (id)listRequestWithBucket:(NSString *)bucket;
17.38 +
17.39 +
17.40 +// Returns an array of ASIS3BucketObjects created from the XML response
17.41 +- (NSArray *)bucketObjects;
17.42 +
17.43 +//Builds a query string out of the list parameters we supplied
17.44 +- (void)createQueryString;
17.45 +
17.46 +@property (retain) NSString *prefix;
17.47 +@property (retain) NSString *marker;
17.48 +@property (assign) int maxResultCount;
17.49 +@property (retain) NSString *delimiter;
17.50 +@end
18.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
18.2 +++ b/ASI-HTTP/ASIS3ListRequest.m Thu Dec 24 00:14:02 2009 +0000
18.3 @@ -0,0 +1,136 @@
18.4 +//
18.5 +// ASIS3ListRequest.m
18.6 +// Mac
18.7 +//
18.8 +// Created by Ben Copsey on 13/07/2009.
18.9 +// Copyright 2009 All-Seeing Interactive. All rights reserved.
18.10 +//
18.11 +#import "ASIS3ListRequest.h"
18.12 +#import "ASIS3BucketObject.h"
18.13 +
18.14 +
18.15 +static NSDateFormatter *dateFormatter = nil;
18.16 +
18.17 +// Private stuff
18.18 +@interface ASIS3ListRequest ()
18.19 + @property (retain, nonatomic) NSString *currentContent;
18.20 + @property (retain, nonatomic) NSString *currentElement;
18.21 + @property (retain, nonatomic) ASIS3BucketObject *currentObject;
18.22 + @property (retain, nonatomic) NSMutableArray *objects;
18.23 +@end
18.24 +
18.25 +@implementation ASIS3ListRequest
18.26 +
18.27 ++ (void)initialize
18.28 +{
18.29 + dateFormatter = [[NSDateFormatter alloc] init];
18.30 + [dateFormatter setLocale:[[[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"] autorelease]];
18.31 + [dateFormatter setTimeZone:[NSTimeZone timeZoneWithAbbreviation:@"UTC"]];
18.32 + [dateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss'.000Z'"];
18.33 +}
18.34 +
18.35 ++ (id)listRequestWithBucket:(NSString *)bucket
18.36 +{
18.37 + ASIS3ListRequest *request = [[[self alloc] initWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"http://%@.s3.amazonaws.com",bucket]]] autorelease];
18.38 + [request setBucket:bucket];
18.39 + return request;
18.40 +}
18.41 +
18.42 +- (void)dealloc
18.43 +{
18.44 + [currentObject release];
18.45 + [currentElement release];
18.46 + [currentContent release];
18.47 + [objects release];
18.48 + [prefix release];
18.49 + [marker release];
18.50 + [delimiter release];
18.51 + [super dealloc];
18.52 +}
18.53 +
18.54 +- (void)createQueryString
18.55 +{
18.56 + NSMutableArray *queryParts = [[[NSMutableArray alloc] init] autorelease];
18.57 + if ([self prefix]) {
18.58 + [queryParts addObject:[NSString stringWithFormat:@"prefix=%@",[[self prefix] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]];
18.59 + }
18.60 + if ([self marker]) {
18.61 + [queryParts addObject:[NSString stringWithFormat:@"marker=%@",[[self marker] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]];
18.62 + }
18.63 + if ([self delimiter]) {
18.64 + [queryParts addObject:[NSString stringWithFormat:@"delimiter=%@",[[self delimiter] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]];
18.65 + }
18.66 + if ([self maxResultCount] > 0) {
18.67 + [queryParts addObject:[NSString stringWithFormat:@"delimiter=%hi",[self maxResultCount]]];
18.68 + }
18.69 + if ([queryParts count]) {
18.70 + [self setURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@?%@",[[self url] absoluteString],[queryParts componentsJoinedByString:@"&"]]]];
18.71 + }
18.72 +}
18.73 +
18.74 +- (void)main
18.75 +{
18.76 + [self createQueryString];
18.77 + [super main];
18.78 +}
18.79 +
18.80 +- (NSArray *)bucketObjects
18.81 +{
18.82 + if ([self objects]) {
18.83 + return [self objects];
18.84 + }
18.85 + [self setObjects:[[[NSMutableArray alloc] init] autorelease]];
18.86 + NSXMLParser *parser = [[[NSXMLParser alloc] initWithData:[self responseData]] autorelease];
18.87 + [parser setDelegate:self];
18.88 + [parser setShouldProcessNamespaces:NO];
18.89 + [parser setShouldReportNamespacePrefixes:NO];
18.90 + [parser setShouldResolveExternalEntities:NO];
18.91 + [parser parse];
18.92 + return [self objects];
18.93 +}
18.94 +
18.95 +- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict
18.96 +{
18.97 + [self setCurrentElement:elementName];
18.98 +
18.99 + if ([elementName isEqualToString:@"Contents"]) {
18.100 + [self setCurrentObject:[ASIS3BucketObject objectWithBucket:[self bucket]]];
18.101 + }
18.102 + [self setCurrentContent:@""];
18.103 +}
18.104 +
18.105 +- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
18.106 +{
18.107 + if ([elementName isEqualToString:@"Contents"]) {
18.108 + [objects addObject:currentObject];
18.109 + [self setCurrentObject:nil];
18.110 + } else if ([elementName isEqualToString:@"Key"]) {
18.111 + [[self currentObject] setKey:[self currentContent]];
18.112 + } else if ([elementName isEqualToString:@"LastModified"]) {
18.113 + [[self currentObject] setLastModified:[dateFormatter dateFromString:[self currentContent]]];
18.114 + } else if ([elementName isEqualToString:@"ETag"]) {
18.115 + [[self currentObject] setETag:[self currentContent]];
18.116 + } else if ([elementName isEqualToString:@"Size"]) {
18.117 + [[self currentObject] setSize:(unsigned long long)[[self currentContent] longLongValue]];
18.118 + } else if ([elementName isEqualToString:@"ID"]) {
18.119 + [[self currentObject] setOwnerID:[self currentContent]];
18.120 + } else if ([elementName isEqualToString:@"DisplayName"]) {
18.121 + [[self currentObject] setOwnerName:[self currentContent]];
18.122 + }
18.123 +}
18.124 +
18.125 +- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
18.126 +{
18.127 + [self setCurrentContent:[[self currentContent] stringByAppendingString:string]];
18.128 +}
18.129 +
18.130 +
18.131 +@synthesize currentContent;
18.132 +@synthesize currentElement;
18.133 +@synthesize currentObject;
18.134 +@synthesize objects;
18.135 +@synthesize prefix;
18.136 +@synthesize marker;
18.137 +@synthesize maxResultCount;
18.138 +@synthesize delimiter;
18.139 +@end
19.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
19.2 +++ b/ASI-HTTP/ASIS3Request.h Thu Dec 24 00:14:02 2009 +0000
19.3 @@ -0,0 +1,107 @@
19.4 +//
19.5 +// ASIS3Request.h
19.6 +//
19.7 +// Created by Ben Copsey on 30/06/2009.
19.8 +// Copyright 2009 All-Seeing Interactive. All rights reserved.
19.9 +//
19.10 +// A (basic) class for accessing data stored on Amazon's Simple Storage Service (http://aws.amazon.com/s3/) using the REST API
19.11 +
19.12 +#import <Foundation/Foundation.h>
19.13 +#import "ASIHTTPRequest.h"
19.14 +
19.15 +// See http://docs.amazonwebservices.com/AmazonS3/2006-03-01/index.html?RESTAccessPolicy.html for what these mean
19.16 +extern NSString *const ASIS3AccessPolicyPrivate; // This is the default in S3 when no access policy header is provided
19.17 +extern NSString *const ASIS3AccessPolicyPublicRead;
19.18 +extern NSString *const ASIS3AccessPolicyPublicReadWrote;
19.19 +extern NSString *const ASIS3AccessPolicyAuthenticatedRead;
19.20 +
19.21 +typedef enum _ASIS3ErrorType {
19.22 + ASIS3ResponseParsingFailedType = 1,
19.23 + ASIS3ResponseErrorType = 2
19.24 +
19.25 +} ASIS3ErrorType;
19.26 +
19.27 +// Prevent warning about missing NSXMLParserDelegate on Leopard and iPhone
19.28 +#if !TARGET_OS_IPHONE && MAC_OS_X_VERSION_10_5 < MAC_OS_X_VERSION_MAX_ALLOWED
19.29 +@interface ASIS3Request : ASIHTTPRequest <NSXMLParserDelegate> {
19.30 +#else
19.31 +@interface ASIS3Request : ASIHTTPRequest {
19.32 +
19.33 +#endif
19.34 + // Your S3 access key. Set it on the request, or set it globally using [ASIS3Request setSharedAccessKey:]
19.35 + NSString *accessKey;
19.36 +
19.37 + // Your S3 secret access key. Set it on the request, or set it globally using [ASIS3Request setSharedSecretAccessKey:]
19.38 + NSString *secretAccessKey;
19.39 +
19.40 + // Name of the bucket to talk to
19.41 + NSString *bucket;
19.42 +
19.43 + // Path to the resource you want to access on S3. Leave empty for the bucket root
19.44 + NSString *path;
19.45 +
19.46 + // The string that will be used in the HTTP date header. Generally you'll want to ignore this and let the class add the current date for you, but the accessor is used by the tests
19.47 + NSString *dateString;
19.48 +
19.49 + // The mime type of the content for PUT requests
19.50 + // Set this if having the correct mime type returned to you when you GET the data is important (eg it will be served by a web-server)
19.51 + // Will be set to 'application/octet-stream' otherwise in iPhone apps, or autodetected on Mac OS X
19.52 + NSString *mimeType;
19.53 +
19.54 + // The access policy to use when PUTting a file (see the string constants at the top of this header)
19.55 + NSString *accessPolicy;
19.56 +
19.57 + // The bucket + path of the object to be copied (used with COPYRequestFromBucket:path:toBucket:path:)
19.58 + NSString *sourceBucket;
19.59 + NSString *sourcePath;
19.60 +
19.61 + // Internally used while parsing errors
19.62 + NSString *currentErrorString;
19.63 +
19.64 +}
19.65 +
19.66 +#pragma mark Constructors
19.67 +
19.68 +// Create a request, building an appropriate url
19.69 ++ (id)requestWithBucket:(NSString *)bucket path:(NSString *)path;
19.70 +
19.71 +// Create a PUT request using the file at filePath as the body
19.72 ++ (id)PUTRequestForFile:(NSString *)filePath withBucket:(NSString *)bucket path:(NSString *)path;
19.73 +
19.74 +// Create a DELETE request for the object at path
19.75 ++ (id)DELETERequestWithBucket:(NSString *)bucket path:(NSString *)path;
19.76 +
19.77 +// Create a PUT request to copy an object from one location to another
19.78 +// Clang will complain because it thinks this method should return an object with +1 retain :(
19.79 ++ (id)COPYRequestFromBucket:(NSString *)sourceBucket path:(NSString *)sourcePath toBucket:(NSString *)bucket path:(NSString *)path;
19.80 +
19.81 +// Creates a HEAD request for the object at path
19.82 ++ (id)HEADRequestWithBucket:(NSString *)bucket path:(NSString *)path;
19.83 +
19.84 +
19.85 +// Generates the request headers S3 needs
19.86 +// Automatically called before the request begins in startRequest
19.87 +- (void)generateS3Headers;
19.88 +
19.89 +// Uses the supplied date to create a Date header string
19.90 +- (void)setDate:(NSDate *)date;
19.91 +
19.92 +#pragma mark Shared access keys
19.93 +
19.94 +// Get and set the global access key, this will be used for all requests the access key hasn't been set for
19.95 ++ (NSString *)sharedAccessKey;
19.96 ++ (void)setSharedAccessKey:(NSString *)newAccessKey;
19.97 ++ (NSString *)sharedSecretAccessKey;
19.98 ++ (void)setSharedSecretAccessKey:(NSString *)newAccessKey;
19.99 +
19.100 +
19.101 +@property (retain) NSString *bucket;
19.102 +@property (retain) NSString *path;
19.103 +@property (retain) NSString *dateString;
19.104 +@property (retain) NSString *mimeType;
19.105 +@property (retain) NSString *accessKey;
19.106 +@property (retain) NSString *secretAccessKey;
19.107 +@property (retain) NSString *accessPolicy;
19.108 +@property (retain) NSString *sourceBucket;
19.109 +@property (retain) NSString *sourcePath;
19.110 +@end
20.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
20.2 +++ b/ASI-HTTP/ASIS3Request.m Thu Dec 24 00:14:02 2009 +0000
20.3 @@ -0,0 +1,291 @@
20.4 +//
20.5 +// ASIS3Request.m
20.6 +//
20.7 +// Created by Ben Copsey on 30/06/2009.
20.8 +// Copyright 2009 All-Seeing Interactive. All rights reserved.
20.9 +//
20.10 +
20.11 +#import "ASIS3Request.h"
20.12 +#import <CommonCrypto/CommonHMAC.h>
20.13 +
20.14 +NSString* const ASIS3AccessPolicyPrivate = @"private";
20.15 +NSString* const ASIS3AccessPolicyPublicRead = @"public-read";
20.16 +NSString* const ASIS3AccessPolicyPublicReadWrote = @"public-read-write";
20.17 +NSString* const ASIS3AccessPolicyAuthenticatedRead = @"authenticated-read";
20.18 +
20.19 +static NSString *sharedAccessKey = nil;
20.20 +static NSString *sharedSecretAccessKey = nil;
20.21 +
20.22 +// Private stuff
20.23 +@interface ASIS3Request ()
20.24 + - (void)parseError;
20.25 + + (NSData *)HMACSHA1withKey:(NSString *)key forString:(NSString *)string;
20.26 + + (NSString *)base64forData:(NSData *)theData;
20.27 + @property (retain, nonatomic) NSString *currentErrorString;
20.28 +@end
20.29 +
20.30 +@implementation ASIS3Request
20.31 +
20.32 +#pragma mark Constructors
20.33 +
20.34 ++ (id)requestWithBucket:(NSString *)bucket path:(NSString *)path
20.35 +{
20.36 + ASIS3Request *request = [[[self alloc] initWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"http://%@.s3.amazonaws.com%@",bucket,path]]] autorelease];
20.37 + [request setBucket:bucket];
20.38 + [request setPath:path];
20.39 + return request;
20.40 +}
20.41 +
20.42 ++ (id)PUTRequestForFile:(NSString *)filePath withBucket:(NSString *)bucket path:(NSString *)path
20.43 +{
20.44 + ASIS3Request *request = [self requestWithBucket:bucket path:path];
20.45 + [request setPostBodyFilePath:filePath];
20.46 + [request setShouldStreamPostDataFromDisk:YES];
20.47 + [request setRequestMethod:@"PUT"];
20.48 + [request setMimeType:[ASIHTTPRequest mimeTypeForFileAtPath:filePath]];
20.49 + return request;
20.50 +}
20.51 +
20.52 ++ (id)DELETERequestWithBucket:(NSString *)bucket path:(NSString *)path
20.53 +{
20.54 + ASIS3Request *request = [self requestWithBucket:bucket path:path];
20.55 + [request setRequestMethod:@"DELETE"];
20.56 + return request;
20.57 +}
20.58 +
20.59 ++ (id)COPYRequestFromBucket:(NSString *)sourceBucket path:(NSString *)sourcePath toBucket:(NSString *)bucket path:(NSString *)path
20.60 +{
20.61 + ASIS3Request *request = [self requestWithBucket:bucket path:path];
20.62 + [request setRequestMethod:@"PUT"];
20.63 + [request setSourceBucket:sourceBucket];
20.64 + [request setSourcePath:sourcePath];
20.65 + return request;
20.66 +}
20.67 +
20.68 ++ (id)HEADRequestWithBucket:(NSString *)bucket path:(NSString *)path
20.69 +{
20.70 + ASIS3Request *request = [self requestWithBucket:bucket path:path];
20.71 + [request setRequestMethod:@"HEAD"];
20.72 + return request;
20.73 +}
20.74 +
20.75 +- (void)dealloc
20.76 +{
20.77 + [bucket release];
20.78 + [path release];
20.79 + [dateString release];
20.80 + [mimeType release];
20.81 + [accessKey release];
20.82 + [secretAccessKey release];
20.83 + [sourcePath release];
20.84 + [sourceBucket release];
20.85 + [super dealloc];
20.86 +}
20.87 +
20.88 +
20.89 +- (void)setDate:(NSDate *)date
20.90 +{
20.91 + NSDateFormatter *dateFormatter = [[[NSDateFormatter alloc] init] autorelease];
20.92 + // Prevent problems with dates generated by other locales (tip from: http://rel.me/t/date/)
20.93 + [dateFormatter setLocale:[[[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"] autorelease]];
20.94 + [dateFormatter setDateFormat:@"EEE, d MMM yyyy HH:mm:ss Z"];
20.95 + [self setDateString:[dateFormatter stringFromDate:date]];
20.96 +}
20.97 +
20.98 +
20.99 +- (void)generateS3Headers
20.100 +{
20.101 + // If an access key / secret access keyu haven't been set for this request, let's use the shared keys
20.102 + if (![self accessKey]) {
20.103 + [self setAccessKey:[ASIS3Request sharedAccessKey]];
20.104 + }
20.105 + if (![self secretAccessKey]) {
20.106 + [self setSecretAccessKey:[ASIS3Request sharedSecretAccessKey]];
20.107 + }
20.108 + // If a date string hasn't been set, we'll create one from the current time
20.109 + if (![self dateString]) {
20.110 + [self setDate:[NSDate date]];
20.111 + }
20.112 + [self addRequestHeader:@"Date" value:[self dateString]];
20.113 +
20.114 + // Ensure our formatted string doesn't use '(null)' for the empty path
20.115 + if (![self path]) {
20.116 + [self setPath:@"/"];
20.117 + }
20.118 +
20.119 + NSString *canonicalizedResource = [NSString stringWithFormat:@"/%@%@",[self bucket],[self path]];
20.120 +
20.121 + // Add a header for the access policy if one was set, otherwise we won't add one (and S3 will default to private)
20.122 + NSMutableDictionary *amzHeaders = [[[NSMutableDictionary alloc] init] autorelease];
20.123 + NSString *canonicalizedAmzHeaders = @"";
20.124 + if ([self accessPolicy]) {
20.125 + [amzHeaders setObject:[self accessPolicy] forKey:@"x-amz-acl"];
20.126 + }
20.127 + if ([self sourcePath]) {
20.128 + [amzHeaders setObject:[[self sourceBucket] stringByAppendingString:[self sourcePath]] forKey:@"x-amz-copy-source"];
20.129 + }
20.130 + for (NSString *key in [amzHeaders keyEnumerator]) {
20.131 + canonicalizedAmzHeaders = [NSString stringWithFormat:@"%@%@:%@\n",canonicalizedAmzHeaders,[key lowercaseString],[amzHeaders objectForKey:key]];
20.132 + [self addRequestHeader:key value:[amzHeaders objectForKey:key]];
20.133 + }
20.134 +
20.135 +
20.136 + // Jump through hoops while eating hot food
20.137 + NSString *stringToSign;
20.138 + if ([[self requestMethod] isEqualToString:@"PUT"] && ![self sourcePath]) {
20.139 + [self addRequestHeader:@"Content-Type" value:[self mimeType]];
20.140 + stringToSign = [NSString stringWithFormat:@"PUT\n\n%@\n%@\n%@%@",[self mimeType],dateString,canonicalizedAmzHeaders,canonicalizedResource];
20.141 + } else {
20.142 + stringToSign = [NSString stringWithFormat:@"%@\n\n\n%@\n%@%@",[self requestMethod],dateString,canonicalizedAmzHeaders,canonicalizedResource];
20.143 + }
20.144 + NSString *signature = [ASIS3Request base64forData:[ASIS3Request HMACSHA1withKey:[self secretAccessKey] forString:stringToSign]];
20.145 + NSString *authorizationString = [NSString stringWithFormat:@"AWS %@:%@",[self accessKey],signature];
20.146 + [self addRequestHeader:@"Authorization" value:authorizationString];
20.147 +}
20.148 +
20.149 +- (void)main
20.150 +{
20.151 + [self generateS3Headers];
20.152 + [super main];
20.153 +}
20.154 +
20.155 +- (void)requestFinished
20.156 +{
20.157 + // COPY requests return a 200 whether they succeed or fail, so we need to look at the XML to see if we were successful.
20.158 + if ([self responseStatusCode] == 200 && [self sourcePath] && [self sourceBucket]) {
20.159 + [self parseError];
20.160 + return;
20.161 + }
20.162 + if ([self responseStatusCode] < 207) {
20.163 + [super requestFinished];
20.164 + return;
20.165 + }
20.166 + [self parseError];
20.167 +}
20.168 +
20.169 +#pragma mark Error XML parsing
20.170 +
20.171 +- (void)parseError
20.172 +{
20.173 + NSXMLParser *parser = [[[NSXMLParser alloc] initWithData:[self responseData]] autorelease];
20.174 + [parser setDelegate:self];
20.175 + [parser setShouldProcessNamespaces:NO];
20.176 + [parser setShouldReportNamespacePrefixes:NO];
20.177 + [parser setShouldResolveExternalEntities:NO];
20.178 + [parser parse];
20.179 +
20.180 +}
20.181 +
20.182 +- (void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError
20.183 +{
20.184 + [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIS3ResponseParsingFailedType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Parsing the resposnse failed",NSLocalizedDescriptionKey,parseError,NSUnderlyingErrorKey,nil]]];
20.185 +}
20.186 +
20.187 +- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict
20.188 +{
20.189 + [self setCurrentErrorString:@""];
20.190 +}
20.191 +
20.192 +- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
20.193 +{
20.194 + if ([elementName isEqualToString:@"Message"]) {
20.195 + [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIS3ResponseErrorType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[self currentErrorString],NSLocalizedDescriptionKey,nil]]];
20.196 + }
20.197 +}
20.198 +
20.199 +- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
20.200 +{
20.201 + [self setCurrentErrorString:[[self currentErrorString] stringByAppendingString:string]];
20.202 +}
20.203 +
20.204 +
20.205 +#pragma mark Shared access keys
20.206 +
20.207 ++ (NSString *)sharedAccessKey
20.208 +{
20.209 + return sharedAccessKey;
20.210 +}
20.211 +
20.212 ++ (void)setSharedAccessKey:(NSString *)newAccessKey
20.213 +{
20.214 + [sharedAccessKey release];
20.215 + sharedAccessKey = [newAccessKey retain];
20.216 +}
20.217 +
20.218 ++ (NSString *)sharedSecretAccessKey
20.219 +{
20.220 + return sharedSecretAccessKey;
20.221 +}
20.222 +
20.223 ++ (void)setSharedSecretAccessKey:(NSString *)newAccessKey
20.224 +{
20.225 + [sharedSecretAccessKey release];
20.226 + sharedSecretAccessKey = [newAccessKey retain];
20.227 +}
20.228 +
20.229 +
20.230 +
20.231 +#pragma mark S3 Authentication helpers
20.232 +
20.233 +// From: http://stackoverflow.com/questions/476455/is-there-a-library-for-iphone-to-work-with-hmac-sha-1-encoding
20.234 +
20.235 ++ (NSData *)HMACSHA1withKey:(NSString *)key forString:(NSString *)string
20.236 +{
20.237 + NSData *clearTextData = [string dataUsingEncoding:NSUTF8StringEncoding];
20.238 + NSData *keyData = [key dataUsingEncoding:NSUTF8StringEncoding];
20.239 +
20.240 + uint8_t digest[CC_SHA1_DIGEST_LENGTH] = {0};
20.241 +
20.242 + CCHmacContext hmacContext;
20.243 + CCHmacInit(&hmacContext, kCCHmacAlgSHA1, keyData.bytes, keyData.length);
20.244 + CCHmacUpdate(&hmacContext, clearTextData.bytes, clearTextData.length);
20.245 + CCHmacFinal(&hmacContext, digest);
20.246 +
20.247 + return [NSData dataWithBytes:digest length:CC_SHA1_DIGEST_LENGTH];
20.248 +}
20.249 +
20.250 +
20.251 +// From: http://www.cocoadev.com/index.pl?BaseSixtyFour
20.252 +
20.253 ++ (NSString*)base64forData:(NSData*)theData {
20.254 +
20.255 + const uint8_t* input = (const uint8_t*)[theData bytes];
20.256 + NSInteger length = [theData length];
20.257 +
20.258 + static char table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
20.259 +
20.260 + NSMutableData* data = [NSMutableData dataWithLength:((length + 2) / 3) * 4];
20.261 + uint8_t* output = (uint8_t*)data.mutableBytes;
20.262 +
20.263 + for (NSInteger i = 0; i < length; i += 3) {
20.264 + NSInteger value = 0;
20.265 + for (NSInteger j = i; j < (i + 3); j++) {
20.266 + value <<= 8;
20.267 +
20.268 + if (j < length) {
20.269 + value |= (0xFF & input[j]);
20.270 + }
20.271 + }
20.272 +
20.273 + NSInteger index = (i / 3) * 4;
20.274 + output[index + 0] = table[(value >> 18) & 0x3F];
20.275 + output[index + 1] = table[(value >> 12) & 0x3F];
20.276 + output[index + 2] = (i + 1) < length ? table[(value >> 6) & 0x3F] : '=';
20.277 + output[index + 3] = (i + 2) < length ? table[(value >> 0) & 0x3F] : '=';
20.278 + }
20.279 +
20.280 + return [[[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding] autorelease];
20.281 +}
20.282 +
20.283 +
20.284 +@synthesize bucket;
20.285 +@synthesize path;
20.286 +@synthesize dateString;
20.287 +@synthesize mimeType;
20.288 +@synthesize accessKey;
20.289 +@synthesize secretAccessKey;
20.290 +@synthesize accessPolicy;
20.291 +@synthesize currentErrorString;
20.292 +@synthesize sourceBucket;
20.293 +@synthesize sourcePath;
20.294 +@end
21.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
21.2 +++ b/ASI-HTTP/Reachability.h Thu Dec 24 00:14:02 2009 +0000
21.3 @@ -0,0 +1,122 @@
21.4 +/*
21.5 +
21.6 +File: Reachability.h
21.7 +Abstract: SystemConfiguration framework wrapper.
21.8 +
21.9 +Version: 1.5
21.10 +
21.11 +Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Inc.
21.12 +("Apple") in consideration of your agreement to the following terms, and your
21.13 +use, installation, modification or redistribution of this Apple software
21.14 +constitutes acceptance of these terms. If you do not agree with these terms,
21.15 +please do not use, install, modify or redistribute this Apple software.
21.16 +
21.17 +In consideration of your agreement to abide by the following terms, and subject
21.18 +to these terms, Apple grants you a personal, non-exclusive license, under
21.19 +Apple's copyrights in this original Apple software (the "Apple Software"), to
21.20 +use, reproduce, modify and redistribute the Apple Software, with or without
21.21 +modifications, in source and/or binary forms; provided that if you redistribute
21.22 +the Apple Software in its entirety and without modifications, you must retain
21.23 +this notice and the following text and disclaimers in all such redistributions
21.24 +of the Apple Software.
21.25 +Neither the name, trademarks, service marks or logos of Apple Inc. may be used
21.26 +to endorse or promote products derived from the Apple Software without specific
21.27 +prior written permission from Apple. Except as expressly stated in this notice,
21.28 +no other rights or licenses, express or implied, are granted by Apple herein,
21.29 +including but not limited to any patent rights that may be infringed by your
21.30 +derivative works or by other works in which the Apple Software may be
21.31 +incorporated.
21.32 +
21.33 +The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO
21.34 +WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED
21.35 +WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR
21.36 +PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN
21.37 +COMBINATION WITH YOUR PRODUCTS.
21.38 +
21.39 +IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR
21.40 +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
21.41 +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21.42 +ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR
21.43 +DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF
21.44 +CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF
21.45 +APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
21.46 +
21.47 +Copyright (C) 2008 Apple Inc. All Rights Reserved.
21.48 +
21.49 +*/
21.50 +
21.51 +#import <UIKit/UIKit.h>
21.52 +#import <SystemConfiguration/SystemConfiguration.h>
21.53 +
21.54 +@class Reachability;
21.55 +
21.56 +@interface Reachability : NSObject {
21.57 +
21.58 +@private
21.59 + BOOL _networkStatusNotificationsEnabled;
21.60 +
21.61 + NSString *_hostName;
21.62 + NSString *_address;
21.63 +
21.64 + NSMutableDictionary *_reachabilityQueries;
21.65 +}
21.66 +
21.67 +/*
21.68 + An enumeration that defines the return values of the network state
21.69 + of the device.
21.70 + */
21.71 +typedef enum {
21.72 + NotReachable = 0,
21.73 + ReachableViaCarrierDataNetwork,
21.74 + ReachableViaWiFiNetwork
21.75 +} NetworkStatus;
21.76 +
21.77 +
21.78 +// Set to YES to register for changes in network status. Otherwise reachability queries
21.79 +// will be handled synchronously.
21.80 +@property BOOL networkStatusNotificationsEnabled;
21.81 +// The remote host whose reachability will be queried.
21.82 +// Either this or 'addressName' must be set.
21.83 +@property (nonatomic, retain) NSString *hostName;
21.84 +// The IP address of the remote host whose reachability will be queried.
21.85 +// Either this or 'hostName' must be set.
21.86 +@property (nonatomic, retain) NSString *address;
21.87 +// A cache of ReachabilityQuery objects, which encapsulate a SCNetworkReachabilityRef, a host or address, and a run loop. The keys are host names or addresses.
21.88 +@property (nonatomic, assign) NSMutableDictionary *reachabilityQueries;
21.89 +
21.90 +// This class is intended to be used as a singleton.
21.91 ++ (Reachability *)sharedReachability;
21.92 +
21.93 +// Is self.hostName is not nil, determines its reachability.
21.94 +// If self.hostName is nil and self.address is not nil, determines the reachability of self.address.
21.95 +- (NetworkStatus)remoteHostStatus;
21.96 +// Is the device able to communicate with Internet hosts? If so, through which network interface?
21.97 +- (NetworkStatus)internetConnectionStatus;
21.98 +// Is the device able to communicate with hosts on the local WiFi network? (Typically these are Bonjour hosts).
21.99 +- (NetworkStatus)localWiFiConnectionStatus;
21.100 +
21.101 +/*
21.102 + When reachability change notifications are posted, the callback method 'ReachabilityCallback' is called
21.103 + and posts a notification that the client application can observe to learn about changes.
21.104 + */
21.105 +static void ReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void *info);
21.106 +
21.107 +@end
21.108 +
21.109 +@interface ReachabilityQuery : NSObject
21.110 +{
21.111 +@private
21.112 + SCNetworkReachabilityRef _reachabilityRef;
21.113 + CFMutableArrayRef _runLoops;
21.114 + NSString *_hostNameOrAddress;
21.115 +}
21.116 +// Keep around each network reachability query object so that we can
21.117 +// register for updates from those objects.
21.118 +@property (nonatomic) SCNetworkReachabilityRef reachabilityRef;
21.119 +@property (nonatomic, retain) NSString *hostNameOrAddress;
21.120 +@property (nonatomic) CFMutableArrayRef runLoops;
21.121 +
21.122 +- (void)scheduleOnRunLoop:(NSRunLoop *)inRunLoop;
21.123 +
21.124 +@end
21.125 +
22.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
22.2 +++ b/ASI-HTTP/Reachability.m Thu Dec 24 00:14:02 2009 +0000
22.3 @@ -0,0 +1,585 @@
22.4 +/*
22.5 +
22.6 +File: Reachability.m
22.7 +Abstract: SystemConfiguration framework wrapper.
22.8 +
22.9 +Version: 1.5
22.10 +
22.11 +Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Inc.
22.12 +("Apple") in consideration of your agreement to the following terms, and your
22.13 +use, installation, modification or redistribution of this Apple software
22.14 +constitutes acceptance of these terms. If you do not agree with these terms,
22.15 +please do not use, install, modify or redistribute this Apple software.
22.16 +
22.17 +In consideration of your agreement to abide by the following terms, and subject
22.18 +to these terms, Apple grants you a personal, non-exclusive license, under
22.19 +Apple's copyrights in this original Apple software (the "Apple Software"), to
22.20 +use, reproduce, modify and redistribute the Apple Software, with or without
22.21 +modifications, in source and/or binary forms; provided that if you redistribute
22.22 +the Apple Software in its entirety and without modifications, you must retain
22.23 +this notice and the following text and disclaimers in all such redistributions
22.24 +of the Apple Software.
22.25 +Neither the name, trademarks, service marks or logos of Apple Inc. may be used
22.26 +to endorse or promote products derived from the Apple Software without specific
22.27 +prior written permission from Apple. Except as expressly stated in this notice,
22.28 +no other rights or licenses, express or implied, are granted by Apple herein,
22.29 +including but not limited to any patent rights that may be infringed by your
22.30 +derivative works or by other works in which the Apple Software may be
22.31 +incorporated.
22.32 +
22.33 +The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO
22.34 +WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED
22.35 +WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22.36 +PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN
22.37 +COMBINATION WITH YOUR PRODUCTS.
22.38 +
22.39 +IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR
22.40 +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
22.41 +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22.42 +ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR
22.43 +DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF
22.44 +CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF
22.45 +APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
22.46 +
22.47 +Copyright (C) 2008 Apple Inc. All Rights Reserved.
22.48 +
22.49 +*/
22.50 +
22.51 +#import <sys/socket.h>
22.52 +#import <netinet/in.h>
22.53 +#import <netinet6/in6.h>
22.54 +#import <arpa/inet.h>
22.55 +#import <ifaddrs.h>
22.56 +#include <netdb.h>
22.57 +
22.58 +#import "Reachability.h"
22.59 +#import <SystemConfiguration/SCNetworkReachability.h>
22.60 +
22.61 +static NSString *kLinkLocalAddressKey = @"169.254.0.0";
22.62 +static NSString *kDefaultRouteKey = @"0.0.0.0";
22.63 +
22.64 +static Reachability *_sharedReachability;
22.65 +
22.66 +// A class extension that declares internal methods for this class.
22.67 +@interface Reachability()
22.68 +- (BOOL)isAdHocWiFiNetworkAvailableFlags:(SCNetworkReachabilityFlags *)outFlags;
22.69 +- (BOOL)isNetworkAvailableFlags:(SCNetworkReachabilityFlags *)outFlags;
22.70 +- (BOOL)isReachableWithoutRequiringConnection:(SCNetworkReachabilityFlags)flags;
22.71 +- (SCNetworkReachabilityRef)reachabilityRefForHostName:(NSString *)hostName;
22.72 +- (SCNetworkReachabilityRef)reachabilityRefForAddress:(NSString *)address;
22.73 +- (BOOL)addressFromString:(NSString *)IPAddress address:(struct sockaddr_in *)outAddress;
22.74 +- (void)stopListeningForReachabilityChanges;
22.75 +@end
22.76 +
22.77 +@implementation Reachability
22.78 +
22.79 +@synthesize networkStatusNotificationsEnabled = _networkStatusNotificationsEnabled;
22.80 +@synthesize hostName = _hostName;
22.81 +@synthesize address = _address;
22.82 +@synthesize reachabilityQueries = _reachabilityQueries;
22.83 +
22.84 ++ (Reachability *)sharedReachability
22.85 +{
22.86 + if (!_sharedReachability) {
22.87 + _sharedReachability = [[Reachability alloc] init];
22.88 + // Clients of Reachability will typically call [[Reachability sharedReachability] setHostName:]
22.89 + // before calling one of the status methods.
22.90 + _sharedReachability.hostName = nil;
22.91 + _sharedReachability.address = nil;
22.92 + _sharedReachability.networkStatusNotificationsEnabled = NO;
22.93 + _sharedReachability.reachabilityQueries = [[NSMutableDictionary alloc] init];
22.94 + }
22.95 + return _sharedReachability;
22.96 +}
22.97 +
22.98 +- (void) dealloc
22.99 +{
22.100 + [self stopListeningForReachabilityChanges];
22.101 +
22.102 + [_sharedReachability.reachabilityQueries release];
22.103 + [_sharedReachability release];
22.104 + [super dealloc];
22.105 +}
22.106 +
22.107 +- (BOOL)isReachableWithoutRequiringConnection:(SCNetworkReachabilityFlags)flags
22.108 +{
22.109 + // kSCNetworkReachabilityFlagsReachable indicates that the specified nodename or address can
22.110 + // be reached using the current network configuration.
22.111 + BOOL isReachable = flags & kSCNetworkReachabilityFlagsReachable;
22.112 +
22.113 + // This flag indicates that the specified nodename or address can
22.114 + // be reached using the current network configuration, but a
22.115 + // connection must first be established.
22.116 + //
22.117 + // If the flag is false, we don't have a connection. But because CFNetwork
22.118 + // automatically attempts to bring up a WWAN connection, if the WWAN reachability
22.119 + // flag is present, a connection is not required.
22.120 + BOOL noConnectionRequired = !(flags & kSCNetworkReachabilityFlagsConnectionRequired);
22.121 + if ((flags & kSCNetworkReachabilityFlagsIsWWAN)) {
22.122 + noConnectionRequired = YES;
22.123 + }
22.124 +
22.125 + return (isReachable && noConnectionRequired) ? YES : NO;
22.126 +}
22.127 +
22.128 +// Returns whether or not the current host name is reachable with the current network configuration.
22.129 +- (BOOL)isHostReachable:(NSString *)host
22.130 +{
22.131 + if (!host || ![host length]) {
22.132 + return NO;
22.133 + }
22.134 +
22.135 + SCNetworkReachabilityFlags flags;
22.136 + SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithName(NULL, [host UTF8String]);
22.137 + BOOL gotFlags = SCNetworkReachabilityGetFlags(reachability, &flags);
22.138 +
22.139 + CFRelease(reachability);
22.140 +
22.141 + if (!gotFlags) {
22.142 + return NO;
22.143 + }
22.144 +
22.145 + return [self isReachableWithoutRequiringConnection:flags];
22.146 +}
22.147 +
22.148 +// This returns YES if the address 169.254.0.0 is reachable without requiring a connection.
22.149 +- (BOOL)isAdHocWiFiNetworkAvailableFlags:(SCNetworkReachabilityFlags *)outFlags
22.150 +{
22.151 + // Look in the cache of reachability queries for one that matches this query.
22.152 + ReachabilityQuery *query = [self.reachabilityQueries objectForKey:kLinkLocalAddressKey];
22.153 + SCNetworkReachabilityRef adHocWiFiNetworkReachability = query.reachabilityRef;
22.154 +
22.155 + // If a cached reachability query was not found, create one.
22.156 + if (!adHocWiFiNetworkReachability) {
22.157 +
22.158 + // Build a sockaddr_in that we can pass to the address reachability query.
22.159 + struct sockaddr_in sin;
22.160 +
22.161 + bzero(&sin, sizeof(sin));
22.162 + sin.sin_len = sizeof(sin);
22.163 + sin.sin_family = AF_INET;
22.164 + // IN_LINKLOCALNETNUM is defined in <netinet/in.h> as 169.254.0.0
22.165 + sin.sin_addr.s_addr = htonl(IN_LINKLOCALNETNUM);
22.166 +
22.167 + adHocWiFiNetworkReachability = SCNetworkReachabilityCreateWithAddress(NULL, (struct sockaddr *)&sin);
22.168 +
22.169 + query = [[[ReachabilityQuery alloc] init] autorelease];
22.170 + query.hostNameOrAddress = kLinkLocalAddressKey;
22.171 + query.reachabilityRef = adHocWiFiNetworkReachability;
22.172 +
22.173 + // Add the reachability query to the cache.
22.174 + [self.reachabilityQueries setObject:query forKey:kLinkLocalAddressKey];
22.175 + }
22.176 +
22.177 + // If necessary, register for notifcations for the SCNetworkReachabilityRef on the current run loop.
22.178 + // If an existing SCNetworkReachabilityRef was found in the cache, we can reuse it and register
22.179 + // to receive notifications from it in the current run loop, which may be different than the run loop
22.180 + // that was previously used when registering the SCNetworkReachabilityRef for notifications.
22.181 + // -scheduleOnRunLoop: will schedule only if network status notifications are enabled in the Reachability instance.
22.182 + // By default, they are not enabled.
22.183 + [query scheduleOnRunLoop:[NSRunLoop currentRunLoop]];
22.184 +
22.185 + SCNetworkReachabilityFlags addressReachabilityFlags;
22.186 + BOOL gotFlags = SCNetworkReachabilityGetFlags(adHocWiFiNetworkReachability, &addressReachabilityFlags);
22.187 + if (!gotFlags) {
22.188 + // There was an error getting the reachability flags.
22.189 + return NO;
22.190 + }
22.191 +
22.192 + // Callers of this method might want to use the reachability flags, so if an 'out' parameter
22.193 + // was passed in, assign the reachability flags to it.
22.194 + if (outFlags) {
22.195 + *outFlags = addressReachabilityFlags;
22.196 + }
22.197 +
22.198 + return [self isReachableWithoutRequiringConnection:addressReachabilityFlags];
22.199 +}
22.200 +
22.201 +// ReachabilityCallback is registered as the callback for network state changes in startListeningForReachabilityChanges.
22.202 +static void ReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void *info)
22.203 +{
22.204 + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
22.205 +
22.206 + // Post a notification to notify the client that the network reachability changed.
22.207 + [[NSNotificationCenter defaultCenter] postNotificationName:@"kNetworkReachabilityChangedNotification" object:nil];
22.208 +
22.209 + [pool release];
22.210 +}
22.211 +
22.212 +// Perform a reachability query for the address 0.0.0.0. If that address is reachable without
22.213 +// requiring a connection, a network interface is available. We'll have to do more work to
22.214 +// determine which network interface is available.
22.215 +- (BOOL)isNetworkAvailableFlags:(SCNetworkReachabilityFlags *)outFlags
22.216 +{
22.217 + ReachabilityQuery *query = [self.reachabilityQueries objectForKey:kDefaultRouteKey];
22.218 + SCNetworkReachabilityRef defaultRouteReachability = query.reachabilityRef;
22.219 +
22.220 + // If a cached reachability query was not found, create one.
22.221 + if (!defaultRouteReachability) {
22.222 +
22.223 + struct sockaddr_in zeroAddress;
22.224 + bzero(&zeroAddress, sizeof(zeroAddress));
22.225 + zeroAddress.sin_len = sizeof(zeroAddress);
22.226 + zeroAddress.sin_family = AF_INET;
22.227 +
22.228 + defaultRouteReachability = SCNetworkReachabilityCreateWithAddress(NULL, (struct sockaddr *)&zeroAddress);
22.229 +
22.230 + ReachabilityQuery *query = [[[ReachabilityQuery alloc] init] autorelease];
22.231 + query.hostNameOrAddress = kDefaultRouteKey;
22.232 + query.reachabilityRef = defaultRouteReachability;
22.233 +
22.234 + [self.reachabilityQueries setObject:query forKey:kDefaultRouteKey];
22.235 + }
22.236 +
22.237 + // If necessary, register for notifcations for the SCNetworkReachabilityRef on the current run loop.
22.238 + // If an existing SCNetworkReachabilityRef was found in the cache, we can reuse it and register
22.239 + // to receive notifications from it in the current run loop, which may be different than the run loop
22.240 + // that was previously used when registering the SCNetworkReachabilityRef for notifications.
22.241 + // -scheduleOnRunLoop: will schedule only if network status notifications are enabled in the Reachability instance.
22.242 + // By default, they are not enabled.
22.243 + [query scheduleOnRunLoop:[NSRunLoop currentRunLoop]];
22.244 +
22.245 + SCNetworkReachabilityFlags flags;
22.246 + BOOL gotFlags = SCNetworkReachabilityGetFlags(defaultRouteReachability, &flags);
22.247 + if (!gotFlags) {
22.248 + return NO;
22.249 + }
22.250 +
22.251 + BOOL isReachable = [self isReachableWithoutRequiringConnection:flags];
22.252 +
22.253 + // Callers of this method might want to use the reachability flags, so if an 'out' parameter
22.254 + // was passed in, assign the reachability flags to it.
22.255 + if (outFlags) {
22.256 + *outFlags = flags;
22.257 + }
22.258 +
22.259 + return isReachable;
22.260 +}
22.261 +
22.262 +// Be a good citizen and unregister for network state changes when the application terminates.
22.263 +- (void)stopListeningForReachabilityChanges
22.264 +{
22.265 + // Walk through the cache that holds SCNetworkReachabilityRefs for reachability
22.266 + // queries to particular hosts or addresses.
22.267 + NSEnumerator *enumerator = [self.reachabilityQueries objectEnumerator];
22.268 + ReachabilityQuery *reachabilityQuery;
22.269 +
22.270 + while (reachabilityQuery = [enumerator nextObject]) {
22.271 +
22.272 + CFArrayRef runLoops = reachabilityQuery.runLoops;
22.273 + NSUInteger runLoopCounter, maxRunLoops = CFArrayGetCount(runLoops);
22.274 +
22.275 + for (runLoopCounter = 0; runLoopCounter < maxRunLoops; runLoopCounter++) {
22.276 + CFRunLoopRef nextRunLoop = (CFRunLoopRef)CFArrayGetValueAtIndex(runLoops, runLoopCounter);
22.277 +
22.278 + SCNetworkReachabilityUnscheduleFromRunLoop(reachabilityQuery.reachabilityRef, nextRunLoop, kCFRunLoopDefaultMode);
22.279 + }
22.280 +
22.281 + CFArrayRemoveAllValues(reachabilityQuery.runLoops);
22.282 + }
22.283 +}
22.284 +
22.285 +/*
22.286 + Create a SCNetworkReachabilityRef for hostName, which lets us determine if hostName
22.287 + is currently reachable, and lets us register to receive notifications when the
22.288 + reachability of hostName changes.
22.289 + */
22.290 +- (SCNetworkReachabilityRef)reachabilityRefForHostName:(NSString *)hostName
22.291 +{
22.292 + if (!hostName || ![hostName length]) {
22.293 + return NULL;
22.294 + }
22.295 +
22.296 + // Look in the cache for an existing SCNetworkReachabilityRef for hostName.
22.297 + ReachabilityQuery *cachedQuery = [self.reachabilityQueries objectForKey:hostName];
22.298 + SCNetworkReachabilityRef reachabilityRefForHostName = cachedQuery.reachabilityRef;
22.299 +
22.300 + if (reachabilityRefForHostName) {
22.301 + return reachabilityRefForHostName;
22.302 + }
22.303 +
22.304 + // Didn't find an existing SCNetworkReachabilityRef for hostName, so create one ...
22.305 + reachabilityRefForHostName = SCNetworkReachabilityCreateWithName(kCFAllocatorDefault, [hostName UTF8String]);
22.306 +
22.307 + NSAssert1(reachabilityRefForHostName != NULL, @"Failed to create SCNetworkReachabilityRef for host: %@", hostName);
22.308 +
22.309 + ReachabilityQuery *query = [[[ReachabilityQuery alloc] init] autorelease];
22.310 + query.hostNameOrAddress = hostName;
22.311 + query.reachabilityRef = reachabilityRefForHostName;
22.312 +
22.313 + // If necessary, register for notifcations for the SCNetworkReachabilityRef on the current run loop.
22.314 + // If an existing SCNetworkReachabilityRef was found in the cache, we can reuse it and register
22.315 + // to receive notifications from it in the current run loop, which may be different than the run loop
22.316 + // that was previously used when registering the SCNetworkReachabilityRef for notifications.
22.317 + // -scheduleOnRunLoop: will schedule only if network status notifications are enabled in the Reachability instance.
22.318 + // By default, they are not enabled.
22.319 + [query scheduleOnRunLoop:[NSRunLoop currentRunLoop]];
22.320 +
22.321 + // ... and add it to the cache.
22.322 + [self.reachabilityQueries setObject:query forKey:hostName];
22.323 + return reachabilityRefForHostName;
22.324 +}
22.325 +
22.326 +/*
22.327 + Create a SCNetworkReachabilityRef for the IP address in addressString, which lets us determine if
22.328 + the address is currently reachable, and lets us register to receive notifications when the
22.329 + reachability of the address changes.
22.330 + */
22.331 +- (SCNetworkReachabilityRef)reachabilityRefForAddress:(NSString *)addressString
22.332 +{
22.333 + if (!addressString || ![addressString length]) {
22.334 + return NULL;
22.335 + }
22.336 +
22.337 + struct sockaddr_in address;
22.338 +
22.339 + BOOL gotAddress = [self addressFromString:addressString address:&address];
22.340 + if (!gotAddress) {
22.341 + // The attempt to convert addressString to a sockaddr_in failed.
22.342 + NSAssert1(gotAddress != NO, @"Failed to convert an IP address string to a sockaddr_in: %@", addressString);
22.343 + return NULL;
22.344 + }
22.345 +
22.346 + // Look in the cache for an existing SCNetworkReachabilityRef for addressString.
22.347 + ReachabilityQuery *cachedQuery = [self.reachabilityQueries objectForKey:addressString];
22.348 + SCNetworkReachabilityRef reachabilityRefForAddress = cachedQuery.reachabilityRef;
22.349 +
22.350 + if (reachabilityRefForAddress) {
22.351 + return reachabilityRefForAddress;
22.352 + }
22.353 +
22.354 + // Didn't find an existing SCNetworkReachabilityRef for addressString, so create one.
22.355 + reachabilityRefForAddress = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (struct sockaddr *)&address);
22.356 +
22.357 + NSAssert1(reachabilityRefForAddress != NULL, @"Failed to create SCNetworkReachabilityRef for address: %@", addressString);
22.358 +
22.359 + ReachabilityQuery *query = [[[ReachabilityQuery alloc] init] autorelease];
22.360 + query.hostNameOrAddress = addressString;
22.361 + query.reachabilityRef = reachabilityRefForAddress;
22.362 +
22.363 + // If necessary, register for notifcations for the SCNetworkReachabilityRef on the current run loop.
22.364 + // If an existing SCNetworkReachabilityRef was found in the cache, we can reuse it and register
22.365 + // to receive notifications from it in the current run loop, which may be different than the run loop
22.366 + // that was previously used when registering the SCNetworkReachabilityRef for notifications.
22.367 + // -scheduleOnRunLoop: will schedule only if network status notifications are enabled in the Reachability instance.
22.368 + // By default, they are not enabled.
22.369 + [query scheduleOnRunLoop:[NSRunLoop currentRunLoop]];
22.370 +
22.371 + // ... and add it to the cache.
22.372 + [self.reachabilityQueries setObject:query forKey:addressString];
22.373 + return reachabilityRefForAddress;
22.374 +}
22.375 +
22.376 +- (NetworkStatus)remoteHostStatus
22.377 +{
22.378 + /*
22.379 + If the current host name or address is reachable, determine which network interface it is reachable through.
22.380 + If the host is reachable and the reachability flags include kSCNetworkReachabilityFlagsIsWWAN, it
22.381 + is reachable through the carrier data network. If the host is reachable and the reachability
22.382 + flags do not include kSCNetworkReachabilityFlagsIsWWAN, it is reachable through the WiFi network.
22.383 + */
22.384 +
22.385 + SCNetworkReachabilityRef reachabilityRef = nil;
22.386 + if (self.hostName) {
22.387 + reachabilityRef = [self reachabilityRefForHostName:self.hostName];
22.388 +
22.389 + } else if (self.address) {
22.390 + reachabilityRef = [self reachabilityRefForAddress:self.address];
22.391 +
22.392 + } else {
22.393 + NSAssert(self.hostName != nil && self.address != nil, @"No hostName or address specified. Cannot determine reachability.");
22.394 + return NotReachable;
22.395 + }
22.396 +
22.397 + if (!reachabilityRef) {
22.398 + return NotReachable;
22.399 + }
22.400 +
22.401 + SCNetworkReachabilityFlags reachabilityFlags;
22.402 + BOOL gotFlags = SCNetworkReachabilityGetFlags(reachabilityRef, &reachabilityFlags);
22.403 + if (!gotFlags) {
22.404 + return NotReachable;
22.405 + }
22.406 +
22.407 + BOOL reachable = [self isReachableWithoutRequiringConnection:reachabilityFlags];
22.408 +
22.409 + if (!reachable) {
22.410 + return NotReachable;
22.411 + }
22.412 + if (reachabilityFlags & ReachableViaCarrierDataNetwork) {
22.413 + return ReachableViaCarrierDataNetwork;
22.414 + }
22.415 +
22.416 + return ReachableViaWiFiNetwork;
22.417 +}
22.418 +
22.419 +- (NetworkStatus)internetConnectionStatus
22.420 +{
22.421 + /*
22.422 + To determine if the device has an Internet connection, query the address
22.423 + 0.0.0.0. If it's reachable without requiring a connection, first check
22.424 + for the kSCNetworkReachabilityFlagsIsDirect flag, which tell us if the connection
22.425 + is to an ad-hoc WiFi network. If it is not, the device can access the Internet.
22.426 + The next thing to determine is how the device can access the Internet, which
22.427 + can either be through the carrier data network (EDGE or other service) or through
22.428 + a WiFi connection.
22.429 +
22.430 + Note: Knowing that the device has an Internet connection is not the same as
22.431 + knowing if the device can reach a particular host. To know that, use
22.432 + -[Reachability remoteHostStatus].
22.433 + */
22.434 +
22.435 + SCNetworkReachabilityFlags defaultRouteFlags;
22.436 + BOOL defaultRouteIsAvailable = [self isNetworkAvailableFlags:&defaultRouteFlags];
22.437 + if (defaultRouteIsAvailable) {
22.438 +
22.439 + if (defaultRouteFlags & kSCNetworkReachabilityFlagsIsDirect) {
22.440 +
22.441 + // The connection is to an ad-hoc WiFi network, so Internet access is not available.
22.442 + return NotReachable;
22.443 + }
22.444 + else if (defaultRouteFlags & ReachableViaCarrierDataNetwork) {
22.445 + return ReachableViaCarrierDataNetwork;
22.446 + }
22.447 +
22.448 + return ReachableViaWiFiNetwork;
22.449 + }
22.450 +
22.451 + return NotReachable;
22.452 +}
22.453 +
22.454 +- (NetworkStatus)localWiFiConnectionStatus
22.455 +{
22.456 + SCNetworkReachabilityFlags selfAssignedAddressFlags;
22.457 +
22.458 + /*
22.459 + To determine if the WiFi connection is to a local ad-hoc network,
22.460 + check the availability of the address 169.254.x.x. That's an address
22.461 + in the self-assigned range, and the device will have a self-assigned IP
22.462 + when it's connected to a ad-hoc WiFi network. So to test if the device
22.463 + has a self-assigned IP, look for the kSCNetworkReachabilityFlagsIsDirect flag
22.464 + in the address query. If it's present, we know that the WiFi connection
22.465 + is to an ad-hoc network.
22.466 + */
22.467 + // This returns YES if the address 169.254.0.0 is reachable without requiring a connection.
22.468 + BOOL hasLinkLocalNetworkAccess = [self isAdHocWiFiNetworkAvailableFlags:&selfAssignedAddressFlags];
22.469 +
22.470 + if (hasLinkLocalNetworkAccess && (selfAssignedAddressFlags & kSCNetworkReachabilityFlagsIsDirect)) {
22.471 + return ReachableViaWiFiNetwork;
22.472 + }
22.473 +
22.474 + return NotReachable;
22.475 +}
22.476 +
22.477 +// Convert an IP address from an NSString to a sockaddr_in * that can be used to create
22.478 +// the reachability request.
22.479 +- (BOOL)addressFromString:(NSString *)IPAddress address:(struct sockaddr_in *)address
22.480 +{
22.481 + if (!IPAddress || ![IPAddress length]) {
22.482 + return NO;
22.483 + }
22.484 +
22.485 + memset((char *) address, sizeof(struct sockaddr_in), 0);
22.486 + address->sin_family = AF_INET;
22.487 + address->sin_len = sizeof(struct sockaddr_in);
22.488 +
22.489 + int conversionResult = inet_aton([IPAddress UTF8String], &address->sin_addr);
22.490 + if (conversionResult == 0) {
22.491 + NSAssert1(conversionResult != 1, @"Failed to convert the IP address string into a sockaddr_in: %@", IPAddress);
22.492 + return NO;
22.493 + }
22.494 +
22.495 + return YES;
22.496 +}
22.497 +
22.498 +@end
22.499 +
22.500 +@interface ReachabilityQuery ()
22.501 +- (CFRunLoopRef)startListeningForReachabilityChanges:(SCNetworkReachabilityRef)reachability onRunLoop:(CFRunLoopRef)runLoop;
22.502 +@end
22.503 +
22.504 +@implementation ReachabilityQuery
22.505 +
22.506 +@synthesize reachabilityRef = _reachabilityRef;
22.507 +@synthesize runLoops = _runLoops;
22.508 +@synthesize hostNameOrAddress = _hostNameOrAddress;
22.509 +
22.510 +- (id)init
22.511 +{
22.512 + self = [super init];
22.513 + if (self != nil) {
22.514 + self.runLoops = CFArrayCreateMutable(kCFAllocatorDefault, 0, NULL);
22.515 + }
22.516 + return self;
22.517 +}
22.518 +
22.519 +- (void)dealloc
22.520 +{
22.521 + CFRelease(self.runLoops);
22.522 + [super dealloc];
22.523 +}
22.524 +
22.525 +- (BOOL)isScheduledOnRunLoop:(CFRunLoopRef)runLoop
22.526 +{
22.527 + NSUInteger runLoopCounter, maxRunLoops = CFArrayGetCount(self.runLoops);
22.528 +
22.529 + for (runLoopCounter = 0; runLoopCounter < maxRunLoops; runLoopCounter++) {
22.530 + CFRunLoopRef nextRunLoop = (CFRunLoopRef)CFArrayGetValueAtIndex(self.runLoops, runLoopCounter);
22.531 +
22.532 + if (nextRunLoop == runLoop) {
22.533 + return YES;
22.534 + }
22.535 + }
22.536 +
22.537 + return NO;
22.538 +}
22.539 +
22.540 +- (void)scheduleOnRunLoop:(NSRunLoop *)inRunLoop
22.541 +{
22.542 + // Only register for network state changes if the client has specifically enabled them.
22.543 + if ([[Reachability sharedReachability] networkStatusNotificationsEnabled] == NO) {
22.544 + return;
22.545 + }
22.546 +
22.547 + if (!inRunLoop) {
22.548 + return;
22.549 + }
22.550 +
22.551 + CFRunLoopRef runLoop = [inRunLoop getCFRunLoop];
22.552 +
22.553 + // Notifications of status changes for each reachability query can be scheduled on multiple run loops.
22.554 + // To support that, register for notifications for each runLoop.
22.555 + // -isScheduledOnRunLoop: iterates over all of the run loops that have previously been used
22.556 + // to register for notifications. If one is found that matches the passed in runLoop argument, there's
22.557 + // no need to register for notifications again. If one is not found, register for notifications
22.558 + // using the current runLoop.
22.559 + if (![self isScheduledOnRunLoop:runLoop]) {
22.560 +
22.561 + CFRunLoopRef notificationRunLoop = [self startListeningForReachabilityChanges:self.reachabilityRef onRunLoop:runLoop];
22.562 + if (notificationRunLoop) {
22.563 + CFArrayAppendValue(self.runLoops, notificationRunLoop);
22.564 + }
22.565 + }
22.566 +}
22.567 +
22.568 +// Register to receive changes to the 'reachability' query so that we can update the
22.569 +// user interface when the network state changes.
22.570 +- (CFRunLoopRef)startListeningForReachabilityChanges:(SCNetworkReachabilityRef)reachability onRunLoop:(CFRunLoopRef)runLoop
22.571 +{
22.572 + if (!reachability) {
22.573 + return NULL;
22.574 + }
22.575 +
22.576 + if (!runLoop) {
22.577 + return NULL;
22.578 + }
22.579 +
22.580 + SCNetworkReachabilityContext context = {0, self, NULL, NULL, NULL};
22.581 + SCNetworkReachabilitySetCallback(reachability, ReachabilityCallback, &context);
22.582 + SCNetworkReachabilityScheduleWithRunLoop(reachability, runLoop, kCFRunLoopDefaultMode);
22.583 +
22.584 + return runLoop;
22.585 +}
22.586 +
22.587 +
22.588 +@end
23.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
23.2 +++ b/JSON/JSON.h Thu Dec 24 00:14:02 2009 +0000
23.3 @@ -0,0 +1,50 @@
23.4 +/*
23.5 + Copyright (C) 2009 Stig Brautaset. All rights reserved.
23.6 +
23.7 + Redistribution and use in source and binary forms, with or without
23.8 + modification, are permitted provided that the following conditions are met:
23.9 +
23.10 + * Redistributions of source code must retain the above copyright notice, this
23.11 + list of conditions and the following disclaimer.
23.12 +
23.13 + * Redistributions in binary form must reproduce the above copyright notice,
23.14 + this list of conditions and the following disclaimer in the documentation
23.15 + and/or other materials provided with the distribution.
23.16 +
23.17 + * Neither the name of the author nor the names of its contributors may be used
23.18 + to endorse or promote products derived from this software without specific
23.19 + prior written permission.
23.20 +
23.21 + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
23.22 + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23.23 + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23.24 + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
23.25 + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23.26 + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
23.27 + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
23.28 + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
23.29 + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23.30 + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23.31 + */
23.32 +
23.33 +/**
23.34 + @mainpage A strict JSON parser and generator for Objective-C
23.35 +
23.36 + JSON (JavaScript Object Notation) is a lightweight data-interchange
23.37 + format. This framework provides two apis for parsing and generating
23.38 + JSON. One standard object-based and a higher level api consisting of
23.39 + categories added to existing Objective-C classes.
23.40 +
23.41 + Learn more on the http://code.google.com/p/json-framework project site.
23.42 +
23.43 + This framework does its best to be as strict as possible, both in what it
23.44 + accepts and what it generates. For example, it does not support trailing commas
23.45 + in arrays or objects. Nor does it support embedded comments, or
23.46 + anything else not in the JSON specification. This is considered a feature.
23.47 +
23.48 +*/
23.49 +
23.50 +#import "SBJSON.h"
23.51 +#import "NSObject+SBJSON.h"
23.52 +#import "NSString+SBJSON.h"
23.53 +
24.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
24.2 +++ b/JSON/NSObject+SBJSON.h Thu Dec 24 00:14:02 2009 +0000
24.3 @@ -0,0 +1,68 @@
24.4 +/*
24.5 + Copyright (C) 2009 Stig Brautaset. All rights reserved.
24.6 +
24.7 + Redistribution and use in source and binary forms, with or without
24.8 + modification, are permitted provided that the following conditions are met:
24.9 +
24.10 + * Redistributions of source code must retain the above copyright notice, this
24.11 + list of conditions and the following disclaimer.
24.12 +
24.13 + * Redistributions in binary form must reproduce the above copyright notice,
24.14 + this list of conditions and the following disclaimer in the documentation
24.15 + and/or other materials provided with the distribution.
24.16 +
24.17 + * Neither the name of the author nor the names of its contributors may be used
24.18 + to endorse or promote products derived from this software without specific
24.19 + prior written permission.
24.20 +
24.21 + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
24.22 + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24.23 + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
24.24 + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
24.25 + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24.26 + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24.27 + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
24.28 + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24.29 + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24.30 + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24.31 + */
24.32 +
24.33 +#import <Foundation/Foundation.h>
24.34 +
24.35 +
24.36 +/**
24.37 + @brief Adds JSON generation to Foundation classes
24.38 +
24.39 + This is a category on NSObject that adds methods for returning JSON representations
24.40 + of standard objects to the objects themselves. This means you can call the
24.41 + -JSONRepresentation method on an NSArray object and it'll do what you want.
24.42 + */
24.43 +@interface NSObject (NSObject_SBJSON)
24.44 +
24.45 +/**
24.46 + @brief Returns a string containing the receiver encoded as a JSON fragment.
24.47 +
24.48 + This method is added as a category on NSObject but is only actually
24.49 + supported for the following objects:
24.50 + @li NSDictionary
24.51 + @li NSArray
24.52 + @li NSString
24.53 + @li NSNumber (also used for booleans)
24.54 + @li NSNull
24.55 +
24.56 + @deprecated Given we bill ourselves as a "strict" JSON library, this method should be removed.
24.57 + */
24.58 +- (NSString *)JSONFragment;
24.59 +
24.60 +/**
24.61 + @brief Returns a string containing the receiver encoded in JSON.
24.62 +
24.63 + This method is added as a category on NSObject but is only actually
24.64 + supported for the following objects:
24.65 + @li NSDictionary
24.66 + @li NSArray
24.67 + */
24.68 +- (NSString *)JSONRepresentation;
24.69 +
24.70 +@end
24.71 +
25.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
25.2 +++ b/JSON/NSObject+SBJSON.m Thu Dec 24 00:14:02 2009 +0000
25.3 @@ -0,0 +1,53 @@
25.4 +/*
25.5 + Copyright (C) 2009 Stig Brautaset. All rights reserved.
25.6 +
25.7 + Redistribution and use in source and binary forms, with or without
25.8 + modification, are permitted provided that the following conditions are met:
25.9 +
25.10 + * Redistributions of source code must retain the above copyright notice, this
25.11 + list of conditions and the following disclaimer.
25.12 +
25.13 + * Redistributions in binary form must reproduce the above copyright notice,
25.14 + this list of conditions and the following disclaimer in the documentation
25.15 + and/or other materials provided with the distribution.
25.16 +
25.17 + * Neither the name of the author nor the names of its contributors may be used
25.18 + to endorse or promote products derived from this software without specific
25.19 + prior written permission.
25.20 +
25.21 + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
25.22 + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25.23 + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
25.24 + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
25.25 + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25.26 + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25.27 + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
25.28 + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
25.29 + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25.30 + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25.31 + */
25.32 +
25.33 +#import "NSObject+SBJSON.h"
25.34 +#import "SBJsonWriter.h"
25.35 +
25.36 +@implementation NSObject (NSObject_SBJSON)
25.37 +
25.38 +- (NSString *)JSONFragment {
25.39 + SBJsonWriter *jsonWriter = [SBJsonWriter new];
25.40 + NSString *json = [jsonWriter stringWithFragment:self];
25.41 + if (json)
25.42 + NSLog(@"-JSONFragment failed. Error trace is: %@", [jsonWriter errorTrace]);
25.43 + [jsonWriter release];
25.44 + return json;
25.45 +}
25.46 +
25.47 +- (NSString *)JSONRepresentation {
25.48 + SBJsonWriter *jsonWriter = [SBJsonWriter new];
25.49 + NSString *json = [jsonWriter stringWithObject:self];
25.50 + if (json)
25.51 + NSLog(@"-JSONRepresentation failed. Error trace is: %@", [jsonWriter errorTrace]);
25.52 + [jsonWriter release];
25.53 + return json;
25.54 +}
25.55 +
25.56 +@end
26.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
26.2 +++ b/JSON/NSString+SBJSON.h Thu Dec 24 00:14:02 2009 +0000
26.3 @@ -0,0 +1,58 @@
26.4 +/*
26.5 + Copyright (C) 2009 Stig Brautaset. All rights reserved.
26.6 +
26.7 + Redistribution and use in source and binary forms, with or without
26.8 + modification, are permitted provided that the following conditions are met:
26.9 +
26.10 + * Redistributions of source code must retain the above copyright notice, this
26.11 + list of conditions and the following disclaimer.
26.12 +
26.13 + * Redistributions in binary form must reproduce the above copyright notice,
26.14 + this list of conditions and the following disclaimer in the documentation
26.15 + and/or other materials provided with the distribution.
26.16 +
26.17 + * Neither the name of the author nor the names of its contributors may be used
26.18 + to endorse or promote products derived from this software without specific
26.19 + prior written permission.
26.20 +
26.21 + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
26.22 + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26.23 + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
26.24 + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
26.25 + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26.26 + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26.27 + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26.28 + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26.29 + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26.30 + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26.31 + */
26.32 +
26.33 +#import <Foundation/Foundation.h>
26.34 +
26.35 +/**
26.36 + @brief Adds JSON parsing methods to NSString
26.37 +
26.38 +This is a category on NSString that adds methods for parsing the target string.
26.39 +*/
26.40 +@interface NSString (NSString_SBJSON)
26.41 +
26.42 +
26.43 +/**
26.44 + @brief Returns the object represented in the receiver, or nil on error.
26.45 +
26.46 + Returns a a scalar object represented by the string's JSON fragment representation.
26.47 +
26.48 + @deprecated Given we bill ourselves as a "strict" JSON library, this method should be removed.
26.49 + */
26.50 +- (id)JSONFragmentValue;
26.51 +
26.52 +/**
26.53 + @brief Returns the NSDictionary or NSArray represented by the current string's JSON representation.
26.54 +
26.55 + Returns the dictionary or array represented in the receiver, or nil on error.
26.56 +
26.57 + Returns the NSDictionary or NSArray represented by the current string's JSON representation.
26.58 + */
26.59 +- (id)JSONValue;
26.60 +
26.61 +@end
27.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
27.2 +++ b/JSON/NSString+SBJSON.m Thu Dec 24 00:14:02 2009 +0000
27.3 @@ -0,0 +1,55 @@
27.4 +/*
27.5 + Copyright (C) 2007-2009 Stig Brautaset. All rights reserved.
27.6 +
27.7 + Redistribution and use in source and binary forms, with or without
27.8 + modification, are permitted provided that the following conditions are met:
27.9 +
27.10 + * Redistributions of source code must retain the above copyright notice, this
27.11 + list of conditions and the following disclaimer.
27.12 +
27.13 + * Redistributions in binary form must reproduce the above copyright notice,
27.14 + this list of conditions and the following disclaimer in the documentation
27.15 + and/or other materials provided with the distribution.
27.16 +
27.17 + * Neither the name of the author nor the names of its contributors may be used
27.18 + to endorse or promote products derived from this software without specific
27.19 + prior written permission.
27.20 +
27.21 + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
27.22 + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
27.23 + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
27.24 + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
27.25 + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27.26 + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
27.27 + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27.28 + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27.29 + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27.30 + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27.31 + */
27.32 +
27.33 +#import "NSString+SBJSON.h"
27.34 +#import "SBJsonParser.h"
27.35 +
27.36 +@implementation NSString (NSString_SBJSON)
27.37 +
27.38 +- (id)JSONFragmentValue
27.39 +{
27.40 + SBJsonParser *jsonParser = [SBJsonParser new];
27.41 + id repr = [jsonParser fragmentWithString:self];
27.42 + if (repr)
27.43 + NSLog(@"-JSONFragmentValue failed. Error trace is: %@", [jsonParser errorTrace]);
27.44 + [jsonParser release];
27.45 + return repr;
27.46 +}
27.47 +
27.48 +- (id)JSONValue
27.49 +{
27.50 + SBJsonParser *jsonParser = [SBJsonParser new];
27.51 + id repr = [jsonParser objectWithString:self];
27.52 + if (!repr)
27.53 + NSLog(@"-JSONValue failed. Error trace is: %@", [jsonParser errorTrace]);
27.54 + [jsonParser release];
27.55 + return repr;
27.56 +}
27.57 +
27.58 +@end
28.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
28.2 +++ b/JSON/SBJSON.h Thu Dec 24 00:14:02 2009 +0000
28.3 @@ -0,0 +1,75 @@
28.4 +/*
28.5 + Copyright (C) 2007-2009 Stig Brautaset. All rights reserved.
28.6 +
28.7 + Redistribution and use in source and binary forms, with or without
28.8 + modification, are permitted provided that the following conditions are met:
28.9 +
28.10 + * Redistributions of source code must retain the above copyright notice, this
28.11 + list of conditions and the following disclaimer.
28.12 +
28.13 + * Redistributions in binary form must reproduce the above copyright notice,
28.14 + this list of conditions and the following disclaimer in the documentation
28.15 + and/or other materials provided with the distribution.
28.16 +
28.17 + * Neither the name of the author nor the names of its contributors may be used
28.18 + to endorse or promote products derived from this software without specific
28.19 + prior written permission.
28.20 +
28.21 + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
28.22 + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
28.23 + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
28.24 + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
28.25 + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28.26 + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
28.27 + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
28.28 + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28.29 + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28.30 + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28.31 + */
28.32 +
28.33 +#import <Foundation/Foundation.h>
28.34 +#import "SBJsonParser.h"
28.35 +#import "SBJsonWriter.h"
28.36 +
28.37 +/**
28.38 + @brief Facade for SBJsonWriter/SBJsonParser.
28.39 +
28.40 + Requests are forwarded to instances of SBJsonWriter and SBJsonParser.
28.41 + */
28.42 +@interface SBJSON : SBJsonBase <SBJsonParser, SBJsonWriter> {
28.43 +
28.44 +@private
28.45 + SBJsonParser *jsonParser;
28.46 + SBJsonWriter *jsonWriter;
28.47 +}
28.48 +
28.49 +
28.50 +/// Return the fragment represented by the given string
28.51 +- (id)fragmentWithString:(NSString*)jsonrep
28.52 + error:(NSError**)error;
28.53 +
28.54 +/// Return the object represented by the given string
28.55 +- (id)objectWithString:(NSString*)jsonrep
28.56 + error:(NSError**)error;
28.57 +
28.58 +/// Parse the string and return the represented object (or scalar)
28.59 +- (id)objectWithString:(id)value
28.60 + allowScalar:(BOOL)x
28.61 + error:(NSError**)error;
28.62 +
28.63 +
28.64 +/// Return JSON representation of an array or dictionary
28.65 +- (NSString*)stringWithObject:(id)value
28.66 + error:(NSError**)error;
28.67 +
28.68 +/// Return JSON representation of any legal JSON value
28.69 +- (NSString*)stringWithFragment:(id)value
28.70 + error:(NSError**)error;
28.71 +
28.72 +/// Return JSON representation (or fragment) for the given object
28.73 +- (NSString*)stringWithObject:(id)value
28.74 + allowScalar:(BOOL)x
28.75 + error:(NSError**)error;
28.76 +
28.77 +
28.78 +@end
29.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
29.2 +++ b/JSON/SBJSON.m Thu Dec 24 00:14:02 2009 +0000
29.3 @@ -0,0 +1,212 @@
29.4 +/*
29.5 + Copyright (C) 2007-2009 Stig Brautaset. All rights reserved.
29.6 +
29.7 + Redistribution and use in source and binary forms, with or without
29.8 + modification, are permitted provided that the following conditions are met:
29.9 +
29.10 + * Redistributions of source code must retain the above copyright notice, this
29.11 + list of conditions and the following disclaimer.
29.12 +
29.13 + * Redistributions in binary form must reproduce the above copyright notice,
29.14 + this list of conditions and the following disclaimer in the documentation
29.15 + and/or other materials provided with the distribution.
29.16 +
29.17 + * Neither the name of the author nor the names of its contributors may be used
29.18 + to endorse or promote products derived from this software without specific
29.19 + prior written permission.
29.20 +
29.21 + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
29.22 + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
29.23 + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
29.24 + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
29.25 + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29.26 + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
29.27 + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
29.28 + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
29.29 + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29.30 + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29.31 + */
29.32 +
29.33 +#import "SBJSON.h"
29.34 +
29.35 +@implementation SBJSON
29.36 +
29.37 +- (id)init {
29.38 + self = [super init];
29.39 + if (self) {
29.40 + jsonWriter = [SBJsonWriter new];
29.41 + jsonParser = [SBJsonParser new];
29.42 + [self setMaxDepth:512];
29.43 +
29.44 + }
29.45 + return self;
29.46 +}
29.47 +
29.48 +- (void)dealloc {
29.49 + [jsonWriter release];
29.50 + [jsonParser release];
29.51 + [super dealloc];
29.52 +}
29.53 +
29.54 +#pragma mark Writer
29.55 +
29.56 +
29.57 +- (NSString *)stringWithObject:(id)obj {
29.58 + NSString *repr = [jsonWriter stringWithObject:obj];
29.59 + if (repr)
29.60 + return repr;
29.61 +
29.62 + [errorTrace release];
29.63 + errorTrace = [[jsonWriter errorTrace] mutableCopy];
29.64 + return nil;
29.65 +}
29.66 +
29.67 +/**
29.68 + Returns a string containing JSON representation of the passed in value, or nil on error.
29.69 + If nil is returned and @p error is not NULL, @p *error can be interrogated to find the cause of the error.
29.70 +
29.71 + @param value any instance that can be represented as a JSON fragment
29.72 + @param allowScalar wether to return json fragments for scalar objects
29.73 + @param error used to return an error by reference (pass NULL if this is not desired)
29.74 +
29.75 +@deprecated Given we bill ourselves as a "strict" JSON library, this method should be removed.
29.76 + */
29.77 +- (NSString*)stringWithObject:(id)value allowScalar:(BOOL)allowScalar error:(NSError**)error {
29.78 +
29.79 + NSString *json = allowScalar ? [jsonWriter stringWithFragment:value] : [jsonWriter stringWithObject:value];
29.80 + if (json)
29.81 + return json;
29.82 +
29.83 + [errorTrace release];
29.84 + errorTrace = [[jsonWriter errorTrace] mutableCopy];
29.85 +
29.86 + if (error)
29.87 + *error = [errorTrace lastObject];
29.88 + return nil;
29.89 +}
29.90 +
29.91 +/**
29.92 + Returns a string containing JSON representation of the passed in value, or nil on error.
29.93 + If nil is returned and @p error is not NULL, @p error can be interrogated to find the cause of the error.
29.94 +
29.95 + @param value any instance that can be represented as a JSON fragment
29.96 + @param error used to return an error by reference (pass NULL if this is not desired)
29.97 +
29.98 + @deprecated Given we bill ourselves as a "strict" JSON library, this method should be removed.
29.99 + */
29.100 +- (NSString*)stringWithFragment:(id)value error:(NSError**)error {
29.101 + return [self stringWithObject:value
29.102 + allowScalar:YES
29.103 + error:error];
29.104 +}
29.105 +
29.106 +/**
29.107 + Returns a string containing JSON representation of the passed in value, or nil on error.
29.108 + If nil is returned and @p error is not NULL, @p error can be interrogated to find the cause of the error.
29.109 +
29.110 + @param value a NSDictionary or NSArray instance
29.111 + @param error used to return an error by reference (pass NULL if this is not desired)
29.112 + */
29.113 +- (NSString*)stringWithObject:(id)value error:(NSError**)error {
29.114 + return [self stringWithObject:value
29.115 + allowScalar:NO
29.116 + error:error];
29.117 +}
29.118 +
29.119 +#pragma mark Parsing
29.120 +
29.121 +- (id)objectWithString:(NSString *)repr {
29.122 + id obj = [jsonParser objectWithString:repr];
29.123 + if (obj)
29.124 + return obj;
29.125 +
29.126 + [errorTrace release];
29.127 + errorTrace = [[jsonParser errorTrace] mutableCopy];
29.128 +
29.129 + return nil;
29.130 +}
29.131 +
29.132 +/**
29.133 + Returns the object represented by the passed-in string or nil on error. The returned object can be
29.134 + a string, number, boolean, null, array or dictionary.
29.135 +
29.136 + @param value the json string to parse
29.137 + @param allowScalar whether to return objects for JSON fragments
29.138 + @param error used to return an error by reference (pass NULL if this is not desired)
29.139 +
29.140 + @deprecated Given we bill ourselves as a "strict" JSON library, this method should be removed.
29.141 + */
29.142 +- (id)objectWithString:(id)value allowScalar:(BOOL)allowScalar error:(NSError**)error {
29.143 +
29.144 + id obj = allowScalar ? [jsonParser fragmentWithString:value] : [jsonParser objectWithString:value];
29.145 + if (obj)
29.146 + return obj;
29.147 +
29.148 + [errorTrace release];
29.149 + errorTrace = [[jsonParser errorTrace] mutableCopy];
29.150 +
29.151 + if (error)
29.152 + *error = [errorTrace lastObject];
29.153 + return nil;
29.154 +}
29.155 +
29.156 +/**
29.157 + Returns the object represented by the passed-in string or nil on error. The returned object can be
29.158 + a string, number, boolean, null, array or dictionary.
29.159 +
29.160 + @param repr the json string to parse
29.161 + @param error used to return an error by reference (pass NULL if this is not desired)
29.162 +
29.163 + @deprecated Given we bill ourselves as a "strict" JSON library, this method should be removed.
29.164 + */
29.165 +- (id)fragmentWithString:(NSString*)repr error:(NSError**)error {
29.166 + return [self objectWithString:repr
29.167 + allowScalar:YES
29.168 + error:error];
29.169 +}
29.170 +
29.171 +/**
29.172 + Returns the object represented by the passed-in string or nil on error. The returned object
29.173 + will be either a dictionary or an array.
29.174 +
29.175 + @param repr the json string to parse
29.176 + @param error used to return an error by reference (pass NULL if this is not desired)
29.177 + */
29.178 +- (id)objectWithString:(NSString*)repr error:(NSError**)error {
29.179 + return [self objectWithString:repr
29.180 + allowScalar:NO
29.181 + error:error];
29.182 +}
29.183 +
29.184 +
29.185 +
29.186 +#pragma mark Properties - parsing
29.187 +
29.188 +- (NSUInteger)maxDepth {
29.189 + return jsonParser.maxDepth;
29.190 +}
29.191 +
29.192 +- (void)setMaxDepth:(NSUInteger)d {
29.193 + jsonWriter.maxDepth = jsonParser.maxDepth = d;
29.194 +}
29.195 +
29.196 +
29.197 +#pragma mark Properties - writing
29.198 +
29.199 +- (BOOL)humanReadable {
29.200 + return jsonWriter.humanReadable;
29.201 +}
29.202 +
29.203 +- (void)setHumanReadable:(BOOL)x {
29.204 + jsonWriter.humanReadable = x;
29.205 +}
29.206 +
29.207 +- (BOOL)sortKeys {
29.208 + return jsonWriter.sortKeys;
29.209 +}
29.210 +
29.211 +- (void)setSortKeys:(BOOL)x {
29.212 + jsonWriter.sortKeys = x;
29.213 +}
29.214 +
29.215 +@end
30.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
30.2 +++ b/JSON/SBJsonBase.h Thu Dec 24 00:14:02 2009 +0000
30.3 @@ -0,0 +1,86 @@
30.4 +/*
30.5 + Copyright (C) 2009 Stig Brautaset. All rights reserved.
30.6 +
30.7 + Redistribution and use in source and binary forms, with or without
30.8 + modification, are permitted provided that the following conditions are met:
30.9 +
30.10 + * Redistributions of source code must retain the above copyright notice, this
30.11 + list of conditions and the following disclaimer.
30.12 +
30.13 + * Redistributions in binary form must reproduce the above copyright notice,
30.14 + this list of conditions and the following disclaimer in the documentation
30.15 + and/or other materials provided with the distribution.
30.16 +
30.17 + * Neither the name of the author nor the names of its contributors may be used
30.18 + to endorse or promote products derived from this software without specific
30.19 + prior written permission.
30.20 +
30.21 + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
30.22 + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
30.23 + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
30.24 + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
30.25 + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
30.26 + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
30.27 + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
30.28 + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
30.29 + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30.30 + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30.31 + */
30.32 +
30.33 +#import <Foundation/Foundation.h>
30.34 +
30.35 +extern NSString * SBJSONErrorDomain;
30.36 +
30.37 +
30.38 +enum {
30.39 + EUNSUPPORTED = 1,
30.40 + EPARSENUM,
30.41 + EPARSE,
30.42 + EFRAGMENT,
30.43 + ECTRL,
30.44 + EUNICODE,
30.45 + EDEPTH,
30.46 + EESCAPE,
30.47 + ETRAILCOMMA,
30.48 + ETRAILGARBAGE,
30.49 + EEOF,
30.50 + EINPUT
30.51 +};
30.52 +
30.53 +/**
30.54 + @brief Common base class for parsing & writing.
30.55 +
30.56 + This class contains the common error-handling code and option between the parser/writer.
30.57 + */
30.58 +@interface SBJsonBase : NSObject {
30.59 + NSMutableArray *errorTrace;
30.60 +
30.61 +@protected
30.62 + NSUInteger depth, maxDepth;
30.63 +}
30.64 +
30.65 +/**
30.66 + @brief The maximum recursing depth.
30.67 +
30.68 + Defaults to 512. If the input is nested deeper than this the input will be deemed to be
30.69 + malicious and the parser returns nil, signalling an error. ("Nested too deep".) You can
30.70 + turn off this security feature by setting the maxDepth value to 0.
30.71 + */
30.72 +@property NSUInteger maxDepth;
30.73 +
30.74 +/**
30.75 + @brief Return an error trace, or nil if there was no errors.
30.76 +
30.77 + Note that this method returns the trace of the last method that failed.
30.78 + You need to check the return value of the call you're making to figure out
30.79 + if the call actually failed, before you know call this method.
30.80 + */
30.81 + @property(copy,readonly) NSArray* errorTrace;
30.82 +
30.83 +/// @internal for use in subclasses to add errors to the stack trace
30.84 +- (void)addErrorWithCode:(NSUInteger)code description:(NSString*)str;
30.85 +
30.86 +/// @internal for use in subclasess to clear the error before a new parsing attempt
30.87 +- (void)clearErrorTrace;
30.88 +
30.89 +@end
31.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
31.2 +++ b/JSON/SBJsonBase.m Thu Dec 24 00:14:02 2009 +0000
31.3 @@ -0,0 +1,78 @@
31.4 +/*
31.5 + Copyright (C) 2009 Stig Brautaset. All rights reserved.
31.6 +
31.7 + Redistribution and use in source and binary forms, with or without
31.8 + modification, are permitted provided that the following conditions are met:
31.9 +
31.10 + * Redistributions of source code must retain the above copyright notice, this
31.11 + list of conditions and the following disclaimer.
31.12 +
31.13 + * Redistributions in binary form must reproduce the above copyright notice,
31.14 + this list of conditions and the following disclaimer in the documentation
31.15 + and/or other materials provided with the distribution.
31.16 +
31.17 + * Neither the name of the author nor the names of its contributors may be used
31.18 + to endorse or promote products derived from this software without specific
31.19 + prior written permission.
31.20 +
31.21 + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
31.22 + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
31.23 + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
31.24 + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
31.25 + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
31.26 + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
31.27 + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
31.28 + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
31.29 + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
31.30 + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31.31 + */
31.32 +
31.33 +#import "SBJsonBase.h"
31.34 +NSString * SBJSONErrorDomain = @"org.brautaset.JSON.ErrorDomain";
31.35 +
31.36 +
31.37 +@implementation SBJsonBase
31.38 +
31.39 +@synthesize errorTrace;
31.40 +@synthesize maxDepth;
31.41 +
31.42 +- (id)init {
31.43 + self = [super init];
31.44 + if (self)
31.45 + self.maxDepth = 512;
31.46 + return self;
31.47 +}
31.48 +
31.49 +- (void)dealloc {
31.50 + [errorTrace release];
31.51 + [super dealloc];
31.52 +}
31.53 +
31.54 +- (void)addErrorWithCode:(NSUInteger)code description:(NSString*)str {
31.55 + NSDictionary *userInfo;
31.56 + if (!errorTrace) {
31.57 + errorTrace = [NSMutableArray new];
31.58 + userInfo = [NSDictionary dictionaryWithObject:str forKey:NSLocalizedDescriptionKey];
31.59 +
31.60 + } else {
31.61 + userInfo = [NSDictionary dictionaryWithObjectsAndKeys:
31.62 + str, NSLocalizedDescriptionKey,
31.63 + [errorTrace lastObject], NSUnderlyingErrorKey,
31.64 + nil];
31.65 + }
31.66 +
31.67 + NSError *error = [NSError errorWithDomain:SBJSONErrorDomain code:code userInfo:userInfo];
31.68 +
31.69 + [self willChangeValueForKey:@"errorTrace"];
31.70 + [errorTrace addObject:error];
31.71 + [self didChangeValueForKey:@"errorTrace"];
31.72 +}
31.73 +
31.74 +- (void)clearErrorTrace {
31.75 + [self willChangeValueForKey:@"errorTrace"];
31.76 + [errorTrace release];
31.77 + errorTrace = nil;
31.78 + [self didChangeValueForKey:@"errorTrace"];
31.79 +}
31.80 +
31.81 +@end
32.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
32.2 +++ b/JSON/SBJsonParser.h Thu Dec 24 00:14:02 2009 +0000
32.3 @@ -0,0 +1,87 @@
32.4 +/*
32.5 + Copyright (C) 2009 Stig Brautaset. All rights reserved.
32.6 +
32.7 + Redistribution and use in source and binary forms, with or without
32.8 + modification, are permitted provided that the following conditions are met:
32.9 +
32.10 + * Redistributions of source code must retain the above copyright notice, this
32.11 + list of conditions and the following disclaimer.
32.12 +
32.13 + * Redistributions in binary form must reproduce the above copyright notice,
32.14 + this list of conditions and the following disclaimer in the documentation
32.15 + and/or other materials provided with the distribution.
32.16 +
32.17 + * Neither the name of the author nor the names of its contributors may be used
32.18 + to endorse or promote products derived from this software without specific
32.19 + prior written permission.
32.20 +
32.21 + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
32.22 + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
32.23 + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
32.24 + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
32.25 + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
32.26 + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
32.27 + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
32.28 + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
32.29 + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
32.30 + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32.31 + */
32.32 +
32.33 +#import <Foundation/Foundation.h>
32.34 +#import "SBJsonBase.h"
32.35 +
32.36 +/**
32.37 + @brief Options for the parser class.
32.38 +
32.39 + This exists so the SBJSON facade can implement the options in the parser without having to re-declare them.
32.40 + */
32.41 +@protocol SBJsonParser
32.42 +
32.43 +/**
32.44 + @brief Return the object represented by the given string.
32.45 +
32.46 + Returns the object represented by the passed-in string or nil on error. The returned object can be
32.47 + a string, number, boolean, null, array or dictionary.
32.48 +
32.49 + @param repr the json string to parse
32.50 + */
32.51 +- (id)objectWithString:(NSString *)repr;
32.52 +
32.53 +@end
32.54 +
32.55 +
32.56 +/**
32.57 + @brief The JSON parser class.
32.58 +
32.59 + JSON is mapped to Objective-C types in the following way:
32.60 +
32.61 + @li Null -> NSNull
32.62 + @li String -> NSMutableString
32.63 + @li Array -> NSMutableArray
32.64 + @li Object -> NSMutableDictionary
32.65 + @li Boolean -> NSNumber (initialised with -initWithBool:)
32.66 + @li Number -> NSDecimalNumber
32.67 +
32.68 + Since Objective-C doesn't have a dedicated class for boolean values, these turns into NSNumber
32.69 + instances. These are initialised with the -initWithBool: method, and
32.70 + round-trip back to JSON properly. (They won't silently suddenly become 0 or 1; they'll be
32.71 + represented as 'true' and 'false' again.)
32.72 +
32.73 + JSON numbers turn into NSDecimalNumber instances,
32.74 + as we can thus avoid any loss of precision. (JSON allows ridiculously large numbers.)
32.75 +
32.76 + */
32.77 +@interface SBJsonParser : SBJsonBase <SBJsonParser> {
32.78 +
32.79 +@private
32.80 + const char *c;
32.81 +}
32.82 +
32.83 +@end
32.84 +
32.85 +// don't use - exists for backwards compatibility with 2.1.x only. Will be removed in 2.3.
32.86 +@interface SBJsonParser (Private)
32.87 +- (id)fragmentWithString:(id)repr;
32.88 +@end
32.89 +
32.90 +
33.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
33.2 +++ b/JSON/SBJsonParser.m Thu Dec 24 00:14:02 2009 +0000
33.3 @@ -0,0 +1,475 @@
33.4 +/*
33.5 + Copyright (C) 2009 Stig Brautaset. All rights reserved.
33.6 +
33.7 + Redistribution and use in source and binary forms, with or without
33.8 + modification, are permitted provided that the following conditions are met:
33.9 +
33.10 + * Redistributions of source code must retain the above copyright notice, this
33.11 + list of conditions and the following disclaimer.
33.12 +
33.13 + * Redistributions in binary form must reproduce the above copyright notice,
33.14 + this list of conditions and the following disclaimer in the documentation
33.15 + and/or other materials provided with the distribution.
33.16 +
33.17 + * Neither the name of the author nor the names of its contributors may be used
33.18 + to endorse or promote products derived from this software without specific
33.19 + prior written permission.
33.20 +
33.21 + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
33.22 + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
33.23 + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
33.24 + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
33.25 + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
33.26 + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
33.27 + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
33.28 + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
33.29 + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
33.30 + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33.31 + */
33.32 +
33.33 +#import "SBJsonParser.h"
33.34 +
33.35 +@interface SBJsonParser ()
33.36 +
33.37 +- (BOOL)scanValue:(NSObject **)o;
33.38 +
33.39 +- (BOOL)scanRestOfArray:(NSMutableArray **)o;
33.40 +- (BOOL)scanRestOfDictionary:(NSMutableDictionary **)o;
33.41 +- (BOOL)scanRestOfNull:(NSNull **)o;
33.42 +- (BOOL)scanRestOfFalse:(NSNumber **)o;
33.43 +- (BOOL)scanRestOfTrue:(NSNumber **)o;
33.44 +- (BOOL)scanRestOfString:(NSMutableString **)o;
33.45 +
33.46 +// Cannot manage without looking at the first digit
33.47 +- (BOOL)scanNumber:(NSNumber **)o;
33.48 +
33.49 +- (BOOL)scanHexQuad:(unichar *)x;
33.50 +- (BOOL)scanUnicodeChar:(unichar *)x;
33.51 +
33.52 +- (BOOL)scanIsAtEnd;
33.53 +
33.54 +@end
33.55 +
33.56 +#define skipWhitespace(c) while (isspace(*c)) c++
33.57 +#define skipDigits(c) while (isdigit(*c)) c++
33.58 +
33.59 +
33.60 +@implementation SBJsonParser
33.61 +
33.62 +static char ctrl[0x22];
33.63 +
33.64 ++ (void)initialize
33.65 +{
33.66 + ctrl[0] = '\"';
33.67 + ctrl[1] = '\\';
33.68 + for (int i = 1; i < 0x20; i++)
33.69 + ctrl[i+1] = i;
33.70 + ctrl[0x21] = 0;
33.71 +}
33.72 +
33.73 +/**
33.74 + @deprecated This exists in order to provide fragment support in older APIs in one more version.
33.75 + It should be removed in the next major version.
33.76 + */
33.77 +- (id)fragmentWithString:(id)repr {
33.78 + [self clearErrorTrace];
33.79 +
33.80 + if (!repr) {
33.81 + [self addErrorWithCode:EINPUT description:@"Input was 'nil'"];
33.82 + return nil;
33.83 + }
33.84 +
33.85 + depth = 0;
33.86 + c = [repr UTF8String];
33.87 +
33.88 + id o;
33.89 + if (![self scanValue:&o]) {
33.90 + return nil;
33.91 + }
33.92 +
33.93 + // We found some valid JSON. But did it also contain something else?
33.94 + if (![self scanIsAtEnd]) {
33.95 + [self addErrorWithCode:ETRAILGARBAGE description:@"Garbage after JSON"];
33.96 + return nil;
33.97 + }
33.98 +
33.99 + NSAssert1(o, @"Should have a valid object from %@", repr);
33.100 + return o;
33.101 +}
33.102 +
33.103 +- (id)objectWithString:(NSString *)repr {
33.104 +
33.105 + id o = [self fragmentWithString:repr];
33.106 + if (!o)
33.107 + return nil;
33.108 +
33.109 + // Check that the object we've found is a valid JSON container.
33.110 + if (![o isKindOfClass:[NSDictionary class]] && ![o isKindOfClass:[NSArray class]]) {
33.111 + [self addErrorWithCode:EFRAGMENT description:@"Valid fragment, but not JSON"];
33.112 + return nil;
33.113 + }
33.114 +
33.115 + return o;
33.116 +}
33.117 +
33.118 +/*
33.119 + In contrast to the public methods, it is an error to omit the error parameter here.
33.120 + */
33.121 +- (BOOL)scanValue:(NSObject **)o
33.122 +{
33.123 + skipWhitespace(c);
33.124 +
33.125 + switch (*c++) {
33.126 + case '{':
33.127 + return [self scanRestOfDictionary:(NSMutableDictionary **)o];
33.128 + break;
33.129 + case '[':
33.130 + return [self scanRestOfArray:(NSMutableArray **)o];
33.131 + break;
33.132 + case '"':
33.133 + return [self scanRestOfString:(NSMutableString **)o];
33.134 + break;
33.135 + case 'f':
33.136 + return [self scanRestOfFalse:(NSNumber **)o];
33.137 + break;
33.138 + case 't':
33.139 + return [self scanRestOfTrue:(NSNumber **)o];
33.140 + break;
33.141 + case 'n':
33.142 + return [self scanRestOfNull:(NSNull **)o];
33.143 + break;
33.144 + case '-':
33.145 + case '0'...'9':
33.146 + c--; // cannot verify number correctly without the first character
33.147 + return [self scanNumber:(NSNumber **)o];
33.148 + break;
33.149 + case '+':
33.150 + [self addErrorWithCode:EPARSENUM description: @"Leading + disallowed in number"];
33.151 + return NO;
33.152 + break;
33.153 + case 0x0:
33.154 + [self addErrorWithCode:EEOF description:@"Unexpected end of string"];
33.155 + return NO;
33.156 + break;
33.157 + default:
33.158 + [self addErrorWithCode:EPARSE description: @"Unrecognised leading character"];
33.159 + return NO;
33.160 + break;
33.161 + }
33.162 +
33.163 + NSAssert(0, @"Should never get here");
33.164 + return NO;
33.165 +}
33.166 +
33.167 +- (BOOL)scanRestOfTrue:(NSNumber **)o
33.168 +{
33.169 + if (!strncmp(c, "rue", 3)) {
33.170 + c += 3;
33.171 + *o = [NSNumber numberWithBool:YES];
33.172 + return YES;
33.173 + }
33.174 + [self addErrorWithCode:EPARSE description:@"Expected 'true'"];
33.175 + return NO;
33.176 +}
33.177 +
33.178 +- (BOOL)scanRestOfFalse:(NSNumber **)o
33.179 +{
33.180 + if (!strncmp(c, "alse", 4)) {
33.181 + c += 4;
33.182 + *o = [NSNumber numberWithBool:NO];
33.183 + return YES;
33.184 + }
33.185 + [self addErrorWithCode:EPARSE description: @"Expected 'false'"];
33.186 + return NO;
33.187 +}
33.188 +
33.189 +- (BOOL)scanRestOfNull:(NSNull **)o {
33.190 + if (!strncmp(c, "ull", 3)) {
33.191 + c += 3;
33.192 + *o = [NSNull null];
33.193 + return YES;
33.194 + }
33.195 + [self addErrorWithCode:EPARSE description: @"Expected 'null'"];
33.196 + return NO;
33.197 +}
33.198 +
33.199 +- (BOOL)scanRestOfArray:(NSMutableArray **)o {
33.200 + if (maxDepth && ++depth > maxDepth) {
33.201 + [self addErrorWithCode:EDEPTH description: @"Nested too deep"];
33.202 + return NO;
33.203 + }
33.204 +
33.205 + *o = [NSMutableArray arrayWithCapacity:8];
33.206 +
33.207 + for (; *c ;) {
33.208 + id v;
33.209 +
33.210 + skipWhitespace(c);
33.211 + if (*c == ']' && c++) {
33.212 + depth--;
33.213 + return YES;
33.214 + }
33.215 +
33.216 + if (![self scanValue:&v]) {
33.217 + [self addErrorWithCode:EPARSE description:@"Expected value while parsing array"];
33.218 + return NO;
33.219 + }
33.220 +
33.221 + [*o addObject:v];
33.222 +
33.223 + skipWhitespace(c);
33.224 + if (*c == ',' && c++) {
33.225 + skipWhitespace(c);
33.226 + if (*c == ']') {
33.227 + [self addErrorWithCode:ETRAILCOMMA description: @"Trailing comma disallowed in array"];
33.228 + return NO;
33.229 + }
33.230 + }
33.231 + }
33.232 +
33.233 + [self addErrorWithCode:EEOF description: @"End of input while parsing array"];
33.234 + return NO;
33.235 +}
33.236 +
33.237 +- (BOOL)scanRestOfDictionary:(NSMutableDictionary **)o
33.238 +{
33.239 + if (maxDepth && ++depth > maxDepth) {
33.240 + [self addErrorWithCode:EDEPTH description: @"Nested too deep"];
33.241 + return NO;
33.242 + }
33.243 +
33.244 + *o = [NSMutableDictionary dictionaryWithCapacity:7];
33.245 +
33.246 + for (; *c ;) {
33.247 + id k, v;
33.248 +
33.249 + skipWhitespace(c);
33.250 + if (*c == '}' && c++) {
33.251 + depth--;
33.252 + return YES;
33.253 + }
33.254 +
33.255 + if (!(*c == '\"' && c++ && [self scanRestOfString:&k])) {
33.256 + [self addErrorWithCode:EPARSE description: @"Object key string expected"];
33.257 + return NO;
33.258 + }
33.259 +
33.260 + skipWhitespace(c);
33.261 + if (*c != ':') {
33.262 + [self addErrorWithCode:EPARSE description: @"Expected ':' separating key and value"];
33.263 + return NO;
33.264 + }
33.265 +
33.266 + c++;
33.267 + if (![self scanValue:&v]) {
33.268 + NSString *string = [NSString stringWithFormat:@"Object value expected for key: %@", k];
33.269 + [self addErrorWithCode:EPARSE description: string];
33.270 + return NO;
33.271 + }
33.272 +
33.273 + [*o setObject:v forKey:k];
33.274 +
33.275 + skipWhitespace(c);
33.276 + if (*c == ',' && c++) {
33.277 + skipWhitespace(c);
33.278 + if (*c == '}') {
33.279 + [self addErrorWithCode:ETRAILCOMMA description: @"Trailing comma disallowed in object"];
33.280 + return NO;
33.281 + }
33.282 + }
33.283 + }
33.284 +
33.285 + [self addErrorWithCode:EEOF description: @"End of input while parsing object"];
33.286 + return NO;
33.287 +}
33.288 +
33.289 +- (BOOL)scanRestOfString:(NSMutableString **)o
33.290 +{
33.291 + *o = [NSMutableString stringWithCapacity:16];
33.292 + do {
33.293 + // First see if there's a portion we can grab in one go.
33.294 + // Doing this caused a massive speedup on the long string.
33.295 + size_t len = strcspn(c, ctrl);
33.296 + if (len) {
33.297 + // check for
33.298 + id t = [[NSString alloc] initWithBytesNoCopy:(char*)c
33.299 + length:len
33.300 + encoding:NSUTF8StringEncoding
33.301 + freeWhenDone:NO];
33.302 + if (t) {
33.303 + [*o appendString:t];
33.304 + [t release];
33.305 + c += len;
33.306 + }
33.307 + }
33.308 +
33.309 + if (*c == '"') {
33.310 + c++;
33.311 + return YES;
33.312 +
33.313 + } else if (*c == '\\') {
33.314 + unichar uc = *++c;
33.315 + switch (uc) {
33.316 + case '\\':
33.317 + case '/':
33.318 + case '"':
33.319 + break;
33.320 +
33.321 + case 'b': uc = '\b'; break;
33.322 + case 'n': uc = '\n'; break;
33.323 + case 'r': uc = '\r'; break;
33.324 + case 't': uc = '\t'; break;
33.325 + case 'f': uc = '\f'; break;
33.326 +
33.327 + case 'u':
33.328 + c++;
33.329 + if (![self scanUnicodeChar:&uc]) {
33.330 + [self addErrorWithCode:EUNICODE description: @"Broken unicode character"];
33.331 + return NO;
33.332 + }
33.333 + c--; // hack.
33.334 + break;
33.335 + default:
33.336 + [self addErrorWithCode:EESCAPE description: [NSString stringWithFormat:@"Illegal escape sequence '0x%x'", uc]];
33.337 + return NO;
33.338 + break;
33.339 + }
33.340 + CFStringAppendCharacters((CFMutableStringRef)*o, &uc, 1);
33.341 + c++;
33.342 +
33.343 + } else if (*c < 0x20) {
33.344 + [self addErrorWithCode:ECTRL description: [NSString stringWithFormat:@"Unescaped control character '0x%x'", *c]];
33.345 + return NO;
33.346 +
33.347 + } else {
33.348 + NSLog(@"should not be able to get here");
33.349 + }
33.350 + } while (*c);
33.351 +
33.352 + [self addErrorWithCode:EEOF description:@"Unexpected EOF while parsing string"];
33.353 + return NO;
33.354 +}
33.355 +
33.356 +- (BOOL)scanUnicodeChar:(unichar *)x
33.357 +{
33.358 + unichar hi, lo;
33.359 +
33.360 + if (![self scanHexQuad:&hi]) {
33.361 + [self addErrorWithCode:EUNICODE description: @"Missing hex quad"];
33.362 + return NO;
33.363 + }
33.364 +
33.365 + if (hi >= 0xd800) { // high surrogate char?
33.366 + if (hi < 0xdc00) { // yes - expect a low char
33.367 +
33.368 + if (!(*c == '\\' && ++c && *c == 'u' && ++c && [self scanHexQuad:&lo])) {
33.369 + [self addErrorWithCode:EUNICODE description: @"Missing low character in surrogate pair"];
33.370 + return NO;
33.371 + }
33.372 +
33.373 + if (lo < 0xdc00 || lo >= 0xdfff) {
33.374 + [self addErrorWithCode:EUNICODE description:@"Invalid low surrogate char"];
33.375 + return NO;
33.376 + }
33.377 +
33.378 + hi = (hi - 0xd800) * 0x400 + (lo - 0xdc00) + 0x10000;
33.379 +
33.380 + } else if (hi < 0xe000) {
33.381 + [self addErrorWithCode:EUNICODE description:@"Invalid high character in surrogate pair"];
33.382 + return NO;
33.383 + }
33.384 + }
33.385 +
33.386 + *x = hi;
33.387 + return YES;
33.388 +}
33.389 +
33.390 +- (BOOL)scanHexQuad:(unichar *)x
33.391 +{
33.392 + *x = 0;
33.393 + for (int i = 0; i < 4; i++) {
33.394 + unichar uc = *c;
33.395 + c++;
33.396 + int d = (uc >= '0' && uc <= '9')
33.397 + ? uc - '0' : (uc >= 'a' && uc <= 'f')
33.398 + ? (uc - 'a' + 10) : (uc >= 'A' && uc <= 'F')
33.399 + ? (uc - 'A' + 10) : -1;
33.400 + if (d == -1) {
33.401 + [self addErrorWithCode:EUNICODE description:@"Missing hex digit in quad"];
33.402 + return NO;
33.403 + }
33.404 + *x *= 16;
33.405 + *x += d;
33.406 + }
33.407 + return YES;
33.408 +}
33.409 +
33.410 +- (BOOL)scanNumber:(NSNumber **)o
33.411 +{
33.412 + const char *ns = c;
33.413 +
33.414 + // The logic to test for validity of the number formatting is relicensed
33.415 + // from JSON::XS with permission from its author Marc Lehmann.
33.416 + // (Available at the CPAN: http://search.cpan.org/dist/JSON-XS/ .)
33.417 +
33.418 + if ('-' == *c)
33.419 + c++;
33.420 +
33.421 + if ('0' == *c && c++) {
33.422 + if (isdigit(*c)) {
33.423 + [self addErrorWithCode:EPARSENUM description: @"Leading 0 disallowed in number"];
33.424 + return NO;
33.425 + }
33.426 +
33.427 + } else if (!isdigit(*c) && c != ns) {
33.428 + [self addErrorWithCode:EPARSENUM description: @"No digits after initial minus"];
33.429 + return NO;
33.430 +
33.431 + } else {
33.432 + skipDigits(c);
33.433 + }
33.434 +
33.435 + // Fractional part
33.436 + if ('.' == *c && c++) {
33.437 +
33.438 + if (!isdigit(*c)) {
33.439 + [self addErrorWithCode:EPARSENUM description: @"No digits after decimal point"];
33.440 + return NO;
33.441 + }
33.442 + skipDigits(c);
33.443 + }
33.444 +
33.445 + // Exponential part
33.446 + if ('e' == *c || 'E' == *c) {
33.447 + c++;
33.448 +
33.449 + if ('-' == *c || '+' == *c)
33.450 + c++;
33.451 +
33.452 + if (!isdigit(*c)) {
33.453 + [self addErrorWithCode:EPARSENUM description: @"No digits after exponent"];
33.454 + return NO;
33.455 + }
33.456 + skipDigits(c);
33.457 + }
33.458 +
33.459 + id str = [[NSString alloc] initWithBytesNoCopy:(char*)ns
33.460 + length:c - ns
33.461 + encoding:NSUTF8StringEncoding
33.462 + freeWhenDone:NO];
33.463 + [str autorelease];
33.464 + if (str && (*o = [NSDecimalNumber decimalNumberWithString:str]))
33.465 + return YES;
33.466 +
33.467 + [self addErrorWithCode:EPARSENUM description: @"Failed creating decimal instance"];
33.468 + return NO;
33.469 +}
33.470 +
33.471 +- (BOOL)scanIsAtEnd
33.472 +{
33.473 + skipWhitespace(c);
33.474 + return !*c;
33.475 +}
33.476 +
33.477 +
33.478 +@end
34.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
34.2 +++ b/JSON/SBJsonWriter.h Thu Dec 24 00:14:02 2009 +0000
34.3 @@ -0,0 +1,129 @@
34.4 +/*
34.5 + Copyright (C) 2009 Stig Brautaset. All rights reserved.
34.6 +
34.7 + Redistribution and use in source and binary forms, with or without
34.8 + modification, are permitted provided that the following conditions are met:
34.9 +
34.10 + * Redistributions of source code must retain the above copyright notice, this
34.11 + list of conditions and the following disclaimer.
34.12 +
34.13 + * Redistributions in binary form must reproduce the above copyright notice,
34.14 + this list of conditions and the following disclaimer in the documentation
34.15 + and/or other materials provided with the distribution.
34.16 +
34.17 + * Neither the name of the author nor the names of its contributors may be used
34.18 + to endorse or promote products derived from this software without specific
34.19 + prior written permission.
34.20 +
34.21 + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
34.22 + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
34.23 + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
34.24 + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
34.25 + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
34.26 + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
34.27 + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
34.28 + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
34.29 + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
34.30 + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34.31 + */
34.32 +
34.33 +#import <Foundation/Foundation.h>
34.34 +#import "SBJsonBase.h"
34.35 +
34.36 +/**
34.37 + @brief Options for the writer class.
34.38 +
34.39 + This exists so the SBJSON facade can implement the options in the writer without having to re-declare them.
34.40 + */
34.41 +@protocol SBJsonWriter
34.42 +
34.43 +/**
34.44 + @brief Whether we are generating human-readable (multiline) JSON.
34.45 +
34.46 + Set whether or not to generate human-readable JSON. The default is NO, which produces
34.47 + JSON without any whitespace. (Except inside strings.) If set to YES, generates human-readable
34.48 + JSON with linebreaks after each array value and dictionary key/value pair, indented two
34.49 + spaces per nesting level.
34.50 + */
34.51 +@property BOOL humanReadable;
34.52 +
34.53 +/**
34.54 + @brief Whether or not to sort the dictionary keys in the output.
34.55 +
34.56 + If this is set to YES, the dictionary keys in the JSON output will be in sorted order.
34.57 + (This is useful if you need to compare two structures, for example.) The default is NO.
34.58 + */
34.59 +@property BOOL sortKeys;
34.60 +
34.61 +/**
34.62 + @brief Return JSON representation (or fragment) for the given object.
34.63 +
34.64 + Returns a string containing JSON representation of the passed in value, or nil on error.
34.65 + If nil is returned and @p error is not NULL, @p *error can be interrogated to find the cause of the error.
34.66 +
34.67 + @param value any instance that can be represented as a JSON fragment
34.68 +
34.69 + */
34.70 +- (NSString*)stringWithObject:(id)value;
34.71 +
34.72 +@end
34.73 +
34.74 +
34.75 +/**
34.76 + @brief The JSON writer class.
34.77 +
34.78 + Objective-C types are mapped to JSON types in the following way:
34.79 +
34.80 + @li NSNull -> Null
34.81 + @li NSString -> String
34.82 + @li NSArray -> Array
34.83 + @li NSDictionary -> Object
34.84 + @li NSNumber (-initWithBool:) -> Boolean
34.85 + @li NSNumber -> Number
34.86 +
34.87 + In JSON the keys of an object must be strings. NSDictionary keys need
34.88 + not be, but attempting to convert an NSDictionary with non-string keys
34.89 + into JSON will throw an exception.
34.90 +
34.91 + NSNumber instances created with the +initWithBool: method are
34.92 + converted into the JSON boolean "true" and "false" values, and vice
34.93 + versa. Any other NSNumber instances are converted to a JSON number the
34.94 + way you would expect.
34.95 +
34.96 + */
34.97 +@interface SBJsonWriter : SBJsonBase <SBJsonWriter> {
34.98 +
34.99 +@private
34.100 + BOOL sortKeys, humanReadable;
34.101 +}
34.102 +
34.103 +@end
34.104 +
34.105 +// don't use - exists for backwards compatibility. Will be removed in 2.3.
34.106 +@interface SBJsonWriter (Private)
34.107 +- (NSString*)stringWithFragment:(id)value;
34.108 +@end
34.109 +
34.110 +/**
34.111 + @brief Allows generation of JSON for otherwise unsupported classes.
34.112 +
34.113 + If you have a custom class that you want to create a JSON representation for you can implement
34.114 + this method in your class. It should return a representation of your object defined
34.115 + in terms of objects that can be translated into JSON. For example, a Person
34.116 + object might implement it like this:
34.117 +
34.118 + @code
34.119 + - (id)jsonProxyObject {
34.120 + return [NSDictionary dictionaryWithObjectsAndKeys:
34.121 + name, @"name",
34.122 + phone, @"phone",
34.123 + email, @"email",
34.124 + nil];
34.125 + }
34.126 + @endcode
34.127 +
34.128 + */
34.129 +@interface NSObject (SBProxyForJson)
34.130 +- (id)proxyForJson;
34.131 +@end
34.132 +
35.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
35.2 +++ b/JSON/SBJsonWriter.m Thu Dec 24 00:14:02 2009 +0000
35.3 @@ -0,0 +1,228 @@
35.4 +/*
35.5 + Copyright (C) 2009 Stig Brautaset. All rights reserved.
35.6 +
35.7 + Redistribution and use in source and binary forms, with or without
35.8 + modification, are permitted provided that the following conditions are met:
35.9 +
35.10 + * Redistributions of source code must retain the above copyright notice, this
35.11 + list of conditions and the following disclaimer.
35.12 +
35.13 + * Redistributions in binary form must reproduce the above copyright notice,
35.14 + this list of conditions and the following disclaimer in the documentation
35.15 + and/or other materials provided with the distribution.
35.16 +
35.17 + * Neither the name of the author nor the names of its contributors may be used
35.18 + to endorse or promote products derived from this software without specific
35.19 + prior written permission.
35.20 +
35.21 + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
35.22 + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
35.23 + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
35.24 + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
35.25 + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
35.26 + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
35.27 + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
35.28 + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
35.29 + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
35.30 + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
35.31 + */
35.32 +
35.33 +#import "SBJsonWriter.h"
35.34 +
35.35 +@interface SBJsonWriter ()
35.36 +
35.37 +- (BOOL)appendValue:(id)fragment into:(NSMutableString*)json;
35.38 +- (BOOL)appendArray:(NSArray*)fragment into:(NSMutableString*)json;
35.39 +- (BOOL)appendDictionary:(NSDictionary*)fragment into:(NSMutableString*)json;
35.40 +- (BOOL)appendString:(NSString*)fragment into:(NSMutableString*)json;
35.41 +
35.42 +- (NSString*)indent;
35.43 +
35.44 +@end
35.45 +
35.46 +@implementation SBJsonWriter
35.47 +
35.48 +@synthesize sortKeys;
35.49 +@synthesize humanReadable;
35.50 +
35.51 +/**
35.52 + @deprecated This exists in order to provide fragment support in older APIs in one more version.
35.53 + It should be removed in the next major version.
35.54 + */
35.55 +- (NSString*)stringWithFragment:(id)value {
35.56 + [self clearErrorTrace];
35.57 + depth = 0;
35.58 + NSMutableString *json = [NSMutableString stringWithCapacity:128];
35.59 +
35.60 + if ([self appendValue:value into:json])
35.61 + return json;
35.62 +
35.63 + return nil;
35.64 +}
35.65 +
35.66 +
35.67 +- (NSString*)stringWithObject:(id)value {
35.68 +
35.69 + if ([value isKindOfClass:[NSDictionary class]] || [value isKindOfClass:[NSArray class]]) {
35.70 + return [self stringWithFragment:value];
35.71 + }
35.72 +
35.73 + [self clearErrorTrace];
35.74 + [self addErrorWithCode:EFRAGMENT description:@"Not valid type for JSON"];
35.75 + return nil;
35.76 +}
35.77 +
35.78 +
35.79 +- (NSString*)indent {
35.80 + return [@"\n" stringByPaddingToLength:1 + 2 * depth withString:@" " startingAtIndex:0];
35.81 +}
35.82 +
35.83 +- (BOOL)appendValue:(id)fragment into:(NSMutableString*)json {
35.84 + if ([fragment isKindOfClass:[NSDictionary class]]) {
35.85 + if (![self appendDictionary:fragment into:json])
35.86 + return NO;
35.87 +
35.88 + } else if ([fragment isKindOfClass:[NSArray class]]) {
35.89 + if (![self appendArray:fragment into:json])
35.90 + return NO;
35.91 +
35.92 + } else if ([fragment isKindOfClass:[NSString class]]) {
35.93 + if (![self appendString:fragment into:json])
35.94 + return NO;
35.95 +
35.96 + } else if ([fragment isKindOfClass:[NSNumber class]]) {
35.97 + if ('c' == *[fragment objCType])
35.98 + [json appendString:[fragment boolValue] ? @"true" : @"false"];
35.99 + else
35.100 + [json appendString:[fragment stringValue]];
35.101 +
35.102 + } else if ([fragment isKindOfClass:[NSNull class]]) {
35.103 + [json appendString:@"null"];
35.104 + } else if ([fragment respondsToSelector:@selector(proxyForJson)]) {
35.105 + [self appendValue:[fragment proxyForJson] into:json];
35.106 +
35.107 + } else {
35.108 + [self addErrorWithCode:EUNSUPPORTED description:[NSString stringWithFormat:@"JSON serialisation not supported for %@", [fragment class]]];
35.109 + return NO;
35.110 + }
35.111 + return YES;
35.112 +}
35.113 +
35.114 +- (BOOL)appendArray:(NSArray*)fragment into:(NSMutableString*)json {
35.115 + if (maxDepth && ++depth > maxDepth) {
35.116 + [self addErrorWithCode:EDEPTH description: @"Nested too deep"];
35.117 + return NO;
35.118 + }
35.119 + [json appendString:@"["];
35.120 +
35.121 + BOOL addComma = NO;
35.122 + for (id value in fragment) {
35.123 + if (addComma)
35.124 + [json appendString:@","];
35.125 + else
35.126 + addComma = YES;
35.127 +
35.128 + if ([self humanReadable])
35.129 + [json appendString:[self indent]];
35.130 +
35.131 + if (![self appendValue:value into:json]) {
35.132 + return NO;
35.133 + }
35.134 + }
35.135 +
35.136 + depth--;
35.137 + if ([self humanReadable] && [fragment count])
35.138 + [json appendString:[self indent]];
35.139 + [json appendString:@"]"];
35.140 + return YES;
35.141 +}
35.142 +
35.143 +- (BOOL)appendDictionary:(NSDictionary*)fragment into:(NSMutableString*)json {
35.144 + if (maxDepth && ++depth > maxDepth) {
35.145 + [self addErrorWithCode:EDEPTH description: @"Nested too deep"];
35.146 + return NO;
35.147 + }
35.148 + [json appendString:@"{"];
35.149 +
35.150 + NSString *colon = [self humanReadable] ? @" : " : @":";
35.151 + BOOL addComma = NO;
35.152 + NSArray *keys = [fragment allKeys];
35.153 + if (self.sortKeys)
35.154 + keys = [keys sortedArrayUsingSelector:@selector(compare:)];
35.155 +
35.156 + for (id value in keys) {
35.157 + if (addComma)
35.158 + [json appendString:@","];
35.159 + else
35.160 + addComma = YES;
35.161 +
35.162 + if ([self humanReadable])
35.163 + [json appendString:[self indent]];
35.164 +
35.165 + if (![value isKindOfClass:[NSString class]]) {
35.166 + [self addErrorWithCode:EUNSUPPORTED description: @"JSON object key must be string"];
35.167 + return NO;
35.168 + }
35.169 +
35.170 + if (![self appendString:value into:json])
35.171 + return NO;
35.172 +
35.173 + [json appendString:colon];
35.174 + if (![self appendValue:[fragment objectForKey:value] into:json]) {
35.175 + [self addErrorWithCode:EUNSUPPORTED description:[NSString stringWithFormat:@"Unsupported value for key %@ in object", value]];
35.176 + return NO;
35.177 + }
35.178 + }
35.179 +
35.180 + depth--;
35.181 + if ([self humanReadable] && [fragment count])
35.182 + [json appendString:[self indent]];
35.183 + [json appendString:@"}"];
35.184 + return YES;
35.185 +}
35.186 +
35.187 +- (BOOL)appendString:(NSString*)fragment into:(NSMutableString*)json {
35.188 +
35.189 + static NSMutableCharacterSet *kEscapeChars;
35.190 + if( ! kEscapeChars ) {
35.191 + kEscapeChars = [[NSMutableCharacterSet characterSetWithRange: NSMakeRange(0,32)] retain];
35.192 + [kEscapeChars addCharactersInString: @"\"\\"];
35.193 + }
35.194 +
35.195 + [json appendString:@"\""];
35.196 +
35.197 + NSRange esc = [fragment rangeOfCharacterFromSet:kEscapeChars];
35.198 + if ( !esc.length ) {
35.199 + // No special chars -- can just add the raw string:
35.200 + [json appendString:fragment];
35.201 +
35.202 + } else {
35.203 + NSUInteger length = [fragment length];
35.204 + for (NSUInteger i = 0; i < length; i++) {
35.205 + unichar uc = [fragment characterAtIndex:i];
35.206 + switch (uc) {
35.207 + case '"': [json appendString:@"\\\""]; break;
35.208 + case '\\': [json appendString:@"\\\\"]; break;
35.209 + case '\t': [json appendString:@"\\t"]; break;
35.210 + case '\n': [json appendString:@"\\n"]; break;
35.211 + case '\r': [json appendString:@"\\r"]; break;
35.212 + case '\b': [json appendString:@"\\b"]; break;
35.213 + case '\f': [json appendString:@"\\f"]; break;
35.214 + default:
35.215 + if (uc < 0x20) {
35.216 + [json appendFormat:@"\\u%04x", uc];
35.217 + } else {
35.218 + CFStringAppendCharacters((CFMutableStringRef)json, &uc, 1);
35.219 + }
35.220 + break;
35.221 +
35.222 + }
35.223 + }
35.224 + }
35.225 +
35.226 + [json appendString:@"\""];
35.227 + return YES;
35.228 +}
35.229 +
35.230 +
35.231 +@end