Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

example project

  • Loading branch information...
commit f5de9ec41773a4a272a98bc43d9f63dd68ff52fa 1 parent 2eca700
Michael Mayo authored
Showing with 9,955 additions and 0 deletions.
  1. +3 −0  .gitignore
  2. +28 −0 Example/ASIAuthenticationDialog.h
  3. +236 −0 Example/ASIAuthenticationDialog.m
  4. +76 −0 Example/ASIFormDataRequest.h
  5. +358 −0 Example/ASIFormDataRequest.m
  6. +769 −0 Example/ASIHTTPRequest.h
  7. +3,764 −0 Example/ASIHTTPRequest.m
  8. +55 −0 Example/ASIHTTPRequestConfig.h
  9. +33 −0 Example/ASIHTTPRequestDelegate.h
  10. +26 −0 Example/ASIInputStream.h
  11. +136 −0 Example/ASIInputStream.m
  12. +16 −0 Example/ASINSStringAdditions.h
  13. +28 −0 Example/ASINSStringAdditions.m
  14. +110 −0 Example/ASINetworkQueue.h
  15. +339 −0 Example/ASINetworkQueue.m
  16. +38 −0 Example/ASIProgressDelegate.h
  17. +25 −0 Example/AtomParser.h
  18. +105 −0 Example/AtomParser.m
  19. +36 −0 Example/Classes/DetailViewController.h
  20. +295 −0 Example/Classes/DetailViewController.m
  21. +31 −0 Example/Classes/ExampleAppDelegate.h
  22. +52 −0 Example/Classes/ExampleAppDelegate.m
  23. +19 −0 Example/Classes/RootViewController.h
  24. +161 −0 Example/Classes/RootViewController.m
  25. +498 −0 Example/DetailView.xib
  26. +37 −0 Example/Example-Info.plist
  27. +382 −0 Example/Example.xcodeproj/project.pbxproj
  28. +14 −0 Example/Example_Prefix.pch
  29. +33 −0 Example/FeedItem.h
  30. +30 −0 Example/FeedItem.m
  31. +570 −0 Example/FeedItemCell.xib
  32. +579 −0 Example/MainWindow.xib
  33. 0  Example/README
  34. +554 −0 Example/RSSEmptyCell.xib
  35. +25 −0 Example/RSSParser.h
  36. +115 −0 Example/RSSParser.m
  37. +88 −0 Example/Reachability.h
  38. +274 −0 Example/Reachability.m
  39. +17 −0 Example/main.m
  40. BIN  Example/rssgrey337.png
3  .gitignore
View
@@ -0,0 +1,3 @@
+Example/build
+Example/Example.xcodeproj/mike.pbxuser
+Example/Example.xcodeproj/mike.mode1v3
28 Example/ASIAuthenticationDialog.h
View
@@ -0,0 +1,28 @@
+//
+// ASIAuthenticationDialog.h
+// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest
+//
+// Created by Ben Copsey on 21/08/2009.
+// Copyright 2009 All-Seeing Interactive. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+@class ASIHTTPRequest;
+
+typedef enum _ASIAuthenticationType {
+ ASIStandardAuthenticationType = 0,
+ ASIProxyAuthenticationType = 1
+} ASIAuthenticationType;
+
+@interface ASIAuthenticationDialog : NSObject <UIActionSheetDelegate, UITableViewDelegate, UITableViewDataSource> {
+ ASIHTTPRequest *request;
+ UIActionSheet *loginDialog;
+ ASIAuthenticationType type;
+}
++ (void)presentAuthenticationDialogForRequest:(ASIHTTPRequest *)request;
++ (void)presentProxyAuthenticationDialogForRequest:(ASIHTTPRequest *)request;
+
+@property (retain) ASIHTTPRequest *request;
+@property (retain) UIActionSheet *loginDialog;
+@property (assign) ASIAuthenticationType type;
+@end
236 Example/ASIAuthenticationDialog.m
View
@@ -0,0 +1,236 @@
+//
+// ASIAuthenticationDialog.m
+// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest
+//
+// Created by Ben Copsey on 21/08/2009.
+// Copyright 2009 All-Seeing Interactive. All rights reserved.
+//
+
+#import "ASIAuthenticationDialog.h"
+#import "ASIHTTPRequest.h"
+
+ASIAuthenticationDialog *sharedDialog = nil;
+NSLock *dialogLock = nil;
+
+@interface ASIAuthenticationDialog ()
+- (void)show;
+@end
+
+@implementation ASIAuthenticationDialog
+
++ (void)initialize
+{
+ if (self == [ASIAuthenticationDialog class]) {
+ dialogLock = [[NSLock alloc] init];
+ }
+}
+
++ (void)presentProxyAuthenticationDialogForRequest:(ASIHTTPRequest *)request
+{
+ [dialogLock lock];
+ [sharedDialog release];
+ sharedDialog = [[self alloc] init];
+ [sharedDialog setRequest:request];
+ [sharedDialog setType:ASIProxyAuthenticationType];
+ [sharedDialog show];
+ [dialogLock unlock];
+}
+
++ (void)presentAuthenticationDialogForRequest:(ASIHTTPRequest *)request
+{
+ [dialogLock lock];
+ [sharedDialog release];
+ sharedDialog = [[self alloc] init];
+ [sharedDialog setRequest:request];
+ [sharedDialog show];
+ [dialogLock unlock];
+
+}
+
+- (void)show
+{
+ // Create an action sheet to show the login dialog
+ [self setLoginDialog:[[[UIActionSheet alloc] init] autorelease]];
+ [[self loginDialog] setActionSheetStyle:UIActionSheetStyleBlackOpaque];
+ [[self loginDialog] setDelegate:self];
+
+ // We show the login form in a table view, similar to Safari's authentication dialog
+ UITableView *table = [[[UITableView alloc] initWithFrame:CGRectMake(0,80,320,480) style:UITableViewStyleGrouped] autorelease];
+ [table setDelegate:self];
+ [table setDataSource:self];
+ [[self loginDialog] addSubview:table];
+ [[self loginDialog] showInView:[[[UIApplication sharedApplication] windows] objectAtIndex:0]];
+ [[self loginDialog] setFrame:CGRectMake(0,0,320,480)];
+
+ // Setup the title (Couldn't figure out how to put this in the same toolbar as the buttons)
+ UIToolbar *titleBar = [[[UIToolbar alloc] initWithFrame:CGRectMake(0,0,320,30)] autorelease];
+ UILabel *label = [[[UILabel alloc] initWithFrame:CGRectMake(10,0,300,30)] autorelease];
+ if ([self type] == ASIProxyAuthenticationType) {
+ [label setText:@"Login to this secure proxy server."];
+ } else {
+ [label setText:@"Login to this secure server."];
+ }
+ [label setTextColor:[UIColor blackColor]];
+ [label setFont:[UIFont systemFontOfSize:13.0]];
+ [label setShadowColor:[UIColor colorWithRed:1 green:1 blue:1 alpha:0.5]];
+ [label setShadowOffset:CGSizeMake(0, 1.0)];
+ [label setOpaque:NO];
+ [label setBackgroundColor:nil];
+ [label setTextAlignment:UITextAlignmentCenter];
+
+ [titleBar addSubview:label];
+ [[self loginDialog] addSubview:titleBar];
+
+ // Setup the toolbar
+ UIToolbar *toolbar = [[[UIToolbar alloc] initWithFrame:CGRectMake(0,30,320,50)] autorelease];
+
+ NSMutableArray *items = [[[NSMutableArray alloc] init] autorelease];
+ UIBarButtonItem *backButton = [[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel target:self action:@selector(cancelAuthenticationFromDialog:)] autorelease];
+ [items addObject:backButton];
+
+ label = [[[UILabel alloc] initWithFrame:CGRectMake(0,0,170,50)] autorelease];
+ if ([self type] == ASIProxyAuthenticationType) {
+ [label setText:[[self request] proxyHost]];
+ } else {
+ [label setText:[[[self request] url] host]];
+ }
+ [label setTextColor:[UIColor whiteColor]];
+ [label setFont:[UIFont boldSystemFontOfSize:22.0]];
+ [label setShadowColor:[UIColor colorWithRed:0 green:0 blue:0 alpha:0.5]];
+ [label setShadowOffset:CGSizeMake(0, -1.0)];
+ [label setOpaque:NO];
+ [label setBackgroundColor:nil];
+ [label setTextAlignment:UITextAlignmentCenter];
+
+ [toolbar addSubview:label];
+
+ UIBarButtonItem *labelButton = [[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel target:nil action:nil] autorelease];
+ [labelButton setCustomView:label];
+ [items addObject:labelButton];
+ [items addObject:[[[UIBarButtonItem alloc] initWithTitle:@"Login" style:UIBarButtonItemStyleDone target:self action:@selector(loginWithCredentialsFromDialog:)] autorelease]];
+ [toolbar setItems:items];
+
+ [[self loginDialog] addSubview:toolbar];
+
+ // Force reload the table content, and focus the first field to show the keyboard
+ [table reloadData];
+ [[[[table cellForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]] subviews] objectAtIndex:2] becomeFirstResponder];
+
+}
+
+- (void)cancelAuthenticationFromDialog:(id)sender
+{
+ [[self request] cancelAuthentication];
+ [[self loginDialog] dismissWithClickedButtonIndex:0 animated:YES];
+}
+
+- (void)loginWithCredentialsFromDialog:(id)sender
+{
+ NSString *username = [[[[[[[self loginDialog] subviews] objectAtIndex:0] cellForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]] subviews] objectAtIndex:2] text];
+ NSString *password = [[[[[[[self loginDialog] subviews] objectAtIndex:0] cellForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:1]] subviews] objectAtIndex:2] text];
+
+ if ([self type] == ASIProxyAuthenticationType) {
+ [[self request] setProxyUsername:username];
+ [[self request] setProxyPassword:password];
+ } else {
+ [[self request] setUsername:username];
+ [[self request] setPassword:password];
+ }
+
+ // Handle NTLM domains
+ NSString *scheme = ([self type] == ASIStandardAuthenticationType) ? [[self request] authenticationScheme] : [[self request] proxyAuthenticationScheme];
+ if ([scheme isEqualToString:(NSString *)kCFHTTPAuthenticationSchemeNTLM]) {
+ NSString *domain = [[[[[[[self loginDialog] subviews] objectAtIndex:0] cellForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:2]] subviews] objectAtIndex:2] text];
+ if ([self type] == ASIProxyAuthenticationType) {
+ [[self request] setProxyDomain:domain];
+ } else {
+ [[self request] setDomain:domain];
+ }
+ }
+
+ [[self loginDialog] dismissWithClickedButtonIndex:1 animated:YES];
+ [[self request] retryUsingSuppliedCredentials];
+}
+
+
+- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
+{
+ NSString *scheme = ([self type] == ASIStandardAuthenticationType) ? [[self request] authenticationScheme] : [[self request] proxyAuthenticationScheme];
+ if ([scheme isEqualToString:(NSString *)kCFHTTPAuthenticationSchemeNTLM]) {
+ return 3;
+ }
+ return 2;
+}
+
+- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section
+{
+ if (section == [self numberOfSectionsInTableView:tableView]-1) {
+ return 30;
+ }
+ return 0;
+}
+
+- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
+{
+ if (section == 0) {
+ return 30;
+ }
+ return 0;
+}
+
+- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
+{
+ if (section == 0) {
+ return [[self request] authenticationRealm];
+ }
+ return nil;
+}
+
+- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
+{
+#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_3_0
+ UITableViewCell *cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:nil] autorelease];
+#else
+ UITableViewCell *cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil] autorelease];
+#endif
+
+ [cell setSelectionStyle:UITableViewCellSelectionStyleNone];
+ UITextField *textField = [[[UITextField alloc] initWithFrame:CGRectMake(20,12,260,25)] autorelease];
+ [textField setAutocapitalizationType:UITextAutocapitalizationTypeNone];
+ if ([indexPath section] == 0) {
+ [textField setPlaceholder:@"User"];
+ } else if ([indexPath section] == 1) {
+ [textField setPlaceholder:@"Password"];
+ [textField setSecureTextEntry:YES];
+ } else if ([indexPath section] == 2) {
+ [textField setPlaceholder:@"Domain"];
+ }
+ [cell addSubview:textField];
+
+ return cell;
+}
+
+- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
+{
+ return 1;
+}
+
+
+- (NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section
+{
+ if (section == [self numberOfSectionsInTableView:tableView]-1) {
+ // If we're using Basic authentication and the connection is not using SSL, we'll show the plain text message
+ if ([[[self request] authenticationScheme] isEqualToString:(NSString *)kCFHTTPAuthenticationSchemeBasic] && ![[[[self request] url] scheme] isEqualToString:@"https"]) {
+ return @"Password will be sent in the clear.";
+ // We are using Digest, NTLM, or any scheme over SSL
+ } else {
+ return @"Password will be sent securely.";
+ }
+ }
+ return nil;
+}
+
+@synthesize request;
+@synthesize loginDialog;
+@synthesize type;
+@end
76 Example/ASIFormDataRequest.h
View
@@ -0,0 +1,76 @@
+//
+// ASIFormDataRequest.h
+// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest
+//
+// Created by Ben Copsey on 07/11/2008.
+// Copyright 2008-2009 All-Seeing Interactive. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+#import "ASIHTTPRequest.h"
+#import "ASIHTTPRequestConfig.h"
+
+typedef enum _ASIPostFormat {
+ ASIMultipartFormDataPostFormat = 0,
+ ASIURLEncodedPostFormat = 1
+
+} ASIPostFormat;
+
+@interface ASIFormDataRequest : ASIHTTPRequest <NSCopying> {
+
+ // Parameters that will be POSTed to the url
+ NSMutableArray *postData;
+
+ // Files that will be POSTed to the url
+ NSMutableArray *fileData;
+
+ ASIPostFormat postFormat;
+
+ NSStringEncoding stringEncoding;
+
+#if DEBUG_FORM_DATA_REQUEST
+ // Will store a string version of the request body that will be printed to the console when ASIHTTPREQUEST_DEBUG is set in GCC_PREPROCESSOR_DEFINITIONS
+ NSString *debugBodyString;
+#endif
+
+}
+
+#pragma mark utilities
+- (NSString*)encodeURL:(NSString *)string;
+
+#pragma mark setup request
+
+// Add a POST variable to the request
+- (void)addPostValue:(id <NSObject>)value forKey:(NSString *)key;
+
+// Set a POST variable for this request, clearing any others with the same key
+- (void)setPostValue:(id <NSObject>)value forKey:(NSString *)key;
+
+// Add the contents of a local file to the request
+- (void)addFile:(NSString *)filePath forKey:(NSString *)key;
+
+// Same as above, but you can specify the content-type and file name
+- (void)addFile:(id)data withFileName:(NSString *)fileName andContentType:(NSString *)contentType forKey:(NSString *)key;
+
+// Add the contents of a local file to the request, clearing any others with the same key
+- (void)setFile:(NSString *)filePath forKey:(NSString *)key;
+
+// Same as above, but you can specify the content-type and file name
+- (void)setFile:(id)data withFileName:(NSString *)fileName andContentType:(NSString *)contentType forKey:(NSString *)key;
+
+// Add the contents of an NSData object to the request
+- (void)addData:(NSData *)data forKey:(NSString *)key;
+
+// Same as above, but you can specify the content-type and file name
+- (void)addData:(id)data withFileName:(NSString *)fileName andContentType:(NSString *)contentType forKey:(NSString *)key;
+
+// Add the contents of an NSData object to the request, clearing any others with the same key
+- (void)setData:(NSData *)data forKey:(NSString *)key;
+
+// Same as above, but you can specify the content-type and file name
+- (void)setData:(id)data withFileName:(NSString *)fileName andContentType:(NSString *)contentType forKey:(NSString *)key;
+
+
+@property (assign) ASIPostFormat postFormat;
+@property (assign) NSStringEncoding stringEncoding;
+@end
358 Example/ASIFormDataRequest.m
View
@@ -0,0 +1,358 @@
+//
+// ASIFormDataRequest.m
+// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest
+//
+// Created by Ben Copsey on 07/11/2008.
+// Copyright 2008-2009 All-Seeing Interactive. All rights reserved.
+//
+
+#import "ASIFormDataRequest.h"
+
+
+// Private stuff
+@interface ASIFormDataRequest ()
+- (void)buildMultipartFormDataPostBody;
+- (void)buildURLEncodedPostBody;
+- (void)appendPostString:(NSString *)string;
+
+@property (retain) NSMutableArray *postData;
+@property (retain) NSMutableArray *fileData;
+
+#if DEBUG_FORM_DATA_REQUEST
+- (void)addToDebugBody:(NSString *)string;
+@property (retain, nonatomic) NSString *debugBodyString;
+#endif
+
+@end
+
+@implementation ASIFormDataRequest
+
+#pragma mark utilities
+- (NSString*)encodeURL:(NSString *)string
+{
+ NSString *newString = [(NSString *)CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)string, NULL, CFSTR(":/?#[]@!$ &'()*+,;=\"<>%{}|\\^~`"), CFStringConvertNSStringEncodingToEncoding([self stringEncoding])) autorelease];
+ if (newString) {
+ return newString;
+ }
+ return @"";
+}
+
+#pragma mark init / dealloc
+
++ (id)requestWithURL:(NSURL *)newURL
+{
+ return [[[self alloc] initWithURL:newURL] autorelease];
+}
+
+- (id)initWithURL:(NSURL *)newURL
+{
+ self = [super initWithURL:newURL];
+ [self setPostFormat:ASIURLEncodedPostFormat];
+ [self setStringEncoding:NSUTF8StringEncoding];
+ return self;
+}
+
+- (void)dealloc
+{
+#if DEBUG_FORM_DATA_REQUEST
+ [debugBodyString release];
+#endif
+
+ [postData release];
+ [fileData release];
+ [super dealloc];
+}
+
+#pragma mark setup request
+
+- (void)addPostValue:(id <NSObject>)value forKey:(NSString *)key
+{
+ if (![self postData]) {
+ [self setPostData:[NSMutableArray array]];
+ }
+ [[self postData] addObject:[NSDictionary dictionaryWithObjectsAndKeys:[value description],@"value",key,@"key",nil]];
+}
+
+- (void)setPostValue:(id <NSObject>)value forKey:(NSString *)key
+{
+ // Remove any existing value
+ NSUInteger i;
+ for (i=0; i<[[self postData] count]; i++) {
+ NSDictionary *val = [[self postData] objectAtIndex:i];
+ if ([[val objectForKey:@"key"] isEqualToString:key]) {
+ [[self postData] removeObjectAtIndex:i];
+ i--;
+ }
+ }
+ [self addPostValue:value forKey:key];
+}
+
+
+- (void)addFile:(NSString *)filePath forKey:(NSString *)key
+{
+ [self addFile:filePath withFileName:nil andContentType:nil forKey:key];
+}
+
+- (void)addFile:(id)data withFileName:(NSString *)fileName andContentType:(NSString *)contentType forKey:(NSString *)key
+{
+ if (![self fileData]) {
+ [self setFileData:[NSMutableArray array]];
+ }
+
+ // If data is a path to a local file
+ if ([data isKindOfClass:[NSString class]]) {
+ BOOL isDirectory = NO;
+ BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:(NSString *)data isDirectory:&isDirectory];
+ if (!fileExists || isDirectory) {
+ [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIInternalErrorWhileBuildingRequestType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"No file exists at %@",data],NSLocalizedDescriptionKey,nil]]];
+ }
+
+ // If the caller didn't specify a custom file name, we'll use the file name of the file we were passed
+ if (!fileName) {
+ fileName = [(NSString *)data lastPathComponent];
+ }
+
+ // If we were given the path to a file, and the user didn't specify a mime type, we can detect it from the file extension
+ if (!contentType) {
+ contentType = [ASIHTTPRequest mimeTypeForFileAtPath:data];
+ }
+ }
+
+ NSDictionary *fileInfo = [NSDictionary dictionaryWithObjectsAndKeys:data, @"data", contentType, @"contentType", fileName, @"fileName", key, @"key", nil];
+ [[self fileData] addObject:fileInfo];
+}
+
+
+- (void)setFile:(NSString *)filePath forKey:(NSString *)key
+{
+ [self setFile:filePath withFileName:nil andContentType:nil forKey:key];
+}
+
+- (void)setFile:(id)data withFileName:(NSString *)fileName andContentType:(NSString *)contentType forKey:(NSString *)key
+{
+ // Remove any existing value
+ NSUInteger i;
+ for (i=0; i<[[self fileData] count]; i++) {
+ NSDictionary *val = [[self fileData] objectAtIndex:i];
+ if ([[val objectForKey:@"key"] isEqualToString:key]) {
+ [[self fileData] removeObjectAtIndex:i];
+ i--;
+ }
+ }
+ [self addFile:data withFileName:fileName andContentType:contentType forKey:key];
+}
+
+- (void)addData:(NSData *)data forKey:(NSString *)key
+{
+ [self addData:data withFileName:@"file" andContentType:nil forKey:key];
+}
+
+- (void)addData:(id)data withFileName:(NSString *)fileName andContentType:(NSString *)contentType forKey:(NSString *)key
+{
+ if (![self fileData]) {
+ [self setFileData:[NSMutableArray array]];
+ }
+ if (!contentType) {
+ contentType = @"application/octet-stream";
+ }
+
+ NSDictionary *fileInfo = [NSDictionary dictionaryWithObjectsAndKeys:data, @"data", contentType, @"contentType", fileName, @"fileName", key, @"key", nil];
+ [[self fileData] addObject:fileInfo];
+}
+
+- (void)setData:(NSData *)data forKey:(NSString *)key
+{
+ [self setData:data withFileName:@"file" andContentType:nil forKey:key];
+}
+
+- (void)setData:(id)data withFileName:(NSString *)fileName andContentType:(NSString *)contentType forKey:(NSString *)key
+{
+ // Remove any existing value
+ NSUInteger i;
+ for (i=0; i<[[self fileData] count]; i++) {
+ NSDictionary *val = [[self fileData] objectAtIndex:i];
+ if ([[val objectForKey:@"key"] isEqualToString:key]) {
+ [[self fileData] removeObjectAtIndex:i];
+ i--;
+ }
+ }
+ [self addData:data withFileName:fileName andContentType:contentType forKey:key];
+}
+
+- (void)buildPostBody
+{
+ if ([self haveBuiltPostBody]) {
+ return;
+ }
+ if (![[self requestMethod] isEqualToString:@"PUT"]) {
+ [self setRequestMethod:@"POST"];
+ }
+
+#if DEBUG_FORM_DATA_REQUEST
+ [self setDebugBodyString:@""];
+#endif
+
+ if (![self postData] && ![self fileData]) {
+ [super buildPostBody];
+ return;
+ }
+ if ([[self fileData] count] > 0) {
+ [self setShouldStreamPostDataFromDisk:YES];
+ }
+
+ if ([self postFormat] == ASIURLEncodedPostFormat) {
+ [self buildURLEncodedPostBody];
+ } else {
+ [self buildMultipartFormDataPostBody];
+ }
+
+ [super buildPostBody];
+
+#if DEBUG_FORM_DATA_REQUEST
+ NSLog(@"%@",[self debugBodyString]);
+ [self setDebugBodyString:nil];
+#endif
+}
+
+
+- (void)buildMultipartFormDataPostBody
+{
+#if DEBUG_FORM_DATA_REQUEST
+ [self addToDebugBody:@"\r\n==== Building a multipart/form-data body ====\r\n"];
+#endif
+
+ NSString *charset = (NSString *)CFStringConvertEncodingToIANACharSetName(CFStringConvertNSStringEncodingToEncoding([self stringEncoding]));
+
+ // 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.
+ NSString *stringBoundary = @"0xKhTmLbOuNdArY";
+
+ [self addRequestHeader:@"Content-Type" value:[NSString stringWithFormat:@"multipart/form-data; charset=%@; boundary=%@", charset, stringBoundary]];
+
+ [self appendPostString:[NSString stringWithFormat:@"--%@\r\n",stringBoundary]];
+
+ // Adds post data
+ NSString *endItemBoundary = [NSString stringWithFormat:@"\r\n--%@\r\n",stringBoundary];
+ NSUInteger i=0;
+ for (NSDictionary *val in [self postData]) {
+ [self appendPostString:[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n\r\n",[val objectForKey:@"key"]]];
+ [self appendPostString:[val objectForKey:@"value"]];
+ i++;
+ if (i != [[self postData] count] || [[self fileData] count] > 0) { //Only add the boundary if this is not the last item in the post body
+ [self appendPostString:endItemBoundary];
+ }
+ }
+
+ // Adds files to upload
+ i=0;
+ for (NSDictionary *val in [self fileData]) {
+
+ [self appendPostString:[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\"%@\"\r\n", [val objectForKey:@"key"], [val objectForKey:@"fileName"]]];
+ [self appendPostString:[NSString stringWithFormat:@"Content-Type: %@\r\n\r\n", [val objectForKey:@"contentType"]]];
+
+ id data = [val objectForKey:@"data"];
+ if ([data isKindOfClass:[NSString class]]) {
+ [self appendPostDataFromFile:data];
+ } else {
+ [self appendPostData:data];
+ }
+ i++;
+ // Only add the boundary if this is not the last item in the post body
+ if (i != [[self fileData] count]) {
+ [self appendPostString:endItemBoundary];
+ }
+ }
+
+ [self appendPostString:[NSString stringWithFormat:@"\r\n--%@--\r\n",stringBoundary]];
+
+#if DEBUG_FORM_DATA_REQUEST
+ [self addToDebugBody:@"==== End of multipart/form-data body ====\r\n"];
+#endif
+}
+
+- (void)buildURLEncodedPostBody
+{
+
+ // We can't post binary data using application/x-www-form-urlencoded
+ if ([[self fileData] count] > 0) {
+ [self setPostFormat:ASIMultipartFormDataPostFormat];
+ [self buildMultipartFormDataPostBody];
+ return;
+ }
+
+#if DEBUG_FORM_DATA_REQUEST
+ [self addToDebugBody:@"\r\n==== Building an application/x-www-form-urlencoded body ====\r\n"];
+#endif
+
+
+ NSString *charset = (NSString *)CFStringConvertEncodingToIANACharSetName(CFStringConvertNSStringEncodingToEncoding([self stringEncoding]));
+
+ [self addRequestHeader:@"Content-Type" value:[NSString stringWithFormat:@"application/x-www-form-urlencoded; charset=%@",charset]];
+
+
+ NSUInteger i=0;
+ NSUInteger count = [[self postData] count]-1;
+ for (NSDictionary *val in [self postData]) {
+ NSString *data = [NSString stringWithFormat:@"%@=%@%@", [self encodeURL:[val objectForKey:@"key"]], [self encodeURL:[val objectForKey:@"value"]],(i<count ? @"&" : @"")];
+ [self appendPostString:data];
+ i++;
+ }
+#if DEBUG_FORM_DATA_REQUEST
+ [self addToDebugBody:@"\r\n==== End of application/x-www-form-urlencoded body ====\r\n"];
+#endif
+}
+
+- (void)appendPostString:(NSString *)string
+{
+#if DEBUG_FORM_DATA_REQUEST
+ [self addToDebugBody:string];
+#endif
+ [super appendPostData:[string dataUsingEncoding:[self stringEncoding]]];
+}
+
+#if DEBUG_FORM_DATA_REQUEST
+- (void)appendPostData:(NSData *)data
+{
+ [self addToDebugBody:[NSString stringWithFormat:@"[%lu bytes of data]",(unsigned long)[data length]]];
+ [super appendPostData:data];
+}
+
+- (void)appendPostDataFromFile:(NSString *)file
+{
+ NSError *err = nil;
+ unsigned long long fileSize = [[[[NSFileManager defaultManager] attributesOfItemAtPath:file error:&err] objectForKey:NSFileSize] unsignedLongLongValue];
+ if (err) {
+ [self addToDebugBody:[NSString stringWithFormat:@"[Error: Failed to obtain the size of the file at '%@']",file]];
+ } else {
+ [self addToDebugBody:[NSString stringWithFormat:@"[%llu bytes of data from file '%@']",fileSize,file]];
+ }
+
+ [super appendPostDataFromFile:file];
+}
+
+- (void)addToDebugBody:(NSString *)string
+{
+ [self setDebugBodyString:[[self debugBodyString] stringByAppendingString:string]];
+}
+#endif
+
+#pragma mark NSCopying
+
+- (id)copyWithZone:(NSZone *)zone
+{
+ ASIFormDataRequest *newRequest = [super copyWithZone:zone];
+ [newRequest setPostData:[[[self postData] copyWithZone:zone] autorelease]];
+ [newRequest setFileData:[[[self fileData] copyWithZone:zone] autorelease]];
+ [newRequest setPostFormat:[self postFormat]];
+ [newRequest setStringEncoding:[self stringEncoding]];
+ [newRequest setRequestMethod:[self requestMethod]];
+ return newRequest;
+}
+
+@synthesize postData;
+@synthesize fileData;
+@synthesize postFormat;
+@synthesize stringEncoding;
+#if DEBUG_FORM_DATA_REQUEST
+@synthesize debugBodyString;
+#endif
+@end
769 Example/ASIHTTPRequest.h
View
@@ -0,0 +1,769 @@
+//
+// ASIHTTPRequest.h
+//
+// Created by Ben Copsey on 04/10/2007.
+// Copyright 2007-2010 All-Seeing Interactive. All rights reserved.
+//
+// A guide to the main features is available at:
+// http://allseeing-i.com/ASIHTTPRequest
+//
+// Portions are based on the ImageClient example from Apple:
+// See: http://developer.apple.com/samplecode/ImageClient/listing37.html
+
+#import <Foundation/Foundation.h>
+#if TARGET_OS_IPHONE
+ #import <CFNetwork/CFNetwork.h>
+#endif
+#import <stdio.h>
+#import "ASIHTTPRequestConfig.h"
+#import "ASIHTTPRequestDelegate.h"
+#import "ASIProgressDelegate.h"
+
+extern NSString *ASIHTTPRequestVersion;
+
+// Make targeting 2.2.1 more reliable
+// See: http://www.blumtnwerx.com/blog/2009/06/cross-sdk-code-hygiene-in-xcode/
+#ifndef __IPHONE_3_0
+ #define __IPHONE_3_0 30000
+#endif
+
+typedef enum _ASIAuthenticationState {
+ ASINoAuthenticationNeededYet = 0,
+ ASIHTTPAuthenticationNeeded = 1,
+ ASIProxyAuthenticationNeeded = 2
+} ASIAuthenticationState;
+
+typedef enum _ASINetworkErrorType {
+ ASIConnectionFailureErrorType = 1,
+ ASIRequestTimedOutErrorType = 2,
+ ASIAuthenticationErrorType = 3,
+ ASIRequestCancelledErrorType = 4,
+ ASIUnableToCreateRequestErrorType = 5,
+ ASIInternalErrorWhileBuildingRequestType = 6,
+ ASIInternalErrorWhileApplyingCredentialsType = 7,
+ ASIFileManagementError = 8,
+ ASITooMuchRedirectionErrorType = 9,
+ ASIUnhandledExceptionError = 10
+
+} ASINetworkErrorType;
+
+// The error domain that all errors generated by ASIHTTPRequest use
+extern NSString* const NetworkRequestErrorDomain;
+
+// You can use this number to throttle upload and download bandwidth in iPhone OS apps send or receive a large amount of data
+// This may help apps that might otherwise be rejected for inclusion into the app store for using excessive bandwidth
+// This number is not official, as far as I know there is no officially documented bandwidth limit
+extern unsigned long const ASIWWANBandwidthThrottleAmount;
+
+@interface ASIHTTPRequest : NSOperation <NSCopying> {
+
+ // The url for this operation, should include GET params in the query string where appropriate
+ NSURL *url;
+
+ // Will always contain the original url used for making the request (the value of url can change when a request is redirected)
+ NSURL *originalURL;
+
+ // The delegate, you need to manage setting and talking to your delegate in your subclasses
+ id <ASIHTTPRequestDelegate> delegate;
+
+ // Another delegate that is also notified of request status changes and progress updates
+ // Generally, you won't use this directly, but ASINetworkQueue sets itself as the queue so it can proxy updates to its own delegates
+ id <ASIHTTPRequestDelegate, ASIProgressDelegate> queue;
+
+ // HTTP method to use (GET / POST / PUT / DELETE / HEAD). Defaults to GET
+ NSString *requestMethod;
+
+ // Request body - only used when the whole body is stored in memory (shouldStreamPostDataFromDisk is false)
+ NSMutableData *postBody;
+
+ // gzipped request body used when shouldCompressRequestBody is YES
+ NSData *compressedPostBody;
+
+ // When true, post body will be streamed from a file on disk, rather than loaded into memory at once (useful for large uploads)
+ // Automatically set to true in ASIFormDataRequests when using setFile:forKey:
+ BOOL shouldStreamPostDataFromDisk;
+
+ // Path to file used to store post body (when shouldStreamPostDataFromDisk is true)
+ // You can set this yourself - useful if you want to PUT a file from local disk
+ NSString *postBodyFilePath;
+
+ // Path to a temporary file used to store a deflated post body (when shouldCompressPostBody is YES)
+ NSString *compressedPostBodyFilePath;
+
+ // 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)
+ BOOL didCreateTemporaryPostDataFile;
+
+ // Used when writing to the post body when shouldStreamPostDataFromDisk is true (via appendPostData: or appendPostDataFromFile:)
+ NSOutputStream *postBodyWriteStream;
+
+ // Used for reading from the post body when sending the request
+ NSInputStream *postBodyReadStream;
+
+ // Dictionary for custom HTTP request headers
+ NSMutableDictionary *requestHeaders;
+
+ // Set to YES when the request header dictionary has been populated, used to prevent this happening more than once
+ BOOL haveBuiltRequestHeaders;
+
+ // Will be populated with HTTP response headers from the server
+ NSDictionary *responseHeaders;
+
+ // Can be used to manually insert cookie headers to a request, but it's more likely that sessionCookies will do this for you
+ NSMutableArray *requestCookies;
+
+ // Will be populated with cookies
+ NSArray *responseCookies;
+
+ // If use useCookiePersistence is true, network requests will present valid cookies from previous requests
+ BOOL useCookiePersistence;
+
+ // If useKeychainPersistence is true, network requests will attempt to read credentials from the keychain, and will save them in the keychain when they are successfully presented
+ BOOL useKeychainPersistence;
+
+ // If useSessionPersistence is true, network requests will save credentials and reuse for the duration of the session (until clearSession is called)
+ BOOL useSessionPersistence;
+
+ // If allowCompressedResponse is true, requests will inform the server they can accept compressed data, and will automatically decompress gzipped responses. Default is true.
+ BOOL allowCompressedResponse;
+
+ // If shouldCompressRequestBody is true, the request body will be gzipped. Default is false.
+ // You will probably need to enable this feature on your webserver to make this work. Tested with apache only.
+ BOOL shouldCompressRequestBody;
+
+ // When downloadDestinationPath is set, the result of this request will be downloaded to the file at this location
+ // If downloadDestinationPath is not set, download data will be stored in memory
+ NSString *downloadDestinationPath;
+
+ //The location that files will be downloaded to. Once a download is complete, files will be decompressed (if necessary) and moved to downloadDestinationPath
+ NSString *temporaryFileDownloadPath;
+
+ // Used for writing data to a file when downloadDestinationPath is set
+ NSOutputStream *fileDownloadOutputStream;
+
+ // When the request fails or completes successfully, complete will be true
+ BOOL complete;
+
+ // If an error occurs, error will contain an NSError
+ // If error code is = ASIConnectionFailureErrorType (1, Connection failure occurred) - inspect [[error userInfo] objectForKey:NSUnderlyingErrorKey] for more information
+ NSError *error;
+
+ // Username and password used for authentication
+ NSString *username;
+ NSString *password;
+
+ // Domain used for NTLM authentication
+ NSString *domain;
+
+ // Username and password used for proxy authentication
+ NSString *proxyUsername;
+ NSString *proxyPassword;
+
+ // Domain used for NTLM proxy authentication
+ NSString *proxyDomain;
+
+ // Delegate for displaying upload progress (usually an NSProgressIndicator, but you can supply a different object and handle this yourself)
+ id <ASIProgressDelegate> uploadProgressDelegate;
+
+ // Delegate for displaying download progress (usually an NSProgressIndicator, but you can supply a different object and handle this yourself)
+ id <ASIProgressDelegate> downloadProgressDelegate;
+
+ // Whether we've seen the headers of the response yet
+ BOOL haveExaminedHeaders;
+
+ // Data we receive will be stored here. Data may be compressed unless allowCompressedResponse is false - you should use [request responseData] instead in most cases
+ NSMutableData *rawResponseData;
+
+ // Used for sending and receiving data
+ CFHTTPMessageRef request;
+ NSInputStream *readStream;
+
+ // Used for authentication
+ CFHTTPAuthenticationRef requestAuthentication;
+ NSMutableDictionary *requestCredentials;
+
+ // Used during NTLM authentication
+ int authenticationRetryCount;
+
+ // Authentication scheme (Basic, Digest, NTLM)
+ NSString *authenticationScheme;
+
+ // Realm for authentication when credentials are required
+ NSString *authenticationRealm;
+
+ // When YES, ASIHTTPRequest will present a dialog allowing users to enter credentials when no-matching credentials were found for a server that requires authentication
+ // The dialog will not be shown if your delegate responds to authenticationNeededForRequest:
+ // Default is NO.
+ BOOL shouldPresentAuthenticationDialog;
+
+ // 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
+ // The dialog will not be shown if your delegate responds to proxyAuthenticationNeededForRequest:
+ // Default is YES (basically, because most people won't want the hassle of adding support for authenticating proxies to their apps)
+ BOOL shouldPresentProxyAuthenticationDialog;
+
+ // Used for proxy authentication
+ CFHTTPAuthenticationRef proxyAuthentication;
+ NSMutableDictionary *proxyCredentials;
+
+ // Used during authentication with an NTLM proxy
+ int proxyAuthenticationRetryCount;
+
+ // Authentication scheme for the proxy (Basic, Digest, NTLM)
+ NSString *proxyAuthenticationScheme;
+
+ // Realm for proxy authentication when credentials are required
+ NSString *proxyAuthenticationRealm;
+
+ // HTTP status code, eg: 200 = OK, 404 = Not found etc
+ int responseStatusCode;
+
+ // Description of the HTTP status code
+ NSString *responseStatusMessage;
+
+ // Size of the response
+ unsigned long long contentLength;
+
+ // Size of the partially downloaded content
+ unsigned long long partialDownloadSize;
+
+ // Size of the POST payload
+ unsigned long long postLength;
+
+ // The total amount of downloaded data
+ unsigned long long totalBytesRead;
+
+ // The total amount of uploaded data
+ unsigned long long totalBytesSent;
+
+ // Last amount of data read (used for incrementing progress)
+ unsigned long long lastBytesRead;
+
+ // Last amount of data sent (used for incrementing progress)
+ unsigned long long lastBytesSent;
+
+ // This lock prevents the operation from being cancelled at an inopportune moment
+ NSRecursiveLock *cancelledLock;
+
+ // Called on the delegate (if implemented) when the request starts. Default is requestStarted:
+ SEL didStartSelector;
+
+ // Called on the delegate (if implemented) when the request receives response headers. Default is requestDidReceiveResponseHeaders:
+ SEL didReceiveResponseHeadersSelector;
+
+ // Called on the delegate (if implemented) when the request completes successfully. Default is requestFinished:
+ SEL didFinishSelector;
+
+ // Called on the delegate (if implemented) when the request fails. Default is requestFailed:
+ SEL didFailSelector;
+
+ // Called on the delegate (if implemented) when the request receives data. Default is request:didReceiveData:
+ // If you set this and implement the method in your delegate, you must handle the data yourself - ASIHTTPRequest will not populate responseData or write the data to downloadDestinationPath
+ SEL didReceiveDataSelector;
+
+ // 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
+ NSDate *lastActivityTime;
+
+ // Number of seconds to wait before timing out - default is 10
+ NSTimeInterval timeOutSeconds;
+
+ // Will be YES when a HEAD request will handle the content-length before this request starts
+ BOOL shouldResetUploadProgress;
+ BOOL shouldResetDownloadProgress;
+
+ // Used by HEAD requests when showAccurateProgress is YES to preset the content-length for this request
+ ASIHTTPRequest *mainRequest;
+
+ // When NO, this request will only update the progress indicator when it completes
+ // When YES, this request will update the progress indicator according to how much data it has received so far
+ // The default for requests is YES
+ // Also see the comments in ASINetworkQueue.h
+ BOOL showAccurateProgress;
+
+ // Used to ensure the progress indicator is only incremented once when showAccurateProgress = NO
+ BOOL updatedProgress;
+
+ // Prevents the body of the post being built more than once (largely for subclasses)
+ BOOL haveBuiltPostBody;
+
+ // Used internally, may reflect the size of the internal buffer used by CFNetwork
+ // POST / PUT operations with body sizes greater than uploadBufferSize will not timeout unless more than uploadBufferSize bytes have been sent
+ // Likely to be 32KB on iPhone 3.0, 128KB on Mac OS X Leopard and iPhone 2.2.x
+ unsigned long long uploadBufferSize;
+
+ // Text encoding for responses that do not send a Content-Type with a charset value. Defaults to NSISOLatin1StringEncoding
+ NSStringEncoding defaultResponseEncoding;
+
+ // The text encoding of the response, will be defaultResponseEncoding if the server didn't specify. Can't be set.
+ NSStringEncoding responseEncoding;
+
+ // Tells ASIHTTPRequest not to delete partial downloads, and allows it to use an existing file to resume a download. Defaults to NO.
+ BOOL allowResumeForFileDownloads;
+
+ // Custom user information associated with the request
+ NSDictionary *userInfo;
+
+ // Use HTTP 1.0 rather than 1.1 (defaults to false)
+ BOOL useHTTPVersionOne;
+
+ // When YES, requests will automatically redirect when they get a HTTP 30x header (defaults to YES)
+ BOOL shouldRedirect;
+
+ // Used internally to tell the main loop we need to stop and retry with a new url
+ BOOL needsRedirect;
+
+ // Incremented every time this request redirects. When it reaches 5, we give up
+ int redirectCount;
+
+ // When NO, requests will not check the secure certificate is valid (use for self-signed certificates during development, DO NOT USE IN PRODUCTION) Default is YES
+ BOOL validatesSecureCertificate;
+
+ // Details on the proxy to use - you could set these yourself, but it's probably best to let ASIHTTPRequest detect the system proxy settings
+ NSString *proxyHost;
+ int proxyPort;
+
+ // 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
+ NSURL *PACurl;
+
+ // See ASIAuthenticationState values above. 0 == default == No authentication needed yet
+ ASIAuthenticationState authenticationNeeded;
+
+ // When YES, ASIHTTPRequests will present credentials from the session store for requests to the same server before being asked for them
+ // 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
+ // Set to NO to only present credentials when explicitly asked for them
+ // This only affects credentials stored in the session cache when useSessionPersistence is YES. Credentials from the keychain are never presented unless the server asks for them
+ // Default is YES
+ BOOL shouldPresentCredentialsBeforeChallenge;
+
+ // YES when the request is run with runSynchronous, NO otherwise. READ-ONLY
+ BOOL isSynchronous;
+
+ // YES when the request hasn't finished yet. Will still be YES even if the request isn't doing anything (eg it's waiting for delegate authentication). READ-ONLY
+ BOOL inProgress;
+
+ // Used internally to track whether the stream is scheduled on the run loop or not
+ // Bandwidth throttling can unschedule the stream to slow things down while a request is in progress
+ BOOL readStreamIsScheduled;
+
+ // Set to allow a request to automatically retry itself on timeout
+ // Default is zero - timeout will stop the request
+ int numberOfTimesToRetryOnTimeout;
+
+ // The number of times this request has retried (when numberOfTimesToRetryOnTimeout > 0)
+ int retryCount;
+
+ // When YES, requests will keep the connection to the server alive for a while to allow subsequent requests to re-use it for a substantial speed-boost
+ // Persistent connections will not be used if the server explicitly closes the connection
+ // Default is YES
+ BOOL shouldAttemptPersistentConnection;
+
+ // Number of seconds to keep an inactive persistent connection open on the client side
+ // Default is 60
+ // If we get a keep-alive header, this is this value is replaced with how long the server told us to keep the connection around
+ // A future date is created from this and used for expiring the connection, this is stored in connectionInfo's expires value
+ NSTimeInterval persistentConnectionTimeoutSeconds;
+
+ // Set to yes when an appropriate keep-alive header is found
+ BOOL connectionCanBeReused;
+
+ // Stores information about the persistent connection that is currently in use.
+ // It may contain:
+ // * The id we set for a particular connection, incremented every time we want to specify that we need a new connection
+ // * The date that connection should expire
+ // * A host, port and scheme for the connection. These are used to determine whether that connection can be reused by a subsequent request (all must match the new request)
+ // * An id for the request that is currently using the connection. This is used for determining if a connection is available or not (we store a number rather than a reference to the request so we don't need to hang onto a request until the connection expires)
+ // * A reference to the stream that is currently using the connection. This is necessary because we need to keep the old stream open until we've opened a new one.
+ // The stream will be closed + released either when another request comes to use the connection, or when the timer fires to tell the connection to expire
+ NSMutableDictionary *connectionInfo;
+
+ // This timer checks up on the request every 0.25 seconds, and updates progress
+ NSTimer *statusTimer;
+
+ // When set to YES, 301 and 302 automatic redirects will use the original method and and body, according to the HTTP 1.1 standard
+ // Default is NO (to follow the behaviour of most browsers)
+ BOOL shouldUseRFC2616RedirectBehaviour;
+
+ // Used internally to record when a request has finished downloading data
+ BOOL downloadComplete;
+
+ // An ID that uniquely identifies this request - primarily used for debugging persistent connections
+ NSNumber *requestID;
+
+ // Will be ASIHTTPRequestRunLoopMode for synchronous requests, NSDefaultRunLoopMode for all other requests
+ NSString *runLoopMode;
+}
+
+#pragma mark init / dealloc
+
+// Should be an HTTP or HTTPS url, may include username and password if appropriate
+- (id)initWithURL:(NSURL *)newURL;
+
+// Convenience constructor
++ (id)requestWithURL:(NSURL *)newURL;
+
+#pragma mark setup request
+
+// Add a custom header to the request
+- (void)addRequestHeader:(NSString *)header value:(NSString *)value;
+
+// Called during buildRequestHeaders and after a redirect to create a cookie header from request cookies and the global store
+- (void)applyCookieHeader;
+
+// Populate the request headers dictionary. Called before a request is started, or by a HEAD request that needs to borrow them
+- (void)buildRequestHeaders;
+
+// Used to apply authorization header to a request before it is sent (when shouldPresentCredentialsBeforeChallenge is YES)
+- (void)applyAuthorizationHeader;
+
+
+// Create the post body
+- (void)buildPostBody;
+
+// Called to add data to the post body. Will append to postBody when shouldStreamPostDataFromDisk is false, or write to postBodyWriteStream when true
+- (void)appendPostData:(NSData *)data;
+- (void)appendPostDataFromFile:(NSString *)file;
+
+#pragma mark get information about this request
+
+// Returns the contents of the result as an NSString (not appropriate for binary data - used responseData instead)
+- (NSString *)responseString;
+
+// Response data, automatically uncompressed where appropriate
+- (NSData *)responseData;
+
+// Returns true if the response was gzip compressed
+- (BOOL)isResponseCompressed;
+
+#pragma mark running a request
+
+
+// Run a request synchronously, and return control when the request completes or fails
+- (void)startSynchronous;
+
+// Run request in the background
+- (void)startAsynchronous;
+
+#pragma mark request logic
+
+// Call to delete the temporary file used during a file download (if it exists)
+// No need to call this if the request succeeds - it is removed automatically
+- (void)removeTemporaryDownloadFile;
+
+// Call to remove the file used as the request body
+// No need to call this if the request succeeds and you didn't specify postBodyFilePath manually - it is removed automatically
+- (void)removePostDataFile;
+
+#pragma mark HEAD request
+
+// Used by ASINetworkQueue to create a HEAD request appropriate for this request with the same headers (though you can use it yourself)
+- (ASIHTTPRequest *)HEADRequest;
+
+#pragma mark upload/download progress
+
+// Called approximately every 0.25 seconds to update the progress delegates
+- (void)updateProgressIndicators;
+
+// Updates upload progress (notifies the queue and/or uploadProgressDelegate of this request)
+- (void)updateUploadProgress;
+
+// Updates download progress (notifies the queue and/or uploadProgressDelegate of this request)
+- (void)updateDownloadProgress;
+
+// Called when authorisation is needed, as we only find out we don't have permission to something when the upload is complete
+- (void)removeUploadProgressSoFar;
+
+// Called when we get a content-length header and shouldResetDownloadProgress is true
+- (void)incrementDownloadSizeBy:(long long)length;
+
+// Called when a request starts and shouldResetUploadProgress is true
+// Also called (with a negative length) to remove the size of the underlying buffer used for uploading
+- (void)incrementUploadSizeBy:(long long)length;
+
+// Helper method for interacting with progress indicators to abstract the details of different APIS (NSProgressIndicator and UIProgressView)
++ (void)updateProgressIndicator:(id)indicator withProgress:(unsigned long long)progress ofTotal:(unsigned long long)total;
+
+// Helper method used for performing invocations on the main thread (used for progress)
++ (void)performSelector:(SEL)selector onTarget:(id)target withObject:(id)object amount:(void *)amount;
+
+#pragma mark handling request complete / failure
+
+// Called when a request starts, lets the delegate know via didStartSelector
+- (void)requestStarted;
+
+// Called when a request receives response headers, lets the delegate know via didReceiveResponseHeadersSelector
+- (void)requestReceivedResponseHeaders;
+
+// Called when a request completes successfully, lets the delegate know via didFinishSelector
+- (void)requestFinished;
+
+// Called when a request fails, and lets the delegate know via didFailSelector
+- (void)failWithError:(NSError *)theError;
+
+// Called to retry our request when our persistent connection is closed
+// Returns YES if we haven't already retried, and connection will be restarted
+// Otherwise, returns NO, and nothing will happen
+- (BOOL)retryUsingNewConnection;
+
+#pragma mark parsing HTTP response headers
+
+// Reads the response headers to find the content length, encoding, cookies for the session
+// Also initiates request redirection when shouldRedirect is true
+// And works out if HTTP auth is required
+- (void)readResponseHeaders;
+
+#pragma mark http authentication stuff
+
+// Apply credentials to this request
+- (BOOL)applyCredentials:(NSDictionary *)newCredentials;
+- (BOOL)applyProxyCredentials:(NSDictionary *)newCredentials;
+
+// Attempt to obtain credentials for this request from the URL, username and password or keychain
+- (NSMutableDictionary *)findCredentials;
+- (NSMutableDictionary *)findProxyCredentials;
+
+// Unlock (unpause) the request thread so it can resume the request
+// Should be called by delegates when they have populated the authentication information after an authentication challenge
+- (void)retryUsingSuppliedCredentials;
+
+// Should be called by delegates when they wish to cancel authentication and stop
+- (void)cancelAuthentication;
+
+// Apply authentication information and resume the request after an authentication challenge
+- (void)attemptToApplyCredentialsAndResume;
+- (void)attemptToApplyProxyCredentialsAndResume;
+
+// Attempt to show the built-in authentication dialog, returns YES if credentials were supplied, NO if user cancelled dialog / dialog is disabled / running on main thread
+// Currently only used on iPhone OS
+- (BOOL)showProxyAuthenticationDialog;
+- (BOOL)showAuthenticationDialog;
+
+// Construct a basic authentication header from the username and password supplied, and add it to the request headers
+// Used when shouldPresentCredentialsBeforeChallenge is YES
+- (void)addBasicAuthenticationHeaderWithUsername:(NSString *)theUsername andPassword:(NSString *)thePassword;
+
+#pragma mark stream status handlers
+
+// CFnetwork event handlers
+- (void)handleNetworkEvent:(CFStreamEventType)type;
+- (void)handleBytesAvailable;
+- (void)handleStreamComplete;
+- (void)handleStreamError;
+
+// Get the ID of the connection this request used (only really useful in tests and debugging)
+- (NSNumber *)connectionID;
+
+// Called automatically when a request is started to clean up any persistent connections that have expired
++ (void)expirePersistentConnections;
+
+#pragma mark default time out
+
++ (NSTimeInterval)defaultTimeOutSeconds;
++ (void)setDefaultTimeOutSeconds:(NSTimeInterval)newTimeOutSeconds;
+
+#pragma mark session credentials
+
++ (NSMutableArray *)sessionProxyCredentialsStore;
++ (NSMutableArray *)sessionCredentialsStore;
+
++ (void)storeProxyAuthenticationCredentialsInSessionStore:(NSDictionary *)credentials;
++ (void)storeAuthenticationCredentialsInSessionStore:(NSDictionary *)credentials;
+
++ (void)removeProxyAuthenticationCredentialsFromSessionStore:(NSDictionary *)credentials;
++ (void)removeAuthenticationCredentialsFromSessionStore:(NSDictionary *)credentials;
+
+- (NSDictionary *)findSessionProxyAuthenticationCredentials;
+- (NSDictionary *)findSessionAuthenticationCredentials;
+
+
+#pragma mark keychain storage
+
+// Save credentials for this request to the keychain
+- (void)saveCredentialsToKeychain:(NSDictionary *)newCredentials;
+
+// Save credentials to the keychain
++ (void)saveCredentials:(NSURLCredential *)credentials forHost:(NSString *)host port:(int)port protocol:(NSString *)protocol realm:(NSString *)realm;
++ (void)saveCredentials:(NSURLCredential *)credentials forProxy:(NSString *)host port:(int)port realm:(NSString *)realm;
+
+// Return credentials from the keychain
++ (NSURLCredential *)savedCredentialsForHost:(NSString *)host port:(int)port protocol:(NSString *)protocol realm:(NSString *)realm;
++ (NSURLCredential *)savedCredentialsForProxy:(NSString *)host port:(int)port protocol:(NSString *)protocol realm:(NSString *)realm;
+
+// Remove credentials from the keychain
++ (void)removeCredentialsForHost:(NSString *)host port:(int)port protocol:(NSString *)protocol realm:(NSString *)realm;
++ (void)removeCredentialsForProxy:(NSString *)host port:(int)port realm:(NSString *)realm;
+
+// We keep track of any cookies we accept, so that we can remove them from the persistent store later
++ (void)setSessionCookies:(NSMutableArray *)newSessionCookies;
++ (NSMutableArray *)sessionCookies;
+
+// Adds a cookie to our list of cookies we've accepted, checking first for an old version of the same cookie and removing that
++ (void)addSessionCookie:(NSHTTPCookie *)newCookie;
+
+// Dump all session data (authentication and cookies)
++ (void)clearSession;
+
+#pragma mark gzip decompression
+
+// Uncompress gzipped data with zlib
++ (NSData *)uncompressZippedData:(NSData*)compressedData;
+
+// Uncompress gzipped data from a file into another file, used when downloading to a file
++ (int)uncompressZippedDataFromFile:(NSString *)sourcePath toFile:(NSString *)destinationPath;
++ (int)uncompressZippedDataFromSource:(FILE *)source toDestination:(FILE *)dest;
+
+#pragma mark gzip compression
+
+// Compress data with gzip using zlib
++ (NSData *)compressData:(NSData*)uncompressedData;
+
+// gzip compress data from a file, saving to another file, used for uploading when shouldCompressRequestBody is true
++ (int)compressDataFromFile:(NSString *)sourcePath toFile:(NSString *)destinationPath;
++ (int)compressDataFromSource:(FILE *)source toDestination:(FILE *)dest;
+
+#pragma mark get user agent
+
+// Will be used as a user agent if requests do not specify a custom user agent
+// Is only used when you have specified a Bundle Display Name (CFDisplayBundleName) or Bundle Name (CFBundleName) in your plist
++ (NSString *)defaultUserAgentString;
+
+#pragma mark proxy autoconfiguration
+
+// Returns an array of proxies to use for a particular url, given the url of a PAC script
++ (NSArray *)proxiesForURL:(NSURL *)theURL fromPAC:(NSURL *)pacScriptURL;
+
+#pragma mark mime-type detection
+
+// Return the mime type for a file
++ (NSString *)mimeTypeForFileAtPath:(NSString *)path;
+
+#pragma mark bandwidth measurement / throttling
+
+// The maximum number of bytes ALL requests can send / receive in a second
+// This is a rough figure. The actual amount used will be slightly more, this does not include HTTP headers
++ (unsigned long)maxBandwidthPerSecond;
++ (void)setMaxBandwidthPerSecond:(unsigned long)bytes;
+
+// Get a rough average (for the last 5 seconds) of how much bandwidth is being used, in bytes
++ (unsigned long)averageBandwidthUsedPerSecond;
+
+- (void)performThrottling;
+
+// Will return YES is bandwidth throttling is currently in use
++ (BOOL)isBandwidthThrottled;
+
+// Used internally to record bandwidth use, and by ASIInputStreams when uploading. It's probably best if you don't mess with this.
++ (void)incrementBandwidthUsedInLastSecond:(unsigned long)bytes;
+
+// On iPhone, ASIHTTPRequest can automatically turn throttling on and off as the connection type changes between WWAN and WiFi
+
+#if TARGET_OS_IPHONE
+// Set to YES to automatically turn on throttling when WWAN is connected, and automatically turn it off when it isn't
++ (void)setShouldThrottleBandwidthForWWAN:(BOOL)throttle;
+
+// Turns on throttling automatically when WWAN is connected using a custom limit, and turns it off automatically when it isn't
++ (void)throttleBandwidthForWWANUsingLimit:(unsigned long)limit;
+
+
+#pragma mark reachability
+// Returns YES when an iPhone OS device is connected via WWAN, false when connected via WIFI or not connected
++ (BOOL)isNetworkReachableViaWWAN;
+
+#endif
+
+// Returns the maximum amount of data we can read as part of the current measurement period, and sleeps this thread if our allowance is used up
++ (unsigned long)maxUploadReadLength;
+
+#pragma mark miscellany
+
+// Determines whether we're on iPhone OS 2.0 at runtime, currently used to determine whether we should apply a workaround for an issue with converting longs to doubles on iPhone OS 2
++ (BOOL)isiPhoneOS2;
+
+// Used for generating Authorization header when using basic authentication when shouldPresentCredentialsBeforeChallenge is true
+// And also by ASIS3Request
++ (NSString *)base64forData:(NSData *)theData;
+
+#pragma mark ===
+
+@property (retain) NSString *username;
+@property (retain) NSString *password;
+@property (retain) NSString *domain;
+
+@property (retain) NSString *proxyUsername;
+@property (retain) NSString *proxyPassword;
+@property (retain) NSString *proxyDomain;
+
+@property (retain) NSString *proxyHost;
+@property (assign) int proxyPort;
+
+@property (retain,setter=setURL:) NSURL *url;
+@property (retain) NSURL *originalURL;
+@property (assign) id delegate;
+@property (assign) id queue;
+@property (assign) id uploadProgressDelegate;
+@property (assign) id downloadProgressDelegate;
+@property (assign) BOOL useKeychainPersistence;
+@property (assign) BOOL useSessionPersistence;
+@property (retain) NSString *downloadDestinationPath;
+@property (retain) NSString *temporaryFileDownloadPath;
+@property (assign) SEL didStartSelector;
+@property (assign) SEL didReceiveResponseHeadersSelector;
+@property (assign) SEL didFinishSelector;
+@property (assign) SEL didFailSelector;
+@property (assign) SEL didReceiveDataSelector;
+@property (retain,readonly) NSString *authenticationRealm;
+@property (retain,readonly) NSString *proxyAuthenticationRealm;
+@property (retain) NSError *error;
+@property (assign,readonly) BOOL complete;
+@property (retain,readonly) NSDictionary *responseHeaders;
+@property (retain) NSMutableDictionary *requestHeaders;
+@property (retain) NSMutableArray *requestCookies;
+@property (retain,readonly) NSArray *responseCookies;
+@property (assign) BOOL useCookiePersistence;
+@property (retain) NSDictionary *requestCredentials;
+@property (retain) NSDictionary *proxyCredentials;
+@property (assign,readonly) int responseStatusCode;
+@property (retain,readonly) NSString *responseStatusMessage;
+@property (retain,readonly) NSMutableData *rawResponseData;
+@property (assign) NSTimeInterval timeOutSeconds;
+@property (retain) NSString *requestMethod;
+@property (retain) NSMutableData *postBody;
+@property (assign,readonly) unsigned long long contentLength;
+@property (assign) unsigned long long postLength;
+@property (assign) BOOL shouldResetDownloadProgress;
+@property (assign) BOOL shouldResetUploadProgress;
+@property (assign) ASIHTTPRequest *mainRequest;
+@property (assign) BOOL showAccurateProgress;
+@property (assign,readonly) unsigned long long totalBytesRead;
+@property (assign,readonly) unsigned long long totalBytesSent;
+@property (assign) NSStringEncoding defaultResponseEncoding;
+@property (assign,readonly) NSStringEncoding responseEncoding;
+@property (assign) BOOL allowCompressedResponse;
+@property (assign) BOOL allowResumeForFileDownloads;
+@property (retain) NSDictionary *userInfo;
+@property (retain) NSString *postBodyFilePath;
+@property (assign) BOOL shouldStreamPostDataFromDisk;
+@property (assign) BOOL didCreateTemporaryPostDataFile;
+@property (assign) BOOL useHTTPVersionOne;
+@property (assign, readonly) unsigned long long partialDownloadSize;
+@property (assign) BOOL shouldRedirect;
+@property (assign) BOOL validatesSecureCertificate;
+@property (assign) BOOL shouldCompressRequestBody;
+@property (retain) NSURL *PACurl;
+@property (retain) NSString *authenticationScheme;
+@property (retain) NSString *proxyAuthenticationScheme;
+@property (assign) BOOL shouldPresentAuthenticationDialog;
+@property (assign) BOOL shouldPresentProxyAuthenticationDialog;
+@property (assign, readonly) ASIAuthenticationState authenticationNeeded;
+@property (assign) BOOL shouldPresentCredentialsBeforeChallenge;
+@property (assign, readonly) int authenticationRetryCount;
+@property (assign, readonly) int proxyAuthenticationRetryCount;
+@property (assign) BOOL haveBuiltRequestHeaders;
+@property (assign, nonatomic) BOOL haveBuiltPostBody;
+@property (assign, readonly) BOOL isSynchronous;
+@property (assign, readonly) BOOL inProgress;
+@property (assign) int numberOfTimesToRetryOnTimeout;
+@property (assign, readonly) int retryCount;
+@property (assign) BOOL shouldAttemptPersistentConnection;
+@property (assign) NSTimeInterval persistentConnectionTimeoutSeconds;
+@property (assign) BOOL shouldUseRFC2616RedirectBehaviour;
+@property (assign, readonly) BOOL connectionCanBeReused;
+@property (retain, readonly) NSNumber *requestID;
+@end
3,764 Example/ASIHTTPRequest.m
View
3,764 additions, 0 deletions not shown
55 Example/ASIHTTPRequestConfig.h
View
@@ -0,0 +1,55 @@
+//
+// ASIHTTPRequestConfig.h
+// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest
+//
+// Created by Ben Copsey on 14/12/2009.
+// Copyright 2009 All-Seeing Interactive. All rights reserved.
+//
+
+
+// ======
+// Debug output configuration options
+// ======
+
+// When set to 1 ASIHTTPRequests will print information about what a request is doing
+#ifndef DEBUG_REQUEST_STATUS
+ #define DEBUG_REQUEST_STATUS 0
+#endif
+
+// When set to 1, ASIFormDataRequests will print information about the request body to the console
+#ifndef DEBUG_FORM_DATA_REQUEST
+ #define DEBUG_FORM_DATA_REQUEST 0
+#endif
+
+// When set to 1, ASIHTTPRequests will print information about bandwidth throttling to the console
+#ifndef DEBUG_THROTTLING
+ #define DEBUG_THROTTLING 0
+#endif
+
+// When set to 1, ASIHTTPRequests will print information about persistent connections to the console
+#ifndef DEBUG_PERSISTENT_CONNECTIONS
+ #define DEBUG_PERSISTENT_CONNECTIONS 0
+#endif
+
+// ======
+// Reachability API (iPhone only)
+// ======
+
+/*
+ASIHTTPRequest uses Apple's Reachability class (http://developer.apple.com/iphone/library/samplecode/Reachability/) to turn bandwidth throttling on and off automatically when shouldThrottleBandwidthForWWAN is set to YES on iPhone OS
+
+There are two versions of Apple's Reachability class, both of which are included in the source distribution of ASIHTTPRequest in the External/Reachability folder.
+
+ * Version 2.0 is the latest version. You should use this if you are targeting iPhone OS 3.x and later
+ To use Version 2.0, set this to 1, and include Reachability.h + Reachability.m from the Reachability 2.0 folder in your project
+
+ * Version 1.5 is the old version, but it is compatible with both iPhone OS 2.2.1 and iPhone OS 3.0 and later. You should use this if your application needs to work on iPhone OS 2.2.1.
+ To use Version 1.5, set this to 0, and include Reachability.h + Reachability.m from the Reachability 1.5 folder in your project
+
+This config option is not used for apps targeting Mac OS X
+*/
+
+#ifndef REACHABILITY_20_API
+ #define REACHABILITY_20_API 1
+#endif
+
33 Example/ASIHTTPRequestDelegate.h
View
@@ -0,0 +1,33 @@
+//
+// ASIHTTPRequestDelegate.h
+// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest
+//
+// Created by Ben Copsey on 13/04/2010.
+// Copyright 2010 All-Seeing Interactive. All rights reserved.
+//
+
+@class ASIHTTPRequest;
+
+@protocol ASIHTTPRequestDelegate <NSObject>
+
+@optional
+
+// These are the default delegate methods for request status
+// You can use different ones by setting didStartSelector / didFinishSelector / didFailSelector
+- (void)requestStarted:(ASIHTTPRequest *)request;
+- (void)requestReceivedResponseHeaders:(ASIHTTPRequest *)request;
+- (void)requestFinished:(ASIHTTPRequest *)request;
+- (void)requestFailed:(ASIHTTPRequest *)request;
+
+// When a delegate implements this method, it is expected to process all incoming data itself
+// This means that responseData / responseString / downloadDestinationPath etc are ignored
+// You can have the request call a different method by setting didReceiveDataSelector
+- (void)request:(ASIHTTPRequest *)request didReceiveData:(NSData *)data;
+
+// If a delegate implements one of these, it will be asked to supply credentials when none are available
+// The delegate can then either restart the request ([request retryUsingSuppliedCredentials]) once credentials have been set
+// or cancel it ([request cancelAuthentication])
+- (void)authenticationNeededForRequest:(ASIHTTPRequest *)request;
+- (void)proxyAuthenticationNeededForRequest:(ASIHTTPRequest *)request;
+
+@end
26 Example/ASIInputStream.h
View
@@ -0,0 +1,26 @@
+//
+// ASIInputStream.h
+// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest
+//
+// Created by Ben Copsey on 10/08/2009.
+// Copyright 2009 All-Seeing Interactive. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+@class ASIHTTPRequest;
+
+// This is a wrapper for NSInputStream that pretends to be an NSInputStream itself
+// Subclassing NSInputStream seems to be tricky, and may involve overriding undocumented methods, so we'll cheat instead.
+// It is used by ASIHTTPRequest whenever we have a request body, and handles measuring and throttling the bandwidth used for uploading
+
+@interface ASIInputStream : NSObject {
+ NSInputStream *stream;
+ ASIHTTPRequest *request;
+}
++ (id)inputStreamWithFileAtPath:(NSString *)path request:(ASIHTTPRequest *)request;
++ (id)inputStreamWithData:(NSData *)data request:(ASIHTTPRequest *)request;
+
+@property (retain, nonatomic) NSInputStream *stream;
+@property (assign, nonatomic) ASIHTTPRequest *request;
+@end
136 Example/ASIInputStream.m
View
@@ -0,0 +1,136 @@
+//
+// ASIInputStream.m
+// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest
+//
+// Created by Ben Copsey on 10/08/2009.
+// Copyright 2009 All-Seeing Interactive. All rights reserved.
+//
+
+#import "ASIInputStream.h"
+#import "ASIHTTPRequest.h"
+
+// Used to ensure only one request can read data at once
+static NSLock *readLock = nil;
+
+@implementation ASIInputStream
+
++ (void)initialize
+{
+ if (self == [ASIInputStream class]) {
+ readLock = [[NSLock alloc] init];
+ }
+}
+
++ (id)inputStreamWithFileAtPath:(NSString *)path request:(ASIHTTPRequest *)request
+{
+ ASIInputStream *stream = [[[self alloc] init] autorelease];
+ [stream setRequest:request];
+ [stream setStream:[NSInputStream inputStreamWithFileAtPath:path]];
+ return stream;
+}
+
++ (id)inputStreamWithData:(NSData *)data request:(ASIHTTPRequest *)request
+{
+ ASIInputStream *stream = [[[self alloc] init] autorelease];
+ [stream setRequest:request];
+ [stream setStream:[NSInputStream inputStreamWithData:data]];
+ return stream;
+}
+
+- (void)dealloc
+{
+ [stream release];
+ [super dealloc];
+}
+
+// Called when CFNetwork wants to read more of our request body
+// When throttling is on, we ask ASIHTTPRequest for the maximum amount of data we can read
+- (NSInteger)read:(uint8_t *)buffer maxLength:(NSUInteger)len
+{
+ [readLock lock];
+ unsigned long toRead = len;
+ if ([ASIHTTPRequest isBandwidthThrottled]) {
+ toRead = [ASIHTTPRequest maxUploadReadLength];
+ if (toRead > len) {
+ toRead = len;
+ } else if (toRead == 0) {
+ toRead = 1;
+ }
+ [request performThrottling];
+ }
+ [ASIHTTPRequest incrementBandwidthUsedInLastSecond:toRead];
+ [readLock unlock];
+ return [stream read:buffer maxLength:toRead];
+}
+
+/*
+ * Implement NSInputStream mandatory methods to make sure they are implemented
+ * (necessary for MacRuby for example) and avoir the overhead of method
+ * forwarding for these common methods.
+ */
+- (void)open
+{
+ [stream open];
+}
+
+- (void)close
+{
+ [stream close];
+}
+
+- (id)delegate
+{
+ return [stream delegate];
+}
+
+- (void)setDelegate:(id)delegate
+{
+ [stream setDelegate:delegate];
+}
+
+- (void)scheduleInRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode
+{
+ [stream scheduleInRunLoop:aRunLoop forMode:mode];
+}
+
+- (void)removeFromRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode
+{
+ [stream removeFromRunLoop:aRunLoop forMode:mode];
+}
+
+- (id)propertyForKey:(NSString *)key
+{
+ return [stream propertyForKey:key];
+}
+
+- (BOOL)setProperty:(id)property forKey:(NSString *)key
+{
+ return [stream setProperty:property forKey:key];
+}
+
+- (NSStreamStatus)streamStatus
+{
+ return [stream streamStatus];
+}
+
+- (NSError *)streamError
+{
+ return [stream streamError];
+}
+
+// If we get asked to perform a method we don't have (probably internal ones),
+// we'll just forward the message to our stream
+
+- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
+{
+ return [stream methodSignatureForSelector:aSelector];
+}
+
+- (void)forwardInvocation:(NSInvocation *)anInvocation
+{
+ [anInvocation invokeWithTarget:stream];
+}
+
+@synthesize stream;
+@synthesize request;
+@end
16 Example/ASINSStringAdditions.h
View
@@ -0,0 +1,16 @@
+//
+// ASINSStringAdditions.h
+// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest
+//
+// Created by Ben Copsey on 12/09/2008.
+// Copyright 2008 All-Seeing Interactive. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+@interface NSString (CookieValueEncodingAdditions)
+
+- (NSString *)encodedCookieValue;
+- (NSString *)decodedCookieValue;
+
+@end
28 Example/ASINSStringAdditions.m
View
@@ -0,0 +1,28 @@
+//
+// ASINSStringAdditions.m
+// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest
+//
+// Created by Ben Copsey on 12/09/2008.
+// Copyright 2008 All-Seeing Interactive. All rights reserved.
+//
+
+#import "ASINSStringAdditions.h"
+
+@implementation NSString (CookieValueEncodingAdditions)
+
+- (NSString *)decodedCookieValue
+{
+ NSMutableString *s = [NSMutableString stringWithString:[self stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
+ //Also swap plus signs for spaces
+ [s replaceOccurrencesOfString:@"+" withString:@" " options:NSLiteralSearch range:NSMakeRange(0, [s length])];
+ return [NSString stringWithString:s];
+}
+
+- (NSString *)encodedCookieValue
+{
+ return [self stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
+}
+
+@end
+
+
110 Example/ASINetworkQueue.h
View
@@ -0,0 +1,110 @@
+//
+// ASINetworkQueue.h
+// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest
+//
+// Created by Ben Copsey on 07/11/2008.
+// Copyright 2008-2009 All-Seeing Interactive. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+#import "ASIHTTPRequestDelegate.h"
+#import "ASIProgressDelegate.h"
+
+@interface ASINetworkQueue : NSOperationQueue <ASIProgressDelegate, ASIHTTPRequestDelegate, NSCopying> {
+
+ // Delegate will get didFail + didFinish messages (if set)
+ id delegate;
+
+ // Will be called when a request starts with the request as the argument
+ SEL requestDidStartSelector;
+
+ // Will be called when a request receives response headers with the request as the argument
+ SEL requestDidReceiveResponseHeadersSelector;
+
+ // Will be called when a request completes with the request as the argument
+ SEL requestDidFinishSelector;
+
+ // Will be called when a request fails with the request as the argument
+ SEL requestDidFailSelector;
+
+ // Will be called when the queue finishes with the queue as the argument
+ SEL queueDidFinishSelector;
+
+ // Upload progress indicator, probably an NSProgressIndicator or UIProgressView
+ id uploadProgressDelegate;
+
+ // Total amount uploaded so far for all requests in this queue
+ unsigned long long bytesUploadedSoFar;
+
+ // 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
+ unsigned long long totalBytesToUpload;
+
+ // Download progress indicator, probably an NSProgressIndicator or UIProgressView
+ id downloadProgressDelegate;
+
+ // Total amount downloaded so far for all requests in this queue
+ unsigned long long bytesDownloadedSoFar;
+
+ // Total amount to be downloaded for all requests in this queue - requests add to this figure as they receive Content-Length headers
+ unsigned long long totalBytesToDownload;
+
+ // When YES, the queue will cancel all requests when a request fails. Default is YES
+ BOOL shouldCancelAllRequestsOnFailure;
+
+ //Number of real requests (excludes HEAD requests created to manage showAccurateProgress)
+ int requestsCount;
+
+ // When NO, this request will only update the progress indicator when it completes
+ // When YES, this request will update the progress indicator according to how much data it has received so far
+ // 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
+ // 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
+ // Set to YES if the size of a requests in the queue varies greatly for much more accurate results
+ // Default for requests in the queue is NO
+ BOOL showAccurateProgress;
+
+ // Storage container for additional queue information.
+ NSDictionary *userInfo;
+
+}
+
+// Convenience constructor
++ (id)queue;
+
+// Call this to reset a queue - it will cancel all operations, clear delegates, and suspend operation
+- (void)reset;
+
+// Used internally to manage HEAD requests when showAccurateProgress is YES, do not use!
+- (void)addHEADOperation:(NSOperation *)operation;
+
+// All ASINetworkQueues are paused when created so that total size can be calculated before the queue starts
+// This method will start the queue
+- (void)go;
+
+// Used on iPhone platform to show / hide the network activity indicator (in the status bar)
+// On mac, you could subclass to do something else
+- (void)updateNetworkActivityIndicator;
+
+// Returns YES if the queue is in progress
+- (BOOL)isNetworkActive;
+
+
+@property (assign,setter=setUploadProgressDelegate:) id uploadProgressDelegate;
+@property (assign,setter=setDownloadProgressDelegate:) id downloadProgressDelegate;
+
+@property (assign) SEL requestDidStartSelector;
+@property (assign) SEL requestDidReceiveResponseHeadersSelector;
+@property (assign) SEL requestDidFinishSelector;
+@property (assign) SEL requestDidFailSelector;
+@property (assign) SEL queueDidFinishSelector;
+@property (assign) BOOL shouldCancelAllRequestsOnFailure;
+@property (assign) id delegate;
+@property (assign) BOOL showAccurateProgress;
+@property (assign, readonly) int requestsCount;
+@property (retain) NSDictionary *userInfo;
+
+@property (assign) unsigned long long bytesUploadedSoFar;
+@property (assign) unsigned long long totalBytesToUpload;
+@property (assign) unsigned long long bytesDownloadedSoFar;
+@property (assign) unsigned long long totalBytesToDownload;
+
+@end
339 Example/ASINetworkQueue.m
View
@@ -0,0 +1,339 @@
+//
+// ASINetworkQueue.m
+// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest
+//
+// Created by Ben Copsey on 07/11/2008.
+// Copyright 2008-2009 All-Seeing Interactive. All rights reserved.
+//
+
+#import "ASINetworkQueue.h"
+#import "ASIHTTPRequest.h"
+
+// Private stuff
+@interface ASINetworkQueue ()
+ - (void)resetProgressDelegate:(id)progressDelegate;
+ @property (assign) int requestsCount;
+@end
+
+@implementation ASINetworkQueue
+
+- (id)init
+{
+ self = [super init];
+ [self setShouldCancelAllRequestsOnFailure:YES];
+ [self setMaxConcurrentOperationCount:4];
+ [self setSuspended:YES];
+
+ return self;
+}
+
++ (id)queue
+{
+ return [[[self alloc] init] autorelease];
+}
+
+- (void)dealloc
+{
+ //We need to clear the queue 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
+ for (ASIHTTPRequest *request in [self operations]) {
+ [request setQueue:nil];
+ }
+ [userInfo release];
+ [super dealloc];
+}
+
+- (BOOL)isNetworkActive
+{
+ return ([self requestsCount] > 0 && ![self isSuspended]);
+}
+
+- (void)updateNetworkActivityIndicator
+{
+#if TARGET_OS_IPHONE
+ [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:[self isNetworkActive]];
+#endif
+}
+
+- (void)setSuspended:(BOOL)suspend
+{
+ [super setSuspended:suspend];
+ [self updateNetworkActivityIndicator];
+}
+
+- (void)reset
+{
+ [self cancelAllOperations];
+ [self setDelegate:nil];
+ [self setDownloadProgressDelegate:nil];
+ [self setUploadProgressDelegate:nil];
+ [self setRequestDidStartSelector:NULL];
+ [self setRequestDidReceiveResponseHeadersSelector:NULL];
+ [self setRequestDidFailSelector:NULL];
+ [self setRequestDidFinishSelector:NULL];
+ [self setQueueDidFinishSelector:NULL];
+ [self setSuspended:YES];
+}
+
+
+- (void)go
+{
+ [self setSuspended:NO];
+}
+
+- (void)cancelAllOperations
+{
+ [self setBytesUploadedSoFar:0];
+ [self setTotalBytesToUpload:0];
+ [self setBytesDownloadedSoFar:0];
+ [self setTotalBytesToDownload:0];
+ [super cancelAllOperations];
+ [self updateNetworkActivityIndicator];
+}
+
+- (void)setUploadProgressDelegate:(id)newDelegate
+{
+ uploadProgressDelegate = newDelegate;
+ [self resetProgressDelegate:newDelegate];
+
+}
+
+- (void)setDownloadProgressDelegate:(id)newDelegate
+{
+ downloadProgressDelegate = newDelegate;
+ [self resetProgressDelegate:newDelegate];
+}
+
+- (void)resetProgressDelegate:(id)progressDelegate
+{
+#if !TARGET_OS_IPHONE
+ // If the uploadProgressDelegate is an NSProgressIndicator, we set its MaxValue to 1.0 so we can treat it similarly to UIProgressViews
+ SEL selector = @selector(setMaxValue:);
+ if ([progressDelegate respondsToSelector:selector]) {
+ double max = 1.0;
+ [ASIHTTPRequest performSelector:selector onTarget:progressDelegate withObject:nil amount:&max];
+ }
+ selector = @selector(setDoubleValue:);
+ if ([progressDelegate respondsToSelector:selector]) {
+ double value = 0.0;
+ [ASIHTTPRequest performSelector:selector onTarget:progressDelegate withObject:nil amount:&value];
+ }
+#else
+ SEL selector = @selector(setProgress:);
+ if ([progressDelegate respondsToSelector:selector]) {
+ float value = 0.0f;
+ [ASIHTTPRequest performSelector:selector onTarget:progressDelegate withObject:nil amount:&value];
+ }
+#endif
+}
+
+- (void)addHEADOperation:(NSOperation *)operation
+{
+ if ([operation isKindOfClass:[ASIHTTPRequest class]]) {
+
+ ASIHTTPRequest *request = (ASIHTTPRequest *)operation;
+ [request setRequestMethod:@"HEAD"];
+ [request setQueuePriority:10];
+ [request setShowAccurateProgress:YES];
+ [request setQueue:self];
+
+ // Important - we are calling NSOperation's add method - we don't want to add this as a normal request!
+ [super addOperation:request];
+ }
+}
+
+// Only add ASIHTTPRequests to this queue!!
+- (void)addOperation:(NSOperation *)operation
+{
+ if (![operation isKindOfClass:[ASIHTTPRequest class]]) {
+ [NSException raise:@"AttemptToAddInvalidRequest" format:@"Attempted to add an object that was not an ASIHTTPRequest to an ASINetworkQueue"];
+ }
+
+ [self setRequestsCount:[self requestsCount]+1];
+
+ ASIHTTPRequest *request = (ASIHTTPRequest *)operation;
+
+ if ([self showAccurateProgress]) {
+
+ // Force the request to build its body (this may change requestMethod)
+ [request buildPostBody];
+
+ // If this is a GET request and we want accurate progress, perform a HEAD request first to get the content-length
+ // We'll only do this before the queue is started
+ // If requests are added after the queue is started they will probably move the overall progress backwards anyway, so there's no value performing the HEAD requests first
+ // Instead, they'll update the total progress if and when they receive a content-length header
+ if ([[request requestMethod] isEqualToString:@"GET"]) {
+ if ([self isSuspended]) {
+ ASIHTTPRequest *HEADRequest = [request HEADRequest];
+ [self addHEADOperation:HEADRequest];
+ [request addDependency:HEADRequest];
+ if ([request shouldResetDownloadProgress]) {
+ [self resetProgressDelegate:[request downloadProgressDelegate]];
+ [request setShouldResetDownloadProgress:NO];
+ }
+ }
+ }
+ [request buildPostBody];
+ [self request:nil incrementUploadSizeBy:[request postLength]];
+
+
+ } else {
+ [self request:nil incrementDownloadSizeBy:1];
+ [self request:nil incrementUploadSizeBy:1];
+ }
+ // Tell the request not to increment the upload size when it starts, as we've already added its length
+ if ([request shouldResetUploadProgress]) {
+ [self resetProgressDelegate:[request uploadProgressDelegate]];
+ [request setShouldResetUploadProgress:NO];
+ }
+
+ [request setShowAccurateProgress:[self showAccurateProgress]];
+
+ [request setQueue:self];
+ [super addOperation:request];
+ [self updateNetworkActivityIndicator];
+
+}
+
+- (void)requestStarted:(ASIHTTPRequest *)request
+{
+ if ([self requestDidStartSelector]) {
+ [[self delegate] performSelector:[self requestDidStartSelector] withObject:request];
+ }
+}
+
+- (void)requestReceivedResponseHeaders:(ASIHTTPRequest *)request
+{
+ if ([self requestDidReceiveResponseHeadersSelector]) {
+ [[self delegate] performSelector:[self requestDidReceiveResponseHeadersSelector] withObject:request];
+ }
+}
+
+
+- (void)requestFinished:(ASIHTTPRequest *)request
+{
+ [self setRequestsCount:[self requestsCount]-1];
+ [self updateNetworkActivityIndicator];
+ if ([self requestDidFinishSelector]) {
+ [[self delegate] performSelector:[self requestDidFinishSelector] withObject:request];
+ }
+ if ([self requestsCount] == 0) {
+ if ([self queueDidFinishSelector]) {
+ [[self delegate] performSelector:[self queueDidFinishSelector] withObject:self];
+ }
+ }
+}
+
+- (void)requestFailed:(ASIHTTPRequest *)request
+{
+ [self setRequestsCount:[self requestsCount]-1];
+ [self updateNetworkActivityIndicator];
+ if ([self requestDidFailSelector]) {
+ [[self delegate] performSelector:[self requestDidFailSelector] withObject:request];
+ }
+ if ([self requestsCount] == 0) {
+ if ([self queueDidFinishSelector]) {
+ [[self delegate] performSelector:[self queueDidFinishSelector] withObject:self];
+ }
+ }
+ if ([self shouldCancelAllRequestsOnFailure] && [self requestsCount] > 0) {
+ [self cancelAllOperations];
+ }
+
+}
+
+
+- (void)request:(ASIHTTPRequest *)request didReceiveBytes:(long long)bytes
+{
+ [self setBytesDownloadedSoFar:[self bytesDownloadedSoFar]+bytes];
+ if ([self downloadProgressDelegate]) {
+ [ASIHTTPRequest updateProgressIndicator:[self downloadProgressDelegate] withProgress:[self bytesDownloadedSoFar] ofTotal:[self totalBytesToDownload]];
+ }
+}
+
+- (void)request:(ASIHTTPRequest *)request didSendBytes:(long long)bytes
+{
+ [self setBytesUploadedSoFar:[self bytesUploadedSoFar]+bytes];
+ if ([self uploadProgressDelegate]) {
+ [ASIHTTPRequest updateProgressIndicator:[self uploadProgressDelegate] withProgress:[self bytesUploadedSoFar] ofTotal:[self totalBytesToUpload]];
+ }
+}
+
+- (void)request:(ASIHTTPRequest *)request incrementDownloadSizeBy:(long long)newLength
+{
+ [self setTotalBytesToDownload:[self totalBytesToDownload]+newLength];
+}
+
+- (void)request:(ASIHTTPRequest *)request incrementUploadSizeBy:(long long)newLength
+{
+ [self setTotalBytesToUpload:[self totalBytesToUpload]+newLength];
+}
+
+
+// Since this queue takes over as the delegate for all requests it contains, it should forward authorisation requests to its own delegate
+- (void)authenticationNeededForRequest:(ASIHTTPRequest *)request
+{
+ if ([[self delegate] respondsToSelector:@selector(authenticationNeededForRequest:)]) {
+ [[self delegate] performSelector:@selector(authenticationNeededForRequest:) withObject:request];
+ }
+}
+
+- (void)proxyAuthenticationNeededForRequest:(ASIHTTPRequest *)request
+{
+ if ([[self delegate] respondsToSelector:@selector(proxyAuthenticationNeededForRequest:)]) {
+ [[self delegate] performSelector:@selector(proxyAuthenticationNeededForRequest:) withObject:request];
+ }
+}
+
+
+- (BOOL)respondsToSelector:(SEL)selector
+{
+ if (selector == @selector(authenticationNeededForRequest:)) {
+ if ([[self delegate] respondsToSelector:@selector(authenticationNeededForRequest:)]) {
+ return YES;
+ }
+ return NO;
+ } else if (selector == @selector(proxyAuthenticationNeededForRequest:)) {
+ if ([[self delegate] respondsToSelector:@selector(proxyAuthenticationNeededForRequest:)]) {
+ return YES;
+ }
+ return NO;
+ }
+ return [super respondsToSelector:selector];
+}
+
+#pragma mark NSCopying
+
+- (id)copyWithZone:(NSZone *)zone
+{
+ ASINetworkQueue *newQueue = [[[self class] alloc] init];
+ [newQueue setDelegate:[self delegate]];
+ [newQueue setRequestDidStartSelector:[self requestDidStartSelector]];
+ [newQueue setRequestDidFinishSelector:[self requestDidFinishSelector]];
+ [newQueue setRequestDidFailSelector:[self requestDidFailSelector]];
+ [newQueue setQueueDidFinishSelector:[self queueDidFinishSelector]];
+ [newQueue setUploadProgressDelegate:[self uploadProgressDelegate]];
+ [newQueue setDownloadProgressDelegate:[self downloadProgressDelegate]];
+ [newQueue setShouldCancelAllRequestsOnFailure:[self shouldCancelAllRequestsOnFailure]];
+ [newQueue setShowAccurateProgress:[self showAccurateProgress]];
+ [newQueue setUserInfo:[[[self userInfo] copyWithZone:zone] autorelease]];
+ return newQueue;
+}
+
+
+@synthesize requestsCount;
+@synthesize bytesUploadedSoFar;
+@synthesize totalBytesToUpload;
+@synthesize bytesDownloadedSoFar;
+@synthesize totalBytesToDownload;
+@synthesize shouldCancelAllRequestsOnFailure;
+@synthesize uploadProgressDelegate;
+@synthesize downloadProgressDelegate;
+@synthesize requestDidStartSelector;
+@synthesize requestDidReceiveResponseHeadersSelector;
+@synthesize requestDidFinishSelector;
+@synthesize requestDidFailSelector;
+@synthesize queueDidFinishSelector;
+@synthesize delegate;
+@synthesize showAccurateProgress;
+@synthesize userInfo;
+@end
38 Example/ASIProgressDelegate.h
View
@@ -0,0 +1,38 @@
+//
+// ASIProgressDelegate.h
+// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest
+//
+// Created by Ben Copsey on 13/04/2010.
+// Copyright 2010 All-Seeing Interactive. All rights reserved.
+//
+
+@class ASIHTTPRequest;
+
+@protocol ASIProgressDelegate <NSObject>
+
+@optional
+
+// These methods are used to update UIProgressViews (iPhone OS) or NSProgressIndicators (Mac OS X)
+// If you are using a custom progress delegate, you may find it easier to implement didReceiveBytes / didSendBytes instead
+#if TARGET_OS_IPHONE
+- (void)setProgress:(float)newProgress;
+#else
+- (void)setDoubleValue:(double)newProgress;
+- (void)setMaxValue:(double)newMax;
+#endif
+
+// Called when the request receives some data - bytes is the length of that data
+- (void)request:(ASIHTTPRequest *)request didReceiveBytes:(long long)bytes;
+
+// Called when the request sends some data
+// The first 32KB (128KB on older platforms) of data sent is not included in this amount because of limitations with the CFNetwork API
+// bytes may be less than zero if a request needs to remove upload progress (probably because the request needs to run again)
+- (void)request:(ASIHTTPRequest *)request didSendBytes:(long long)bytes;
+
+// Called when a request needs to change the length of the content to download
+- (void)request:(ASIHTTPRequest *)request incrementDownloadSizeBy:(long long)newLength;
+
+// Called when a request needs to change the length of the content to upload
+// newLength may be less than zero when a request needs to remove the size of the internal buffer from progress tracking
+- (void)request:(ASIHTTPRequest *)request incrementUploadSizeBy:(long long)newLength;
+@end
25 Example/AtomParser.h
View
@@ -0,0 +1,25 @@
+//
+// AtomParser.h
+//
+// Created by Mike Mayo on 1/28/10.
+// Copyright Mike Mayo 2010. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+@class FeedItem;
+
+@interface AtomParser : NSObject {
+ NSMutableString *currentElementValue;
+ NSString *currentDataType;
+ FeedItem *feedItem;
+ NSMutableArray *feedItems;
+ BOOL parsingItem;
+ BOOL parsingContent;
+}
+
+@property (nonatomic, retain) FeedItem *feedItem;
+@property (nonatomic, retain) NSString *currentDataType;
+@property (nonatomic, retain) NSMutableArray *feedItems;
+
+@end
105 Example/AtomParser.m
View
@@ -0,0 +1,105 @@
+//
+// AtomParser.m
+//
+// Created by Mike Mayo on 1/28/10.
+// Copyright Mike Mayo 2010. All rights reserved.
+//
+
+#import "AtomParser.h"
+#import "FeedItem.h"
+
+
+@implementation AtomParser
+
+@synthesize feedItem, currentDataType, feedItems;
+
+#pragma mark -
+#pragma mark Date Parser
+
+-(NSDate *)dateFromString:(NSString *)dateString {
+ NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
+ [dateFormatter setLocale:[[[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"] autorelease]];
+ [dateFormatter setDateFormat:@"yyyy-MM-dd'T'H:mm:sszzzz"];
+ NSDate *date = [dateFormatter dateFromString:dateString];
+ [dateFormatter release];
+ return date;
+}
+
+#pragma mark -
+#pragma mark XML Parser Delegate Methods
+
+- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qualifiedName attributes:(NSDictionary *)attributeDict {
+
+ if ([elementName isEqualToString:@"feed"]) {
+ // we're getting started, so go ahead and alloc the array
+ self.feedItems = [[NSMutableArray alloc] initWithCapacity:1];
+ } else if ([elementName isEqualToString:@"entry"]) {
+ self.feedItem = [[FeedItem alloc] init];
+ parsingItem = YES;
+ } else if ([elementName isEqualToString:@"content"]) {
+ self.feedItem.content = @"";
+ parsingContent = YES;
+ }
+}
+
+- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {
+
+ if ([elementName isEqualToString:@"entry"]) {
+ [self.feedItems addObject:self.feedItem];
+ parsingItem = NO;
+ } else if ([elementName isEqualToString:@"title"]) {
+ feedItem.title = [currentElementValue stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
+// } else if ([elementName isEqualToString:@"link"]) {
+// feedItem.link = [currentElementValue stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
+ } else if ([elementName isEqualToString:@"id"]) {
+ feedItem.guid = [currentElementValue stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
+ } else if ([elementName isEqualToString:@"summary"]) {
+ feedItem.description = [currentElementValue stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
+ } else if ([elementName isEqualToString:@"content"]) {
+ //feedItem.content = [currentElementValue stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
+ parsingContent = NO;
+ } else if ([elementName isEqualToString:@"name"]) {
+ feedItem.creator = [currentElementValue stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
+ } else if ([elementName isEqualToString:@"published"]) {
+ feedItem.pubDate = [self dateFromString:[currentElementValue stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]];
+ }
+
+ if (parsingContent) {
+ if ([elementName isEqualToString:@"div"]) {
+ // the div is just a wrapper for the rackcloud status item
+ } else if ([elementName isEqualToString:@"p"]) {
+ NSString *newLine = [currentElementValue stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
+ feedItem.content = [feedItem.content stringByReplacingOccurrencesOfString:@"\n" withString:@" "];
+ feedItem.content = [feedItem.content stringByReplacingOccurrencesOfString:@"\r" withString:@""];
+ feedItem.content = [feedItem.content stringByAppendingString:[NSString stringWithFormat:@"\n\n%@", newLine]];
+ }
+ }
+
+ if ([feedItem.content length] > 0 && [feedItem.content characterAtIndex:0] == ' ') {
+ feedItem.content = [feedItem.content substringFromIndex:1];
+ }
+
+
+ [currentElementValue release];
+ currentElementValue = nil;
+}
+
+- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
+ if (!currentElementValue) {
+ currentElementValue = [[NSMutableString alloc] initWithString:string];
+ } else {
+ [currentElementValue appendString:string];
+ }
+}
+
+#pragma mark -
+#pragma mark Memory Management
+
+- (void)dealloc {
+ [feedItem release];
+ [currentDataType release];
+ [feedItems release];
+ [super dealloc];
+}
+
+@end
36 Example/Classes/DetailViewController.h
View
@@ -0,0 +1,36 @@
+//
+// DetailViewController.h
+// Example
+//
+// Created by Michael Mayo on 5/28/10.
+// Copyright __MyCompanyName__ 2010. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+
+@interface DetailViewController : UIViewController <UIPopoverControllerDelegate, UISplitViewControllerDelegate, UITableViewDelegate, UITableViewDataSource> {
+
+ UIPopoverController *popoverController;
+ UIToolbar *toolbar;
+
+ id detailItem;
+ UILabel *detailDescriptionLabel;
+
+ NSMutableArray *feedItems;
+ IBOutlet UITableViewCell *nibLoadedFeedItemCell;
+ IBOutlet UITableViewCell *nibLoadedRSSEmptyCell;
+ IBOutlet UITableView *tableView;
+ BOOL rssRequestFailed;
+}
+
+@property (nonatomic, retain) IBOutlet UIToolbar *toolbar;
+
+@property (nonatomic, retain) id detailItem;
+@property (nonatomic, retain) IBOutlet UILabel *detailDescriptionLabel;
+
+@property (nonatomic, retain) NSMutableArray *feedItems;
+@property (nonatomic, retain) IBOutlet UITableViewCell *nibLoadedFeedItemCell;
+@property (nonatomic, retain) IBOutlet UITableViewCell *nibLoadedRSSEmptyCell;
+@property (nonatomic, retain) IBOutlet UITableView *tableView;
+
+@end
295 Example/Classes/DetailViewController.m
View
@@ -0,0 +1,295 @@
+//
+// DetailViewController.m
+// Example
+//
+// Created by Michael Mayo on 5/28/10.
+// Copyright __MyCompanyName__ 2010. All rights reserved.
+//
+
+#import "DetailViewController.h"
+#import "RootViewController.h"
+#import "ASIHTTPRequest.h"
+#import "RSSParser.h"
+#import "FeedItem.h"
+
+// Feed Item Cell Tags
+#define kDateTag 1
+#define kTitleTag 2
+#define kBodyTag 3
+#define kAuthorTag 4
+
+
+
+@interface DetailViewController ()
+@property (nonatomic, retain) UIPopoverController *popoverController;
+- (void)configureView;
+@end
+
+
+
+@implementation DetailViewController
+
+@synthesize toolbar, popoverController, detailItem, detailDescriptionLabel;
+@synthesize feedItems, tableView, nibLoadedFeedItemCell, nibLoadedRSSEmptyCell;
+
+#pragma mark -
+#pragma mark Managing the detail item
+
+/*
+ When setting the detail item, update the view and dismiss the popover controller if it's showing.
+ */
+- (void)setDetailItem:(id)newDetailItem {
+ if (detailItem != newDetailItem) {
+ [detailItem release];
+ detailItem = [newDetailItem retain];
+
+ // Update the view.
+ [self configureView];
+ }
+
+ if (popoverController != nil) {
+ [popoverController dismissPopoverAnimated:YES];
+ }
+}
+
+
+- (void)configureView {
+ // Update the user interface for the detail item.
+ detailDescriptionLabel.text = [detailItem description];
+}
+
+
+#pragma mark -
+#pragma mark Split view support
+
+- (void)splitViewController: (UISplitViewController*)svc willHideViewController:(UIViewController *)aViewController withBarButtonItem:(UIBarButtonItem*)barButtonItem forPopoverController: (UIPopoverController*)pc {
+
+ barButtonItem.title = @"Root List";
+ NSMutableArray *items = [[toolbar items] mutableCopy];
+ [items insertObject:barButtonItem atIndex:0];
+ [toolbar setItems:items animated:YES];
+ [items release];
+ self.popoverController = pc;
+}
+
+
+// Called when the view is shown again in the split view, invalidating the button and popover controller.
+- (void)splitViewController: (UISplitViewController*)svc willShowViewController:(UIViewController *)aViewController invalidatingBarButtonItem:(UIBarButtonItem *)barButtonItem {
+
+ NSMutableArray *items = [[toolbar items] mutableCopy];
+ [items removeObjectAtIndex:0];
+ [toolbar setItems:items animated:YES];
+ [items release];
+ self.popoverController = nil;
+}
+
+
+#pragma mark -
+#pragma mark Rotation support