added download feature for iOS port
required libs:
* ZipArchive - Obj-C impl of zip
* asi-http-request : http request help to assist with asynchoronous downloading of files
* minizip : support for ZipArchive
*
Added default splash screen for iOS app. (using the Wagic background to keep it neutral to module)
TODO: refine handling for iPad splash screen
* add selection screen and input screen for location of downloadable content. (ie core files, image files, etc )
* add support to opt out of backing up to iCloud for core files. Right now iOS will automatically backup all files under Documents folder to iCloud. Consider only allowing player data to be backed up to iCloud. All graphics and other assets are considered volatile.
This commit is contained in:
35
projects/mtg/iOS/asi-http-request/ASIAuthenticationDialog.h
Normal file
35
projects/mtg/iOS/asi-http-request/ASIAuthenticationDialog.h
Normal file
@@ -0,0 +1,35 @@
|
||||
//
|
||||
// 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>
|
||||
#import <UIKit/UIKit.h>
|
||||
@class ASIHTTPRequest;
|
||||
|
||||
typedef enum _ASIAuthenticationType {
|
||||
ASIStandardAuthenticationType = 0,
|
||||
ASIProxyAuthenticationType = 1
|
||||
} ASIAuthenticationType;
|
||||
|
||||
@interface ASIAutorotatingViewController : UIViewController
|
||||
@end
|
||||
|
||||
@interface ASIAuthenticationDialog : ASIAutorotatingViewController <UIActionSheetDelegate, UITableViewDelegate, UITableViewDataSource> {
|
||||
ASIHTTPRequest *request;
|
||||
ASIAuthenticationType type;
|
||||
UITableView *tableView;
|
||||
UIViewController *presentingController;
|
||||
BOOL didEnableRotationNotifications;
|
||||
}
|
||||
+ (void)presentAuthenticationDialogForRequest:(ASIHTTPRequest *)request;
|
||||
+ (void)dismiss;
|
||||
|
||||
@property (retain) ASIHTTPRequest *request;
|
||||
@property (assign) ASIAuthenticationType type;
|
||||
@property (assign) BOOL didEnableRotationNotifications;
|
||||
@property (retain, nonatomic) UIViewController *presentingController;
|
||||
@end
|
||||
487
projects/mtg/iOS/asi-http-request/ASIAuthenticationDialog.m
Normal file
487
projects/mtg/iOS/asi-http-request/ASIAuthenticationDialog.m
Normal file
@@ -0,0 +1,487 @@
|
||||
//
|
||||
// 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"
|
||||
#import <QuartzCore/QuartzCore.h>
|
||||
|
||||
static ASIAuthenticationDialog *sharedDialog = nil;
|
||||
BOOL isDismissing = NO;
|
||||
static NSMutableArray *requestsNeedingAuthentication = nil;
|
||||
|
||||
static const NSUInteger kUsernameRow = 0;
|
||||
static const NSUInteger kUsernameSection = 0;
|
||||
static const NSUInteger kPasswordRow = 1;
|
||||
static const NSUInteger kPasswordSection = 0;
|
||||
static const NSUInteger kDomainRow = 0;
|
||||
static const NSUInteger kDomainSection = 1;
|
||||
|
||||
|
||||
@implementation ASIAutorotatingViewController
|
||||
|
||||
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@interface ASIAuthenticationDialog ()
|
||||
- (void)showTitle;
|
||||
- (void)show;
|
||||
- (NSArray *)requestsRequiringTheseCredentials;
|
||||
- (void)presentNextDialog;
|
||||
- (void)keyboardWillShow:(NSNotification *)notification;
|
||||
- (void)orientationChanged:(NSNotification *)notification;
|
||||
- (void)cancelAuthenticationFromDialog:(id)sender;
|
||||
- (void)loginWithCredentialsFromDialog:(id)sender;
|
||||
@property (retain) UITableView *tableView;
|
||||
@end
|
||||
|
||||
@implementation ASIAuthenticationDialog
|
||||
|
||||
#pragma mark init / dealloc
|
||||
|
||||
+ (void)initialize
|
||||
{
|
||||
if (self == [ASIAuthenticationDialog class]) {
|
||||
requestsNeedingAuthentication = [[NSMutableArray array] retain];
|
||||
}
|
||||
}
|
||||
|
||||
+ (void)presentAuthenticationDialogForRequest:(ASIHTTPRequest *)theRequest
|
||||
{
|
||||
// No need for a lock here, this will always be called on the main thread
|
||||
if (!sharedDialog) {
|
||||
sharedDialog = [[self alloc] init];
|
||||
[sharedDialog setRequest:theRequest];
|
||||
if ([theRequest authenticationNeeded] == ASIProxyAuthenticationNeeded) {
|
||||
[sharedDialog setType:ASIProxyAuthenticationType];
|
||||
} else {
|
||||
[sharedDialog setType:ASIStandardAuthenticationType];
|
||||
}
|
||||
[sharedDialog show];
|
||||
} else {
|
||||
[requestsNeedingAuthentication addObject:theRequest];
|
||||
}
|
||||
}
|
||||
|
||||
- (id)init
|
||||
{
|
||||
if ((self = [self initWithNibName:nil bundle:nil])) {
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
|
||||
|
||||
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_3_2
|
||||
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
|
||||
#endif
|
||||
if (![UIDevice currentDevice].generatesDeviceOrientationNotifications) {
|
||||
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
|
||||
[self setDidEnableRotationNotifications:YES];
|
||||
}
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(orientationChanged:) name:UIDeviceOrientationDidChangeNotification object:nil];
|
||||
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_3_2
|
||||
}
|
||||
#endif
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
if ([self didEnableRotationNotifications]) {
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIDeviceOrientationDidChangeNotification object:nil];
|
||||
}
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillShowNotification object:nil];
|
||||
|
||||
[request release];
|
||||
[tableView release];
|
||||
[presentingController.view removeFromSuperview];
|
||||
[presentingController release];
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
#pragma mark keyboard notifications
|
||||
|
||||
- (void)keyboardWillShow:(NSNotification *)notification
|
||||
{
|
||||
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_3_2
|
||||
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
|
||||
#endif
|
||||
#if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_3_2
|
||||
NSValue *keyboardBoundsValue = [[notification userInfo] objectForKey:UIKeyboardFrameEndUserInfoKey];
|
||||
#else
|
||||
NSValue *keyboardBoundsValue = [[notification userInfo] objectForKey:UIKeyboardBoundsUserInfoKey];
|
||||
#endif
|
||||
CGRect keyboardBounds;
|
||||
[keyboardBoundsValue getValue:&keyboardBounds];
|
||||
UIEdgeInsets e = UIEdgeInsetsMake(0, 0, keyboardBounds.size.height, 0);
|
||||
[[self tableView] setScrollIndicatorInsets:e];
|
||||
[[self tableView] setContentInset:e];
|
||||
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_3_2
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// Manually handles orientation changes on iPhone
|
||||
- (void)orientationChanged:(NSNotification *)notification
|
||||
{
|
||||
[self showTitle];
|
||||
|
||||
UIInterfaceOrientation o = (UIInterfaceOrientation)[[UIApplication sharedApplication] statusBarOrientation];
|
||||
CGFloat angle = 0;
|
||||
switch (o) {
|
||||
case UIDeviceOrientationLandscapeLeft: angle = 90; break;
|
||||
case UIDeviceOrientationLandscapeRight: angle = -90; break;
|
||||
case UIDeviceOrientationPortraitUpsideDown: angle = 180; break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
CGRect f = [[UIScreen mainScreen] applicationFrame];
|
||||
|
||||
// Swap the frame height and width if necessary
|
||||
if (UIDeviceOrientationIsLandscape(o)) {
|
||||
CGFloat t;
|
||||
t = f.size.width;
|
||||
f.size.width = f.size.height;
|
||||
f.size.height = t;
|
||||
}
|
||||
|
||||
CGAffineTransform previousTransform = self.view.layer.affineTransform;
|
||||
CGAffineTransform newTransform = CGAffineTransformMakeRotation((CGFloat)(angle * M_PI / 180.0));
|
||||
|
||||
// Reset the transform so we can set the size
|
||||
self.view.layer.affineTransform = CGAffineTransformIdentity;
|
||||
self.view.frame = (CGRect){ { 0, 0 }, f.size};
|
||||
|
||||
// Revert to the previous transform for correct animation
|
||||
self.view.layer.affineTransform = previousTransform;
|
||||
|
||||
[UIView beginAnimations:nil context:NULL];
|
||||
[UIView setAnimationDuration:0.3];
|
||||
|
||||
// Set the new transform
|
||||
self.view.layer.affineTransform = newTransform;
|
||||
|
||||
// Fix the view origin
|
||||
self.view.frame = (CGRect){ { f.origin.x, f.origin.y },self.view.frame.size};
|
||||
[UIView commitAnimations];
|
||||
}
|
||||
|
||||
#pragma mark utilities
|
||||
|
||||
- (UIViewController *)presentingController
|
||||
{
|
||||
if (!presentingController) {
|
||||
presentingController = [[ASIAutorotatingViewController alloc] initWithNibName:nil bundle:nil];
|
||||
|
||||
// Attach to the window, but don't interfere.
|
||||
UIWindow *window = [[[UIApplication sharedApplication] windows] objectAtIndex:0];
|
||||
[window addSubview:[presentingController view]];
|
||||
[[presentingController view] setFrame:CGRectZero];
|
||||
[[presentingController view] setUserInteractionEnabled:NO];
|
||||
}
|
||||
|
||||
return presentingController;
|
||||
}
|
||||
|
||||
- (UITextField *)textFieldInRow:(NSUInteger)row section:(NSUInteger)section
|
||||
{
|
||||
return [[[[[self tableView] cellForRowAtIndexPath:
|
||||
[NSIndexPath indexPathForRow:row inSection:section]]
|
||||
contentView] subviews] objectAtIndex:0];
|
||||
}
|
||||
|
||||
- (UITextField *)usernameField
|
||||
{
|
||||
return [self textFieldInRow:kUsernameRow section:kUsernameSection];
|
||||
}
|
||||
|
||||
- (UITextField *)passwordField
|
||||
{
|
||||
return [self textFieldInRow:kPasswordRow section:kPasswordSection];
|
||||
}
|
||||
|
||||
- (UITextField *)domainField
|
||||
{
|
||||
return [self textFieldInRow:kDomainRow section:kDomainSection];
|
||||
}
|
||||
|
||||
#pragma mark show / dismiss
|
||||
|
||||
+ (void)dismiss
|
||||
{
|
||||
[[sharedDialog parentViewController] dismissModalViewControllerAnimated:YES];
|
||||
}
|
||||
|
||||
- (void)viewDidDisappear:(BOOL)animated
|
||||
{
|
||||
[self retain];
|
||||
[sharedDialog release];
|
||||
sharedDialog = nil;
|
||||
[self performSelector:@selector(presentNextDialog) withObject:nil afterDelay:0];
|
||||
[self release];
|
||||
}
|
||||
|
||||
- (void)dismiss
|
||||
{
|
||||
if (self == sharedDialog) {
|
||||
[[self class] dismiss];
|
||||
} else {
|
||||
[[self parentViewController] dismissModalViewControllerAnimated:YES];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)showTitle
|
||||
{
|
||||
UINavigationBar *navigationBar = [[[self view] subviews] objectAtIndex:0];
|
||||
UINavigationItem *navItem = [[navigationBar items] objectAtIndex:0];
|
||||
if (UIInterfaceOrientationIsPortrait([[UIDevice currentDevice] orientation])) {
|
||||
// Setup the title
|
||||
if ([self type] == ASIProxyAuthenticationType) {
|
||||
[navItem setPrompt:@"Login to this secure proxy server."];
|
||||
} else {
|
||||
[navItem setPrompt:@"Login to this secure server."];
|
||||
}
|
||||
} else {
|
||||
[navItem setPrompt:nil];
|
||||
}
|
||||
[navigationBar sizeToFit];
|
||||
CGRect f = [[self view] bounds];
|
||||
f.origin.y = [navigationBar frame].size.height;
|
||||
f.size.height -= f.origin.y;
|
||||
[[self tableView] setFrame:f];
|
||||
}
|
||||
|
||||
- (void)show
|
||||
{
|
||||
// Remove all subviews
|
||||
UIView *v;
|
||||
while ((v = [[[self view] subviews] lastObject])) {
|
||||
[v removeFromSuperview];
|
||||
}
|
||||
|
||||
// Setup toolbar
|
||||
UINavigationBar *bar = [[[UINavigationBar alloc] init] autorelease];
|
||||
[bar setAutoresizingMask:UIViewAutoresizingFlexibleWidth];
|
||||
|
||||
UINavigationItem *navItem = [[[UINavigationItem alloc] init] autorelease];
|
||||
bar.items = [NSArray arrayWithObject:navItem];
|
||||
|
||||
[[self view] addSubview:bar];
|
||||
|
||||
[self showTitle];
|
||||
|
||||
// Setup toolbar buttons
|
||||
if ([self type] == ASIProxyAuthenticationType) {
|
||||
[navItem setTitle:[[self request] proxyHost]];
|
||||
} else {
|
||||
[navItem setTitle:[[[self request] url] host]];
|
||||
}
|
||||
|
||||
[navItem setLeftBarButtonItem:[[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel target:self action:@selector(cancelAuthenticationFromDialog:)] autorelease]];
|
||||
[navItem setRightBarButtonItem:[[[UIBarButtonItem alloc] initWithTitle:@"Login" style:UIBarButtonItemStyleDone target:self action:@selector(loginWithCredentialsFromDialog:)] autorelease]];
|
||||
|
||||
// We show the login form in a table view, similar to Safari's authentication dialog
|
||||
[bar sizeToFit];
|
||||
CGRect f = [[self view] bounds];
|
||||
f.origin.y = [bar frame].size.height;
|
||||
f.size.height -= f.origin.y;
|
||||
|
||||
[self setTableView:[[[UITableView alloc] initWithFrame:f style:UITableViewStyleGrouped] autorelease]];
|
||||
[[self tableView] setDelegate:self];
|
||||
[[self tableView] setDataSource:self];
|
||||
[[self tableView] setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight];
|
||||
[[self view] addSubview:[self tableView]];
|
||||
|
||||
// Force reload the table content, and focus the first field to show the keyboard
|
||||
[[self tableView] reloadData];
|
||||
[[[[[self tableView] cellForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]].contentView subviews] objectAtIndex:0] becomeFirstResponder];
|
||||
|
||||
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_3_2
|
||||
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
|
||||
[self setModalPresentationStyle:UIModalPresentationFormSheet];
|
||||
}
|
||||
#endif
|
||||
|
||||
[[self presentingController] presentModalViewController:self animated:YES];
|
||||
}
|
||||
|
||||
#pragma mark button callbacks
|
||||
|
||||
- (void)cancelAuthenticationFromDialog:(id)sender
|
||||
{
|
||||
for (ASIHTTPRequest *theRequest in [self requestsRequiringTheseCredentials]) {
|
||||
[theRequest cancelAuthentication];
|
||||
[requestsNeedingAuthentication removeObject:theRequest];
|
||||
}
|
||||
[self dismiss];
|
||||
}
|
||||
|
||||
- (NSArray *)requestsRequiringTheseCredentials
|
||||
{
|
||||
NSMutableArray *requestsRequiringTheseCredentials = [NSMutableArray array];
|
||||
NSURL *requestURL = [[self request] url];
|
||||
for (ASIHTTPRequest *otherRequest in requestsNeedingAuthentication) {
|
||||
NSURL *theURL = [otherRequest url];
|
||||
if (([otherRequest authenticationNeeded] == [[self request] authenticationNeeded]) && [[theURL host] isEqualToString:[requestURL host]] && ([theURL port] == [requestURL port] || ([requestURL port] && [[theURL port] isEqualToNumber:[requestURL port]])) && [[theURL scheme] isEqualToString:[requestURL scheme]] && ((![otherRequest authenticationRealm] && ![[self request] authenticationRealm]) || ([otherRequest authenticationRealm] && [[self request] authenticationRealm] && [[[self request] authenticationRealm] isEqualToString:[otherRequest authenticationRealm]]))) {
|
||||
[requestsRequiringTheseCredentials addObject:otherRequest];
|
||||
}
|
||||
}
|
||||
[requestsRequiringTheseCredentials addObject:[self request]];
|
||||
return requestsRequiringTheseCredentials;
|
||||
}
|
||||
|
||||
- (void)presentNextDialog
|
||||
{
|
||||
if ([requestsNeedingAuthentication count]) {
|
||||
ASIHTTPRequest *nextRequest = [requestsNeedingAuthentication objectAtIndex:0];
|
||||
[requestsNeedingAuthentication removeObjectAtIndex:0];
|
||||
[[self class] presentAuthenticationDialogForRequest:nextRequest];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
- (void)loginWithCredentialsFromDialog:(id)sender
|
||||
{
|
||||
for (ASIHTTPRequest *theRequest in [self requestsRequiringTheseCredentials]) {
|
||||
|
||||
NSString *username = [[self usernameField] text];
|
||||
NSString *password = [[self passwordField] text];
|
||||
|
||||
if (username == nil) { username = @""; }
|
||||
if (password == nil) { password = @""; }
|
||||
|
||||
if ([self type] == ASIProxyAuthenticationType) {
|
||||
[theRequest setProxyUsername:username];
|
||||
[theRequest setProxyPassword:password];
|
||||
} else {
|
||||
[theRequest setUsername:username];
|
||||
[theRequest setPassword:password];
|
||||
}
|
||||
|
||||
// Handle NTLM domains
|
||||
NSString *scheme = ([self type] == ASIStandardAuthenticationType) ? [[self request] authenticationScheme] : [[self request] proxyAuthenticationScheme];
|
||||
if ([scheme isEqualToString:(NSString *)kCFHTTPAuthenticationSchemeNTLM]) {
|
||||
NSString *domain = [[self domainField] text];
|
||||
if ([self type] == ASIProxyAuthenticationType) {
|
||||
[theRequest setProxyDomain:domain];
|
||||
} else {
|
||||
[theRequest setDomain:domain];
|
||||
}
|
||||
}
|
||||
|
||||
[theRequest retryUsingSuppliedCredentials];
|
||||
[requestsNeedingAuthentication removeObject:theRequest];
|
||||
}
|
||||
[self dismiss];
|
||||
}
|
||||
|
||||
#pragma mark table view data source
|
||||
|
||||
- (NSInteger)numberOfSectionsInTableView:(UITableView *)aTableView
|
||||
{
|
||||
NSString *scheme = ([self type] == ASIStandardAuthenticationType) ? [[self request] authenticationScheme] : [[self request] proxyAuthenticationScheme];
|
||||
if ([scheme isEqualToString:(NSString *)kCFHTTPAuthenticationSchemeNTLM]) {
|
||||
return 2;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
- (CGFloat)tableView:(UITableView *)aTableView heightForFooterInSection:(NSInteger)section
|
||||
{
|
||||
if (section == [self numberOfSectionsInTableView:aTableView]-1) {
|
||||
return 30;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
- (CGFloat)tableView:(UITableView *)aTableView heightForHeaderInSection:(NSInteger)section
|
||||
{
|
||||
if (section == 0) {
|
||||
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_3_2
|
||||
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
|
||||
return 54;
|
||||
}
|
||||
#endif
|
||||
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] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil] autorelease];
|
||||
#else
|
||||
UITableViewCell *cell = [[[UITableViewCell alloc] initWithFrame:CGRectMake(0,0,0,0) reuseIdentifier:nil] autorelease];
|
||||
#endif
|
||||
|
||||
[cell setSelectionStyle:UITableViewCellSelectionStyleNone];
|
||||
|
||||
CGRect f = CGRectInset([cell bounds], 10, 10);
|
||||
UITextField *textField = [[[UITextField alloc] initWithFrame:f] autorelease];
|
||||
[textField setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight];
|
||||
[textField setAutocapitalizationType:UITextAutocapitalizationTypeNone];
|
||||
[textField setAutocorrectionType:UITextAutocorrectionTypeNo];
|
||||
|
||||
NSUInteger s = [indexPath section];
|
||||
NSUInteger r = [indexPath row];
|
||||
|
||||
if (s == kUsernameSection && r == kUsernameRow) {
|
||||
[textField setPlaceholder:@"User"];
|
||||
} else if (s == kPasswordSection && r == kPasswordRow) {
|
||||
[textField setPlaceholder:@"Password"];
|
||||
[textField setSecureTextEntry:YES];
|
||||
} else if (s == kDomainSection && r == kDomainRow) {
|
||||
[textField setPlaceholder:@"Domain"];
|
||||
}
|
||||
[cell.contentView addSubview:textField];
|
||||
|
||||
return cell;
|
||||
}
|
||||
|
||||
- (NSInteger)tableView:(UITableView *)aTableView numberOfRowsInSection:(NSInteger)section
|
||||
{
|
||||
if (section == 0) {
|
||||
return 2;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)tableView:(UITableView *)aTableView titleForFooterInSection:(NSInteger)section
|
||||
{
|
||||
if (section == [self numberOfSectionsInTableView:aTableView]-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;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@synthesize request;
|
||||
@synthesize type;
|
||||
@synthesize tableView;
|
||||
@synthesize didEnableRotationNotifications;
|
||||
@synthesize presentingController;
|
||||
@end
|
||||
103
projects/mtg/iOS/asi-http-request/ASICacheDelegate.h
Normal file
103
projects/mtg/iOS/asi-http-request/ASICacheDelegate.h
Normal file
@@ -0,0 +1,103 @@
|
||||
//
|
||||
// ASICacheDelegate.h
|
||||
// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest
|
||||
//
|
||||
// Created by Ben Copsey on 01/05/2010.
|
||||
// Copyright 2010 All-Seeing Interactive. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
@class ASIHTTPRequest;
|
||||
|
||||
// Cache policies control the behaviour of a cache and how requests use the cache
|
||||
// When setting a cache policy, you can use a combination of these values as a bitmask
|
||||
// For example: [request setCachePolicy:ASIAskServerIfModifiedCachePolicy|ASIFallbackToCacheIfLoadFailsCachePolicy|ASIDoNotWriteToCacheCachePolicy];
|
||||
// Note that some of the behaviours below are mutally exclusive - you cannot combine ASIAskServerIfModifiedWhenStaleCachePolicy and ASIAskServerIfModifiedCachePolicy, for example.
|
||||
typedef enum _ASICachePolicy {
|
||||
|
||||
// The default cache policy. When you set a request to use this, it will use the cache's defaultCachePolicy
|
||||
// ASIDownloadCache's default cache policy is 'ASIAskServerIfModifiedWhenStaleCachePolicy'
|
||||
ASIUseDefaultCachePolicy = 0,
|
||||
|
||||
// Tell the request not to read from the cache
|
||||
ASIDoNotReadFromCacheCachePolicy = 1,
|
||||
|
||||
// The the request not to write to the cache
|
||||
ASIDoNotWriteToCacheCachePolicy = 2,
|
||||
|
||||
// Ask the server if there is an updated version of this resource (using a conditional GET) ONLY when the cached data is stale
|
||||
ASIAskServerIfModifiedWhenStaleCachePolicy = 4,
|
||||
|
||||
// Always ask the server if there is an updated version of this resource (using a conditional GET)
|
||||
ASIAskServerIfModifiedCachePolicy = 8,
|
||||
|
||||
// If cached data exists, use it even if it is stale. This means requests will not talk to the server unless the resource they are requesting is not in the cache
|
||||
ASIOnlyLoadIfNotCachedCachePolicy = 16,
|
||||
|
||||
// If cached data exists, use it even if it is stale. If cached data does not exist, stop (will not set an error on the request)
|
||||
ASIDontLoadCachePolicy = 32,
|
||||
|
||||
// Specifies that cached data may be used if the request fails. If cached data is used, the request will succeed without error. Usually used in combination with other options above.
|
||||
ASIFallbackToCacheIfLoadFailsCachePolicy = 64
|
||||
} ASICachePolicy;
|
||||
|
||||
// Cache storage policies control whether cached data persists between application launches (ASICachePermanentlyCacheStoragePolicy) or not (ASICacheForSessionDurationCacheStoragePolicy)
|
||||
// Calling [ASIHTTPRequest clearSession] will remove any data stored using ASICacheForSessionDurationCacheStoragePolicy
|
||||
typedef enum _ASICacheStoragePolicy {
|
||||
ASICacheForSessionDurationCacheStoragePolicy = 0,
|
||||
ASICachePermanentlyCacheStoragePolicy = 1
|
||||
} ASICacheStoragePolicy;
|
||||
|
||||
|
||||
@protocol ASICacheDelegate <NSObject>
|
||||
|
||||
@required
|
||||
|
||||
// Should return the cache policy that will be used when requests have their cache policy set to ASIUseDefaultCachePolicy
|
||||
- (ASICachePolicy)defaultCachePolicy;
|
||||
|
||||
// Returns the date a cached response should expire on. Pass a non-zero max age to specify a custom date.
|
||||
- (NSDate *)expiryDateForRequest:(ASIHTTPRequest *)request maxAge:(NSTimeInterval)maxAge;
|
||||
|
||||
// Updates cached response headers with a new expiry date. Pass a non-zero max age to specify a custom date.
|
||||
- (void)updateExpiryForRequest:(ASIHTTPRequest *)request maxAge:(NSTimeInterval)maxAge;
|
||||
|
||||
// Looks at the request's cache policy and any cached headers to determine if the cache data is still valid
|
||||
- (BOOL)canUseCachedDataForRequest:(ASIHTTPRequest *)request;
|
||||
|
||||
// Removes cached data for a particular request
|
||||
- (void)removeCachedDataForRequest:(ASIHTTPRequest *)request;
|
||||
|
||||
// Should return YES if the cache considers its cached response current for the request
|
||||
// Should return NO is the data is not cached, or (for example) if the cached headers state the request should have expired
|
||||
- (BOOL)isCachedDataCurrentForRequest:(ASIHTTPRequest *)request;
|
||||
|
||||
// Should store the response for the passed request in the cache
|
||||
// When a non-zero maxAge is passed, it should be used as the expiry time for the cached response
|
||||
- (void)storeResponseForRequest:(ASIHTTPRequest *)request maxAge:(NSTimeInterval)maxAge;
|
||||
|
||||
// Removes cached data for a particular url
|
||||
- (void)removeCachedDataForURL:(NSURL *)url;
|
||||
|
||||
// Should return an NSDictionary of cached headers for the passed URL, if it is stored in the cache
|
||||
- (NSDictionary *)cachedResponseHeadersForURL:(NSURL *)url;
|
||||
|
||||
// Should return the cached body of a response for the passed URL, if it is stored in the cache
|
||||
- (NSData *)cachedResponseDataForURL:(NSURL *)url;
|
||||
|
||||
// Returns a path to the cached response data, if it exists
|
||||
- (NSString *)pathToCachedResponseDataForURL:(NSURL *)url;
|
||||
|
||||
// Returns a path to the cached response headers, if they url
|
||||
- (NSString *)pathToCachedResponseHeadersForURL:(NSURL *)url;
|
||||
|
||||
// Returns the location to use to store cached response headers for a particular request
|
||||
- (NSString *)pathToStoreCachedResponseHeadersForRequest:(ASIHTTPRequest *)request;
|
||||
|
||||
// Returns the location to use to store a cached response body for a particular request
|
||||
- (NSString *)pathToStoreCachedResponseDataForRequest:(ASIHTTPRequest *)request;
|
||||
|
||||
// Clear cached data stored for the passed storage policy
|
||||
- (void)clearCachedResponsesForStoragePolicy:(ASICacheStoragePolicy)cachePolicy;
|
||||
|
||||
@end
|
||||
42
projects/mtg/iOS/asi-http-request/ASIDataCompressor.h
Normal file
42
projects/mtg/iOS/asi-http-request/ASIDataCompressor.h
Normal file
@@ -0,0 +1,42 @@
|
||||
//
|
||||
// ASIDataCompressor.h
|
||||
// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest
|
||||
//
|
||||
// Created by Ben Copsey on 17/08/2010.
|
||||
// Copyright 2010 All-Seeing Interactive. All rights reserved.
|
||||
//
|
||||
|
||||
// This is a helper class used by ASIHTTPRequest to handle deflating (compressing) data in memory and on disk
|
||||
// You may also find it helpful if you need to deflate data and files yourself - see the class methods below
|
||||
// Most of the zlib stuff is based on the sample code by Mark Adler available at http://zlib.net
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <zlib.h>
|
||||
|
||||
@interface ASIDataCompressor : NSObject {
|
||||
BOOL streamReady;
|
||||
z_stream zStream;
|
||||
}
|
||||
|
||||
// Convenience constructor will call setupStream for you
|
||||
+ (id)compressor;
|
||||
|
||||
// Compress the passed chunk of data
|
||||
// Passing YES for shouldFinish will finalize the deflated data - you must pass YES when you are on the last chunk of data
|
||||
- (NSData *)compressBytes:(Bytef *)bytes length:(NSUInteger)length error:(NSError **)err shouldFinish:(BOOL)shouldFinish;
|
||||
|
||||
// Convenience method - pass it some data, and you'll get deflated data back
|
||||
+ (NSData *)compressData:(NSData*)uncompressedData error:(NSError **)err;
|
||||
|
||||
// Convenience method - pass it a file containing the data to compress in sourcePath, and it will write deflated data to destinationPath
|
||||
+ (BOOL)compressDataFromFile:(NSString *)sourcePath toFile:(NSString *)destinationPath error:(NSError **)err;
|
||||
|
||||
// Sets up zlib to handle the inflating. You only need to call this yourself if you aren't using the convenience constructor 'compressor'
|
||||
- (NSError *)setupStream;
|
||||
|
||||
// Tells zlib to clean up. You need to call this if you need to cancel deflating part way through
|
||||
// If deflating finishes or fails, this method will be called automatically
|
||||
- (NSError *)closeStream;
|
||||
|
||||
@property (assign, readonly) BOOL streamReady;
|
||||
@end
|
||||
219
projects/mtg/iOS/asi-http-request/ASIDataCompressor.m
Normal file
219
projects/mtg/iOS/asi-http-request/ASIDataCompressor.m
Normal file
@@ -0,0 +1,219 @@
|
||||
//
|
||||
// ASIDataCompressor.m
|
||||
// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest
|
||||
//
|
||||
// Created by Ben Copsey on 17/08/2010.
|
||||
// Copyright 2010 All-Seeing Interactive. All rights reserved.
|
||||
//
|
||||
|
||||
#import "ASIDataCompressor.h"
|
||||
#import "ASIHTTPRequest.h"
|
||||
|
||||
#define DATA_CHUNK_SIZE 262144 // Deal with gzipped data in 256KB chunks
|
||||
#define COMPRESSION_AMOUNT Z_DEFAULT_COMPRESSION
|
||||
|
||||
@interface ASIDataCompressor ()
|
||||
+ (NSError *)deflateErrorWithCode:(int)code;
|
||||
@end
|
||||
|
||||
@implementation ASIDataCompressor
|
||||
|
||||
+ (id)compressor
|
||||
{
|
||||
ASIDataCompressor *compressor = [[[self alloc] init] autorelease];
|
||||
[compressor setupStream];
|
||||
return compressor;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
if (streamReady) {
|
||||
[self closeStream];
|
||||
}
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (NSError *)setupStream
|
||||
{
|
||||
if (streamReady) {
|
||||
return nil;
|
||||
}
|
||||
// Setup the inflate stream
|
||||
zStream.zalloc = Z_NULL;
|
||||
zStream.zfree = Z_NULL;
|
||||
zStream.opaque = Z_NULL;
|
||||
zStream.avail_in = 0;
|
||||
zStream.next_in = 0;
|
||||
int status = deflateInit2(&zStream, COMPRESSION_AMOUNT, Z_DEFLATED, (15+16), 8, Z_DEFAULT_STRATEGY);
|
||||
if (status != Z_OK) {
|
||||
return [[self class] deflateErrorWithCode:status];
|
||||
}
|
||||
streamReady = YES;
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (NSError *)closeStream
|
||||
{
|
||||
if (!streamReady) {
|
||||
return nil;
|
||||
}
|
||||
// Close the deflate stream
|
||||
streamReady = NO;
|
||||
int status = deflateEnd(&zStream);
|
||||
if (status != Z_OK) {
|
||||
return [[self class] deflateErrorWithCode:status];
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (NSData *)compressBytes:(Bytef *)bytes length:(NSUInteger)length error:(NSError **)err shouldFinish:(BOOL)shouldFinish
|
||||
{
|
||||
if (length == 0) return nil;
|
||||
|
||||
NSUInteger halfLength = length/2;
|
||||
|
||||
// We'll take a guess that the compressed data will fit in half the size of the original (ie the max to compress at once is half DATA_CHUNK_SIZE), if not, we'll increase it below
|
||||
NSMutableData *outputData = [NSMutableData dataWithLength:length/2];
|
||||
|
||||
int status;
|
||||
|
||||
zStream.next_in = bytes;
|
||||
zStream.avail_in = (unsigned int)length;
|
||||
zStream.avail_out = 0;
|
||||
|
||||
NSInteger bytesProcessedAlready = zStream.total_out;
|
||||
while (zStream.avail_out == 0) {
|
||||
|
||||
if (zStream.total_out-bytesProcessedAlready >= [outputData length]) {
|
||||
[outputData increaseLengthBy:halfLength];
|
||||
}
|
||||
|
||||
zStream.next_out = (Bytef*)[outputData mutableBytes] + zStream.total_out-bytesProcessedAlready;
|
||||
zStream.avail_out = (unsigned int)([outputData length] - (zStream.total_out-bytesProcessedAlready));
|
||||
status = deflate(&zStream, shouldFinish ? Z_FINISH : Z_NO_FLUSH);
|
||||
|
||||
if (status == Z_STREAM_END) {
|
||||
break;
|
||||
} else if (status != Z_OK) {
|
||||
if (err) {
|
||||
*err = [[self class] deflateErrorWithCode:status];
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
|
||||
// Set real length
|
||||
[outputData setLength: zStream.total_out-bytesProcessedAlready];
|
||||
return outputData;
|
||||
}
|
||||
|
||||
|
||||
+ (NSData *)compressData:(NSData*)uncompressedData error:(NSError **)err
|
||||
{
|
||||
NSError *theError = nil;
|
||||
NSData *outputData = [[ASIDataCompressor compressor] compressBytes:(Bytef *)[uncompressedData bytes] length:[uncompressedData length] error:&theError shouldFinish:YES];
|
||||
if (theError) {
|
||||
if (err) {
|
||||
*err = theError;
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
return outputData;
|
||||
}
|
||||
|
||||
|
||||
|
||||
+ (BOOL)compressDataFromFile:(NSString *)sourcePath toFile:(NSString *)destinationPath error:(NSError **)err
|
||||
{
|
||||
NSFileManager *fileManager = [[[NSFileManager alloc] init] autorelease];
|
||||
|
||||
// Create an empty file at the destination path
|
||||
if (![fileManager createFileAtPath:destinationPath contents:[NSData data] attributes:nil]) {
|
||||
if (err) {
|
||||
*err = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Compression of %@ failed because we were to create a file at %@",sourcePath,destinationPath],NSLocalizedDescriptionKey,nil]];
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
// Ensure the source file exists
|
||||
if (![fileManager fileExistsAtPath:sourcePath]) {
|
||||
if (err) {
|
||||
*err = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Compression of %@ failed the file does not exist",sourcePath],NSLocalizedDescriptionKey,nil]];
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
UInt8 inputData[DATA_CHUNK_SIZE];
|
||||
NSData *outputData;
|
||||
NSInteger readLength;
|
||||
NSError *theError = nil;
|
||||
|
||||
ASIDataCompressor *compressor = [ASIDataCompressor compressor];
|
||||
|
||||
NSInputStream *inputStream = [NSInputStream inputStreamWithFileAtPath:sourcePath];
|
||||
[inputStream open];
|
||||
NSOutputStream *outputStream = [NSOutputStream outputStreamToFileAtPath:destinationPath append:NO];
|
||||
[outputStream open];
|
||||
|
||||
while ([compressor streamReady]) {
|
||||
|
||||
// Read some data from the file
|
||||
readLength = [inputStream read:inputData maxLength:DATA_CHUNK_SIZE];
|
||||
|
||||
// Make sure nothing went wrong
|
||||
if ([inputStream streamStatus] == NSStreamEventErrorOccurred) {
|
||||
if (err) {
|
||||
*err = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Compression of %@ failed because we were unable to read from the source data file",sourcePath],NSLocalizedDescriptionKey,[inputStream streamError],NSUnderlyingErrorKey,nil]];
|
||||
}
|
||||
[compressor closeStream];
|
||||
return NO;
|
||||
}
|
||||
// Have we reached the end of the input data?
|
||||
if (!readLength) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Attempt to deflate the chunk of data
|
||||
outputData = [compressor compressBytes:inputData length:readLength error:&theError shouldFinish:readLength < DATA_CHUNK_SIZE ];
|
||||
if (theError) {
|
||||
if (err) {
|
||||
*err = theError;
|
||||
}
|
||||
[compressor closeStream];
|
||||
return NO;
|
||||
}
|
||||
|
||||
// Write the deflated data out to the destination file
|
||||
[outputStream write:(const uint8_t *)[outputData bytes] maxLength:[outputData length]];
|
||||
|
||||
// Make sure nothing went wrong
|
||||
if ([inputStream streamStatus] == NSStreamEventErrorOccurred) {
|
||||
if (err) {
|
||||
*err = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Compression of %@ failed because we were unable to write to the destination data file at %@",sourcePath,destinationPath],NSLocalizedDescriptionKey,[outputStream streamError],NSUnderlyingErrorKey,nil]];
|
||||
}
|
||||
[compressor closeStream];
|
||||
return NO;
|
||||
}
|
||||
|
||||
}
|
||||
[inputStream close];
|
||||
[outputStream close];
|
||||
|
||||
NSError *error = [compressor closeStream];
|
||||
if (error) {
|
||||
if (err) {
|
||||
*err = error;
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
+ (NSError *)deflateErrorWithCode:(int)code
|
||||
{
|
||||
return [NSError errorWithDomain:NetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Compression of data failed with code %hi",code],NSLocalizedDescriptionKey,nil]];
|
||||
}
|
||||
|
||||
@synthesize streamReady;
|
||||
@end
|
||||
41
projects/mtg/iOS/asi-http-request/ASIDataDecompressor.h
Normal file
41
projects/mtg/iOS/asi-http-request/ASIDataDecompressor.h
Normal file
@@ -0,0 +1,41 @@
|
||||
//
|
||||
// ASIDataDecompressor.h
|
||||
// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest
|
||||
//
|
||||
// Created by Ben Copsey on 17/08/2010.
|
||||
// Copyright 2010 All-Seeing Interactive. All rights reserved.
|
||||
//
|
||||
|
||||
// This is a helper class used by ASIHTTPRequest to handle inflating (decompressing) data in memory and on disk
|
||||
// You may also find it helpful if you need to inflate data and files yourself - see the class methods below
|
||||
// Most of the zlib stuff is based on the sample code by Mark Adler available at http://zlib.net
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <zlib.h>
|
||||
|
||||
@interface ASIDataDecompressor : NSObject {
|
||||
BOOL streamReady;
|
||||
z_stream zStream;
|
||||
}
|
||||
|
||||
// Convenience constructor will call setupStream for you
|
||||
+ (id)decompressor;
|
||||
|
||||
// Uncompress the passed chunk of data
|
||||
- (NSData *)uncompressBytes:(Bytef *)bytes length:(NSUInteger)length error:(NSError **)err;
|
||||
|
||||
// Convenience method - pass it some deflated data, and you'll get inflated data back
|
||||
+ (NSData *)uncompressData:(NSData*)compressedData error:(NSError **)err;
|
||||
|
||||
// Convenience method - pass it a file containing deflated data in sourcePath, and it will write inflated data to destinationPath
|
||||
+ (BOOL)uncompressDataFromFile:(NSString *)sourcePath toFile:(NSString *)destinationPath error:(NSError **)err;
|
||||
|
||||
// Sets up zlib to handle the inflating. You only need to call this yourself if you aren't using the convenience constructor 'decompressor'
|
||||
- (NSError *)setupStream;
|
||||
|
||||
// Tells zlib to clean up. You need to call this if you need to cancel inflating part way through
|
||||
// If inflating finishes or fails, this method will be called automatically
|
||||
- (NSError *)closeStream;
|
||||
|
||||
@property (assign, readonly) BOOL streamReady;
|
||||
@end
|
||||
218
projects/mtg/iOS/asi-http-request/ASIDataDecompressor.m
Normal file
218
projects/mtg/iOS/asi-http-request/ASIDataDecompressor.m
Normal file
@@ -0,0 +1,218 @@
|
||||
//
|
||||
// ASIDataDecompressor.m
|
||||
// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest
|
||||
//
|
||||
// Created by Ben Copsey on 17/08/2010.
|
||||
// Copyright 2010 All-Seeing Interactive. All rights reserved.
|
||||
//
|
||||
|
||||
#import "ASIDataDecompressor.h"
|
||||
#import "ASIHTTPRequest.h"
|
||||
|
||||
#define DATA_CHUNK_SIZE 262144 // Deal with gzipped data in 256KB chunks
|
||||
|
||||
@interface ASIDataDecompressor ()
|
||||
+ (NSError *)inflateErrorWithCode:(int)code;
|
||||
@end;
|
||||
|
||||
@implementation ASIDataDecompressor
|
||||
|
||||
+ (id)decompressor
|
||||
{
|
||||
ASIDataDecompressor *decompressor = [[[self alloc] init] autorelease];
|
||||
[decompressor setupStream];
|
||||
return decompressor;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
if (streamReady) {
|
||||
[self closeStream];
|
||||
}
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (NSError *)setupStream
|
||||
{
|
||||
if (streamReady) {
|
||||
return nil;
|
||||
}
|
||||
// Setup the inflate stream
|
||||
zStream.zalloc = Z_NULL;
|
||||
zStream.zfree = Z_NULL;
|
||||
zStream.opaque = Z_NULL;
|
||||
zStream.avail_in = 0;
|
||||
zStream.next_in = 0;
|
||||
int status = inflateInit2(&zStream, (15+32));
|
||||
if (status != Z_OK) {
|
||||
return [[self class] inflateErrorWithCode:status];
|
||||
}
|
||||
streamReady = YES;
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (NSError *)closeStream
|
||||
{
|
||||
if (!streamReady) {
|
||||
return nil;
|
||||
}
|
||||
// Close the inflate stream
|
||||
streamReady = NO;
|
||||
int status = inflateEnd(&zStream);
|
||||
if (status != Z_OK) {
|
||||
return [[self class] inflateErrorWithCode:status];
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (NSData *)uncompressBytes:(Bytef *)bytes length:(NSUInteger)length error:(NSError **)err
|
||||
{
|
||||
if (length == 0) return nil;
|
||||
|
||||
NSUInteger halfLength = length/2;
|
||||
NSMutableData *outputData = [NSMutableData dataWithLength:length+halfLength];
|
||||
|
||||
int status;
|
||||
|
||||
zStream.next_in = bytes;
|
||||
zStream.avail_in = (unsigned int)length;
|
||||
zStream.avail_out = 0;
|
||||
|
||||
NSInteger bytesProcessedAlready = zStream.total_out;
|
||||
while (zStream.avail_in != 0) {
|
||||
|
||||
if (zStream.total_out-bytesProcessedAlready >= [outputData length]) {
|
||||
[outputData increaseLengthBy:halfLength];
|
||||
}
|
||||
|
||||
zStream.next_out = (Bytef*)[outputData mutableBytes] + zStream.total_out-bytesProcessedAlready;
|
||||
zStream.avail_out = (unsigned int)([outputData length] - (zStream.total_out-bytesProcessedAlready));
|
||||
|
||||
status = inflate(&zStream, Z_NO_FLUSH);
|
||||
|
||||
if (status == Z_STREAM_END) {
|
||||
break;
|
||||
} else if (status != Z_OK) {
|
||||
if (err) {
|
||||
*err = [[self class] inflateErrorWithCode:status];
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
// Set real length
|
||||
[outputData setLength: zStream.total_out-bytesProcessedAlready];
|
||||
return outputData;
|
||||
}
|
||||
|
||||
|
||||
+ (NSData *)uncompressData:(NSData*)compressedData error:(NSError **)err
|
||||
{
|
||||
NSError *theError = nil;
|
||||
NSData *outputData = [[ASIDataDecompressor decompressor] uncompressBytes:(Bytef *)[compressedData bytes] length:[compressedData length] error:&theError];
|
||||
if (theError) {
|
||||
if (err) {
|
||||
*err = theError;
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
return outputData;
|
||||
}
|
||||
|
||||
+ (BOOL)uncompressDataFromFile:(NSString *)sourcePath toFile:(NSString *)destinationPath error:(NSError **)err
|
||||
{
|
||||
NSFileManager *fileManager = [[[NSFileManager alloc] init] autorelease];
|
||||
|
||||
// Create an empty file at the destination path
|
||||
if (![fileManager createFileAtPath:destinationPath contents:[NSData data] attributes:nil]) {
|
||||
if (err) {
|
||||
*err = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Decompression of %@ failed because we were to create a file at %@",sourcePath,destinationPath],NSLocalizedDescriptionKey,nil]];
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
// Ensure the source file exists
|
||||
if (![fileManager fileExistsAtPath:sourcePath]) {
|
||||
if (err) {
|
||||
*err = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Decompression of %@ failed the file does not exist",sourcePath],NSLocalizedDescriptionKey,nil]];
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
UInt8 inputData[DATA_CHUNK_SIZE];
|
||||
NSData *outputData;
|
||||
NSInteger readLength;
|
||||
NSError *theError = nil;
|
||||
|
||||
|
||||
ASIDataDecompressor *decompressor = [ASIDataDecompressor decompressor];
|
||||
|
||||
NSInputStream *inputStream = [NSInputStream inputStreamWithFileAtPath:sourcePath];
|
||||
[inputStream open];
|
||||
NSOutputStream *outputStream = [NSOutputStream outputStreamToFileAtPath:destinationPath append:NO];
|
||||
[outputStream open];
|
||||
|
||||
while ([decompressor streamReady]) {
|
||||
|
||||
// Read some data from the file
|
||||
readLength = [inputStream read:inputData maxLength:DATA_CHUNK_SIZE];
|
||||
|
||||
// Make sure nothing went wrong
|
||||
if ([inputStream streamStatus] == NSStreamEventErrorOccurred) {
|
||||
if (err) {
|
||||
*err = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Decompression of %@ failed because we were unable to read from the source data file",sourcePath],NSLocalizedDescriptionKey,[inputStream streamError],NSUnderlyingErrorKey,nil]];
|
||||
}
|
||||
[decompressor closeStream];
|
||||
return NO;
|
||||
}
|
||||
// Have we reached the end of the input data?
|
||||
if (!readLength) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Attempt to inflate the chunk of data
|
||||
outputData = [decompressor uncompressBytes:inputData length:readLength error:&theError];
|
||||
if (theError) {
|
||||
if (err) {
|
||||
*err = theError;
|
||||
}
|
||||
[decompressor closeStream];
|
||||
return NO;
|
||||
}
|
||||
|
||||
// Write the inflated data out to the destination file
|
||||
[outputStream write:(Bytef*)[outputData bytes] maxLength:[outputData length]];
|
||||
|
||||
// Make sure nothing went wrong
|
||||
if ([inputStream streamStatus] == NSStreamEventErrorOccurred) {
|
||||
if (err) {
|
||||
*err = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Decompression of %@ failed because we were unable to write to the destination data file at %@",sourcePath,destinationPath],NSLocalizedDescriptionKey,[outputStream streamError],NSUnderlyingErrorKey,nil]];
|
||||
}
|
||||
[decompressor closeStream];
|
||||
return NO;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
[inputStream close];
|
||||
[outputStream close];
|
||||
|
||||
NSError *error = [decompressor closeStream];
|
||||
if (error) {
|
||||
if (err) {
|
||||
*err = error;
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
|
||||
+ (NSError *)inflateErrorWithCode:(int)code
|
||||
{
|
||||
return [NSError errorWithDomain:NetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Decompression of data failed with code %hi",code],NSLocalizedDescriptionKey,nil]];
|
||||
}
|
||||
|
||||
@synthesize streamReady;
|
||||
@end
|
||||
46
projects/mtg/iOS/asi-http-request/ASIDownloadCache.h
Normal file
46
projects/mtg/iOS/asi-http-request/ASIDownloadCache.h
Normal file
@@ -0,0 +1,46 @@
|
||||
//
|
||||
// ASIDownloadCache.h
|
||||
// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest
|
||||
//
|
||||
// Created by Ben Copsey on 01/05/2010.
|
||||
// Copyright 2010 All-Seeing Interactive. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "ASICacheDelegate.h"
|
||||
|
||||
@interface ASIDownloadCache : NSObject <ASICacheDelegate> {
|
||||
|
||||
// The default cache policy for this cache
|
||||
// Requests that store data in the cache will use this cache policy if their cache policy is set to ASIUseDefaultCachePolicy
|
||||
// Defaults to ASIAskServerIfModifiedWhenStaleCachePolicy
|
||||
ASICachePolicy defaultCachePolicy;
|
||||
|
||||
// The directory in which cached data will be stored
|
||||
// Defaults to a directory called 'ASIHTTPRequestCache' in the temporary directory
|
||||
NSString *storagePath;
|
||||
|
||||
// Mediates access to the cache
|
||||
NSRecursiveLock *accessLock;
|
||||
|
||||
// When YES, the cache will look for cache-control / pragma: no-cache headers, and won't reuse store responses if it finds them
|
||||
BOOL shouldRespectCacheControlHeaders;
|
||||
}
|
||||
|
||||
// Returns a static instance of an ASIDownloadCache
|
||||
// In most circumstances, it will make sense to use this as a global cache, rather than creating your own cache
|
||||
// To make ASIHTTPRequests use it automatically, use [ASIHTTPRequest setDefaultCache:[ASIDownloadCache sharedCache]];
|
||||
+ (id)sharedCache;
|
||||
|
||||
// A helper function that determines if the server has requested data should not be cached by looking at the request's response headers
|
||||
+ (BOOL)serverAllowsResponseCachingForRequest:(ASIHTTPRequest *)request;
|
||||
|
||||
// A list of file extensions that we know won't be readable by a webview when accessed locally
|
||||
// If we're asking for a path to cache a particular url and it has one of these extensions, we change it to '.html'
|
||||
+ (NSArray *)fileExtensionsToHandleAsHTML;
|
||||
|
||||
@property (assign, nonatomic) ASICachePolicy defaultCachePolicy;
|
||||
@property (retain, nonatomic) NSString *storagePath;
|
||||
@property (retain) NSRecursiveLock *accessLock;
|
||||
@property (assign) BOOL shouldRespectCacheControlHeaders;
|
||||
@end
|
||||
514
projects/mtg/iOS/asi-http-request/ASIDownloadCache.m
Normal file
514
projects/mtg/iOS/asi-http-request/ASIDownloadCache.m
Normal file
@@ -0,0 +1,514 @@
|
||||
//
|
||||
// ASIDownloadCache.m
|
||||
// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest
|
||||
//
|
||||
// Created by Ben Copsey on 01/05/2010.
|
||||
// Copyright 2010 All-Seeing Interactive. All rights reserved.
|
||||
//
|
||||
|
||||
#import "ASIDownloadCache.h"
|
||||
#import "ASIHTTPRequest.h"
|
||||
#import <CommonCrypto/CommonHMAC.h>
|
||||
|
||||
static ASIDownloadCache *sharedCache = nil;
|
||||
|
||||
static NSString *sessionCacheFolder = @"SessionStore";
|
||||
static NSString *permanentCacheFolder = @"PermanentStore";
|
||||
static NSArray *fileExtensionsToHandleAsHTML = nil;
|
||||
|
||||
@interface ASIDownloadCache ()
|
||||
+ (NSString *)keyForURL:(NSURL *)url;
|
||||
- (NSString *)pathToFile:(NSString *)file;
|
||||
@end
|
||||
|
||||
@implementation ASIDownloadCache
|
||||
|
||||
+ (void)initialize
|
||||
{
|
||||
if (self == [ASIDownloadCache class]) {
|
||||
// Obviously this is not an exhaustive list, but hopefully these are the most commonly used and this will 'just work' for the widest range of people
|
||||
// I imagine many web developers probably use url rewriting anyway
|
||||
fileExtensionsToHandleAsHTML = [[NSArray alloc] initWithObjects:@"asp",@"aspx",@"jsp",@"php",@"rb",@"py",@"pl",@"cgi", nil];
|
||||
}
|
||||
}
|
||||
|
||||
- (id)init
|
||||
{
|
||||
self = [super init];
|
||||
[self setShouldRespectCacheControlHeaders:YES];
|
||||
[self setDefaultCachePolicy:ASIUseDefaultCachePolicy];
|
||||
[self setAccessLock:[[[NSRecursiveLock alloc] init] autorelease]];
|
||||
return self;
|
||||
}
|
||||
|
||||
+ (id)sharedCache
|
||||
{
|
||||
if (!sharedCache) {
|
||||
@synchronized(self) {
|
||||
if (!sharedCache) {
|
||||
sharedCache = [[self alloc] init];
|
||||
[sharedCache setStoragePath:[[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:@"ASIHTTPRequestCache"]];
|
||||
}
|
||||
}
|
||||
}
|
||||
return sharedCache;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
[storagePath release];
|
||||
[accessLock release];
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (NSString *)storagePath
|
||||
{
|
||||
[[self accessLock] lock];
|
||||
NSString *p = [[storagePath retain] autorelease];
|
||||
[[self accessLock] unlock];
|
||||
return p;
|
||||
}
|
||||
|
||||
|
||||
- (void)setStoragePath:(NSString *)path
|
||||
{
|
||||
[[self accessLock] lock];
|
||||
[self clearCachedResponsesForStoragePolicy:ASICacheForSessionDurationCacheStoragePolicy];
|
||||
[storagePath release];
|
||||
storagePath = [path retain];
|
||||
|
||||
NSFileManager *fileManager = [[[NSFileManager alloc] init] autorelease];
|
||||
|
||||
BOOL isDirectory = NO;
|
||||
NSArray *directories = [NSArray arrayWithObjects:path,[path stringByAppendingPathComponent:sessionCacheFolder],[path stringByAppendingPathComponent:permanentCacheFolder],nil];
|
||||
for (NSString *directory in directories) {
|
||||
BOOL exists = [fileManager fileExistsAtPath:directory isDirectory:&isDirectory];
|
||||
if (exists && !isDirectory) {
|
||||
[[self accessLock] unlock];
|
||||
[NSException raise:@"FileExistsAtCachePath" format:@"Cannot create a directory for the cache at '%@', because a file already exists",directory];
|
||||
} else if (!exists) {
|
||||
[fileManager createDirectoryAtPath:directory withIntermediateDirectories:NO attributes:nil error:nil];
|
||||
if (![fileManager fileExistsAtPath:directory]) {
|
||||
[[self accessLock] unlock];
|
||||
[NSException raise:@"FailedToCreateCacheDirectory" format:@"Failed to create a directory for the cache at '%@'",directory];
|
||||
}
|
||||
}
|
||||
}
|
||||
[self clearCachedResponsesForStoragePolicy:ASICacheForSessionDurationCacheStoragePolicy];
|
||||
[[self accessLock] unlock];
|
||||
}
|
||||
|
||||
- (void)updateExpiryForRequest:(ASIHTTPRequest *)request maxAge:(NSTimeInterval)maxAge
|
||||
{
|
||||
NSString *headerPath = [self pathToStoreCachedResponseHeadersForRequest:request];
|
||||
NSMutableDictionary *cachedHeaders = [NSMutableDictionary dictionaryWithContentsOfFile:headerPath];
|
||||
if (!cachedHeaders) {
|
||||
return;
|
||||
}
|
||||
NSDate *expires = [self expiryDateForRequest:request maxAge:maxAge];
|
||||
if (!expires) {
|
||||
return;
|
||||
}
|
||||
[cachedHeaders setObject:[NSNumber numberWithDouble:[expires timeIntervalSince1970]] forKey:@"X-ASIHTTPRequest-Expires"];
|
||||
[cachedHeaders writeToFile:headerPath atomically:NO];
|
||||
}
|
||||
|
||||
- (NSDate *)expiryDateForRequest:(ASIHTTPRequest *)request maxAge:(NSTimeInterval)maxAge
|
||||
{
|
||||
return [ASIHTTPRequest expiryDateForRequest:request maxAge:maxAge];
|
||||
}
|
||||
|
||||
- (void)storeResponseForRequest:(ASIHTTPRequest *)request maxAge:(NSTimeInterval)maxAge
|
||||
{
|
||||
[[self accessLock] lock];
|
||||
|
||||
if ([request error] || ![request responseHeaders] || ([request cachePolicy] & ASIDoNotWriteToCacheCachePolicy)) {
|
||||
[[self accessLock] unlock];
|
||||
return;
|
||||
}
|
||||
|
||||
// We only cache 200/OK or redirect reponses (redirect responses are cached so the cache works better with no internet connection)
|
||||
int responseCode = [request responseStatusCode];
|
||||
if (responseCode != 200 && responseCode != 301 && responseCode != 302 && responseCode != 303 && responseCode != 307) {
|
||||
[[self accessLock] unlock];
|
||||
return;
|
||||
}
|
||||
|
||||
if ([self shouldRespectCacheControlHeaders] && ![[self class] serverAllowsResponseCachingForRequest:request]) {
|
||||
[[self accessLock] unlock];
|
||||
return;
|
||||
}
|
||||
|
||||
NSString *headerPath = [self pathToStoreCachedResponseHeadersForRequest:request];
|
||||
NSString *dataPath = [self pathToStoreCachedResponseDataForRequest:request];
|
||||
|
||||
NSMutableDictionary *responseHeaders = [NSMutableDictionary dictionaryWithDictionary:[request responseHeaders]];
|
||||
if ([request isResponseCompressed]) {
|
||||
[responseHeaders removeObjectForKey:@"Content-Encoding"];
|
||||
}
|
||||
|
||||
// Create a special 'X-ASIHTTPRequest-Expires' header
|
||||
// This is what we use for deciding if cached data is current, rather than parsing the expires / max-age headers individually each time
|
||||
// We store this as a timestamp to make reading it easier as NSDateFormatter is quite expensive
|
||||
|
||||
NSDate *expires = [self expiryDateForRequest:request maxAge:maxAge];
|
||||
if (expires) {
|
||||
[responseHeaders setObject:[NSNumber numberWithDouble:[expires timeIntervalSince1970]] forKey:@"X-ASIHTTPRequest-Expires"];
|
||||
}
|
||||
|
||||
// Store the response code in a custom header so we can reuse it later
|
||||
|
||||
// We'll change 304/Not Modified to 200/OK because this is likely to be us updating the cached headers with a conditional GET
|
||||
int statusCode = [request responseStatusCode];
|
||||
if (statusCode == 304) {
|
||||
statusCode = 200;
|
||||
}
|
||||
[responseHeaders setObject:[NSNumber numberWithInt:statusCode] forKey:@"X-ASIHTTPRequest-Response-Status-Code"];
|
||||
|
||||
[responseHeaders writeToFile:headerPath atomically:NO];
|
||||
|
||||
if ([request responseData]) {
|
||||
[[request responseData] writeToFile:dataPath atomically:NO];
|
||||
} else if ([request downloadDestinationPath] && ![[request downloadDestinationPath] isEqualToString:dataPath]) {
|
||||
NSError *error = nil;
|
||||
NSFileManager* manager = [[NSFileManager alloc] init];
|
||||
if ([manager fileExistsAtPath:dataPath]) {
|
||||
[manager removeItemAtPath:dataPath error:&error];
|
||||
}
|
||||
[manager copyItemAtPath:[request downloadDestinationPath] toPath:dataPath error:&error];
|
||||
[manager release];
|
||||
}
|
||||
[[self accessLock] unlock];
|
||||
}
|
||||
|
||||
- (NSDictionary *)cachedResponseHeadersForURL:(NSURL *)url
|
||||
{
|
||||
NSString *path = [self pathToCachedResponseHeadersForURL:url];
|
||||
if (path) {
|
||||
return [NSDictionary dictionaryWithContentsOfFile:path];
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (NSData *)cachedResponseDataForURL:(NSURL *)url
|
||||
{
|
||||
NSString *path = [self pathToCachedResponseDataForURL:url];
|
||||
if (path) {
|
||||
return [NSData dataWithContentsOfFile:path];
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (NSString *)pathToCachedResponseDataForURL:(NSURL *)url
|
||||
{
|
||||
// Grab the file extension, if there is one. We do this so we can save the cached response with the same file extension - this is important if you want to display locally cached data in a web view
|
||||
NSString *extension = [[url path] pathExtension];
|
||||
|
||||
// If the url doesn't have an extension, we'll add one so a webview can read it when locally cached
|
||||
// If the url has the extension of a common web scripting language, we'll change the extension on the cached path to html for the same reason
|
||||
if (![extension length] || [[[self class] fileExtensionsToHandleAsHTML] containsObject:[extension lowercaseString]]) {
|
||||
extension = @"html";
|
||||
}
|
||||
return [self pathToFile:[[[self class] keyForURL:url] stringByAppendingPathExtension:extension]];
|
||||
}
|
||||
|
||||
+ (NSArray *)fileExtensionsToHandleAsHTML
|
||||
{
|
||||
return fileExtensionsToHandleAsHTML;
|
||||
}
|
||||
|
||||
|
||||
- (NSString *)pathToCachedResponseHeadersForURL:(NSURL *)url
|
||||
{
|
||||
return [self pathToFile:[[[self class] keyForURL:url] stringByAppendingPathExtension:@"cachedheaders"]];
|
||||
}
|
||||
|
||||
- (NSString *)pathToFile:(NSString *)file
|
||||
{
|
||||
[[self accessLock] lock];
|
||||
if (![self storagePath]) {
|
||||
[[self accessLock] unlock];
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSFileManager *fileManager = [[[NSFileManager alloc] init] autorelease];
|
||||
|
||||
// Look in the session store
|
||||
NSString *dataPath = [[[self storagePath] stringByAppendingPathComponent:sessionCacheFolder] stringByAppendingPathComponent:file];
|
||||
if ([fileManager fileExistsAtPath:dataPath]) {
|
||||
[[self accessLock] unlock];
|
||||
return dataPath;
|
||||
}
|
||||
// Look in the permanent store
|
||||
dataPath = [[[self storagePath] stringByAppendingPathComponent:permanentCacheFolder] stringByAppendingPathComponent:file];
|
||||
if ([fileManager fileExistsAtPath:dataPath]) {
|
||||
[[self accessLock] unlock];
|
||||
return dataPath;
|
||||
}
|
||||
[[self accessLock] unlock];
|
||||
return nil;
|
||||
}
|
||||
|
||||
|
||||
- (NSString *)pathToStoreCachedResponseDataForRequest:(ASIHTTPRequest *)request
|
||||
{
|
||||
[[self accessLock] lock];
|
||||
if (![self storagePath]) {
|
||||
[[self accessLock] unlock];
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSString *path = [[self storagePath] stringByAppendingPathComponent:([request cacheStoragePolicy] == ASICacheForSessionDurationCacheStoragePolicy ? sessionCacheFolder : permanentCacheFolder)];
|
||||
|
||||
// Grab the file extension, if there is one. We do this so we can save the cached response with the same file extension - this is important if you want to display locally cached data in a web view
|
||||
NSString *extension = [[[request url] path] pathExtension];
|
||||
|
||||
// If the url doesn't have an extension, we'll add one so a webview can read it when locally cached
|
||||
// If the url has the extension of a common web scripting language, we'll change the extension on the cached path to html for the same reason
|
||||
if (![extension length] || [[[self class] fileExtensionsToHandleAsHTML] containsObject:[extension lowercaseString]]) {
|
||||
extension = @"html";
|
||||
}
|
||||
path = [path stringByAppendingPathComponent:[[[self class] keyForURL:[request url]] stringByAppendingPathExtension:extension]];
|
||||
[[self accessLock] unlock];
|
||||
return path;
|
||||
}
|
||||
|
||||
- (NSString *)pathToStoreCachedResponseHeadersForRequest:(ASIHTTPRequest *)request
|
||||
{
|
||||
[[self accessLock] lock];
|
||||
if (![self storagePath]) {
|
||||
[[self accessLock] unlock];
|
||||
return nil;
|
||||
}
|
||||
NSString *path = [[self storagePath] stringByAppendingPathComponent:([request cacheStoragePolicy] == ASICacheForSessionDurationCacheStoragePolicy ? sessionCacheFolder : permanentCacheFolder)];
|
||||
path = [path stringByAppendingPathComponent:[[[self class] keyForURL:[request url]] stringByAppendingPathExtension:@"cachedheaders"]];
|
||||
[[self accessLock] unlock];
|
||||
return path;
|
||||
}
|
||||
|
||||
- (void)removeCachedDataForURL:(NSURL *)url
|
||||
{
|
||||
[[self accessLock] lock];
|
||||
if (![self storagePath]) {
|
||||
[[self accessLock] unlock];
|
||||
return;
|
||||
}
|
||||
NSFileManager *fileManager = [[[NSFileManager alloc] init] autorelease];
|
||||
|
||||
NSString *path = [self pathToCachedResponseHeadersForURL:url];
|
||||
if (path) {
|
||||
[fileManager removeItemAtPath:path error:NULL];
|
||||
}
|
||||
|
||||
path = [self pathToCachedResponseDataForURL:url];
|
||||
if (path) {
|
||||
[fileManager removeItemAtPath:path error:NULL];
|
||||
}
|
||||
[[self accessLock] unlock];
|
||||
}
|
||||
|
||||
- (void)removeCachedDataForRequest:(ASIHTTPRequest *)request
|
||||
{
|
||||
[self removeCachedDataForURL:[request url]];
|
||||
}
|
||||
|
||||
- (BOOL)isCachedDataCurrentForRequest:(ASIHTTPRequest *)request
|
||||
{
|
||||
[[self accessLock] lock];
|
||||
if (![self storagePath]) {
|
||||
[[self accessLock] unlock];
|
||||
return NO;
|
||||
}
|
||||
NSDictionary *cachedHeaders = [self cachedResponseHeadersForURL:[request url]];
|
||||
if (!cachedHeaders) {
|
||||
[[self accessLock] unlock];
|
||||
return NO;
|
||||
}
|
||||
NSString *dataPath = [self pathToCachedResponseDataForURL:[request url]];
|
||||
if (!dataPath) {
|
||||
[[self accessLock] unlock];
|
||||
return NO;
|
||||
}
|
||||
|
||||
// New content is not different
|
||||
if ([request responseStatusCode] == 304) {
|
||||
[[self accessLock] unlock];
|
||||
return YES;
|
||||
}
|
||||
|
||||
// If we already have response headers for this request, check to see if the new content is different
|
||||
// We check [request complete] so that we don't end up comparing response headers from a redirection with these
|
||||
if ([request responseHeaders] && [request complete]) {
|
||||
|
||||
// If the Etag or Last-Modified date are different from the one we have, we'll have to fetch this resource again
|
||||
NSArray *headersToCompare = [NSArray arrayWithObjects:@"Etag",@"Last-Modified",nil];
|
||||
for (NSString *header in headersToCompare) {
|
||||
if (![[[request responseHeaders] objectForKey:header] isEqualToString:[cachedHeaders objectForKey:header]]) {
|
||||
[[self accessLock] unlock];
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ([self shouldRespectCacheControlHeaders]) {
|
||||
|
||||
// Look for X-ASIHTTPRequest-Expires header to see if the content is out of date
|
||||
NSNumber *expires = [cachedHeaders objectForKey:@"X-ASIHTTPRequest-Expires"];
|
||||
if (expires) {
|
||||
if ([[NSDate dateWithTimeIntervalSince1970:[expires doubleValue]] timeIntervalSinceNow] >= 0) {
|
||||
[[self accessLock] unlock];
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
|
||||
// No explicit expiration time sent by the server
|
||||
[[self accessLock] unlock];
|
||||
return NO;
|
||||
}
|
||||
|
||||
|
||||
[[self accessLock] unlock];
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (ASICachePolicy)defaultCachePolicy
|
||||
{
|
||||
[[self accessLock] lock];
|
||||
ASICachePolicy cp = defaultCachePolicy;
|
||||
[[self accessLock] unlock];
|
||||
return cp;
|
||||
}
|
||||
|
||||
|
||||
- (void)setDefaultCachePolicy:(ASICachePolicy)cachePolicy
|
||||
{
|
||||
[[self accessLock] lock];
|
||||
if (!cachePolicy) {
|
||||
defaultCachePolicy = ASIAskServerIfModifiedWhenStaleCachePolicy;
|
||||
} else {
|
||||
defaultCachePolicy = cachePolicy;
|
||||
}
|
||||
[[self accessLock] unlock];
|
||||
}
|
||||
|
||||
- (void)clearCachedResponsesForStoragePolicy:(ASICacheStoragePolicy)storagePolicy
|
||||
{
|
||||
[[self accessLock] lock];
|
||||
if (![self storagePath]) {
|
||||
[[self accessLock] unlock];
|
||||
return;
|
||||
}
|
||||
NSString *path = [[self storagePath] stringByAppendingPathComponent:(storagePolicy == ASICacheForSessionDurationCacheStoragePolicy ? sessionCacheFolder : permanentCacheFolder)];
|
||||
|
||||
NSFileManager *fileManager = [[[NSFileManager alloc] init] autorelease];
|
||||
|
||||
BOOL isDirectory = NO;
|
||||
BOOL exists = [fileManager fileExistsAtPath:path isDirectory:&isDirectory];
|
||||
if (!exists || !isDirectory) {
|
||||
[[self accessLock] unlock];
|
||||
return;
|
||||
}
|
||||
NSError *error = nil;
|
||||
NSArray *cacheFiles = [fileManager contentsOfDirectoryAtPath:path error:&error];
|
||||
if (error) {
|
||||
[[self accessLock] unlock];
|
||||
[NSException raise:@"FailedToTraverseCacheDirectory" format:@"Listing cache directory failed at path '%@'",path];
|
||||
}
|
||||
for (NSString *file in cacheFiles) {
|
||||
[fileManager removeItemAtPath:[path stringByAppendingPathComponent:file] error:&error];
|
||||
if (error) {
|
||||
[[self accessLock] unlock];
|
||||
[NSException raise:@"FailedToRemoveCacheFile" format:@"Failed to remove cached data at path '%@'",path];
|
||||
}
|
||||
}
|
||||
[[self accessLock] unlock];
|
||||
}
|
||||
|
||||
+ (BOOL)serverAllowsResponseCachingForRequest:(ASIHTTPRequest *)request
|
||||
{
|
||||
NSString *cacheControl = [[[request responseHeaders] objectForKey:@"Cache-Control"] lowercaseString];
|
||||
if (cacheControl) {
|
||||
if ([cacheControl isEqualToString:@"no-cache"] || [cacheControl isEqualToString:@"no-store"]) {
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
NSString *pragma = [[[request responseHeaders] objectForKey:@"Pragma"] lowercaseString];
|
||||
if (pragma) {
|
||||
if ([pragma isEqualToString:@"no-cache"]) {
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
|
||||
+ (NSString *)keyForURL:(NSURL *)url
|
||||
{
|
||||
NSString *urlString = [url absoluteString];
|
||||
if ([urlString length] == 0) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
// Strip trailing slashes so http://allseeing-i.com/ASIHTTPRequest/ is cached the same as http://allseeing-i.com/ASIHTTPRequest
|
||||
if ([[urlString substringFromIndex:[urlString length]-1] isEqualToString:@"/"]) {
|
||||
urlString = [urlString substringToIndex:[urlString length]-1];
|
||||
}
|
||||
|
||||
// Borrowed from: http://stackoverflow.com/questions/652300/using-md5-hash-on-a-string-in-cocoa
|
||||
const char *cStr = [urlString UTF8String];
|
||||
unsigned char result[16];
|
||||
CC_MD5(cStr, (CC_LONG)strlen(cStr), result);
|
||||
return [NSString stringWithFormat:@"%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X",result[0], result[1], result[2], result[3], result[4], result[5], result[6], result[7],result[8], result[9], result[10], result[11],result[12], result[13], result[14], result[15]];
|
||||
}
|
||||
|
||||
- (BOOL)canUseCachedDataForRequest:(ASIHTTPRequest *)request
|
||||
{
|
||||
// Ensure the request is allowed to read from the cache
|
||||
if ([request cachePolicy] & ASIDoNotReadFromCacheCachePolicy) {
|
||||
return NO;
|
||||
|
||||
// If we don't want to load the request whatever happens, always pretend we have cached data even if we don't
|
||||
} else if ([request cachePolicy] & ASIDontLoadCachePolicy) {
|
||||
return YES;
|
||||
}
|
||||
|
||||
NSDictionary *headers = [self cachedResponseHeadersForURL:[request url]];
|
||||
if (!headers) {
|
||||
return NO;
|
||||
}
|
||||
NSString *dataPath = [self pathToCachedResponseDataForURL:[request url]];
|
||||
if (!dataPath) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
// If we get here, we have cached data
|
||||
|
||||
// If we have cached data, we can use it
|
||||
if ([request cachePolicy] & ASIOnlyLoadIfNotCachedCachePolicy) {
|
||||
return YES;
|
||||
|
||||
// If we want to fallback to the cache after an error
|
||||
} else if ([request complete] && [request cachePolicy] & ASIFallbackToCacheIfLoadFailsCachePolicy) {
|
||||
return YES;
|
||||
|
||||
// If we have cached data that is current, we can use it
|
||||
} else if ([request cachePolicy] & ASIAskServerIfModifiedWhenStaleCachePolicy) {
|
||||
if ([self isCachedDataCurrentForRequest:request]) {
|
||||
return YES;
|
||||
}
|
||||
|
||||
// If we've got headers from a conditional GET and the cached data is still current, we can use it
|
||||
} else if ([request cachePolicy] & ASIAskServerIfModifiedCachePolicy) {
|
||||
if (![request responseHeaders]) {
|
||||
return NO;
|
||||
} else if ([self isCachedDataCurrentForRequest:request]) {
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
@synthesize storagePath;
|
||||
@synthesize defaultCachePolicy;
|
||||
@synthesize accessLock;
|
||||
@synthesize shouldRespectCacheControlHeaders;
|
||||
@end
|
||||
76
projects/mtg/iOS/asi-http-request/ASIFormDataRequest.h
Normal file
76
projects/mtg/iOS/asi-http-request/ASIFormDataRequest.h
Normal file
@@ -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:(NSString *)filePath 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:(NSString *)filePath 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
|
||||
362
projects/mtg/iOS/asi-http-request/ASIFormDataRequest.m
Normal file
362
projects/mtg/iOS/asi-http-request/ASIFormDataRequest.m
Normal file
@@ -0,0 +1,362 @@
|
||||
//
|
||||
// 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 = [NSMakeCollectable(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];
|
||||
[self setRequestMethod:@"POST"];
|
||||
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 (!key) {
|
||||
return;
|
||||
}
|
||||
if (![self postData]) {
|
||||
[self setPostData:[NSMutableArray array]];
|
||||
}
|
||||
NSMutableDictionary *keyValuePair = [NSMutableDictionary dictionaryWithCapacity:2];
|
||||
[keyValuePair setValue:key forKey:@"key"];
|
||||
[keyValuePair setValue:[value description] forKey:@"value"];
|
||||
[[self postData] addObject:keyValuePair];
|
||||
}
|
||||
|
||||
- (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:(NSString *)filePath withFileName:(NSString *)fileName andContentType:(NSString *)contentType forKey:(NSString *)key
|
||||
{
|
||||
BOOL isDirectory = NO;
|
||||
BOOL fileExists = [[[[NSFileManager alloc] init] autorelease] fileExistsAtPath:filePath isDirectory:&isDirectory];
|
||||
if (!fileExists || isDirectory) {
|
||||
[self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIInternalErrorWhileBuildingRequestType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"No file exists at %@",filePath],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 = [filePath 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:filePath];
|
||||
}
|
||||
[self addData:filePath withFileName:fileName andContentType:contentType forKey:key];
|
||||
}
|
||||
|
||||
- (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";
|
||||
}
|
||||
|
||||
NSMutableDictionary *fileInfo = [NSMutableDictionary dictionaryWithCapacity:4];
|
||||
[fileInfo setValue:key forKey:@"key"];
|
||||
[fileInfo setValue:fileName forKey:@"fileName"];
|
||||
[fileInfo setValue:contentType forKey:@"contentType"];
|
||||
[fileInfo setValue:data forKey:@"data"];
|
||||
|
||||
[[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 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
|
||||
ASI_DEBUG_LOG(@"%@",[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]));
|
||||
|
||||
// We don't bother to check if post data contains the boundary, since it's pretty unlikely that it does.
|
||||
CFUUIDRef uuid = CFUUIDCreate(nil);
|
||||
NSString *uuidString = [(NSString*)CFUUIDCreateString(nil, uuid) autorelease];
|
||||
CFRelease(uuid);
|
||||
NSString *stringBoundary = [NSString stringWithFormat:@"0xKhTmLbOuNdArY-%@",uuidString];
|
||||
|
||||
[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 alloc] init] autorelease] 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
|
||||
{
|
||||
if (string) {
|
||||
[self setDebugBodyString:[[self debugBodyString] stringByAppendingString:string]];
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#pragma mark NSCopying
|
||||
|
||||
- (id)copyWithZone:(NSZone *)zone
|
||||
{
|
||||
ASIFormDataRequest *newRequest = [super copyWithZone:zone];
|
||||
[newRequest setPostData:[[[self postData] mutableCopyWithZone:zone] autorelease]];
|
||||
[newRequest setFileData:[[[self fileData] mutableCopyWithZone: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
|
||||
1004
projects/mtg/iOS/asi-http-request/ASIHTTPRequest.h
Normal file
1004
projects/mtg/iOS/asi-http-request/ASIHTTPRequest.h
Normal file
File diff suppressed because it is too large
Load Diff
5119
projects/mtg/iOS/asi-http-request/ASIHTTPRequest.m
Normal file
5119
projects/mtg/iOS/asi-http-request/ASIHTTPRequest.m
Normal file
File diff suppressed because it is too large
Load Diff
43
projects/mtg/iOS/asi-http-request/ASIHTTPRequestConfig.h
Normal file
43
projects/mtg/iOS/asi-http-request/ASIHTTPRequestConfig.h
Normal file
@@ -0,0 +1,43 @@
|
||||
//
|
||||
// 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
|
||||
// ======
|
||||
|
||||
// If defined will use the specified function for debug logging
|
||||
// Otherwise use NSLog
|
||||
#ifndef ASI_DEBUG_LOG
|
||||
#define ASI_DEBUG_LOG NSLog
|
||||
#endif
|
||||
|
||||
// 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
|
||||
|
||||
// When set to 1, ASIHTTPRequests will print information about HTTP authentication (Basic, Digest or NTLM) to the console
|
||||
#ifndef DEBUG_HTTP_AUTHENTICATION
|
||||
#define DEBUG_HTTP_AUTHENTICATION 0
|
||||
#endif
|
||||
35
projects/mtg/iOS/asi-http-request/ASIHTTPRequestDelegate.h
Normal file
35
projects/mtg/iOS/asi-http-request/ASIHTTPRequestDelegate.h
Normal file
@@ -0,0 +1,35 @@
|
||||
//
|
||||
// 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)request:(ASIHTTPRequest *)request didReceiveResponseHeaders:(NSDictionary *)responseHeaders;
|
||||
- (void)request:(ASIHTTPRequest *)request willRedirectToURL:(NSURL *)newURL;
|
||||
- (void)requestFinished:(ASIHTTPRequest *)request;
|
||||
- (void)requestFailed:(ASIHTTPRequest *)request;
|
||||
- (void)requestRedirected:(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
projects/mtg/iOS/asi-http-request/ASIInputStream.h
Normal file
26
projects/mtg/iOS/asi-http-request/ASIInputStream.h
Normal file
@@ -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
|
||||
138
projects/mtg/iOS/asi-http-request/ASIInputStream.m
Normal file
138
projects/mtg/iOS/asi-http-request/ASIInputStream.m
Normal file
@@ -0,0 +1,138 @@
|
||||
//
|
||||
// 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 *)theRequest
|
||||
{
|
||||
ASIInputStream *theStream = [[[self alloc] init] autorelease];
|
||||
[theStream setRequest:theRequest];
|
||||
[theStream setStream:[NSInputStream inputStreamWithFileAtPath:path]];
|
||||
return theStream;
|
||||
}
|
||||
|
||||
+ (id)inputStreamWithData:(NSData *)data request:(ASIHTTPRequest *)theRequest
|
||||
{
|
||||
ASIInputStream *theStream = [[[self alloc] init] autorelease];
|
||||
[theStream setRequest:theRequest];
|
||||
[theStream setStream:[NSInputStream inputStreamWithData:data]];
|
||||
return theStream;
|
||||
}
|
||||
|
||||
- (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];
|
||||
}
|
||||
[readLock unlock];
|
||||
NSInteger rv = [stream read:buffer maxLength:toRead];
|
||||
if (rv > 0)
|
||||
[ASIHTTPRequest incrementBandwidthUsedInLastSecond:rv];
|
||||
return rv;
|
||||
}
|
||||
|
||||
/*
|
||||
* Implement NSInputStream mandatory methods to make sure they are implemented
|
||||
* (necessary for MacRuby for example) and avoid 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
|
||||
108
projects/mtg/iOS/asi-http-request/ASINetworkQueue.h
Normal file
108
projects/mtg/iOS/asi-http-request/ASINetworkQueue.h
Normal file
@@ -0,0 +1,108 @@
|
||||
//
|
||||
// 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
|
||||
// Should take the form request:didRecieveResponseHeaders:, where the first argument is the request, and the second the headers dictionary
|
||||
SEL requestDidReceiveResponseHeadersSelector;
|
||||
|
||||
// Will be called when a request is about to redirect
|
||||
// Should take the form request:willRedirectToURL:, where the first argument is the request, and the second the new url
|
||||
SEL requestWillRedirectSelector;
|
||||
|
||||
// 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;
|
||||
|
||||
@property (assign, nonatomic, setter=setUploadProgressDelegate:) id uploadProgressDelegate;
|
||||
@property (assign, nonatomic, setter=setDownloadProgressDelegate:) id downloadProgressDelegate;
|
||||
|
||||
@property (assign) SEL requestDidStartSelector;
|
||||
@property (assign) SEL requestDidReceiveResponseHeadersSelector;
|
||||
@property (assign) SEL requestWillRedirectSelector;
|
||||
@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
|
||||
343
projects/mtg/iOS/asi-http-request/ASINetworkQueue.m
Normal file
343
projects/mtg/iOS/asi-http-request/ASINetworkQueue.m
Normal file
@@ -0,0 +1,343 @@
|
||||
//
|
||||
// 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];
|
||||
}
|
||||
|
||||
- (void)setSuspended:(BOOL)suspend
|
||||
{
|
||||
[super setSuspended:suspend];
|
||||
}
|
||||
|
||||
- (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];
|
||||
}
|
||||
|
||||
- (void)setUploadProgressDelegate:(id)newDelegate
|
||||
{
|
||||
uploadProgressDelegate = newDelegate;
|
||||
[self resetProgressDelegate:&uploadProgressDelegate];
|
||||
|
||||
}
|
||||
|
||||
- (void)setDownloadProgressDelegate:(id)newDelegate
|
||||
{
|
||||
downloadProgressDelegate = newDelegate;
|
||||
[self resetProgressDelegate:&downloadProgressDelegate];
|
||||
}
|
||||
|
||||
- (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 callerToRetain:nil];
|
||||
}
|
||||
selector = @selector(setDoubleValue:);
|
||||
if ([*progressDelegate respondsToSelector:selector]) {
|
||||
double value = 0.0;
|
||||
[ASIHTTPRequest performSelector:selector onTarget:progressDelegate withObject:nil amount:&value callerToRetain:nil];
|
||||
}
|
||||
#else
|
||||
SEL selector = @selector(setProgress:);
|
||||
if ([*progressDelegate respondsToSelector:selector]) {
|
||||
float value = 0.0f;
|
||||
[ASIHTTPRequest performSelector:selector onTarget:progressDelegate withObject:nil amount:&value callerToRetain:nil];
|
||||
}
|
||||
#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:&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:&uploadProgressDelegate];
|
||||
[request setShouldResetUploadProgress:NO];
|
||||
}
|
||||
|
||||
[request setShowAccurateProgress:[self showAccurateProgress]];
|
||||
|
||||
[request setQueue:self];
|
||||
[super addOperation:request];
|
||||
|
||||
}
|
||||
|
||||
- (void)requestStarted:(ASIHTTPRequest *)request
|
||||
{
|
||||
if ([self requestDidStartSelector]) {
|
||||
[[self delegate] performSelector:[self requestDidStartSelector] withObject:request];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)request:(ASIHTTPRequest *)request didReceiveResponseHeaders:(NSDictionary *)responseHeaders
|
||||
{
|
||||
if ([self requestDidReceiveResponseHeadersSelector]) {
|
||||
[[self delegate] performSelector:[self requestDidReceiveResponseHeadersSelector] withObject:request withObject:responseHeaders];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)request:(ASIHTTPRequest *)request willRedirectToURL:(NSURL *)newURL
|
||||
{
|
||||
if ([self requestWillRedirectSelector]) {
|
||||
[[self delegate] performSelector:[self requestWillRedirectSelector] withObject:request withObject:newURL];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)requestFinished:(ASIHTTPRequest *)request
|
||||
{
|
||||
[self setRequestsCount:[self requestsCount]-1];
|
||||
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];
|
||||
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:&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:&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
|
||||
{
|
||||
// We handle certain methods differently because whether our delegate implements them or not can affect how the request should behave
|
||||
|
||||
// If the delegate implements this, the request will stop to wait for credentials
|
||||
if (selector == @selector(authenticationNeededForRequest:)) {
|
||||
if ([[self delegate] respondsToSelector:@selector(authenticationNeededForRequest:)]) {
|
||||
return YES;
|
||||
}
|
||||
return NO;
|
||||
|
||||
// If the delegate implements this, the request will to wait for credentials
|
||||
} else if (selector == @selector(proxyAuthenticationNeededForRequest:)) {
|
||||
if ([[self delegate] respondsToSelector:@selector(proxyAuthenticationNeededForRequest:)]) {
|
||||
return YES;
|
||||
}
|
||||
return NO;
|
||||
|
||||
// If the delegate implements requestWillRedirectSelector, the request will stop to allow the delegate to change the url
|
||||
} else if (selector == @selector(request:willRedirectToURL:)) {
|
||||
if ([self requestWillRedirectSelector] && [[self delegate] respondsToSelector:[self requestWillRedirectSelector]]) {
|
||||
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 setRequestWillRedirectSelector:[self requestWillRedirectSelector]];
|
||||
[newQueue setRequestDidReceiveResponseHeadersSelector:[self requestDidReceiveResponseHeadersSelector]];
|
||||
[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 requestWillRedirectSelector;
|
||||
@synthesize requestDidFinishSelector;
|
||||
@synthesize requestDidFailSelector;
|
||||
@synthesize queueDidFinishSelector;
|
||||
@synthesize delegate;
|
||||
@synthesize showAccurateProgress;
|
||||
@synthesize userInfo;
|
||||
@end
|
||||
38
projects/mtg/iOS/asi-http-request/ASIProgressDelegate.h
Normal file
38
projects/mtg/iOS/asi-http-request/ASIProgressDelegate.h
Normal file
@@ -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
|
||||
@@ -0,0 +1,80 @@
|
||||
//
|
||||
// ASIWebPageRequest.h
|
||||
// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest
|
||||
//
|
||||
// Created by Ben Copsey on 29/06/2010.
|
||||
// Copyright 2010 All-Seeing Interactive. All rights reserved.
|
||||
//
|
||||
// This is an EXPERIMENTAL class - use at your own risk!
|
||||
// It is strongly recommend to set a downloadDestinationPath when using ASIWebPageRequest
|
||||
// Also, performance will be better if your ASIWebPageRequest has a downloadCache setup
|
||||
// Known issue: You cannot use startSychronous with an ASIWebPageRequest
|
||||
|
||||
#import "ASIHTTPRequest.h"
|
||||
|
||||
@class ASINetworkQueue;
|
||||
|
||||
// Used internally for storing what type of data we got from the server
|
||||
typedef enum _ASIWebContentType {
|
||||
ASINotParsedWebContentType = 0,
|
||||
ASIHTMLWebContentType = 1,
|
||||
ASICSSWebContentType = 2
|
||||
} ASIWebContentType;
|
||||
|
||||
// These correspond with the urlReplacementMode property of ASIWebPageRequest
|
||||
typedef enum _ASIURLReplacementMode {
|
||||
|
||||
// Don't modify html or css content at all
|
||||
ASIDontModifyURLs = 0,
|
||||
|
||||
// Replace external resources urls (images, stylesheets etc) with data uris, so their content is embdedded directly in the html/css
|
||||
ASIReplaceExternalResourcesWithData = 1,
|
||||
|
||||
// Replace external resource urls with the url of locally cached content
|
||||
// You must set the baseURL of a WebView / UIWebView to a file url pointing at the downloadDestinationPath of the main ASIWebPageRequest if you want to display your content
|
||||
// See the Mac or iPhone example projects for a demonstration of how to do this
|
||||
// The hrefs of all hyperlinks are changed to use absolute urls when using this mode
|
||||
ASIReplaceExternalResourcesWithLocalURLs = 2
|
||||
} ASIURLReplacementMode;
|
||||
|
||||
|
||||
|
||||
@interface ASIWebPageRequest : ASIHTTPRequest {
|
||||
|
||||
// Each ASIWebPageRequest for an HTML or CSS file creates its own internal queue to download external resources
|
||||
ASINetworkQueue *externalResourceQueue;
|
||||
|
||||
// This dictionary stores a list of external resources to download, along with their content-type data or a path to the data
|
||||
NSMutableDictionary *resourceList;
|
||||
|
||||
// Used internally for parsing HTML (with libxml)
|
||||
struct _xmlDoc *doc;
|
||||
|
||||
// If the response is an HTML or CSS file, this will be set so the content can be correctly parsed when it has finished fetching external resources
|
||||
ASIWebContentType webContentType;
|
||||
|
||||
// Stores a reference to the ASIWebPageRequest that created this request
|
||||
// Note that a parentRequest can also have a parent request because ASIWebPageRequests parse their contents to look for external resources recursively
|
||||
// For example, a request for an image can be created by a request for a stylesheet which was created by a request for a web page
|
||||
ASIWebPageRequest *parentRequest;
|
||||
|
||||
// Controls what ASIWebPageRequest does with external resources. See the notes above for more.
|
||||
ASIURLReplacementMode urlReplacementMode;
|
||||
|
||||
// When set to NO, loading will stop when an external resource fails to load. Defaults to YES
|
||||
BOOL shouldIgnoreExternalResourceErrors;
|
||||
}
|
||||
|
||||
// Will return a data URI that contains a base64 version of the content at this url
|
||||
// This is used when replacing urls in the html and css with actual data
|
||||
// If you subclass ASIWebPageRequest, you can override this function to return different content or a url pointing at another location
|
||||
- (NSString *)contentForExternalURL:(NSString *)theURL;
|
||||
|
||||
// Returns the location that a downloaded external resource's content will be stored in
|
||||
- (NSString *)cachePathForRequest:(ASIWebPageRequest *)theRequest;
|
||||
|
||||
|
||||
@property (retain, nonatomic) ASIWebPageRequest *parentRequest;
|
||||
@property (assign, nonatomic) ASIURLReplacementMode urlReplacementMode;
|
||||
@property (assign, nonatomic) BOOL shouldIgnoreExternalResourceErrors;
|
||||
@end
|
||||
@@ -0,0 +1,722 @@
|
||||
//
|
||||
// ASIWebPageRequest.m
|
||||
// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest
|
||||
//
|
||||
// Created by Ben Copsey on 29/06/2010.
|
||||
// Copyright 2010 All-Seeing Interactive. All rights reserved.
|
||||
//
|
||||
// This is an EXPERIMENTAL class - use at your own risk!
|
||||
|
||||
#import "ASIWebPageRequest.h"
|
||||
#import "ASINetworkQueue.h"
|
||||
#import <CommonCrypto/CommonHMAC.h>
|
||||
#import <libxml/HTMLparser.h>
|
||||
#import <libxml/xmlsave.h>
|
||||
#import <libxml/xpath.h>
|
||||
#import <libxml/xpathInternals.h>
|
||||
|
||||
// An xPath query that controls the external resources ASIWebPageRequest will fetch
|
||||
// By default, it will fetch stylesheets, javascript files, images, frames, iframes, and html 5 video / audio
|
||||
static xmlChar *xpathExpr = (xmlChar *)"//link/@href|//a/@href|//script/@src|//img/@src|//frame/@src|//iframe/@src|//style|//*/@style|//source/@src|//video/@poster|//audio/@src";
|
||||
|
||||
static NSLock *xmlParsingLock = nil;
|
||||
static NSMutableArray *requestsUsingXMLParser = nil;
|
||||
|
||||
@interface ASIWebPageRequest ()
|
||||
- (void)readResourceURLs;
|
||||
- (void)updateResourceURLs;
|
||||
- (void)parseAsHTML;
|
||||
- (void)parseAsCSS;
|
||||
- (void)addURLToFetch:(NSString *)newURL;
|
||||
+ (NSArray *)CSSURLsFromString:(NSString *)string;
|
||||
- (NSString *)relativePathTo:(NSString *)destinationPath fromPath:(NSString *)sourcePath;
|
||||
|
||||
- (void)finishedFetchingExternalResources:(ASINetworkQueue *)queue;
|
||||
- (void)externalResourceFetchSucceeded:(ASIHTTPRequest *)externalResourceRequest;
|
||||
- (void)externalResourceFetchFailed:(ASIHTTPRequest *)externalResourceRequest;
|
||||
|
||||
@property (retain, nonatomic) ASINetworkQueue *externalResourceQueue;
|
||||
@property (retain, nonatomic) NSMutableDictionary *resourceList;
|
||||
@end
|
||||
|
||||
@implementation ASIWebPageRequest
|
||||
|
||||
+ (void)initialize
|
||||
{
|
||||
if (self == [ASIWebPageRequest class]) {
|
||||
xmlParsingLock = [[NSLock alloc] init];
|
||||
requestsUsingXMLParser = [[NSMutableArray alloc] init];
|
||||
}
|
||||
}
|
||||
|
||||
- (id)initWithURL:(NSURL *)newURL
|
||||
{
|
||||
self = [super initWithURL:newURL];
|
||||
[self setShouldIgnoreExternalResourceErrors:YES];
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
[externalResourceQueue cancelAllOperations];
|
||||
[externalResourceQueue release];
|
||||
[resourceList release];
|
||||
[parentRequest release];
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
// This is a bit of a hack
|
||||
// The role of this method in normal ASIHTTPRequests is to tell the queue we are done with the request, and perform some cleanup
|
||||
// We override it to stop that happening, and instead do that work in the bottom of finishedFetchingExternalResources:
|
||||
- (void)markAsFinished
|
||||
{
|
||||
if ([self error]) {
|
||||
[super markAsFinished];
|
||||
}
|
||||
}
|
||||
|
||||
// This method is normally responsible for telling delegates we are done, but it happens to be the most convenient place to parse the responses
|
||||
// Again, we call the super implementation in finishedFetchingExternalResources:, or here if this download was not an HTML or CSS file
|
||||
- (void)requestFinished
|
||||
{
|
||||
complete = NO;
|
||||
if ([self mainRequest] || [self didUseCachedResponse]) {
|
||||
[super requestFinished];
|
||||
[super markAsFinished];
|
||||
return;
|
||||
}
|
||||
webContentType = ASINotParsedWebContentType;
|
||||
NSString *contentType = [[[self responseHeaders] objectForKey:@"Content-Type"] lowercaseString];
|
||||
contentType = [[contentType componentsSeparatedByString:@";"] objectAtIndex:0];
|
||||
if ([contentType isEqualToString:@"text/html"] || [contentType isEqualToString:@"text/xhtml"] || [contentType isEqualToString:@"text/xhtml+xml"] || [contentType isEqualToString:@"application/xhtml+xml"]) {
|
||||
[self parseAsHTML];
|
||||
return;
|
||||
} else if ([contentType isEqualToString:@"text/css"]) {
|
||||
[self parseAsCSS];
|
||||
return;
|
||||
}
|
||||
[super requestFinished];
|
||||
[super markAsFinished];
|
||||
}
|
||||
|
||||
- (void)parseAsCSS
|
||||
{
|
||||
webContentType = ASICSSWebContentType;
|
||||
|
||||
NSString *responseCSS = nil;
|
||||
NSError *err = nil;
|
||||
if ([self downloadDestinationPath]) {
|
||||
responseCSS = [NSString stringWithContentsOfFile:[self downloadDestinationPath] encoding:[self responseEncoding] error:&err];
|
||||
} else {
|
||||
responseCSS = [self responseString];
|
||||
}
|
||||
if (err) {
|
||||
[self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:100 userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Unable to read HTML string from response",NSLocalizedDescriptionKey,err,NSUnderlyingErrorKey,nil]]];
|
||||
return;
|
||||
} else if (!responseCSS) {
|
||||
[self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:100 userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Unable to read HTML string from response",NSLocalizedDescriptionKey,nil]]];
|
||||
return;
|
||||
}
|
||||
NSArray *urls = [[self class] CSSURLsFromString:responseCSS];
|
||||
|
||||
[self setResourceList:[NSMutableDictionary dictionary]];
|
||||
|
||||
for (NSString *theURL in urls) {
|
||||
[self addURLToFetch:theURL];
|
||||
}
|
||||
if (![[self resourceList] count]) {
|
||||
[super requestFinished];
|
||||
[super markAsFinished];
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a new request for every item in the queue
|
||||
[[self externalResourceQueue] cancelAllOperations];
|
||||
[self setExternalResourceQueue:[ASINetworkQueue queue]];
|
||||
[[self externalResourceQueue] setDelegate:self];
|
||||
[[self externalResourceQueue] setShouldCancelAllRequestsOnFailure:[self shouldIgnoreExternalResourceErrors]];
|
||||
[[self externalResourceQueue] setShowAccurateProgress:[self showAccurateProgress]];
|
||||
[[self externalResourceQueue] setQueueDidFinishSelector:@selector(finishedFetchingExternalResources:)];
|
||||
[[self externalResourceQueue] setRequestDidFinishSelector:@selector(externalResourceFetchSucceeded:)];
|
||||
[[self externalResourceQueue] setRequestDidFailSelector:@selector(externalResourceFetchFailed:)];
|
||||
for (NSString *theURL in [[self resourceList] keyEnumerator]) {
|
||||
ASIWebPageRequest *externalResourceRequest = [ASIWebPageRequest requestWithURL:[NSURL URLWithString:theURL relativeToURL:[self url]]];
|
||||
[externalResourceRequest setRequestHeaders:[self requestHeaders]];
|
||||
[externalResourceRequest setDownloadCache:[self downloadCache]];
|
||||
[externalResourceRequest setCachePolicy:[self cachePolicy]];
|
||||
[externalResourceRequest setCacheStoragePolicy:[self cacheStoragePolicy]];
|
||||
[externalResourceRequest setUserInfo:[NSDictionary dictionaryWithObject:theURL forKey:@"Path"]];
|
||||
[externalResourceRequest setParentRequest:self];
|
||||
[externalResourceRequest setUrlReplacementMode:[self urlReplacementMode]];
|
||||
[externalResourceRequest setShouldResetDownloadProgress:NO];
|
||||
[externalResourceRequest setDelegate:self];
|
||||
[externalResourceRequest setUploadProgressDelegate:self];
|
||||
[externalResourceRequest setDownloadProgressDelegate:self];
|
||||
if ([self downloadDestinationPath]) {
|
||||
[externalResourceRequest setDownloadDestinationPath:[self cachePathForRequest:externalResourceRequest]];
|
||||
}
|
||||
[[self externalResourceQueue] addOperation:externalResourceRequest];
|
||||
}
|
||||
[[self externalResourceQueue] go];
|
||||
}
|
||||
|
||||
- (const char *)encodingName
|
||||
{
|
||||
xmlCharEncoding encoding = XML_CHAR_ENCODING_NONE;
|
||||
switch ([self responseEncoding])
|
||||
{
|
||||
case NSASCIIStringEncoding:
|
||||
encoding = XML_CHAR_ENCODING_ASCII;
|
||||
break;
|
||||
case NSJapaneseEUCStringEncoding:
|
||||
encoding = XML_CHAR_ENCODING_EUC_JP;
|
||||
break;
|
||||
case NSUTF8StringEncoding:
|
||||
encoding = XML_CHAR_ENCODING_UTF8;
|
||||
break;
|
||||
case NSISOLatin1StringEncoding:
|
||||
encoding = XML_CHAR_ENCODING_8859_1;
|
||||
break;
|
||||
case NSShiftJISStringEncoding:
|
||||
encoding = XML_CHAR_ENCODING_SHIFT_JIS;
|
||||
break;
|
||||
case NSISOLatin2StringEncoding:
|
||||
encoding = XML_CHAR_ENCODING_8859_2;
|
||||
break;
|
||||
case NSISO2022JPStringEncoding:
|
||||
encoding = XML_CHAR_ENCODING_2022_JP;
|
||||
break;
|
||||
case NSUTF16BigEndianStringEncoding:
|
||||
encoding = XML_CHAR_ENCODING_UTF16BE;
|
||||
break;
|
||||
case NSUTF16LittleEndianStringEncoding:
|
||||
encoding = XML_CHAR_ENCODING_UTF16LE;
|
||||
break;
|
||||
case NSUTF32BigEndianStringEncoding:
|
||||
encoding = XML_CHAR_ENCODING_UCS4BE;
|
||||
break;
|
||||
case NSUTF32LittleEndianStringEncoding:
|
||||
encoding = XML_CHAR_ENCODING_UCS4LE;
|
||||
break;
|
||||
case NSNEXTSTEPStringEncoding:
|
||||
case NSSymbolStringEncoding:
|
||||
case NSNonLossyASCIIStringEncoding:
|
||||
case NSUnicodeStringEncoding:
|
||||
case NSMacOSRomanStringEncoding:
|
||||
case NSUTF32StringEncoding:
|
||||
default:
|
||||
encoding = XML_CHAR_ENCODING_ERROR;
|
||||
break;
|
||||
}
|
||||
return xmlGetCharEncodingName(encoding);
|
||||
}
|
||||
|
||||
- (void)parseAsHTML
|
||||
{
|
||||
webContentType = ASIHTMLWebContentType;
|
||||
|
||||
// Only allow parsing of a single document at a time
|
||||
[xmlParsingLock lock];
|
||||
|
||||
if (![requestsUsingXMLParser count]) {
|
||||
xmlInitParser();
|
||||
}
|
||||
[requestsUsingXMLParser addObject:self];
|
||||
|
||||
|
||||
/* Load XML document */
|
||||
if ([self downloadDestinationPath]) {
|
||||
doc = htmlReadFile([[self downloadDestinationPath] cStringUsingEncoding:NSUTF8StringEncoding], [self encodingName], HTML_PARSE_NONET | HTML_PARSE_NOWARNING | HTML_PARSE_NOERROR);
|
||||
} else {
|
||||
NSData *data = [self responseData];
|
||||
doc = htmlReadMemory([data bytes], (int)[data length], "", [self encodingName], HTML_PARSE_NONET | HTML_PARSE_NOWARNING | HTML_PARSE_NOERROR);
|
||||
}
|
||||
if (doc == NULL) {
|
||||
[self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:101 userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Error: unable to parse reponse XML",NSLocalizedDescriptionKey,nil]]];
|
||||
return;
|
||||
}
|
||||
|
||||
[self setResourceList:[NSMutableDictionary dictionary]];
|
||||
|
||||
// Populate the list of URLS to download
|
||||
[self readResourceURLs];
|
||||
|
||||
if ([self error] || ![[self resourceList] count]) {
|
||||
[requestsUsingXMLParser removeObject:self];
|
||||
xmlFreeDoc(doc);
|
||||
doc = NULL;
|
||||
}
|
||||
|
||||
[xmlParsingLock unlock];
|
||||
|
||||
if ([self error]) {
|
||||
return;
|
||||
} else if (![[self resourceList] count]) {
|
||||
[super requestFinished];
|
||||
[super markAsFinished];
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a new request for every item in the queue
|
||||
[[self externalResourceQueue] cancelAllOperations];
|
||||
[self setExternalResourceQueue:[ASINetworkQueue queue]];
|
||||
[[self externalResourceQueue] setDelegate:self];
|
||||
[[self externalResourceQueue] setShouldCancelAllRequestsOnFailure:[self shouldIgnoreExternalResourceErrors]];
|
||||
[[self externalResourceQueue] setShowAccurateProgress:[self showAccurateProgress]];
|
||||
[[self externalResourceQueue] setQueueDidFinishSelector:@selector(finishedFetchingExternalResources:)];
|
||||
[[self externalResourceQueue] setRequestDidFinishSelector:@selector(externalResourceFetchSucceeded:)];
|
||||
[[self externalResourceQueue] setRequestDidFailSelector:@selector(externalResourceFetchFailed:)];
|
||||
for (NSString *theURL in [[self resourceList] keyEnumerator]) {
|
||||
ASIWebPageRequest *externalResourceRequest = [ASIWebPageRequest requestWithURL:[NSURL URLWithString:theURL relativeToURL:[self url]]];
|
||||
[externalResourceRequest setRequestHeaders:[self requestHeaders]];
|
||||
[externalResourceRequest setDownloadCache:[self downloadCache]];
|
||||
[externalResourceRequest setCachePolicy:[self cachePolicy]];
|
||||
[externalResourceRequest setCacheStoragePolicy:[self cacheStoragePolicy]];
|
||||
[externalResourceRequest setUserInfo:[NSDictionary dictionaryWithObject:theURL forKey:@"Path"]];
|
||||
[externalResourceRequest setParentRequest:self];
|
||||
[externalResourceRequest setUrlReplacementMode:[self urlReplacementMode]];
|
||||
[externalResourceRequest setShouldResetDownloadProgress:NO];
|
||||
[externalResourceRequest setDelegate:self];
|
||||
[externalResourceRequest setUploadProgressDelegate:self];
|
||||
[externalResourceRequest setDownloadProgressDelegate:self];
|
||||
if ([self downloadDestinationPath]) {
|
||||
[externalResourceRequest setDownloadDestinationPath:[self cachePathForRequest:externalResourceRequest]];
|
||||
}
|
||||
[[self externalResourceQueue] addOperation:externalResourceRequest];
|
||||
}
|
||||
[[self externalResourceQueue] go];
|
||||
}
|
||||
|
||||
- (void)externalResourceFetchSucceeded:(ASIHTTPRequest *)externalResourceRequest
|
||||
{
|
||||
NSString *originalPath = [[externalResourceRequest userInfo] objectForKey:@"Path"];
|
||||
NSMutableDictionary *requestResponse = [[self resourceList] objectForKey:originalPath];
|
||||
NSString *contentType = [[externalResourceRequest responseHeaders] objectForKey:@"Content-Type"];
|
||||
if (!contentType) {
|
||||
contentType = @"application/octet-stream";
|
||||
}
|
||||
[requestResponse setObject:contentType forKey:@"ContentType"];
|
||||
if ([self downloadDestinationPath]) {
|
||||
[requestResponse setObject:[externalResourceRequest downloadDestinationPath] forKey:@"DataPath"];
|
||||
} else {
|
||||
NSData *data = [externalResourceRequest responseData];
|
||||
if (data) {
|
||||
[requestResponse setObject:data forKey:@"Data"];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)externalResourceFetchFailed:(ASIHTTPRequest *)externalResourceRequest
|
||||
{
|
||||
if ([[self externalResourceQueue] shouldCancelAllRequestsOnFailure]) {
|
||||
[self failWithError:[externalResourceRequest error]];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)finishedFetchingExternalResources:(ASINetworkQueue *)queue
|
||||
{
|
||||
if ([self urlReplacementMode] != ASIDontModifyURLs) {
|
||||
if (webContentType == ASICSSWebContentType) {
|
||||
NSMutableString *parsedResponse;
|
||||
NSError *err = nil;
|
||||
if ([self downloadDestinationPath]) {
|
||||
parsedResponse = [NSMutableString stringWithContentsOfFile:[self downloadDestinationPath] encoding:[self responseEncoding] error:&err];
|
||||
} else {
|
||||
parsedResponse = [[[self responseString] mutableCopy] autorelease];
|
||||
}
|
||||
if (err) {
|
||||
[self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:101 userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Error: unable to read response CSS from disk",NSLocalizedDescriptionKey,nil]]];
|
||||
return;
|
||||
}
|
||||
if (![self error]) {
|
||||
for (NSString *resource in [[self resourceList] keyEnumerator]) {
|
||||
if ([parsedResponse rangeOfString:resource].location != NSNotFound) {
|
||||
NSString *newURL = [self contentForExternalURL:resource];
|
||||
if (newURL) {
|
||||
[parsedResponse replaceOccurrencesOfString:resource withString:newURL options:0 range:NSMakeRange(0, [parsedResponse length])];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if ([self downloadDestinationPath]) {
|
||||
[parsedResponse writeToFile:[self downloadDestinationPath] atomically:NO encoding:[self responseEncoding] error:&err];
|
||||
if (err) {
|
||||
[self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:101 userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Error: unable to write response CSS to disk",NSLocalizedDescriptionKey,nil]]];
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
[self setRawResponseData:(id)[parsedResponse dataUsingEncoding:[self responseEncoding]]];
|
||||
}
|
||||
} else {
|
||||
[xmlParsingLock lock];
|
||||
|
||||
[self updateResourceURLs];
|
||||
|
||||
if (![self error]) {
|
||||
|
||||
// We'll use the xmlsave API so we can strip the xml declaration
|
||||
xmlSaveCtxtPtr saveContext;
|
||||
|
||||
if ([self downloadDestinationPath]) {
|
||||
|
||||
// Truncate the file first
|
||||
[[[[NSFileManager alloc] init] autorelease] createFileAtPath:[self downloadDestinationPath] contents:nil attributes:nil];
|
||||
|
||||
saveContext = xmlSaveToFd([[NSFileHandle fileHandleForWritingAtPath:[self downloadDestinationPath]] fileDescriptor],NULL,2); // 2 == XML_SAVE_NO_DECL, this isn't declared on Mac OS 10.5
|
||||
xmlSaveDoc(saveContext, doc);
|
||||
xmlSaveClose(saveContext);
|
||||
|
||||
} else {
|
||||
#if TARGET_OS_MAC && MAC_OS_X_VERSION_MAX_ALLOWED <= __MAC_10_5
|
||||
// xmlSaveToBuffer() is not implemented in the 10.5 version of libxml
|
||||
NSString *tempPath = [NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]];
|
||||
[[[[NSFileManager alloc] init] autorelease] createFileAtPath:tempPath contents:nil attributes:nil];
|
||||
saveContext = xmlSaveToFd([[NSFileHandle fileHandleForWritingAtPath:tempPath] fileDescriptor],NULL,2); // 2 == XML_SAVE_NO_DECL, this isn't declared on Mac OS 10.5
|
||||
xmlSaveDoc(saveContext, doc);
|
||||
xmlSaveClose(saveContext);
|
||||
[self setRawResponseData:[NSMutableData dataWithContentsOfFile:tempPath]];
|
||||
#else
|
||||
xmlBufferPtr buffer = xmlBufferCreate();
|
||||
saveContext = xmlSaveToBuffer(buffer,NULL,2); // 2 == XML_SAVE_NO_DECL, this isn't declared on Mac OS 10.5
|
||||
xmlSaveDoc(saveContext, doc);
|
||||
xmlSaveClose(saveContext);
|
||||
[self setRawResponseData:[[[NSMutableData alloc] initWithBytes:buffer->content length:buffer->use] autorelease]];
|
||||
xmlBufferFree(buffer);
|
||||
#endif
|
||||
}
|
||||
|
||||
// Strip the content encoding if the original response was gzipped
|
||||
if ([self isResponseCompressed]) {
|
||||
NSMutableDictionary *headers = [[[self responseHeaders] mutableCopy] autorelease];
|
||||
[headers removeObjectForKey:@"Content-Encoding"];
|
||||
[self setResponseHeaders:headers];
|
||||
}
|
||||
}
|
||||
|
||||
xmlFreeDoc(doc);
|
||||
doc = nil;
|
||||
|
||||
[requestsUsingXMLParser removeObject:self];
|
||||
if (![requestsUsingXMLParser count]) {
|
||||
xmlCleanupParser();
|
||||
}
|
||||
[xmlParsingLock unlock];
|
||||
}
|
||||
}
|
||||
if (![self parentRequest]) {
|
||||
[[self class] updateProgressIndicator:&downloadProgressDelegate withProgress:contentLength ofTotal:contentLength];
|
||||
}
|
||||
|
||||
NSMutableDictionary *newHeaders = [[[self responseHeaders] mutableCopy] autorelease];
|
||||
[newHeaders removeObjectForKey:@"Content-Encoding"];
|
||||
[self setResponseHeaders:newHeaders];
|
||||
|
||||
// Write the parsed content back to the cache
|
||||
if ([self urlReplacementMode] != ASIDontModifyURLs) {
|
||||
[[self downloadCache] storeResponseForRequest:self maxAge:[self secondsToCache]];
|
||||
}
|
||||
|
||||
[super requestFinished];
|
||||
[super markAsFinished];
|
||||
}
|
||||
|
||||
- (void)readResourceURLs
|
||||
{
|
||||
// Create xpath evaluation context
|
||||
xmlXPathContextPtr xpathCtx = xmlXPathNewContext(doc);
|
||||
if(xpathCtx == NULL) {
|
||||
[self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:101 userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Error: unable to create new XPath context",NSLocalizedDescriptionKey,nil]]];
|
||||
return;
|
||||
}
|
||||
|
||||
// Evaluate xpath expression
|
||||
xmlXPathObjectPtr xpathObj = xmlXPathEvalExpression(xpathExpr, xpathCtx);
|
||||
if(xpathObj == NULL) {
|
||||
xmlXPathFreeContext(xpathCtx);
|
||||
[self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:101 userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Error: unable to evaluate XPath expression!",NSLocalizedDescriptionKey,nil]]];
|
||||
return;
|
||||
}
|
||||
|
||||
// Now loop through our matches
|
||||
xmlNodeSetPtr nodes = xpathObj->nodesetval;
|
||||
|
||||
int size = (nodes) ? nodes->nodeNr : 0;
|
||||
int i;
|
||||
for(i = size - 1; i >= 0; i--) {
|
||||
assert(nodes->nodeTab[i]);
|
||||
NSString *parentName = [NSString stringWithCString:(char *)nodes->nodeTab[i]->parent->name encoding:[self responseEncoding]];
|
||||
NSString *nodeName = [NSString stringWithCString:(char *)nodes->nodeTab[i]->name encoding:[self responseEncoding]];
|
||||
|
||||
xmlChar *nodeValue = xmlNodeGetContent(nodes->nodeTab[i]);
|
||||
NSString *value = [NSString stringWithCString:(char *)nodeValue encoding:[self responseEncoding]];
|
||||
xmlFree(nodeValue);
|
||||
|
||||
// Our xpath query matched all <link> elements, but we're only interested in stylesheets
|
||||
// We do the work here rather than in the xPath query because the query is case-sensitive, and we want to match on 'stylesheet', 'StyleSHEEt' etc
|
||||
if ([[parentName lowercaseString] isEqualToString:@"link"]) {
|
||||
xmlChar *relAttribute = xmlGetNoNsProp(nodes->nodeTab[i]->parent,(xmlChar *)"rel");
|
||||
if (relAttribute) {
|
||||
NSString *rel = [NSString stringWithCString:(char *)relAttribute encoding:[self responseEncoding]];
|
||||
xmlFree(relAttribute);
|
||||
if ([[rel lowercaseString] isEqualToString:@"stylesheet"]) {
|
||||
[self addURLToFetch:value];
|
||||
}
|
||||
}
|
||||
|
||||
// Parse the content of <style> tags and style attributes to find external image urls or external css files
|
||||
} else if ([[nodeName lowercaseString] isEqualToString:@"style"]) {
|
||||
NSArray *externalResources = [[self class] CSSURLsFromString:value];
|
||||
for (NSString *theURL in externalResources) {
|
||||
[self addURLToFetch:theURL];
|
||||
}
|
||||
|
||||
// Parse the content of <source src=""> tags (HTML 5 audio + video)
|
||||
// We explictly disable the download of files with .webm, .ogv and .ogg extensions, since it's highly likely they won't be useful to us
|
||||
} else if ([[parentName lowercaseString] isEqualToString:@"source"] || [[parentName lowercaseString] isEqualToString:@"audio"]) {
|
||||
NSString *fileExtension = [[value pathExtension] lowercaseString];
|
||||
if (![fileExtension isEqualToString:@"ogg"] && ![fileExtension isEqualToString:@"ogv"] && ![fileExtension isEqualToString:@"webm"]) {
|
||||
[self addURLToFetch:value];
|
||||
}
|
||||
|
||||
// For all other elements matched by our xpath query (except hyperlinks), add the content as an external url to fetch
|
||||
} else if (![[parentName lowercaseString] isEqualToString:@"a"]) {
|
||||
[self addURLToFetch:value];
|
||||
}
|
||||
if (nodes->nodeTab[i]->type != XML_NAMESPACE_DECL) {
|
||||
nodes->nodeTab[i] = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
xmlXPathFreeObject(xpathObj);
|
||||
xmlXPathFreeContext(xpathCtx);
|
||||
}
|
||||
|
||||
- (void)addURLToFetch:(NSString *)newURL
|
||||
{
|
||||
// Get rid of any surrounding whitespace
|
||||
newURL = [newURL stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
|
||||
// Don't attempt to fetch data URIs
|
||||
if ([newURL length] > 4) {
|
||||
if (![[[newURL substringToIndex:5] lowercaseString] isEqualToString:@"data:"]) {
|
||||
NSURL *theURL = [NSURL URLWithString:newURL relativeToURL:[self url]];
|
||||
if (theURL) {
|
||||
if (![[self resourceList] objectForKey:newURL]) {
|
||||
[[self resourceList] setObject:[NSMutableDictionary dictionary] forKey:newURL];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
- (void)updateResourceURLs
|
||||
{
|
||||
// Create xpath evaluation context
|
||||
xmlXPathContextPtr xpathCtx = xmlXPathNewContext(doc);
|
||||
if(xpathCtx == NULL) {
|
||||
[self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:101 userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Error: unable to create new XPath context",NSLocalizedDescriptionKey,nil]]];
|
||||
return;
|
||||
}
|
||||
|
||||
// Evaluate xpath expression
|
||||
xmlXPathObjectPtr xpathObj = xmlXPathEvalExpression(xpathExpr, xpathCtx);
|
||||
if(xpathObj == NULL) {
|
||||
xmlXPathFreeContext(xpathCtx);
|
||||
[self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:101 userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Error: unable to evaluate XPath expression!",NSLocalizedDescriptionKey,nil]]];
|
||||
return;
|
||||
}
|
||||
|
||||
// Loop through all the matches, replacing urls where nescessary
|
||||
xmlNodeSetPtr nodes = xpathObj->nodesetval;
|
||||
int size = (nodes) ? nodes->nodeNr : 0;
|
||||
int i;
|
||||
for(i = size - 1; i >= 0; i--) {
|
||||
assert(nodes->nodeTab[i]);
|
||||
NSString *parentName = [NSString stringWithCString:(char *)nodes->nodeTab[i]->parent->name encoding:[self responseEncoding]];
|
||||
NSString *nodeName = [NSString stringWithCString:(char *)nodes->nodeTab[i]->name encoding:[self responseEncoding]];
|
||||
|
||||
xmlChar *nodeValue = xmlNodeGetContent(nodes->nodeTab[i]);
|
||||
NSString *value = [NSString stringWithCString:(char *)nodeValue encoding:[self responseEncoding]];
|
||||
xmlFree(nodeValue);
|
||||
|
||||
// Replace external urls in <style> tags or in style attributes
|
||||
if ([[nodeName lowercaseString] isEqualToString:@"style"]) {
|
||||
NSArray *externalResources = [[self class] CSSURLsFromString:value];
|
||||
for (NSString *theURL in externalResources) {
|
||||
if ([value rangeOfString:theURL].location != NSNotFound) {
|
||||
NSString *newURL = [self contentForExternalURL:theURL];
|
||||
if (newURL) {
|
||||
value = [value stringByReplacingOccurrencesOfString:theURL withString:newURL];
|
||||
}
|
||||
}
|
||||
}
|
||||
xmlNodeSetContent(nodes->nodeTab[i], (xmlChar *)[value cStringUsingEncoding:[self responseEncoding]]);
|
||||
|
||||
// Replace relative hyperlinks with absolute ones, since we will need to set a local baseURL when loading this in a web view
|
||||
} else if ([self urlReplacementMode] == ASIReplaceExternalResourcesWithLocalURLs && [[parentName lowercaseString] isEqualToString:@"a"]) {
|
||||
NSString *newURL = [[NSURL URLWithString:value relativeToURL:[self url]] absoluteString];
|
||||
if (newURL) {
|
||||
xmlNodeSetContent(nodes->nodeTab[i], (xmlChar *)[newURL cStringUsingEncoding:[self responseEncoding]]);
|
||||
}
|
||||
|
||||
// Replace all other external resource urls
|
||||
} else {
|
||||
NSString *newURL = [self contentForExternalURL:value];
|
||||
if (newURL) {
|
||||
xmlNodeSetContent(nodes->nodeTab[i], (xmlChar *)[newURL cStringUsingEncoding:[self responseEncoding]]);
|
||||
}
|
||||
}
|
||||
|
||||
if (nodes->nodeTab[i]->type != XML_NAMESPACE_DECL) {
|
||||
nodes->nodeTab[i] = NULL;
|
||||
}
|
||||
}
|
||||
xmlXPathFreeObject(xpathObj);
|
||||
xmlXPathFreeContext(xpathCtx);
|
||||
}
|
||||
|
||||
// The three methods below are responsible for forwarding delegate methods we want to handle to the parent request's approdiate delegate
|
||||
// Certain delegate methods are ignored (eg setProgress: / setDoubleValue: / setMaxValue:)
|
||||
- (BOOL)respondsToSelector:(SEL)selector
|
||||
{
|
||||
if ([self parentRequest]) {
|
||||
return [[self parentRequest] respondsToSelector:selector];
|
||||
}
|
||||
//Ok, now check for selectors we want to pass on to the delegate
|
||||
if (selector == @selector(requestStarted:) || selector == @selector(request:didReceiveResponseHeaders:) || selector == @selector(request:willRedirectToURL:) || selector == @selector(requestFinished:) || selector == @selector(requestFailed:) || selector == @selector(request:didReceiveData:) || selector == @selector(authenticationNeededForRequest:) || selector == @selector(proxyAuthenticationNeededForRequest:)) {
|
||||
return [delegate respondsToSelector:selector];
|
||||
} else if (selector == @selector(request:didReceiveBytes:) || selector == @selector(request:incrementDownloadSizeBy:)) {
|
||||
return [downloadProgressDelegate respondsToSelector:selector];
|
||||
} else if (selector == @selector(request:didSendBytes:) || selector == @selector(request:incrementUploadSizeBy:)) {
|
||||
return [uploadProgressDelegate respondsToSelector:selector];
|
||||
}
|
||||
return [super respondsToSelector:selector];
|
||||
}
|
||||
|
||||
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector
|
||||
{
|
||||
if ([self parentRequest]) {
|
||||
return [[self parentRequest] methodSignatureForSelector:selector];
|
||||
}
|
||||
if (selector == @selector(requestStarted:) || selector == @selector(request:didReceiveResponseHeaders:) || selector == @selector(request:willRedirectToURL:) || selector == @selector(requestFinished:) || selector == @selector(requestFailed:) || selector == @selector(request:didReceiveData:) || selector == @selector(authenticationNeededForRequest:) || selector == @selector(proxyAuthenticationNeededForRequest:)) {
|
||||
return [(id)delegate methodSignatureForSelector:selector];
|
||||
} else if (selector == @selector(request:didReceiveBytes:) || selector == @selector(request:incrementDownloadSizeBy:)) {
|
||||
return [(id)downloadProgressDelegate methodSignatureForSelector:selector];
|
||||
} else if (selector == @selector(request:didSendBytes:) || selector == @selector(request:incrementUploadSizeBy:)) {
|
||||
return [(id)uploadProgressDelegate methodSignatureForSelector:selector];
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void)forwardInvocation:(NSInvocation *)anInvocation
|
||||
{
|
||||
if ([self parentRequest]) {
|
||||
return [[self parentRequest] forwardInvocation:anInvocation];
|
||||
}
|
||||
SEL selector = [anInvocation selector];
|
||||
if (selector == @selector(requestStarted:) || selector == @selector(request:didReceiveResponseHeaders:) || selector == @selector(request:willRedirectToURL:) || selector == @selector(requestFinished:) || selector == @selector(requestFailed:) || selector == @selector(request:didReceiveData:) || selector == @selector(authenticationNeededForRequest:) || selector == @selector(proxyAuthenticationNeededForRequest:)) {
|
||||
[anInvocation invokeWithTarget:delegate];
|
||||
} else if (selector == @selector(request:didReceiveBytes:) || selector == @selector(request:incrementDownloadSizeBy:)) {
|
||||
[anInvocation invokeWithTarget:downloadProgressDelegate];
|
||||
} else if (selector == @selector(request:didSendBytes:) || selector == @selector(request:incrementUploadSizeBy:)) {
|
||||
[anInvocation invokeWithTarget:uploadProgressDelegate];
|
||||
}
|
||||
}
|
||||
|
||||
// A quick and dirty way to build a list of external resource urls from a css string
|
||||
+ (NSArray *)CSSURLsFromString:(NSString *)string
|
||||
{
|
||||
NSMutableArray *urls = [NSMutableArray array];
|
||||
NSScanner *scanner = [NSScanner scannerWithString:string];
|
||||
[scanner setCaseSensitive:NO];
|
||||
while (1) {
|
||||
NSString *theURL = nil;
|
||||
[scanner scanUpToString:@"url(" intoString:NULL];
|
||||
[scanner scanString:@"url(" intoString:NULL];
|
||||
[scanner scanUpToString:@")" intoString:&theURL];
|
||||
if (!theURL) {
|
||||
break;
|
||||
}
|
||||
// Remove any quotes or whitespace around the url
|
||||
theURL = [theURL stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
|
||||
theURL = [theURL stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"\"'"]];
|
||||
theURL = [theURL stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
|
||||
[urls addObject:theURL];
|
||||
}
|
||||
return urls;
|
||||
}
|
||||
|
||||
// Returns a relative file path from sourcePath to destinationPath (eg ../../foo/bar.txt)
|
||||
- (NSString *)relativePathTo:(NSString *)destinationPath fromPath:(NSString *)sourcePath
|
||||
{
|
||||
NSArray *sourcePathComponents = [sourcePath pathComponents];
|
||||
NSArray *destinationPathComponents = [destinationPath pathComponents];
|
||||
NSUInteger i;
|
||||
NSString *newPath = @"";
|
||||
NSString *sourcePathComponent, *destinationPathComponent;
|
||||
for (i=0; i<[sourcePathComponents count]; i++) {
|
||||
sourcePathComponent = [sourcePathComponents objectAtIndex:i];
|
||||
if ([destinationPathComponents count] > i) {
|
||||
destinationPathComponent = [destinationPathComponents objectAtIndex:i];
|
||||
if (![sourcePathComponent isEqualToString:destinationPathComponent]) {
|
||||
NSUInteger i2;
|
||||
for (i2=i+1; i2<[sourcePathComponents count]; i2++) {
|
||||
newPath = [newPath stringByAppendingPathComponent:@".."];
|
||||
}
|
||||
newPath = [newPath stringByAppendingPathComponent:destinationPathComponent];
|
||||
for (i2=i+1; i2<[destinationPathComponents count]; i2++) {
|
||||
newPath = [newPath stringByAppendingPathComponent:[destinationPathComponents objectAtIndex:i2]];
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return newPath;
|
||||
}
|
||||
|
||||
- (NSString *)contentForExternalURL:(NSString *)theURL
|
||||
{
|
||||
if ([self urlReplacementMode] == ASIReplaceExternalResourcesWithLocalURLs) {
|
||||
NSString *resourcePath = [[resourceList objectForKey:theURL] objectForKey:@"DataPath"];
|
||||
return [self relativePathTo:resourcePath fromPath:[self downloadDestinationPath]];
|
||||
}
|
||||
NSData *data;
|
||||
if ([[resourceList objectForKey:theURL] objectForKey:@"DataPath"]) {
|
||||
data = [NSData dataWithContentsOfFile:[[resourceList objectForKey:theURL] objectForKey:@"DataPath"]];
|
||||
} else {
|
||||
data = [[resourceList objectForKey:theURL] objectForKey:@"Data"];
|
||||
}
|
||||
NSString *contentType = [[resourceList objectForKey:theURL] objectForKey:@"ContentType"];
|
||||
if (data && contentType) {
|
||||
NSString *dataURI = [NSString stringWithFormat:@"data:%@;base64,",contentType];
|
||||
dataURI = [dataURI stringByAppendingString:[ASIHTTPRequest base64forData:data]];
|
||||
return dataURI;
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (NSString *)cachePathForRequest:(ASIWebPageRequest *)theRequest
|
||||
{
|
||||
// If we're using a download cache (and its a good idea to do so when using ASIWebPageRequest), ask it for the location to store this file
|
||||
// This ends up being quite efficient, as we download directly to the cache
|
||||
if ([self downloadCache]) {
|
||||
return [[self downloadCache] pathToStoreCachedResponseDataForRequest:theRequest];
|
||||
|
||||
// This is a fallback for when we don't have a download cache - we store the external resource in a file in the temporary directory
|
||||
} else {
|
||||
// Borrowed from: http://stackoverflow.com/questions/652300/using-md5-hash-on-a-string-in-cocoa
|
||||
const char *cStr = [[[theRequest url] absoluteString] UTF8String];
|
||||
unsigned char result[16];
|
||||
CC_MD5(cStr, (CC_LONG)strlen(cStr), result);
|
||||
NSString *md5 = [NSString stringWithFormat:@"%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X",result[0], result[1], result[2], result[3], result[4], result[5], result[6], result[7],result[8], result[9], result[10], result[11],result[12], result[13], result[14], result[15]];
|
||||
return [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:[md5 stringByAppendingPathExtension:@"html"]];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@synthesize externalResourceQueue;
|
||||
@synthesize resourceList;
|
||||
@synthesize parentRequest;
|
||||
@synthesize urlReplacementMode;
|
||||
@synthesize shouldIgnoreExternalResourceErrors;
|
||||
@end
|
||||
@@ -0,0 +1,60 @@
|
||||
//
|
||||
// ASICloudFilesCDNRequest.h
|
||||
//
|
||||
// Created by Michael Mayo on 1/6/10.
|
||||
//
|
||||
|
||||
#import "ASICloudFilesRequest.h"
|
||||
|
||||
@class ASICloudFilesContainerXMLParserDelegate;
|
||||
|
||||
@interface ASICloudFilesCDNRequest : ASICloudFilesRequest {
|
||||
NSString *accountName;
|
||||
NSString *containerName;
|
||||
ASICloudFilesContainerXMLParserDelegate *xmlParserDelegate;
|
||||
|
||||
}
|
||||
|
||||
@property (retain) NSString *accountName;
|
||||
@property (retain) NSString *containerName;
|
||||
@property (retain) ASICloudFilesContainerXMLParserDelegate *xmlParserDelegate;
|
||||
|
||||
|
||||
// HEAD /<api version>/<account>/<container>
|
||||
// Response:
|
||||
// X-CDN-Enabled: True
|
||||
// X-CDN-URI: http://cdn.cloudfiles.mosso.com/c1234
|
||||
// X-CDN-SSL-URI: https://cdn.ssl.cloudfiles.mosso.com/c1234
|
||||
// X-CDN-TTL: 86400
|
||||
+ (id)containerInfoRequest:(NSString *)containerName;
|
||||
- (BOOL)cdnEnabled;
|
||||
- (NSString *)cdnURI;
|
||||
- (NSString *)cdnSSLURI;
|
||||
- (NSUInteger)cdnTTL;
|
||||
|
||||
|
||||
// GET /<api version>/<account>
|
||||
// limit, marker, format, enabled_only=true
|
||||
+ (id)listRequest;
|
||||
+ (id)listRequestWithLimit:(NSUInteger)limit marker:(NSString *)marker enabledOnly:(BOOL)enabledOnly;
|
||||
- (NSArray *)containers;
|
||||
|
||||
|
||||
// PUT /<api version>/<account>/<container>
|
||||
// PUT operations against a Container are used to CDN-enable that Container.
|
||||
// Include an HTTP header of X-TTL to specify a custom TTL.
|
||||
+ (id)putRequestWithContainer:(NSString *)containerName;
|
||||
+ (id)putRequestWithContainer:(NSString *)containerName ttl:(NSUInteger)ttl;
|
||||
// returns: - (NSString *)cdnURI;
|
||||
|
||||
// POST /<api version>/<account>/<container>
|
||||
// POST operations against a CDN-enabled Container are used to adjust CDN attributes.
|
||||
// The POST operation can be used to set a new TTL cache expiration or to enable/disable public sharing over the CDN.
|
||||
// X-TTL: 86400
|
||||
// X-CDN-Enabled: True
|
||||
+ (id)postRequestWithContainer:(NSString *)containerName;
|
||||
+ (id)postRequestWithContainer:(NSString *)containerName cdnEnabled:(BOOL)cdnEnabled ttl:(NSUInteger)ttl;
|
||||
// returns: - (NSString *)cdnURI;
|
||||
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,167 @@
|
||||
//
|
||||
// ASICloudFilesCDNRequest.m
|
||||
//
|
||||
// Created by Michael Mayo on 1/6/10.
|
||||
//
|
||||
|
||||
#import "ASICloudFilesCDNRequest.h"
|
||||
#import "ASICloudFilesContainerXMLParserDelegate.h"
|
||||
|
||||
|
||||
@implementation ASICloudFilesCDNRequest
|
||||
|
||||
@synthesize accountName, containerName, xmlParserDelegate;
|
||||
|
||||
+ (id)cdnRequestWithMethod:(NSString *)method query:(NSString *)query {
|
||||
NSString *urlString = [NSString stringWithFormat:@"%@%@", [ASICloudFilesRequest cdnManagementURL], query];
|
||||
ASICloudFilesCDNRequest *request = [[[ASICloudFilesCDNRequest alloc] initWithURL:[NSURL URLWithString:urlString]] autorelease];
|
||||
[request setRequestMethod:method];
|
||||
[request addRequestHeader:@"X-Auth-Token" value:[ASICloudFilesRequest authToken]];
|
||||
return request;
|
||||
}
|
||||
|
||||
+ (id)cdnRequestWithMethod:(NSString *)method containerName:(NSString *)containerName {
|
||||
NSString *urlString = [NSString stringWithFormat:@"%@/%@", [ASICloudFilesRequest cdnManagementURL], containerName];
|
||||
ASICloudFilesCDNRequest *request = [[[ASICloudFilesCDNRequest alloc] initWithURL:[NSURL URLWithString:urlString]] autorelease];
|
||||
[request setRequestMethod:method];
|
||||
[request addRequestHeader:@"X-Auth-Token" value:[ASICloudFilesRequest authToken]];
|
||||
request.containerName = containerName;
|
||||
return request;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark HEAD - Container Info
|
||||
|
||||
+ (id)containerInfoRequest:(NSString *)containerName {
|
||||
ASICloudFilesCDNRequest *request = [ASICloudFilesCDNRequest cdnRequestWithMethod:@"HEAD" containerName:containerName];
|
||||
return request;
|
||||
}
|
||||
|
||||
- (BOOL)cdnEnabled {
|
||||
NSNumber *enabled = [[self responseHeaders] objectForKey:@"X-CDN-Enabled"];
|
||||
if (!enabled) {
|
||||
enabled = [[self responseHeaders] objectForKey:@"X-Cdn-Enabled"];
|
||||
}
|
||||
return [enabled boolValue];
|
||||
}
|
||||
|
||||
- (NSString *)cdnURI {
|
||||
NSString *uri = [[self responseHeaders] objectForKey:@"X-CDN-URI"];
|
||||
if (!uri) {
|
||||
uri = [[self responseHeaders] objectForKey:@"X-Cdn-Uri"];
|
||||
}
|
||||
return uri;
|
||||
}
|
||||
|
||||
- (NSString *)cdnSSLURI {
|
||||
NSString *uri = [[self responseHeaders] objectForKey:@"X-CDN-SSL-URI"];
|
||||
if (!uri) {
|
||||
uri = [[self responseHeaders] objectForKey:@"X-Cdn-Ssl-Uri"];
|
||||
}
|
||||
return uri;
|
||||
}
|
||||
|
||||
- (NSUInteger)cdnTTL {
|
||||
NSNumber *ttl = [[self responseHeaders] objectForKey:@"X-TTL"];
|
||||
if (!ttl) {
|
||||
ttl = [[self responseHeaders] objectForKey:@"X-Ttl"];
|
||||
}
|
||||
return [ttl intValue];
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark GET - CDN Container Lists
|
||||
|
||||
+ (id)listRequest {
|
||||
ASICloudFilesCDNRequest *request = [ASICloudFilesCDNRequest cdnRequestWithMethod:@"GET" query:@"?format=xml"];
|
||||
return request;
|
||||
}
|
||||
|
||||
+ (id)listRequestWithLimit:(NSUInteger)limit marker:(NSString *)marker enabledOnly:(BOOL)enabledOnly {
|
||||
NSString *query = @"?format=xml";
|
||||
|
||||
if (limit > 0) {
|
||||
query = [query stringByAppendingString:[NSString stringWithFormat:@"&limit=%i", limit]];
|
||||
}
|
||||
|
||||
if (marker) {
|
||||
query = [query stringByAppendingString:[NSString stringWithFormat:@"&marker=%@", marker]];
|
||||
}
|
||||
|
||||
if (limit > 0) {
|
||||
query = [query stringByAppendingString:[NSString stringWithFormat:@"&limit=%i", limit]];
|
||||
}
|
||||
|
||||
ASICloudFilesCDNRequest *request = [ASICloudFilesCDNRequest cdnRequestWithMethod:@"GET" query:query];
|
||||
return request;
|
||||
}
|
||||
|
||||
- (NSArray *)containers {
|
||||
if (xmlParserDelegate.containerObjects) {
|
||||
return xmlParserDelegate.containerObjects;
|
||||
}
|
||||
|
||||
NSXMLParser *parser = [[[NSXMLParser alloc] initWithData:[self responseData]] autorelease];
|
||||
if (xmlParserDelegate == nil) {
|
||||
xmlParserDelegate = [[ASICloudFilesContainerXMLParserDelegate alloc] init];
|
||||
}
|
||||
|
||||
[parser setDelegate:xmlParserDelegate];
|
||||
[parser setShouldProcessNamespaces:NO];
|
||||
[parser setShouldReportNamespacePrefixes:NO];
|
||||
[parser setShouldResolveExternalEntities:NO];
|
||||
[parser parse];
|
||||
|
||||
return xmlParserDelegate.containerObjects;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark PUT - CDN Enable Container
|
||||
|
||||
// PUT /<api version>/<account>/<container>
|
||||
// PUT operations against a Container are used to CDN-enable that Container.
|
||||
// Include an HTTP header of X-TTL to specify a custom TTL.
|
||||
+ (id)putRequestWithContainer:(NSString *)containerName {
|
||||
ASICloudFilesCDNRequest *request = [ASICloudFilesCDNRequest cdnRequestWithMethod:@"PUT" containerName:containerName];
|
||||
return request;
|
||||
}
|
||||
|
||||
+ (id)putRequestWithContainer:(NSString *)containerName ttl:(NSUInteger)ttl {
|
||||
ASICloudFilesCDNRequest *request = [ASICloudFilesCDNRequest cdnRequestWithMethod:@"PUT" containerName:containerName];
|
||||
[request addRequestHeader:@"X-Ttl" value:[NSString stringWithFormat:@"%i", ttl]];
|
||||
return request;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark POST - Adjust CDN Attributes
|
||||
|
||||
// POST /<api version>/<account>/<container>
|
||||
// POST operations against a CDN-enabled Container are used to adjust CDN attributes.
|
||||
// The POST operation can be used to set a new TTL cache expiration or to enable/disable public sharing over the CDN.
|
||||
// X-TTL: 86400
|
||||
// X-CDN-Enabled: True
|
||||
+ (id)postRequestWithContainer:(NSString *)containerName {
|
||||
ASICloudFilesCDNRequest *request = [ASICloudFilesCDNRequest cdnRequestWithMethod:@"POST" containerName:containerName];
|
||||
return request;
|
||||
}
|
||||
|
||||
+ (id)postRequestWithContainer:(NSString *)containerName cdnEnabled:(BOOL)cdnEnabled ttl:(NSUInteger)ttl {
|
||||
ASICloudFilesCDNRequest *request = [ASICloudFilesCDNRequest cdnRequestWithMethod:@"POST" containerName:containerName];
|
||||
if (ttl > 0) {
|
||||
[request addRequestHeader:@"X-Ttl" value:[NSString stringWithFormat:@"%i", ttl]];
|
||||
}
|
||||
[request addRequestHeader:@"X-CDN-Enabled" value:cdnEnabled ? @"True" : @"False"];
|
||||
return request;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Memory Management
|
||||
|
||||
-(void)dealloc {
|
||||
[accountName release];
|
||||
[containerName release];
|
||||
[xmlParserDelegate release];
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,41 @@
|
||||
//
|
||||
// ASICloudFilesContainer.h
|
||||
//
|
||||
// Created by Michael Mayo on 1/7/10.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
|
||||
@interface ASICloudFilesContainer : NSObject {
|
||||
|
||||
// regular container attributes
|
||||
NSString *name;
|
||||
NSUInteger count;
|
||||
NSUInteger bytes;
|
||||
|
||||
// CDN container attributes
|
||||
BOOL cdnEnabled;
|
||||
NSUInteger ttl;
|
||||
NSString *cdnURL;
|
||||
BOOL logRetention;
|
||||
NSString *referrerACL;
|
||||
NSString *useragentACL;
|
||||
}
|
||||
|
||||
+ (id)container;
|
||||
|
||||
// regular container attributes
|
||||
@property (retain) NSString *name;
|
||||
@property (assign) NSUInteger count;
|
||||
@property (assign) NSUInteger bytes;
|
||||
|
||||
// CDN container attributes
|
||||
@property (assign) BOOL cdnEnabled;
|
||||
@property (assign) NSUInteger ttl;
|
||||
@property (retain) NSString *cdnURL;
|
||||
@property (assign) BOOL logRetention;
|
||||
@property (retain) NSString *referrerACL;
|
||||
@property (retain) NSString *useragentACL;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,31 @@
|
||||
//
|
||||
// ASICloudFilesContainer.m
|
||||
//
|
||||
// Created by Michael Mayo on 1/7/10.
|
||||
//
|
||||
|
||||
#import "ASICloudFilesContainer.h"
|
||||
|
||||
|
||||
@implementation ASICloudFilesContainer
|
||||
|
||||
// regular container attributes
|
||||
@synthesize name, count, bytes;
|
||||
|
||||
// CDN container attributes
|
||||
@synthesize cdnEnabled, ttl, cdnURL, logRetention, referrerACL, useragentACL;
|
||||
|
||||
+ (id)container {
|
||||
ASICloudFilesContainer *container = [[[self alloc] init] autorelease];
|
||||
return container;
|
||||
}
|
||||
|
||||
-(void) dealloc {
|
||||
[name release];
|
||||
[cdnURL release];
|
||||
[referrerACL release];
|
||||
[useragentACL release];
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,43 @@
|
||||
//
|
||||
// ASICloudFilesContainerRequest.h
|
||||
//
|
||||
// Created by Michael Mayo on 1/6/10.
|
||||
//
|
||||
|
||||
#import "ASICloudFilesRequest.h"
|
||||
|
||||
@class ASICloudFilesContainer, ASICloudFilesContainerXMLParserDelegate;
|
||||
|
||||
@interface ASICloudFilesContainerRequest : ASICloudFilesRequest {
|
||||
|
||||
// Internally used while parsing the response
|
||||
NSString *currentContent;
|
||||
NSString *currentElement;
|
||||
ASICloudFilesContainer *currentObject;
|
||||
ASICloudFilesContainerXMLParserDelegate *xmlParserDelegate;
|
||||
}
|
||||
|
||||
@property (retain) NSString *currentElement;
|
||||
@property (retain) NSString *currentContent;
|
||||
@property (retain) ASICloudFilesContainer *currentObject;
|
||||
@property (retain) ASICloudFilesContainerXMLParserDelegate *xmlParserDelegate;
|
||||
|
||||
// HEAD /<api version>/<account>
|
||||
// HEAD operations against an account are performed to retrieve the number of Containers and the total bytes stored in Cloud Files for the account. This information is returned in two custom headers, X-Account-Container-Count and X-Account-Bytes-Used.
|
||||
+ (id)accountInfoRequest;
|
||||
- (NSUInteger)containerCount;
|
||||
- (NSUInteger)bytesUsed;
|
||||
|
||||
// GET /<api version>/<account>/<container>
|
||||
// Create a request to list all containers
|
||||
+ (id)listRequest;
|
||||
+ (id)listRequestWithLimit:(NSUInteger)limit marker:(NSString *)marker;
|
||||
- (NSArray *)containers;
|
||||
|
||||
// PUT /<api version>/<account>/<container>
|
||||
+ (id)createContainerRequest:(NSString *)containerName;
|
||||
|
||||
// DELETE /<api version>/<account>/<container>
|
||||
+ (id)deleteContainerRequest:(NSString *)containerName;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,134 @@
|
||||
//
|
||||
// ASICloudFilesContainerRequest.m
|
||||
//
|
||||
// Created by Michael Mayo on 1/6/10.
|
||||
//
|
||||
|
||||
#import "ASICloudFilesContainerRequest.h"
|
||||
#import "ASICloudFilesContainer.h"
|
||||
#import "ASICloudFilesContainerXMLParserDelegate.h"
|
||||
|
||||
|
||||
@implementation ASICloudFilesContainerRequest
|
||||
|
||||
@synthesize currentElement, currentContent, currentObject;
|
||||
@synthesize xmlParserDelegate;
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Constructors
|
||||
|
||||
+ (id)storageRequestWithMethod:(NSString *)method containerName:(NSString *)containerName queryString:(NSString *)queryString {
|
||||
NSString *urlString;
|
||||
if (containerName == nil) {
|
||||
urlString = [NSString stringWithFormat:@"%@%@", [ASICloudFilesRequest storageURL], queryString];
|
||||
} else {
|
||||
urlString = [NSString stringWithFormat:@"%@/%@%@", [ASICloudFilesRequest storageURL], containerName, queryString];
|
||||
}
|
||||
|
||||
ASICloudFilesContainerRequest *request = [[[ASICloudFilesContainerRequest alloc] initWithURL:[NSURL URLWithString:urlString]] autorelease];
|
||||
[request setRequestMethod:method];
|
||||
[request addRequestHeader:@"X-Auth-Token" value:[ASICloudFilesRequest authToken]];
|
||||
return request;
|
||||
}
|
||||
|
||||
+ (id)storageRequestWithMethod:(NSString *)method queryString:(NSString *)queryString {
|
||||
return [ASICloudFilesContainerRequest storageRequestWithMethod:method containerName:nil queryString:queryString];
|
||||
}
|
||||
|
||||
+ (id)storageRequestWithMethod:(NSString *)method {
|
||||
return [ASICloudFilesContainerRequest storageRequestWithMethod:method queryString:@""];
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark HEAD - Retrieve Container Count and Total Bytes Used
|
||||
|
||||
// HEAD /<api version>/<account>
|
||||
// HEAD operations against an account are performed to retrieve the number of Containers and the total bytes stored in Cloud Files for the account. This information is returned in two custom headers, X-Account-Container-Count and X-Account-Bytes-Used.
|
||||
+ (id)accountInfoRequest {
|
||||
ASICloudFilesContainerRequest *request = [ASICloudFilesContainerRequest storageRequestWithMethod:@"HEAD"];
|
||||
return request;
|
||||
}
|
||||
|
||||
- (NSUInteger)containerCount {
|
||||
return [[[self responseHeaders] objectForKey:@"X-Account-Container-Count"] intValue];
|
||||
}
|
||||
|
||||
- (NSUInteger)bytesUsed {
|
||||
return [[[self responseHeaders] objectForKey:@"X-Account-Bytes-Used"] intValue];
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark GET - Retrieve Container List
|
||||
|
||||
+ (id)listRequestWithLimit:(NSUInteger)limit marker:(NSString *)marker {
|
||||
NSString *queryString = @"?format=xml";
|
||||
|
||||
if (limit > 0) {
|
||||
queryString = [queryString stringByAppendingString:[NSString stringWithFormat:@"&limit=%i", limit]];
|
||||
}
|
||||
|
||||
if (marker != nil) {
|
||||
queryString = [queryString stringByAppendingString:[NSString stringWithFormat:@"&marker=%@", marker]];
|
||||
}
|
||||
|
||||
ASICloudFilesContainerRequest *request = [ASICloudFilesContainerRequest storageRequestWithMethod:@"GET" queryString:queryString];
|
||||
return request;
|
||||
}
|
||||
|
||||
// GET /<api version>/<account>/<container>
|
||||
// Create a request to list all containers
|
||||
+ (id)listRequest {
|
||||
ASICloudFilesContainerRequest *request = [ASICloudFilesContainerRequest storageRequestWithMethod:@"GET"
|
||||
queryString:@"?format=xml"];
|
||||
return request;
|
||||
}
|
||||
|
||||
- (NSArray *)containers {
|
||||
if (xmlParserDelegate.containerObjects) {
|
||||
return xmlParserDelegate.containerObjects;
|
||||
}
|
||||
|
||||
NSXMLParser *parser = [[[NSXMLParser alloc] initWithData:[self responseData]] autorelease];
|
||||
if (xmlParserDelegate == nil) {
|
||||
xmlParserDelegate = [[ASICloudFilesContainerXMLParserDelegate alloc] init];
|
||||
}
|
||||
|
||||
[parser setDelegate:xmlParserDelegate];
|
||||
[parser setShouldProcessNamespaces:NO];
|
||||
[parser setShouldReportNamespacePrefixes:NO];
|
||||
[parser setShouldResolveExternalEntities:NO];
|
||||
[parser parse];
|
||||
|
||||
return xmlParserDelegate.containerObjects;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark PUT - Create Container
|
||||
|
||||
// PUT /<api version>/<account>/<container>
|
||||
+ (id)createContainerRequest:(NSString *)containerName {
|
||||
ASICloudFilesContainerRequest *request = [ASICloudFilesContainerRequest storageRequestWithMethod:@"PUT" containerName:containerName queryString:@""];
|
||||
return request;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark DELETE - Delete Container
|
||||
|
||||
// DELETE /<api version>/<account>/<container>
|
||||
+ (id)deleteContainerRequest:(NSString *)containerName {
|
||||
ASICloudFilesContainerRequest *request = [ASICloudFilesContainerRequest storageRequestWithMethod:@"DELETE" containerName:containerName queryString:@""];
|
||||
return request;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Memory Management
|
||||
|
||||
- (void)dealloc {
|
||||
[currentElement release];
|
||||
[currentContent release];
|
||||
[currentObject release];
|
||||
[xmlParserDelegate release];
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,31 @@
|
||||
//
|
||||
// ASICloudFilesContainerXMLParserDelegate.h
|
||||
//
|
||||
// Created by Michael Mayo on 1/10/10.
|
||||
//
|
||||
|
||||
#import "ASICloudFilesRequest.h"
|
||||
|
||||
#if !TARGET_OS_IPHONE || (TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED < __IPHONE_4_0)
|
||||
#import "ASINSXMLParserCompat.h"
|
||||
#endif
|
||||
|
||||
@class ASICloudFilesContainer;
|
||||
|
||||
@interface ASICloudFilesContainerXMLParserDelegate : NSObject <NSXMLParserDelegate> {
|
||||
|
||||
NSMutableArray *containerObjects;
|
||||
|
||||
// Internally used while parsing the response
|
||||
NSString *currentContent;
|
||||
NSString *currentElement;
|
||||
ASICloudFilesContainer *currentObject;
|
||||
}
|
||||
|
||||
@property (retain) NSMutableArray *containerObjects;
|
||||
|
||||
@property (retain) NSString *currentElement;
|
||||
@property (retain) NSString *currentContent;
|
||||
@property (retain) ASICloudFilesContainer *currentObject;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,72 @@
|
||||
//
|
||||
// ASICloudFilesContainerXMLParserDelegate.m
|
||||
//
|
||||
// Created by Michael Mayo on 1/10/10.
|
||||
//
|
||||
|
||||
#import "ASICloudFilesContainerXMLParserDelegate.h"
|
||||
#import "ASICloudFilesContainer.h"
|
||||
|
||||
|
||||
@implementation ASICloudFilesContainerXMLParserDelegate
|
||||
|
||||
@synthesize containerObjects, currentElement, currentContent, currentObject;
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark XML Parser Delegate
|
||||
|
||||
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict {
|
||||
[self setCurrentElement:elementName];
|
||||
|
||||
if ([elementName isEqualToString:@"container"]) {
|
||||
[self setCurrentObject:[ASICloudFilesContainer container]];
|
||||
}
|
||||
[self setCurrentContent:@""];
|
||||
}
|
||||
|
||||
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {
|
||||
|
||||
if ([elementName isEqualToString:@"name"]) {
|
||||
[self currentObject].name = [self currentContent];
|
||||
} else if ([elementName isEqualToString:@"count"]) {
|
||||
[self currentObject].count = [[self currentContent] intValue];
|
||||
} else if ([elementName isEqualToString:@"bytes"]) {
|
||||
[self currentObject].bytes = [[self currentContent] intValue];
|
||||
} else if ([elementName isEqualToString:@"cdn_enabled"]) {
|
||||
[self currentObject].cdnEnabled = [[self currentObject] isEqual:@"True"];
|
||||
} else if ([elementName isEqualToString:@"ttl"]) {
|
||||
[self currentObject].ttl = [[self currentContent] intValue];
|
||||
} else if ([elementName isEqualToString:@"cdn_url"]) {
|
||||
[self currentObject].cdnURL = [self currentContent];
|
||||
} else if ([elementName isEqualToString:@"log_retention"]) {
|
||||
[self currentObject].logRetention = [[self currentObject] isEqual:@"True"];
|
||||
} else if ([elementName isEqualToString:@"referrer_acl"]) {
|
||||
[self currentObject].referrerACL = [self currentContent];
|
||||
} else if ([elementName isEqualToString:@"useragent_acl"]) {
|
||||
[self currentObject].useragentACL = [self currentContent];
|
||||
} else if ([elementName isEqualToString:@"container"]) {
|
||||
// we're done with this container. time to move on to the next
|
||||
if (containerObjects == nil) {
|
||||
containerObjects = [[NSMutableArray alloc] init];
|
||||
}
|
||||
[containerObjects addObject:currentObject];
|
||||
[self setCurrentObject:nil];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
|
||||
[self setCurrentContent:[[self currentContent] stringByAppendingString:string]];
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Memory Management
|
||||
|
||||
- (void)dealloc {
|
||||
[containerObjects release];
|
||||
[currentElement release];
|
||||
[currentContent release];
|
||||
[currentObject release];
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,30 @@
|
||||
//
|
||||
// ASICloudFilesObject.h
|
||||
//
|
||||
// Created by Michael Mayo on 1/7/10.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
|
||||
@interface ASICloudFilesObject : NSObject {
|
||||
NSString *name;
|
||||
NSString *hash;
|
||||
NSUInteger bytes;
|
||||
NSString *contentType;
|
||||
NSDate *lastModified;
|
||||
NSData *data;
|
||||
NSMutableDictionary *metadata;
|
||||
}
|
||||
|
||||
@property (retain) NSString *name;
|
||||
@property (retain) NSString *hash;
|
||||
@property (assign) NSUInteger bytes;
|
||||
@property (retain) NSString *contentType;
|
||||
@property (retain) NSDate *lastModified;
|
||||
@property (retain) NSData *data;
|
||||
@property (retain) NSMutableDictionary *metadata;
|
||||
|
||||
+ (id)object;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,29 @@
|
||||
//
|
||||
// ASICloudFilesObject.m
|
||||
//
|
||||
// Created by Michael Mayo on 1/7/10.
|
||||
//
|
||||
|
||||
#import "ASICloudFilesObject.h"
|
||||
|
||||
|
||||
@implementation ASICloudFilesObject
|
||||
|
||||
@synthesize name, hash, bytes, contentType, lastModified, data, metadata;
|
||||
|
||||
+ (id)object {
|
||||
ASICloudFilesObject *object = [[[self alloc] init] autorelease];
|
||||
return object;
|
||||
}
|
||||
|
||||
-(void)dealloc {
|
||||
[name release];
|
||||
[hash release];
|
||||
[contentType release];
|
||||
[lastModified release];
|
||||
[data release];
|
||||
[metadata release];
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,71 @@
|
||||
//
|
||||
// ASICloudFilesObjectRequest.h
|
||||
//
|
||||
// Created by Michael Mayo on 1/6/10.
|
||||
//
|
||||
|
||||
#import "ASICloudFilesRequest.h"
|
||||
|
||||
#if !TARGET_OS_IPHONE || (TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED < __IPHONE_4_0)
|
||||
#import "ASINSXMLParserCompat.h"
|
||||
#endif
|
||||
|
||||
@class ASICloudFilesObject;
|
||||
|
||||
@interface ASICloudFilesObjectRequest : ASICloudFilesRequest <NSXMLParserDelegate> {
|
||||
|
||||
|
||||
NSString *accountName;
|
||||
NSString *containerName;
|
||||
|
||||
// Internally used while parsing the response
|
||||
NSString *currentContent;
|
||||
NSString *currentElement;
|
||||
ASICloudFilesObject *currentObject;
|
||||
NSMutableArray *objects;
|
||||
|
||||
}
|
||||
|
||||
@property (retain) NSString *accountName;
|
||||
@property (retain) NSString *containerName;
|
||||
@property (retain) NSString *currentElement;
|
||||
@property (retain) NSString *currentContent;
|
||||
@property (retain) ASICloudFilesObject *currentObject;
|
||||
|
||||
|
||||
// HEAD /<api version>/<account>/<container>
|
||||
// HEAD operations against an account are performed to retrieve the number of Containers and the total bytes stored in Cloud Files for the account. This information is returned in two custom headers, X-Account-Container-Count and X-Account-Bytes-Used.
|
||||
+ (id)containerInfoRequest:(NSString *)containerName;
|
||||
- (NSUInteger)containerObjectCount;
|
||||
- (NSUInteger)containerBytesUsed;
|
||||
|
||||
// HEAD /<api version>/<account>/<container>/<object>
|
||||
// to get metadata
|
||||
+ (id)objectInfoRequest:(NSString *)containerName objectPath:(NSString *)objectPath;
|
||||
- (NSArray *)objects;
|
||||
|
||||
+ (id)listRequestWithContainer:(NSString *)containerName;
|
||||
+ (id)listRequestWithContainer:(NSString *)containerName limit:(NSUInteger)limit marker:(NSString *)marker prefix:(NSString *)prefix path:(NSString *)path;
|
||||
|
||||
// Conditional GET headers: If-Match • If-None-Match • If-Modified-Since • If-Unmodified-Since
|
||||
// HTTP Range header: “Range: bytes=0-5” • “Range: bytes=-5” • “Range: bytes=32-“
|
||||
+ (id)getObjectRequestWithContainer:(NSString *)containerName objectPath:(NSString *)objectPath;
|
||||
- (ASICloudFilesObject *)object;
|
||||
|
||||
// PUT /<api version>/<account>/<container>/<object>
|
||||
// PUT operations are used to write, or overwrite, an Object's metadata and content.
|
||||
// The Object can be created with custom metadata via HTTP headers identified with the “X-Object-Meta-” prefix.
|
||||
+ (id)putObjectRequestWithContainer:(NSString *)containerName object:(ASICloudFilesObject *)object;
|
||||
+ (id)putObjectRequestWithContainer:(NSString *)containerName objectPath:(NSString *)objectPath contentType:(NSString *)contentType objectData:(NSData *)objectData metadata:(NSDictionary *)metadata etag:(NSString *)etag;
|
||||
+ (id)putObjectRequestWithContainer:(NSString *)containerName objectPath:(NSString *)objectPath contentType:(NSString *)contentType file:(NSString *)filePath metadata:(NSDictionary *)metadata etag:(NSString *)etag;
|
||||
|
||||
// POST /<api version>/<account>/<container>/<object>
|
||||
// POST operations against an Object name are used to set and overwrite arbitrary key/value metadata. You cannot use the POST operation to change any of the Object's other headers such as Content-Type, ETag, etc. It is not used to upload storage Objects (see PUT).
|
||||
// A POST request will delete all existing metadata added with a previous PUT/POST.
|
||||
+ (id)postObjectRequestWithContainer:(NSString *)containerName object:(ASICloudFilesObject *)object;
|
||||
+ (id)postObjectRequestWithContainer:(NSString *)containerName objectPath:(NSString *)objectPath metadata:(NSDictionary *)metadata;
|
||||
|
||||
// DELETE /<api version>/<account>/<container>/<object>
|
||||
+ (id)deleteObjectRequestWithContainer:(NSString *)containerName objectPath:(NSString *)objectPath;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,261 @@
|
||||
//
|
||||
// ASICloudFilesObjectRequest.m
|
||||
//
|
||||
// Created by Michael Mayo on 1/6/10.
|
||||
//
|
||||
|
||||
#import "ASICloudFilesObjectRequest.h"
|
||||
#import "ASICloudFilesObject.h"
|
||||
|
||||
|
||||
@implementation ASICloudFilesObjectRequest
|
||||
|
||||
@synthesize currentElement, currentContent, currentObject;
|
||||
@synthesize accountName, containerName;
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Constructors
|
||||
|
||||
+ (id)storageRequestWithMethod:(NSString *)method containerName:(NSString *)containerName {
|
||||
NSString *urlString = [NSString stringWithFormat:@"%@/%@", [ASICloudFilesRequest storageURL], containerName];
|
||||
ASICloudFilesObjectRequest *request = [[[ASICloudFilesObjectRequest alloc] initWithURL:[NSURL URLWithString:urlString]] autorelease];
|
||||
[request setRequestMethod:method];
|
||||
[request addRequestHeader:@"X-Auth-Token" value:[ASICloudFilesRequest authToken]];
|
||||
request.containerName = containerName;
|
||||
return request;
|
||||
}
|
||||
|
||||
+ (id)storageRequestWithMethod:(NSString *)method containerName:(NSString *)containerName queryString:(NSString *)queryString {
|
||||
NSString *urlString = [NSString stringWithFormat:@"%@/%@%@", [ASICloudFilesRequest storageURL], containerName, queryString];
|
||||
ASICloudFilesObjectRequest *request = [[[ASICloudFilesObjectRequest alloc] initWithURL:[NSURL URLWithString:urlString]] autorelease];
|
||||
[request setRequestMethod:method];
|
||||
[request addRequestHeader:@"X-Auth-Token" value:[ASICloudFilesRequest authToken]];
|
||||
request.containerName = containerName;
|
||||
return request;
|
||||
}
|
||||
|
||||
+ (id)storageRequestWithMethod:(NSString *)method containerName:(NSString *)containerName objectPath:(NSString *)objectPath {
|
||||
NSString *urlString = [NSString stringWithFormat:@"%@/%@/%@", [ASICloudFilesRequest storageURL], containerName, objectPath];
|
||||
ASICloudFilesObjectRequest *request = [[[ASICloudFilesObjectRequest alloc] initWithURL:[NSURL URLWithString:urlString]] autorelease];
|
||||
[request setRequestMethod:method];
|
||||
[request addRequestHeader:@"X-Auth-Token" value:[ASICloudFilesRequest authToken]];
|
||||
request.containerName = containerName;
|
||||
return request;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark HEAD - Container Info
|
||||
|
||||
+ (id)containerInfoRequest:(NSString *)containerName {
|
||||
ASICloudFilesObjectRequest *request = [ASICloudFilesObjectRequest storageRequestWithMethod:@"HEAD" containerName:containerName];
|
||||
return request;
|
||||
}
|
||||
|
||||
- (NSUInteger)containerObjectCount {
|
||||
return [[[self responseHeaders] objectForKey:@"X-Container-Object-Count"] intValue];
|
||||
}
|
||||
|
||||
- (NSUInteger)containerBytesUsed {
|
||||
return [[[self responseHeaders] objectForKey:@"X-Container-Bytes-Used"] intValue];
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark HEAD - Object Info
|
||||
|
||||
+ (id)objectInfoRequest:(NSString *)containerName objectPath:(NSString *)objectPath {
|
||||
ASICloudFilesObjectRequest *request = [ASICloudFilesObjectRequest storageRequestWithMethod:@"HEAD" containerName:containerName objectPath:objectPath];
|
||||
return request;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark GET - List Objects
|
||||
|
||||
+ (NSString *)queryStringWithContainer:(NSString *)container limit:(NSUInteger)limit marker:(NSString *)marker prefix:(NSString *)prefix path:(NSString *)path {
|
||||
NSString *queryString = @"?format=xml";
|
||||
|
||||
if (limit && limit > 0) {
|
||||
queryString = [queryString stringByAppendingString:[NSString stringWithFormat:@"&limit=%i", limit]];
|
||||
}
|
||||
if (marker) {
|
||||
queryString = [queryString stringByAppendingString:[NSString stringWithFormat:@"&marker=%@", [marker stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]];
|
||||
}
|
||||
if (path) {
|
||||
queryString = [queryString stringByAppendingString:[NSString stringWithFormat:@"&path=%@", [path stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]];
|
||||
}
|
||||
|
||||
return queryString;
|
||||
}
|
||||
|
||||
+ (id)listRequestWithContainer:(NSString *)containerName limit:(NSUInteger)limit marker:(NSString *)marker prefix:(NSString *)prefix path:(NSString *)path {
|
||||
NSString *queryString = [ASICloudFilesObjectRequest queryStringWithContainer:containerName limit:limit marker:marker prefix:prefix path:path];
|
||||
ASICloudFilesObjectRequest *request = [ASICloudFilesObjectRequest storageRequestWithMethod:@"GET" containerName:containerName queryString:queryString];
|
||||
return request;
|
||||
}
|
||||
|
||||
+ (id)listRequestWithContainer:(NSString *)containerName {
|
||||
return [ASICloudFilesObjectRequest listRequestWithContainer:containerName limit:0 marker:nil prefix:nil path:nil];
|
||||
}
|
||||
|
||||
- (NSArray *)objects {
|
||||
if (objects) {
|
||||
return objects;
|
||||
}
|
||||
objects = [[[NSMutableArray alloc] init] autorelease];
|
||||
|
||||
NSXMLParser *parser = [[[NSXMLParser alloc] initWithData:[self responseData]] autorelease];
|
||||
[parser setDelegate:self];
|
||||
[parser setShouldProcessNamespaces:NO];
|
||||
[parser setShouldReportNamespacePrefixes:NO];
|
||||
[parser setShouldResolveExternalEntities:NO];
|
||||
[parser parse];
|
||||
return objects;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark GET - Retrieve Object
|
||||
|
||||
+ (id)getObjectRequestWithContainer:(NSString *)containerName objectPath:(NSString *)objectPath {
|
||||
return [ASICloudFilesObjectRequest storageRequestWithMethod:@"GET" containerName:containerName objectPath:objectPath];
|
||||
}
|
||||
|
||||
- (ASICloudFilesObject *)object {
|
||||
ASICloudFilesObject *object = [ASICloudFilesObject object];
|
||||
|
||||
NSString *path = [self url].path;
|
||||
NSRange range = [path rangeOfString:self.containerName];
|
||||
path = [path substringFromIndex:range.location + range.length + 1];
|
||||
|
||||
object.name = path;
|
||||
object.hash = [[self responseHeaders] objectForKey:@"ETag"];
|
||||
object.bytes = [[[self responseHeaders] objectForKey:@"Content-Length"] intValue];
|
||||
object.contentType = [[self responseHeaders] objectForKey:@"Content-Type"];
|
||||
object.lastModified = [[self responseHeaders] objectForKey:@"Last-Modified"];
|
||||
object.metadata = [NSMutableDictionary dictionary];
|
||||
|
||||
for (NSString *key in [[self responseHeaders] keyEnumerator]) {
|
||||
NSRange metaRange = [key rangeOfString:@"X-Object-Meta-"];
|
||||
if (metaRange.location == 0) {
|
||||
[object.metadata setObject:[[self responseHeaders] objectForKey:key] forKey:[key substringFromIndex:metaRange.length]];
|
||||
}
|
||||
}
|
||||
|
||||
object.data = [self responseData];
|
||||
|
||||
return object;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark PUT - Upload Object
|
||||
|
||||
+ (id)putObjectRequestWithContainer:(NSString *)containerName object:(ASICloudFilesObject *)object {
|
||||
return [self putObjectRequestWithContainer:containerName objectPath:object.name contentType:object.contentType objectData:object.data metadata:object.metadata etag:nil];
|
||||
}
|
||||
|
||||
+ (id)putObjectRequestWithContainer:(NSString *)containerName objectPath:(NSString *)objectPath contentType:(NSString *)contentType objectData:(NSData *)objectData metadata:(NSDictionary *)metadata etag:(NSString *)etag {
|
||||
|
||||
ASICloudFilesObjectRequest *request = [ASICloudFilesObjectRequest storageRequestWithMethod:@"PUT" containerName:containerName objectPath:objectPath];
|
||||
[request addRequestHeader:@"Content-Type" value:contentType];
|
||||
|
||||
// add metadata to headers
|
||||
if (metadata) {
|
||||
for (NSString *key in [metadata keyEnumerator]) {
|
||||
[request addRequestHeader:[NSString stringWithFormat:@"X-Object-Meta-%@", key] value:[metadata objectForKey:key]];
|
||||
}
|
||||
}
|
||||
|
||||
[request appendPostData:objectData];
|
||||
return request;
|
||||
}
|
||||
|
||||
+ (id)putObjectRequestWithContainer:(NSString *)containerName objectPath:(NSString *)objectPath contentType:(NSString *)contentType file:(NSString *)filePath metadata:(NSDictionary *)metadata etag:(NSString *)etag
|
||||
{
|
||||
ASICloudFilesObjectRequest *request = [ASICloudFilesObjectRequest storageRequestWithMethod:@"PUT" containerName:containerName objectPath:objectPath];
|
||||
[request addRequestHeader:@"Content-Type" value:contentType];
|
||||
|
||||
// add metadata to headers
|
||||
if (metadata) {
|
||||
for (NSString *key in [metadata keyEnumerator]) {
|
||||
[request addRequestHeader:[NSString stringWithFormat:@"X-Object-Meta-%@", key] value:[metadata objectForKey:key]];
|
||||
}
|
||||
}
|
||||
|
||||
[request setShouldStreamPostDataFromDisk:YES];
|
||||
[request setPostBodyFilePath:filePath];
|
||||
return request;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark POST - Set Object Metadata
|
||||
|
||||
+ (id)postObjectRequestWithContainer:(NSString *)containerName object:(ASICloudFilesObject *)object {
|
||||
return [self postObjectRequestWithContainer:containerName objectPath:object.name metadata:object.metadata];
|
||||
}
|
||||
|
||||
+ (id)postObjectRequestWithContainer:(NSString *)containerName objectPath:(NSString *)objectPath metadata:(NSDictionary *)metadata {
|
||||
ASICloudFilesObjectRequest *request = [ASICloudFilesObjectRequest storageRequestWithMethod:@"POST" containerName:containerName objectPath:objectPath];
|
||||
|
||||
// add metadata to headers
|
||||
if (metadata) {
|
||||
for (NSString *key in [metadata keyEnumerator]) {
|
||||
[request addRequestHeader:[NSString stringWithFormat:@"X-Object-Meta-%@", key] value:[metadata objectForKey:key]];
|
||||
}
|
||||
}
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark DELETE - Delete Object
|
||||
|
||||
+ (id)deleteObjectRequestWithContainer:(NSString *)containerName objectPath:(NSString *)objectPath {
|
||||
ASICloudFilesObjectRequest *request = [ASICloudFilesObjectRequest storageRequestWithMethod:@"DELETE" containerName:containerName objectPath:objectPath];
|
||||
return request;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark XML Parser Delegate
|
||||
|
||||
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict {
|
||||
[self setCurrentElement:elementName];
|
||||
|
||||
if ([elementName isEqualToString:@"object"]) {
|
||||
[self setCurrentObject:[ASICloudFilesObject object]];
|
||||
}
|
||||
[self setCurrentContent:@""];
|
||||
}
|
||||
|
||||
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {
|
||||
if ([elementName isEqualToString:@"name"]) {
|
||||
[self currentObject].name = [self currentContent];
|
||||
} else if ([elementName isEqualToString:@"hash"]) {
|
||||
[self currentObject].hash = [self currentContent];
|
||||
} else if ([elementName isEqualToString:@"bytes"]) {
|
||||
[self currentObject].bytes = [[self currentContent] intValue];
|
||||
} else if ([elementName isEqualToString:@"content_type"]) {
|
||||
[self currentObject].contentType = [self currentContent];
|
||||
} else if ([elementName isEqualToString:@"last_modified"]) {
|
||||
[self currentObject].lastModified = [self dateFromString:[self currentContent]];
|
||||
} else if ([elementName isEqualToString:@"object"]) {
|
||||
// we're done with this object. time to move on to the next
|
||||
[objects addObject:currentObject];
|
||||
[self setCurrentObject:nil];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
|
||||
[self setCurrentContent:[[self currentContent] stringByAppendingString:string]];
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Memory Management
|
||||
|
||||
- (void)dealloc {
|
||||
[currentElement release];
|
||||
[currentContent release];
|
||||
[currentObject release];
|
||||
[accountName release];
|
||||
[containerName release];
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,40 @@
|
||||
//
|
||||
// ASICloudFilesRequest.h
|
||||
// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest
|
||||
//
|
||||
// Created by Michael Mayo on 22/12/09.
|
||||
// mike.mayo@rackspace.com or mike@overhrd.com
|
||||
// twitter.com/greenisus
|
||||
// Copyright 2009 All-Seeing Interactive. All rights reserved.
|
||||
//
|
||||
// A class for accessing data stored on the Rackspace Cloud Files Service
|
||||
// http://www.rackspacecloud.com/cloud_hosting_products/files
|
||||
//
|
||||
// Cloud Files Developer Guide:
|
||||
// http://docs.rackspacecloud.com/servers/api/cs-devguide-latest.pdf
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "ASIHTTPRequest.h"
|
||||
|
||||
|
||||
@interface ASICloudFilesRequest : ASIHTTPRequest {
|
||||
}
|
||||
|
||||
+ (NSString *)storageURL;
|
||||
+ (NSString *)cdnManagementURL;
|
||||
+ (NSString *)authToken;
|
||||
|
||||
#pragma mark Rackspace Cloud Authentication
|
||||
|
||||
+ (id)authenticationRequest;
|
||||
+ (NSError *)authenticate;
|
||||
+ (NSString *)username;
|
||||
+ (void)setUsername:(NSString *)username;
|
||||
+ (NSString *)apiKey;
|
||||
+ (void)setApiKey:(NSString *)apiKey;
|
||||
|
||||
// helper to parse dates in the format returned by Cloud Files
|
||||
-(NSDate *)dateFromString:(NSString *)dateString;
|
||||
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,129 @@
|
||||
//
|
||||
// ASICloudFilesRequest.m
|
||||
// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest
|
||||
//
|
||||
// Created by Michael Mayo on 22/12/09.
|
||||
// Copyright 2009 All-Seeing Interactive. All rights reserved.
|
||||
//
|
||||
// A class for accessing data stored on Rackspace's Cloud Files Service
|
||||
// http://www.rackspacecloud.com/cloud_hosting_products/files
|
||||
//
|
||||
// Cloud Files Developer Guide:
|
||||
// http://docs.rackspacecloud.com/servers/api/cs-devguide-latest.pdf
|
||||
|
||||
#import "ASICloudFilesRequest.h"
|
||||
|
||||
static NSString *username = nil;
|
||||
static NSString *apiKey = nil;
|
||||
static NSString *authToken = nil;
|
||||
static NSString *storageURL = nil;
|
||||
static NSString *cdnManagementURL = nil;
|
||||
static NSString *rackspaceCloudAuthURL = @"https://auth.api.rackspacecloud.com/v1.0";
|
||||
|
||||
static NSRecursiveLock *accessDetailsLock = nil;
|
||||
|
||||
@implementation ASICloudFilesRequest
|
||||
|
||||
+ (void)initialize
|
||||
{
|
||||
if (self == [ASICloudFilesRequest class]) {
|
||||
accessDetailsLock = [[NSRecursiveLock alloc] init];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Attributes and Service URLs
|
||||
|
||||
+ (NSString *)authToken {
|
||||
return authToken;
|
||||
}
|
||||
|
||||
+ (NSString *)storageURL {
|
||||
return storageURL;
|
||||
}
|
||||
|
||||
+ (NSString *)cdnManagementURL {
|
||||
return cdnManagementURL;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Authentication
|
||||
|
||||
+ (id)authenticationRequest
|
||||
{
|
||||
[accessDetailsLock lock];
|
||||
ASIHTTPRequest *request = [[[ASIHTTPRequest alloc] initWithURL:[NSURL URLWithString:rackspaceCloudAuthURL]] autorelease];
|
||||
[request addRequestHeader:@"X-Auth-User" value:username];
|
||||
[request addRequestHeader:@"X-Auth-Key" value:apiKey];
|
||||
[accessDetailsLock unlock];
|
||||
return request;
|
||||
}
|
||||
|
||||
+ (NSError *)authenticate
|
||||
{
|
||||
[accessDetailsLock lock];
|
||||
ASIHTTPRequest *request = [ASICloudFilesRequest authenticationRequest];
|
||||
[request startSynchronous];
|
||||
|
||||
if (![request error]) {
|
||||
NSDictionary *responseHeaders = [request responseHeaders];
|
||||
authToken = [responseHeaders objectForKey:@"X-Auth-Token"];
|
||||
storageURL = [responseHeaders objectForKey:@"X-Storage-Url"];
|
||||
cdnManagementURL = [responseHeaders objectForKey:@"X-CDN-Management-Url"];
|
||||
|
||||
// there is a bug in the Cloud Files API for some older accounts that causes
|
||||
// the CDN URL to come back in a slightly different header
|
||||
if (!cdnManagementURL) {
|
||||
cdnManagementURL = [responseHeaders objectForKey:@"X-Cdn-Management-Url"];
|
||||
}
|
||||
}
|
||||
[accessDetailsLock unlock];
|
||||
return [request error];
|
||||
}
|
||||
|
||||
+ (NSString *)username
|
||||
{
|
||||
return username;
|
||||
}
|
||||
|
||||
+ (void)setUsername:(NSString *)newUsername
|
||||
{
|
||||
[accessDetailsLock lock];
|
||||
[username release];
|
||||
username = [newUsername retain];
|
||||
[accessDetailsLock unlock];
|
||||
}
|
||||
|
||||
+ (NSString *)apiKey {
|
||||
return apiKey;
|
||||
}
|
||||
|
||||
+ (void)setApiKey:(NSString *)newApiKey
|
||||
{
|
||||
[accessDetailsLock lock];
|
||||
[apiKey release];
|
||||
apiKey = [newApiKey retain];
|
||||
[accessDetailsLock unlock];
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Date Parser
|
||||
|
||||
-(NSDate *)dateFromString:(NSString *)dateString
|
||||
{
|
||||
// We store our date formatter in the calling thread's dictionary
|
||||
// NSDateFormatter is not thread-safe, this approach ensures each formatter is only used on a single thread
|
||||
// This formatter can be reused many times in parsing a single response, so it would be expensive to keep creating new date formatters
|
||||
NSMutableDictionary *threadDict = [[NSThread currentThread] threadDictionary];
|
||||
NSDateFormatter *dateFormatter = [threadDict objectForKey:@"ASICloudFilesResponseDateFormatter"];
|
||||
if (dateFormatter == nil) {
|
||||
dateFormatter = [[[NSDateFormatter alloc] init] autorelease];
|
||||
[dateFormatter setLocale:[[[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"] autorelease]];
|
||||
// example: 2009-11-04T19:46:20.192723
|
||||
[dateFormatter setDateFormat:@"yyyy-MM-dd'T'H:mm:ss.SSSSSS"];
|
||||
[threadDict setObject:dateFormatter forKey:@"ASICloudFilesResponseDateFormatter"];
|
||||
}
|
||||
return [dateFormatter dateFromString:dateString];
|
||||
}
|
||||
|
||||
@end
|
||||
23
projects/mtg/iOS/asi-http-request/S3/ASINSXMLParserCompat.h
Normal file
23
projects/mtg/iOS/asi-http-request/S3/ASINSXMLParserCompat.h
Normal file
@@ -0,0 +1,23 @@
|
||||
//
|
||||
// ASINSXMLParserCompat.h
|
||||
// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest
|
||||
// This file exists to prevent warnings about the NSXMLParserDelegate protocol when building S3 or Cloud Files stuff
|
||||
//
|
||||
// Created by Ben Copsey on 17/06/2010.
|
||||
// Copyright 2010 All-Seeing Interactive. All rights reserved.
|
||||
//
|
||||
|
||||
|
||||
#if (!TARGET_OS_IPHONE && __MAC_OS_X_VERSION_MAX_ALLOWED < __MAC_10_6) || (TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED <= __IPHONE_4_0)
|
||||
@protocol NSXMLParserDelegate
|
||||
|
||||
@optional
|
||||
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict;
|
||||
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName;
|
||||
|
||||
@end
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
|
||||
34
projects/mtg/iOS/asi-http-request/S3/ASIS3Bucket.h
Normal file
34
projects/mtg/iOS/asi-http-request/S3/ASIS3Bucket.h
Normal file
@@ -0,0 +1,34 @@
|
||||
//
|
||||
// ASIS3Bucket.h
|
||||
// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest
|
||||
//
|
||||
// Created by Ben Copsey on 16/03/2010.
|
||||
// Copyright 2010 All-Seeing Interactive. All rights reserved.
|
||||
//
|
||||
// Instances of this class represent buckets stored on S3
|
||||
// ASIS3ServiceRequests return an array of ASIS3Buckets when you perform a service GET query
|
||||
// You'll probably never need to create instances of ASIS3Bucket yourself
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
|
||||
@interface ASIS3Bucket : NSObject {
|
||||
|
||||
// The name of this bucket (will be unique throughout S3)
|
||||
NSString *name;
|
||||
|
||||
// The date this bucket was created
|
||||
NSDate *creationDate;
|
||||
|
||||
// Information about the owner of this bucket
|
||||
NSString *ownerID;
|
||||
NSString *ownerName;
|
||||
}
|
||||
|
||||
+ (id)bucketWithOwnerID:(NSString *)ownerID ownerName:(NSString *)ownerName;
|
||||
|
||||
@property (retain) NSString *name;
|
||||
@property (retain) NSDate *creationDate;
|
||||
@property (retain) NSString *ownerID;
|
||||
@property (retain) NSString *ownerName;
|
||||
@end
|
||||
40
projects/mtg/iOS/asi-http-request/S3/ASIS3Bucket.m
Normal file
40
projects/mtg/iOS/asi-http-request/S3/ASIS3Bucket.m
Normal file
@@ -0,0 +1,40 @@
|
||||
//
|
||||
// ASIS3Bucket.m
|
||||
// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest
|
||||
//
|
||||
// Created by Ben Copsey on 16/03/2010.
|
||||
// Copyright 2010 All-Seeing Interactive. All rights reserved.
|
||||
//
|
||||
|
||||
#import "ASIS3Bucket.h"
|
||||
|
||||
|
||||
@implementation ASIS3Bucket
|
||||
|
||||
+ (id)bucketWithOwnerID:(NSString *)anOwnerID ownerName:(NSString *)anOwnerName
|
||||
{
|
||||
ASIS3Bucket *bucket = [[[self alloc] init] autorelease];
|
||||
[bucket setOwnerID:anOwnerID];
|
||||
[bucket setOwnerName:anOwnerName];
|
||||
return bucket;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
[name release];
|
||||
[creationDate release];
|
||||
[ownerID release];
|
||||
[ownerName release];
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (NSString *)description
|
||||
{
|
||||
return [NSString stringWithFormat:@"Name: %@ creationDate: %@ ownerID: %@ ownerName: %@",[self name],[self creationDate],[self ownerID],[self ownerName]];
|
||||
}
|
||||
|
||||
@synthesize name;
|
||||
@synthesize creationDate;
|
||||
@synthesize ownerID;
|
||||
@synthesize ownerName;
|
||||
@end
|
||||
54
projects/mtg/iOS/asi-http-request/S3/ASIS3BucketObject.h
Normal file
54
projects/mtg/iOS/asi-http-request/S3/ASIS3BucketObject.h
Normal file
@@ -0,0 +1,54 @@
|
||||
//
|
||||
// ASIS3BucketObject.h
|
||||
// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest
|
||||
//
|
||||
// Created by Ben Copsey on 13/07/2009.
|
||||
// Copyright 2009 All-Seeing Interactive. All rights reserved.
|
||||
//
|
||||
// Instances of this class represent objects stored in a bucket on S3
|
||||
// ASIS3BucketRequests return an array of ASIS3BucketObjects when you perform a list query
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
@class ASIS3ObjectRequest;
|
||||
|
||||
@interface ASIS3BucketObject : NSObject <NSCopying> {
|
||||
|
||||
// The bucket this object belongs to
|
||||
NSString *bucket;
|
||||
|
||||
// The key (path) of this object in the bucket
|
||||
NSString *key;
|
||||
|
||||
// When this object was last modified
|
||||
NSDate *lastModified;
|
||||
|
||||
// The ETag for this object's content
|
||||
NSString *ETag;
|
||||
|
||||
// The size in bytes of this object
|
||||
unsigned long long size;
|
||||
|
||||
// Info about the owner
|
||||
NSString *ownerID;
|
||||
NSString *ownerName;
|
||||
}
|
||||
|
||||
+ (id)objectWithBucket:(NSString *)bucket;
|
||||
|
||||
// Returns a request that will fetch this object when run
|
||||
- (ASIS3ObjectRequest *)GETRequest;
|
||||
|
||||
// Returns a request that will replace this object with the contents of the file at filePath when run
|
||||
- (ASIS3ObjectRequest *)PUTRequestWithFile:(NSString *)filePath;
|
||||
|
||||
// Returns a request that will delete this object when run
|
||||
- (ASIS3ObjectRequest *)DELETERequest;
|
||||
|
||||
@property (retain) NSString *bucket;
|
||||
@property (retain) NSString *key;
|
||||
@property (retain) NSDate *lastModified;
|
||||
@property (retain) NSString *ETag;
|
||||
@property (assign) unsigned long long size;
|
||||
@property (retain) NSString *ownerID;
|
||||
@property (retain) NSString *ownerName;
|
||||
@end
|
||||
74
projects/mtg/iOS/asi-http-request/S3/ASIS3BucketObject.m
Normal file
74
projects/mtg/iOS/asi-http-request/S3/ASIS3BucketObject.m
Normal file
@@ -0,0 +1,74 @@
|
||||
//
|
||||
// ASIS3BucketObject.m
|
||||
// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest
|
||||
//
|
||||
// Created by Ben Copsey on 13/07/2009.
|
||||
// Copyright 2009 All-Seeing Interactive. All rights reserved.
|
||||
//
|
||||
|
||||
#import "ASIS3BucketObject.h"
|
||||
#import "ASIS3ObjectRequest.h"
|
||||
|
||||
@implementation ASIS3BucketObject
|
||||
|
||||
+ (id)objectWithBucket:(NSString *)theBucket
|
||||
{
|
||||
ASIS3BucketObject *object = [[[self alloc] init] autorelease];
|
||||
[object setBucket:theBucket];
|
||||
return object;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
[bucket release];
|
||||
[key release];
|
||||
[lastModified release];
|
||||
[ETag release];
|
||||
[ownerID release];
|
||||
[ownerName release];
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (ASIS3ObjectRequest *)GETRequest
|
||||
{
|
||||
return [ASIS3ObjectRequest requestWithBucket:[self bucket] key:[self key]];
|
||||
}
|
||||
|
||||
- (ASIS3ObjectRequest *)PUTRequestWithFile:(NSString *)filePath
|
||||
{
|
||||
return [ASIS3ObjectRequest PUTRequestForFile:filePath withBucket:[self bucket] key:[self key]];
|
||||
}
|
||||
|
||||
- (ASIS3ObjectRequest *)DELETERequest
|
||||
{
|
||||
ASIS3ObjectRequest *request = [ASIS3ObjectRequest requestWithBucket:[self bucket] key:[self key]];
|
||||
[request setRequestMethod:@"DELETE"];
|
||||
return request;
|
||||
}
|
||||
|
||||
- (NSString *)description
|
||||
{
|
||||
return [NSString stringWithFormat:@"Key: %@ lastModified: %@ ETag: %@ size: %llu ownerID: %@ ownerName: %@",[self key],[self lastModified],[self ETag],[self size],[self ownerID],[self ownerName]];
|
||||
}
|
||||
|
||||
- (id)copyWithZone:(NSZone *)zone
|
||||
{
|
||||
ASIS3BucketObject *newBucketObject = [[[self class] alloc] init];
|
||||
[newBucketObject setBucket:[self bucket]];
|
||||
[newBucketObject setKey:[self key]];
|
||||
[newBucketObject setLastModified:[self lastModified]];
|
||||
[newBucketObject setETag:[self ETag]];
|
||||
[newBucketObject setSize:[self size]];
|
||||
[newBucketObject setOwnerID:[self ownerID]];
|
||||
[newBucketObject setOwnerName:[self ownerName]];
|
||||
return newBucketObject;
|
||||
}
|
||||
|
||||
@synthesize bucket;
|
||||
@synthesize key;
|
||||
@synthesize lastModified;
|
||||
@synthesize ETag;
|
||||
@synthesize size;
|
||||
@synthesize ownerID;
|
||||
@synthesize ownerName;
|
||||
@end
|
||||
72
projects/mtg/iOS/asi-http-request/S3/ASIS3BucketRequest.h
Normal file
72
projects/mtg/iOS/asi-http-request/S3/ASIS3BucketRequest.h
Normal file
@@ -0,0 +1,72 @@
|
||||
//
|
||||
// ASIS3BucketRequest.h
|
||||
// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest
|
||||
//
|
||||
// Created by Ben Copsey on 16/03/2010.
|
||||
// Copyright 2010 All-Seeing Interactive. All rights reserved.
|
||||
//
|
||||
// Use this class to create buckets, fetch a list of their contents, and delete buckets
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "ASIS3Request.h"
|
||||
|
||||
@class ASIS3BucketObject;
|
||||
|
||||
@interface ASIS3BucketRequest : ASIS3Request {
|
||||
|
||||
// Name of the bucket to talk to
|
||||
NSString *bucket;
|
||||
|
||||
// A parameter passed to S3 in the query string to tell it to return specialised information
|
||||
// Consult the S3 REST API documentation for more info
|
||||
NSString *subResource;
|
||||
|
||||
// Options for filtering GET requests
|
||||
// See http://docs.amazonwebservices.com/AmazonS3/2006-03-01/index.html?RESTBucketGET.html
|
||||
NSString *prefix;
|
||||
NSString *marker;
|
||||
int maxResultCount;
|
||||
NSString *delimiter;
|
||||
|
||||
// Internally used while parsing the response
|
||||
ASIS3BucketObject *currentObject;
|
||||
|
||||
// Returns an array of ASIS3BucketObjects created from the XML response
|
||||
NSMutableArray *objects;
|
||||
|
||||
// Will be populated with a list of 'folders' when a delimiter is set
|
||||
NSMutableArray *commonPrefixes;
|
||||
|
||||
// Will be true if this request did not return all the results matching the query (use maxResultCount to configure the number of results to return)
|
||||
BOOL isTruncated;
|
||||
}
|
||||
|
||||
// Fetch a bucket
|
||||
+ (id)requestWithBucket:(NSString *)bucket;
|
||||
|
||||
// Create a bucket request, passing a parameter in the query string
|
||||
// You'll need to parse the response XML yourself
|
||||
// Examples:
|
||||
// Fetch ACL:
|
||||
// ASIS3BucketRequest *request = [ASIS3BucketRequest requestWithBucket:@"mybucket" parameter:@"acl"];
|
||||
// Fetch Location:
|
||||
// ASIS3BucketRequest *request = [ASIS3BucketRequest requestWithBucket:@"mybucket" parameter:@"location"];
|
||||
// See the S3 REST API docs for more information about the parameters you can pass
|
||||
+ (id)requestWithBucket:(NSString *)bucket subResource:(NSString *)subResource;
|
||||
|
||||
// Use for creating new buckets
|
||||
+ (id)PUTRequestWithBucket:(NSString *)bucket;
|
||||
|
||||
// Use for deleting buckets - they must be empty for this to succeed
|
||||
+ (id)DELETERequestWithBucket:(NSString *)bucket;
|
||||
|
||||
@property (retain, nonatomic) NSString *bucket;
|
||||
@property (retain, nonatomic) NSString *subResource;
|
||||
@property (retain, nonatomic) NSString *prefix;
|
||||
@property (retain, nonatomic) NSString *marker;
|
||||
@property (assign, nonatomic) int maxResultCount;
|
||||
@property (retain, nonatomic) NSString *delimiter;
|
||||
@property (retain, readonly) NSMutableArray *objects;
|
||||
@property (retain, readonly) NSMutableArray *commonPrefixes;
|
||||
@property (assign, readonly) BOOL isTruncated;
|
||||
@end
|
||||
175
projects/mtg/iOS/asi-http-request/S3/ASIS3BucketRequest.m
Normal file
175
projects/mtg/iOS/asi-http-request/S3/ASIS3BucketRequest.m
Normal file
@@ -0,0 +1,175 @@
|
||||
//
|
||||
// ASIS3BucketRequest.m
|
||||
// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest
|
||||
//
|
||||
// Created by Ben Copsey on 16/03/2010.
|
||||
// Copyright 2010 All-Seeing Interactive. All rights reserved.
|
||||
//
|
||||
|
||||
#import "ASIS3BucketRequest.h"
|
||||
#import "ASIS3BucketObject.h"
|
||||
|
||||
|
||||
// Private stuff
|
||||
@interface ASIS3BucketRequest ()
|
||||
@property (retain, nonatomic) ASIS3BucketObject *currentObject;
|
||||
@property (retain) NSMutableArray *objects;
|
||||
@property (retain) NSMutableArray *commonPrefixes;
|
||||
@property (assign) BOOL isTruncated;
|
||||
@end
|
||||
|
||||
@implementation ASIS3BucketRequest
|
||||
|
||||
- (id)initWithURL:(NSURL *)newURL
|
||||
{
|
||||
self = [super initWithURL:newURL];
|
||||
[self setObjects:[[[NSMutableArray alloc] init] autorelease]];
|
||||
[self setCommonPrefixes:[[[NSMutableArray alloc] init] autorelease]];
|
||||
return self;
|
||||
}
|
||||
|
||||
+ (id)requestWithBucket:(NSString *)theBucket
|
||||
{
|
||||
ASIS3BucketRequest *request = [[[self alloc] initWithURL:nil] autorelease];
|
||||
[request setBucket:theBucket];
|
||||
return request;
|
||||
}
|
||||
|
||||
+ (id)requestWithBucket:(NSString *)theBucket subResource:(NSString *)theSubResource
|
||||
{
|
||||
ASIS3BucketRequest *request = [[[self alloc] initWithURL:nil] autorelease];
|
||||
[request setBucket:theBucket];
|
||||
[request setSubResource:theSubResource];
|
||||
return request;
|
||||
}
|
||||
|
||||
+ (id)PUTRequestWithBucket:(NSString *)theBucket
|
||||
{
|
||||
ASIS3BucketRequest *request = [self requestWithBucket:theBucket];
|
||||
[request setRequestMethod:@"PUT"];
|
||||
return request;
|
||||
}
|
||||
|
||||
|
||||
+ (id)DELETERequestWithBucket:(NSString *)theBucket
|
||||
{
|
||||
ASIS3BucketRequest *request = [self requestWithBucket:theBucket];
|
||||
[request setRequestMethod:@"DELETE"];
|
||||
return request;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
[currentObject release];
|
||||
[objects release];
|
||||
[commonPrefixes release];
|
||||
[prefix release];
|
||||
[marker release];
|
||||
[delimiter release];
|
||||
[subResource release];
|
||||
[bucket release];
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (NSString *)canonicalizedResource
|
||||
{
|
||||
if ([self subResource]) {
|
||||
return [NSString stringWithFormat:@"/%@/?%@",[self bucket],[self subResource]];
|
||||
}
|
||||
return [NSString stringWithFormat:@"/%@/",[self bucket]];
|
||||
}
|
||||
|
||||
- (void)buildURL
|
||||
{
|
||||
NSString *baseURL;
|
||||
if ([self subResource]) {
|
||||
baseURL = [NSString stringWithFormat:@"%@://%@.%@/?%@",[self requestScheme],[self bucket],[[self class] S3Host],[self subResource]];
|
||||
} else {
|
||||
baseURL = [NSString stringWithFormat:@"%@://%@.%@",[self requestScheme],[self bucket],[[self class] S3Host]];
|
||||
}
|
||||
NSMutableArray *queryParts = [[[NSMutableArray alloc] init] autorelease];
|
||||
if ([self prefix]) {
|
||||
[queryParts addObject:[NSString stringWithFormat:@"prefix=%@",[[self prefix] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]];
|
||||
}
|
||||
if ([self marker]) {
|
||||
[queryParts addObject:[NSString stringWithFormat:@"marker=%@",[[self marker] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]];
|
||||
}
|
||||
if ([self delimiter]) {
|
||||
[queryParts addObject:[NSString stringWithFormat:@"delimiter=%@",[[self delimiter] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]];
|
||||
}
|
||||
if ([self maxResultCount] > 0) {
|
||||
[queryParts addObject:[NSString stringWithFormat:@"max-keys=%hi",[self maxResultCount]]];
|
||||
}
|
||||
if ([queryParts count]) {
|
||||
NSString* template = @"%@?%@";
|
||||
if ([[self subResource] length] > 0) {
|
||||
template = @"%@&%@";
|
||||
}
|
||||
[self setURL:[NSURL URLWithString:[NSString stringWithFormat:template,baseURL,[queryParts componentsJoinedByString:@"&"]]]];
|
||||
} else {
|
||||
[self setURL:[NSURL URLWithString:baseURL]];
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict
|
||||
{
|
||||
if ([elementName isEqualToString:@"Contents"]) {
|
||||
[self setCurrentObject:[ASIS3BucketObject objectWithBucket:[self bucket]]];
|
||||
}
|
||||
[super parser:parser didStartElement:elementName namespaceURI:namespaceURI qualifiedName:qName attributes:attributeDict];
|
||||
}
|
||||
|
||||
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
|
||||
{
|
||||
if ([elementName isEqualToString:@"Contents"]) {
|
||||
[objects addObject:currentObject];
|
||||
[self setCurrentObject:nil];
|
||||
} else if ([elementName isEqualToString:@"Key"]) {
|
||||
[[self currentObject] setKey:[self currentXMLElementContent]];
|
||||
} else if ([elementName isEqualToString:@"LastModified"]) {
|
||||
[[self currentObject] setLastModified:[[ASIS3Request S3ResponseDateFormatter] dateFromString:[self currentXMLElementContent]]];
|
||||
} else if ([elementName isEqualToString:@"ETag"]) {
|
||||
[[self currentObject] setETag:[self currentXMLElementContent]];
|
||||
} else if ([elementName isEqualToString:@"Size"]) {
|
||||
[[self currentObject] setSize:(unsigned long long)[[self currentXMLElementContent] longLongValue]];
|
||||
} else if ([elementName isEqualToString:@"ID"]) {
|
||||
[[self currentObject] setOwnerID:[self currentXMLElementContent]];
|
||||
} else if ([elementName isEqualToString:@"DisplayName"]) {
|
||||
[[self currentObject] setOwnerName:[self currentXMLElementContent]];
|
||||
} else if ([elementName isEqualToString:@"Prefix"] && [[self currentXMLElementStack] count] > 2 && [[[self currentXMLElementStack] objectAtIndex:[[self currentXMLElementStack] count]-2] isEqualToString:@"CommonPrefixes"]) {
|
||||
[[self commonPrefixes] addObject:[self currentXMLElementContent]];
|
||||
} else if ([elementName isEqualToString:@"IsTruncated"]) {
|
||||
[self setIsTruncated:[[self currentXMLElementContent] isEqualToString:@"true"]];
|
||||
} else {
|
||||
// Let ASIS3Request look for error messages
|
||||
[super parser:parser didEndElement:elementName namespaceURI:namespaceURI qualifiedName:qName];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark NSCopying
|
||||
|
||||
- (id)copyWithZone:(NSZone *)zone
|
||||
{
|
||||
ASIS3BucketRequest *newRequest = [super copyWithZone:zone];
|
||||
[newRequest setBucket:[self bucket]];
|
||||
[newRequest setSubResource:[self subResource]];
|
||||
[newRequest setPrefix:[self prefix]];
|
||||
[newRequest setMarker:[self marker]];
|
||||
[newRequest setMaxResultCount:[self maxResultCount]];
|
||||
[newRequest setDelimiter:[self delimiter]];
|
||||
return newRequest;
|
||||
}
|
||||
|
||||
@synthesize bucket;
|
||||
@synthesize subResource;
|
||||
@synthesize currentObject;
|
||||
@synthesize objects;
|
||||
@synthesize commonPrefixes;
|
||||
@synthesize prefix;
|
||||
@synthesize marker;
|
||||
@synthesize maxResultCount;
|
||||
@synthesize delimiter;
|
||||
@synthesize isTruncated;
|
||||
|
||||
@end
|
||||
80
projects/mtg/iOS/asi-http-request/S3/ASIS3ObjectRequest.h
Normal file
80
projects/mtg/iOS/asi-http-request/S3/ASIS3ObjectRequest.h
Normal file
@@ -0,0 +1,80 @@
|
||||
//
|
||||
// ASIS3ObjectRequest.h
|
||||
// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest
|
||||
//
|
||||
// Created by Ben Copsey on 16/03/2010.
|
||||
// Copyright 2010 All-Seeing Interactive. All rights reserved.
|
||||
//
|
||||
// Use an ASIS3ObjectRequest to fetch, upload, copy and delete objects on Amazon S3
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "ASIS3Request.h"
|
||||
|
||||
// Constants for storage class
|
||||
extern NSString *const ASIS3StorageClassStandard;
|
||||
extern NSString *const ASIS3StorageClassReducedRedundancy;
|
||||
|
||||
@interface ASIS3ObjectRequest : ASIS3Request {
|
||||
|
||||
// Name of the bucket to talk to
|
||||
NSString *bucket;
|
||||
|
||||
// Key of the resource you want to access on S3
|
||||
NSString *key;
|
||||
|
||||
// The bucket + path of the object to be copied (used with COPYRequestFromBucket:path:toBucket:path:)
|
||||
NSString *sourceBucket;
|
||||
NSString *sourceKey;
|
||||
|
||||
// The mime type of the content for PUT requests
|
||||
// Set this if having the correct mime type returned to you when you GET the data is important (eg it will be served by a web-server)
|
||||
// Can be autodetected when PUTing a file from disk, will default to 'application/octet-stream' when PUTing data
|
||||
NSString *mimeType;
|
||||
|
||||
// Set this to specify you want to work with a particular subresource (eg an acl for that resource)
|
||||
// See requestWithBucket:key:subResource:, below.
|
||||
NSString* subResource;
|
||||
|
||||
// The storage class to be used for PUT requests
|
||||
// Set this to ASIS3StorageClassReducedRedundancy to save money on storage, at (presumably) a slightly higher risk you will lose the data
|
||||
// If this is not set, no x-amz-storage-class header will be sent to S3, and their default will be used
|
||||
NSString *storageClass;
|
||||
}
|
||||
|
||||
// Create a request, building an appropriate url
|
||||
+ (id)requestWithBucket:(NSString *)bucket key:(NSString *)key;
|
||||
|
||||
// Create a request for an object, passing a parameter in the query string
|
||||
// You'll need to parse the response XML yourself
|
||||
// Examples:
|
||||
// Fetch ACL:
|
||||
// ASIS3ObjectRequest *request = [ASIS3ObjectRequest requestWithBucket:@"mybucket" key:@"my-key" parameter:@"acl"];
|
||||
// Get object torret:
|
||||
// ASIS3ObjectRequest *request = [ASIS3ObjectRequest requestWithBucket:@"mybucket" key:@"my-key" parameter:@"torrent"];
|
||||
// See the S3 REST API docs for more information about the parameters you can pass
|
||||
+ (id)requestWithBucket:(NSString *)bucket key:(NSString *)key subResource:(NSString *)subResource;
|
||||
|
||||
// Create a PUT request using the file at filePath as the body
|
||||
+ (id)PUTRequestForFile:(NSString *)filePath withBucket:(NSString *)bucket key:(NSString *)key;
|
||||
|
||||
// Create a PUT request using the supplied NSData as the body (set the mime-type manually with setMimeType: if necessary)
|
||||
+ (id)PUTRequestForData:(NSData *)data withBucket:(NSString *)bucket key:(NSString *)key;
|
||||
|
||||
// Create a DELETE request for the object at path
|
||||
+ (id)DELETERequestWithBucket:(NSString *)bucket key:(NSString *)key;
|
||||
|
||||
// Create a PUT request to copy an object from one location to another
|
||||
// Clang will complain because it thinks this method should return an object with +1 retain :(
|
||||
+ (id)COPYRequestFromBucket:(NSString *)sourceBucket key:(NSString *)sourceKey toBucket:(NSString *)bucket key:(NSString *)key;
|
||||
|
||||
// Creates a HEAD request for the object at path
|
||||
+ (id)HEADRequestWithBucket:(NSString *)bucket key:(NSString *)key;
|
||||
|
||||
@property (retain, nonatomic) NSString *bucket;
|
||||
@property (retain, nonatomic) NSString *key;
|
||||
@property (retain, nonatomic) NSString *sourceBucket;
|
||||
@property (retain, nonatomic) NSString *sourceKey;
|
||||
@property (retain, nonatomic) NSString *mimeType;
|
||||
@property (retain, nonatomic) NSString *subResource;
|
||||
@property (retain, nonatomic) NSString *storageClass;
|
||||
@end
|
||||
164
projects/mtg/iOS/asi-http-request/S3/ASIS3ObjectRequest.m
Normal file
164
projects/mtg/iOS/asi-http-request/S3/ASIS3ObjectRequest.m
Normal file
@@ -0,0 +1,164 @@
|
||||
//
|
||||
// ASIS3ObjectRequest.m
|
||||
// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest
|
||||
//
|
||||
// Created by Ben Copsey on 16/03/2010.
|
||||
// Copyright 2010 All-Seeing Interactive. All rights reserved.
|
||||
//
|
||||
|
||||
#import "ASIS3ObjectRequest.h"
|
||||
|
||||
NSString *const ASIS3StorageClassStandard = @"STANDARD";
|
||||
NSString *const ASIS3StorageClassReducedRedundancy = @"REDUCED_REDUNDANCY";
|
||||
|
||||
@implementation ASIS3ObjectRequest
|
||||
|
||||
- (ASIHTTPRequest *)HEADRequest
|
||||
{
|
||||
ASIS3ObjectRequest *headRequest = (ASIS3ObjectRequest *)[super HEADRequest];
|
||||
[headRequest setKey:[self key]];
|
||||
[headRequest setBucket:[self bucket]];
|
||||
return headRequest;
|
||||
}
|
||||
|
||||
+ (id)requestWithBucket:(NSString *)theBucket key:(NSString *)theKey
|
||||
{
|
||||
ASIS3ObjectRequest *newRequest = [[[self alloc] initWithURL:nil] autorelease];
|
||||
[newRequest setBucket:theBucket];
|
||||
[newRequest setKey:theKey];
|
||||
return newRequest;
|
||||
}
|
||||
|
||||
+ (id)requestWithBucket:(NSString *)theBucket key:(NSString *)theKey subResource:(NSString *)theSubResource
|
||||
{
|
||||
ASIS3ObjectRequest *newRequest = [[[self alloc] initWithURL:nil] autorelease];
|
||||
[newRequest setSubResource:theSubResource];
|
||||
[newRequest setBucket:theBucket];
|
||||
[newRequest setKey:theKey];
|
||||
return newRequest;
|
||||
}
|
||||
|
||||
+ (id)PUTRequestForData:(NSData *)data withBucket:(NSString *)theBucket key:(NSString *)theKey
|
||||
{
|
||||
ASIS3ObjectRequest *newRequest = [self requestWithBucket:theBucket key:theKey];
|
||||
[newRequest appendPostData:data];
|
||||
[newRequest setRequestMethod:@"PUT"];
|
||||
return newRequest;
|
||||
}
|
||||
|
||||
+ (id)PUTRequestForFile:(NSString *)filePath withBucket:(NSString *)theBucket key:(NSString *)theKey
|
||||
{
|
||||
ASIS3ObjectRequest *newRequest = [self requestWithBucket:theBucket key:theKey];
|
||||
[newRequest setPostBodyFilePath:filePath];
|
||||
[newRequest setShouldStreamPostDataFromDisk:YES];
|
||||
[newRequest setRequestMethod:@"PUT"];
|
||||
[newRequest setMimeType:[ASIHTTPRequest mimeTypeForFileAtPath:filePath]];
|
||||
return newRequest;
|
||||
}
|
||||
|
||||
+ (id)DELETERequestWithBucket:(NSString *)theBucket key:(NSString *)theKey
|
||||
{
|
||||
ASIS3ObjectRequest *newRequest = [self requestWithBucket:theBucket key:theKey];
|
||||
[newRequest setRequestMethod:@"DELETE"];
|
||||
return newRequest;
|
||||
}
|
||||
|
||||
+ (id)COPYRequestFromBucket:(NSString *)theSourceBucket key:(NSString *)theSourceKey toBucket:(NSString *)theBucket key:(NSString *)theKey
|
||||
{
|
||||
ASIS3ObjectRequest *newRequest = [self requestWithBucket:theBucket key:theKey];
|
||||
[newRequest setRequestMethod:@"PUT"];
|
||||
[newRequest setSourceBucket:theSourceBucket];
|
||||
[newRequest setSourceKey:theSourceKey];
|
||||
return newRequest;
|
||||
}
|
||||
|
||||
+ (id)HEADRequestWithBucket:(NSString *)theBucket key:(NSString *)theKey
|
||||
{
|
||||
ASIS3ObjectRequest *newRequest = [self requestWithBucket:theBucket key:theKey];
|
||||
[newRequest setRequestMethod:@"HEAD"];
|
||||
return newRequest;
|
||||
}
|
||||
|
||||
- (id)copyWithZone:(NSZone *)zone
|
||||
{
|
||||
ASIS3ObjectRequest *newRequest = [super copyWithZone:zone];
|
||||
[newRequest setBucket:[self bucket]];
|
||||
[newRequest setKey:[self key]];
|
||||
[newRequest setSourceBucket:[self sourceBucket]];
|
||||
[newRequest setSourceKey:[self sourceKey]];
|
||||
[newRequest setMimeType:[self mimeType]];
|
||||
[newRequest setSubResource:[self subResource]];
|
||||
[newRequest setStorageClass:[self storageClass]];
|
||||
return newRequest;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
[bucket release];
|
||||
[key release];
|
||||
[mimeType release];
|
||||
[sourceKey release];
|
||||
[sourceBucket release];
|
||||
[subResource release];
|
||||
[storageClass release];
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (void)buildURL
|
||||
{
|
||||
if ([self subResource]) {
|
||||
[self setURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@://%@.%@%@?%@",[self requestScheme],[self bucket],[[self class] S3Host],[ASIS3Request stringByURLEncodingForS3Path:[self key]],[self subResource]]]];
|
||||
} else {
|
||||
[self setURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@://%@.%@%@",[self requestScheme],[self bucket],[[self class] S3Host],[ASIS3Request stringByURLEncodingForS3Path:[self key]]]]];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)mimeType
|
||||
{
|
||||
if (mimeType) {
|
||||
return mimeType;
|
||||
} else if ([self postBodyFilePath]) {
|
||||
return [ASIHTTPRequest mimeTypeForFileAtPath:[self postBodyFilePath]];
|
||||
} else {
|
||||
return @"application/octet-stream";
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)canonicalizedResource
|
||||
{
|
||||
if ([[self subResource] length] > 0) {
|
||||
return [NSString stringWithFormat:@"/%@%@?%@",[self bucket],[ASIS3Request stringByURLEncodingForS3Path:[self key]], [self subResource]];
|
||||
}
|
||||
return [NSString stringWithFormat:@"/%@%@",[self bucket],[ASIS3Request stringByURLEncodingForS3Path:[self key]]];
|
||||
}
|
||||
|
||||
- (NSMutableDictionary *)S3Headers
|
||||
{
|
||||
NSMutableDictionary *headers = [super S3Headers];
|
||||
if ([self sourceKey]) {
|
||||
NSString *path = [ASIS3Request stringByURLEncodingForS3Path:[self sourceKey]];
|
||||
[headers setObject:[[self sourceBucket] stringByAppendingString:path] forKey:@"x-amz-copy-source"];
|
||||
}
|
||||
if ([self storageClass]) {
|
||||
[headers setObject:[self storageClass] forKey:@"x-amz-storage-class"];
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
|
||||
- (NSString *)stringToSignForHeaders:(NSString *)canonicalizedAmzHeaders resource:(NSString *)canonicalizedResource
|
||||
{
|
||||
if ([[self requestMethod] isEqualToString:@"PUT"] && ![self sourceKey]) {
|
||||
[self addRequestHeader:@"Content-Type" value:[self mimeType]];
|
||||
return [NSString stringWithFormat:@"PUT\n\n%@\n%@\n%@%@",[self mimeType],dateString,canonicalizedAmzHeaders,canonicalizedResource];
|
||||
}
|
||||
return [super stringToSignForHeaders:canonicalizedAmzHeaders resource:canonicalizedResource];
|
||||
}
|
||||
|
||||
@synthesize bucket;
|
||||
@synthesize key;
|
||||
@synthesize sourceBucket;
|
||||
@synthesize sourceKey;
|
||||
@synthesize mimeType;
|
||||
@synthesize subResource;
|
||||
@synthesize storageClass;
|
||||
@end
|
||||
109
projects/mtg/iOS/asi-http-request/S3/ASIS3Request.h
Normal file
109
projects/mtg/iOS/asi-http-request/S3/ASIS3Request.h
Normal file
@@ -0,0 +1,109 @@
|
||||
//
|
||||
// ASIS3Request.h
|
||||
// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest
|
||||
//
|
||||
// Created by Ben Copsey on 30/06/2009.
|
||||
// Copyright 2009 All-Seeing Interactive. All rights reserved.
|
||||
//
|
||||
// A class for accessing data stored on Amazon's Simple Storage Service (http://aws.amazon.com/s3/) using the REST API
|
||||
// While you can use this class directly, the included subclasses make typical operations easier
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "ASIHTTPRequest.h"
|
||||
|
||||
#if !TARGET_OS_IPHONE || (TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED < __IPHONE_4_0)
|
||||
#import "ASINSXMLParserCompat.h"
|
||||
#endif
|
||||
|
||||
// See http://docs.amazonwebservices.com/AmazonS3/2006-03-01/index.html?RESTAccessPolicy.html for what these mean
|
||||
extern NSString *const ASIS3AccessPolicyPrivate; // This is the default in S3 when no access policy header is provided
|
||||
extern NSString *const ASIS3AccessPolicyPublicRead;
|
||||
extern NSString *const ASIS3AccessPolicyPublicReadWrite;
|
||||
extern NSString *const ASIS3AccessPolicyAuthenticatedRead;
|
||||
extern NSString *const ASIS3AccessPolicyBucketOwnerRead;
|
||||
extern NSString *const ASIS3AccessPolicyBucketOwnerFullControl;
|
||||
|
||||
// Constants for requestScheme - defaults is ASIS3RequestSchemeHTTP
|
||||
extern NSString *const ASIS3RequestSchemeHTTP;
|
||||
extern NSString *const ASIS3RequestSchemeHTTPS;
|
||||
|
||||
|
||||
|
||||
typedef enum _ASIS3ErrorType {
|
||||
ASIS3ResponseParsingFailedType = 1,
|
||||
ASIS3ResponseErrorType = 2
|
||||
} ASIS3ErrorType;
|
||||
|
||||
|
||||
|
||||
@interface ASIS3Request : ASIHTTPRequest <NSCopying, NSXMLParserDelegate> {
|
||||
|
||||
// Your S3 access key. Set it on the request, or set it globally using [ASIS3Request setSharedAccessKey:]
|
||||
NSString *accessKey;
|
||||
|
||||
// Your S3 secret access key. Set it on the request, or set it globally using [ASIS3Request setSharedSecretAccessKey:]
|
||||
NSString *secretAccessKey;
|
||||
|
||||
// Set to ASIS3RequestSchemeHTTPS to send your requests via HTTPS (default is ASIS3RequestSchemeHTTP)
|
||||
NSString *requestScheme;
|
||||
|
||||
// The string that will be used in the HTTP date header. Generally you'll want to ignore this and let the class add the current date for you, but the accessor is used by the tests
|
||||
NSString *dateString;
|
||||
|
||||
// The access policy to use when PUTting a file (see the string constants at the top ASIS3Request.h for details on what the possible options are)
|
||||
NSString *accessPolicy;
|
||||
|
||||
// Internally used while parsing errors
|
||||
NSString *currentXMLElementContent;
|
||||
NSMutableArray *currentXMLElementStack;
|
||||
}
|
||||
|
||||
// Uses the supplied date to create a Date header string
|
||||
- (void)setDate:(NSDate *)date;
|
||||
|
||||
// Will return a dictionary of the 'amz-' headers that wil be sent to S3
|
||||
// Override in subclasses to add new ones
|
||||
- (NSMutableDictionary *)S3Headers;
|
||||
|
||||
// Returns the string that will used to create a signature for this request
|
||||
// Is overridden in ASIS3ObjectRequest
|
||||
- (NSString *)stringToSignForHeaders:(NSString *)canonicalizedAmzHeaders resource:(NSString *)canonicalizedResource;
|
||||
|
||||
// Parses the response to work out if S3 returned an error
|
||||
- (void)parseResponseXML;
|
||||
|
||||
#pragma mark shared access keys
|
||||
|
||||
// Get and set the global access key, this will be used for all requests the access key hasn't been set for
|
||||
+ (NSString *)sharedAccessKey;
|
||||
+ (void)setSharedAccessKey:(NSString *)newAccessKey;
|
||||
+ (NSString *)sharedSecretAccessKey;
|
||||
+ (void)setSharedSecretAccessKey:(NSString *)newAccessKey;
|
||||
|
||||
# pragma mark helpers
|
||||
|
||||
// Returns a date formatter than can be used to parse a date from S3
|
||||
+ (NSDateFormatter*)S3ResponseDateFormatter;
|
||||
|
||||
// Returns a date formatter than can be used to send a date header to S3
|
||||
+ (NSDateFormatter*)S3RequestDateFormatter;
|
||||
|
||||
|
||||
// URL-encodes an S3 key so it can be used in a url
|
||||
// You shouldn't normally need to use this yourself
|
||||
+ (NSString *)stringByURLEncodingForS3Path:(NSString *)key;
|
||||
|
||||
// Returns a string for the hostname used for S3 requests. You shouldn't ever need to change this.
|
||||
+ (NSString *)S3Host;
|
||||
|
||||
// This is called automatically before the request starts to build the request URL (if one has not been manually set already)
|
||||
- (void)buildURL;
|
||||
|
||||
@property (retain) NSString *dateString;
|
||||
@property (retain) NSString *accessKey;
|
||||
@property (retain) NSString *secretAccessKey;
|
||||
@property (retain) NSString *accessPolicy;
|
||||
@property (retain) NSString *currentXMLElementContent;
|
||||
@property (retain) NSMutableArray *currentXMLElementStack;
|
||||
@property (retain) NSString *requestScheme;
|
||||
@end
|
||||
312
projects/mtg/iOS/asi-http-request/S3/ASIS3Request.m
Normal file
312
projects/mtg/iOS/asi-http-request/S3/ASIS3Request.m
Normal file
@@ -0,0 +1,312 @@
|
||||
//
|
||||
// ASIS3Request.m
|
||||
// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest
|
||||
//
|
||||
// Created by Ben Copsey on 30/06/2009.
|
||||
// Copyright 2009 All-Seeing Interactive. All rights reserved.
|
||||
//
|
||||
|
||||
#import "ASIS3Request.h"
|
||||
#import <CommonCrypto/CommonHMAC.h>
|
||||
|
||||
NSString *const ASIS3AccessPolicyPrivate = @"private";
|
||||
NSString *const ASIS3AccessPolicyPublicRead = @"public-read";
|
||||
NSString *const ASIS3AccessPolicyPublicReadWrite = @"public-read-write";
|
||||
NSString *const ASIS3AccessPolicyAuthenticatedRead = @"authenticated-read";
|
||||
NSString *const ASIS3AccessPolicyBucketOwnerRead = @"bucket-owner-read";
|
||||
NSString *const ASIS3AccessPolicyBucketOwnerFullControl = @"bucket-owner-full-control";
|
||||
|
||||
NSString *const ASIS3RequestSchemeHTTP = @"http";
|
||||
NSString *const ASIS3RequestSchemeHTTPS = @"https";
|
||||
|
||||
static NSString *sharedAccessKey = nil;
|
||||
static NSString *sharedSecretAccessKey = nil;
|
||||
|
||||
// Private stuff
|
||||
@interface ASIS3Request ()
|
||||
+ (NSData *)HMACSHA1withKey:(NSString *)key forString:(NSString *)string;
|
||||
@end
|
||||
|
||||
@implementation ASIS3Request
|
||||
|
||||
- (id)initWithURL:(NSURL *)newURL
|
||||
{
|
||||
self = [super initWithURL:newURL];
|
||||
// After a bit of experimentation/guesswork, this number seems to reduce the chance of a 'RequestTimeout' error
|
||||
[self setPersistentConnectionTimeoutSeconds:20];
|
||||
[self setRequestScheme:ASIS3RequestSchemeHTTP];
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
[currentXMLElementContent release];
|
||||
[currentXMLElementStack release];
|
||||
[dateString release];
|
||||
[accessKey release];
|
||||
[secretAccessKey release];
|
||||
[accessPolicy release];
|
||||
[requestScheme release];
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
|
||||
- (void)setDate:(NSDate *)date
|
||||
{
|
||||
[self setDateString:[[ASIS3Request S3RequestDateFormatter] stringFromDate:date]];
|
||||
}
|
||||
|
||||
- (ASIHTTPRequest *)HEADRequest
|
||||
{
|
||||
ASIS3Request *headRequest = (ASIS3Request *)[super HEADRequest];
|
||||
[headRequest setAccessKey:[self accessKey]];
|
||||
[headRequest setSecretAccessKey:[self secretAccessKey]];
|
||||
return headRequest;
|
||||
}
|
||||
|
||||
- (NSMutableDictionary *)S3Headers
|
||||
{
|
||||
NSMutableDictionary *headers = [NSMutableDictionary dictionary];
|
||||
if ([self accessPolicy]) {
|
||||
[headers setObject:[self accessPolicy] forKey:@"x-amz-acl"];
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
|
||||
- (void)main
|
||||
{
|
||||
if (![self url]) {
|
||||
[self buildURL];
|
||||
}
|
||||
[super main];
|
||||
}
|
||||
|
||||
- (NSString *)canonicalizedResource
|
||||
{
|
||||
return @"/";
|
||||
}
|
||||
|
||||
- (NSString *)stringToSignForHeaders:(NSString *)canonicalizedAmzHeaders resource:(NSString *)canonicalizedResource
|
||||
{
|
||||
return [NSString stringWithFormat:@"%@\n\n\n%@\n%@%@",[self requestMethod],[self dateString],canonicalizedAmzHeaders,canonicalizedResource];
|
||||
}
|
||||
|
||||
- (void)buildRequestHeaders
|
||||
{
|
||||
if (![self url]) {
|
||||
[self buildURL];
|
||||
}
|
||||
[super buildRequestHeaders];
|
||||
|
||||
// If an access key / secret access key haven't been set for this request, let's use the shared keys
|
||||
if (![self accessKey]) {
|
||||
[self setAccessKey:[ASIS3Request sharedAccessKey]];
|
||||
}
|
||||
if (![self secretAccessKey]) {
|
||||
[self setSecretAccessKey:[ASIS3Request sharedSecretAccessKey]];
|
||||
}
|
||||
// If a date string hasn't been set, we'll create one from the current time
|
||||
if (![self dateString]) {
|
||||
[self setDate:[NSDate date]];
|
||||
}
|
||||
[self addRequestHeader:@"Date" value:[self dateString]];
|
||||
|
||||
// Ensure our formatted string doesn't use '(null)' for the empty path
|
||||
NSString *canonicalizedResource = [self canonicalizedResource];
|
||||
|
||||
// Add a header for the access policy if one was set, otherwise we won't add one (and S3 will default to private)
|
||||
NSMutableDictionary *amzHeaders = [self S3Headers];
|
||||
NSString *canonicalizedAmzHeaders = @"";
|
||||
for (NSString *header in [amzHeaders keysSortedByValueUsingSelector:@selector(compare:)]) {
|
||||
canonicalizedAmzHeaders = [NSString stringWithFormat:@"%@%@:%@\n",canonicalizedAmzHeaders,[header lowercaseString],[amzHeaders objectForKey:header]];
|
||||
[self addRequestHeader:header value:[amzHeaders objectForKey:header]];
|
||||
}
|
||||
|
||||
// Jump through hoops while eating hot food
|
||||
NSString *stringToSign = [self stringToSignForHeaders:canonicalizedAmzHeaders resource:canonicalizedResource];
|
||||
NSString *signature = [ASIHTTPRequest base64forData:[ASIS3Request HMACSHA1withKey:[self secretAccessKey] forString:stringToSign]];
|
||||
NSString *authorizationString = [NSString stringWithFormat:@"AWS %@:%@",[self accessKey],signature];
|
||||
[self addRequestHeader:@"Authorization" value:authorizationString];
|
||||
|
||||
|
||||
}
|
||||
|
||||
- (void)requestFinished
|
||||
{
|
||||
if ([[[self responseHeaders] objectForKey:@"Content-Type"] isEqualToString:@"application/xml"]) {
|
||||
[self parseResponseXML];
|
||||
}
|
||||
if (![self error]) {
|
||||
[super requestFinished];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark Error XML parsing
|
||||
|
||||
- (void)parseResponseXML
|
||||
{
|
||||
NSData* xmlData = [self responseData];
|
||||
if (![xmlData length]) {
|
||||
return;
|
||||
}
|
||||
NSXMLParser *parser = [[[NSXMLParser alloc] initWithData:xmlData] autorelease];
|
||||
[self setCurrentXMLElementStack:[NSMutableArray array]];
|
||||
[parser setDelegate:self];
|
||||
[parser setShouldProcessNamespaces:NO];
|
||||
[parser setShouldReportNamespacePrefixes:NO];
|
||||
[parser setShouldResolveExternalEntities:NO];
|
||||
[parser parse];
|
||||
|
||||
}
|
||||
|
||||
- (void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError
|
||||
{
|
||||
[self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIS3ResponseParsingFailedType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Parsing the resposnse failed",NSLocalizedDescriptionKey,parseError,NSUnderlyingErrorKey,nil]]];
|
||||
}
|
||||
|
||||
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict
|
||||
{
|
||||
[self setCurrentXMLElementContent:@""];
|
||||
[[self currentXMLElementStack] addObject:elementName];
|
||||
}
|
||||
|
||||
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
|
||||
{
|
||||
[[self currentXMLElementStack] removeLastObject];
|
||||
if ([elementName isEqualToString:@"Message"]) {
|
||||
[self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIS3ResponseErrorType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[self currentXMLElementContent],NSLocalizedDescriptionKey,nil]]];
|
||||
// Handle S3 connection expiry errors
|
||||
} else if ([elementName isEqualToString:@"Code"]) {
|
||||
if ([[self currentXMLElementContent] isEqualToString:@"RequestTimeout"]) {
|
||||
if ([self retryUsingNewConnection]) {
|
||||
[parser abortParsing];
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
|
||||
{
|
||||
[self setCurrentXMLElementContent:[[self currentXMLElementContent] stringByAppendingString:string]];
|
||||
}
|
||||
|
||||
- (id)copyWithZone:(NSZone *)zone
|
||||
{
|
||||
ASIS3Request *newRequest = [super copyWithZone:zone];
|
||||
[newRequest setAccessKey:[self accessKey]];
|
||||
[newRequest setSecretAccessKey:[self secretAccessKey]];
|
||||
[newRequest setRequestScheme:[self requestScheme]];
|
||||
[newRequest setAccessPolicy:[self accessPolicy]];
|
||||
return newRequest;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark Shared access keys
|
||||
|
||||
+ (NSString *)sharedAccessKey
|
||||
{
|
||||
return sharedAccessKey;
|
||||
}
|
||||
|
||||
+ (void)setSharedAccessKey:(NSString *)newAccessKey
|
||||
{
|
||||
[sharedAccessKey release];
|
||||
sharedAccessKey = [newAccessKey retain];
|
||||
}
|
||||
|
||||
+ (NSString *)sharedSecretAccessKey
|
||||
{
|
||||
return sharedSecretAccessKey;
|
||||
}
|
||||
|
||||
+ (void)setSharedSecretAccessKey:(NSString *)newAccessKey
|
||||
{
|
||||
[sharedSecretAccessKey release];
|
||||
sharedSecretAccessKey = [newAccessKey retain];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark helpers
|
||||
|
||||
+ (NSString *)stringByURLEncodingForS3Path:(NSString *)key
|
||||
{
|
||||
if (!key) {
|
||||
return @"/";
|
||||
}
|
||||
NSString *path = [(NSString *)CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)key, NULL, CFSTR(":?#[]@!$ &'()*+,;=\"<>%{}|\\^~`"), CFStringConvertNSStringEncodingToEncoding(NSUTF8StringEncoding)) autorelease];
|
||||
if (![[path substringWithRange:NSMakeRange(0, 1)] isEqualToString:@"/"]) {
|
||||
path = [@"/" stringByAppendingString:path];
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
// Thanks to Tom Andersen for pointing out the threading issues and providing this code!
|
||||
+ (NSDateFormatter*)S3ResponseDateFormatter
|
||||
{
|
||||
// We store our date formatter in the calling thread's dictionary
|
||||
// NSDateFormatter is not thread-safe, this approach ensures each formatter is only used on a single thread
|
||||
// This formatter can be reused 1000 times in parsing a single response, so it would be expensive to keep creating new date formatters
|
||||
NSMutableDictionary *threadDict = [[NSThread currentThread] threadDictionary];
|
||||
NSDateFormatter *dateFormatter = [threadDict objectForKey:@"ASIS3ResponseDateFormatter"];
|
||||
if (dateFormatter == nil) {
|
||||
dateFormatter = [[[NSDateFormatter alloc] init] autorelease];
|
||||
[dateFormatter setLocale:[[[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"] autorelease]];
|
||||
[dateFormatter setTimeZone:[NSTimeZone timeZoneWithAbbreviation:@"UTC"]];
|
||||
[dateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss'.000Z'"];
|
||||
[threadDict setObject:dateFormatter forKey:@"ASIS3ResponseDateFormatter"];
|
||||
}
|
||||
return dateFormatter;
|
||||
}
|
||||
|
||||
+ (NSDateFormatter*)S3RequestDateFormatter
|
||||
{
|
||||
NSMutableDictionary *threadDict = [[NSThread currentThread] threadDictionary];
|
||||
NSDateFormatter *dateFormatter = [threadDict objectForKey:@"ASIS3RequestHeaderDateFormatter"];
|
||||
if (dateFormatter == nil) {
|
||||
dateFormatter = [[[NSDateFormatter alloc] init] autorelease];
|
||||
// Prevent problems with dates generated by other locales (tip from: http://rel.me/t/date/)
|
||||
[dateFormatter setLocale:[[[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"] autorelease]];
|
||||
[dateFormatter setTimeZone:[NSTimeZone timeZoneWithAbbreviation:@"UTC"]];
|
||||
[dateFormatter setDateFormat:@"EEE, d MMM yyyy HH:mm:ss Z"];
|
||||
[threadDict setObject:dateFormatter forKey:@"ASIS3RequestHeaderDateFormatter"];
|
||||
}
|
||||
return dateFormatter;
|
||||
|
||||
}
|
||||
|
||||
// From: http://stackoverflow.com/questions/476455/is-there-a-library-for-iphone-to-work-with-hmac-sha-1-encoding
|
||||
|
||||
+ (NSData *)HMACSHA1withKey:(NSString *)key forString:(NSString *)string
|
||||
{
|
||||
NSData *clearTextData = [string dataUsingEncoding:NSUTF8StringEncoding];
|
||||
NSData *keyData = [key dataUsingEncoding:NSUTF8StringEncoding];
|
||||
|
||||
uint8_t digest[CC_SHA1_DIGEST_LENGTH] = {0};
|
||||
|
||||
CCHmacContext hmacContext;
|
||||
CCHmacInit(&hmacContext, kCCHmacAlgSHA1, keyData.bytes, keyData.length);
|
||||
CCHmacUpdate(&hmacContext, clearTextData.bytes, clearTextData.length);
|
||||
CCHmacFinal(&hmacContext, digest);
|
||||
|
||||
return [NSData dataWithBytes:digest length:CC_SHA1_DIGEST_LENGTH];
|
||||
}
|
||||
|
||||
+ (NSString *)S3Host
|
||||
{
|
||||
return @"s3.amazonaws.com";
|
||||
}
|
||||
|
||||
- (void)buildURL
|
||||
{
|
||||
}
|
||||
|
||||
@synthesize dateString;
|
||||
@synthesize accessKey;
|
||||
@synthesize secretAccessKey;
|
||||
@synthesize currentXMLElementContent;
|
||||
@synthesize currentXMLElementStack;
|
||||
@synthesize accessPolicy;
|
||||
@synthesize requestScheme;
|
||||
@end
|
||||
31
projects/mtg/iOS/asi-http-request/S3/ASIS3ServiceRequest.h
Normal file
31
projects/mtg/iOS/asi-http-request/S3/ASIS3ServiceRequest.h
Normal file
@@ -0,0 +1,31 @@
|
||||
//
|
||||
// ASIS3ServiceRequest.h
|
||||
// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest
|
||||
//
|
||||
// Created by Ben Copsey on 16/03/2010.
|
||||
// Copyright 2010 All-Seeing Interactive. All rights reserved.
|
||||
//
|
||||
// Create an ASIS3ServiceRequest to obtain a list of your buckets
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "ASIS3Request.h"
|
||||
|
||||
@class ASIS3Bucket;
|
||||
|
||||
@interface ASIS3ServiceRequest : ASIS3Request {
|
||||
|
||||
// Internally used while parsing the response
|
||||
ASIS3Bucket *currentBucket;
|
||||
NSString *ownerName;
|
||||
NSString *ownerID;
|
||||
|
||||
// A list of the buckets stored on S3 for this account
|
||||
NSMutableArray *buckets;
|
||||
}
|
||||
|
||||
// Perform a GET request on the S3 service
|
||||
// This will fetch a list of the buckets attached to the S3 account
|
||||
+ (id)serviceRequest;
|
||||
|
||||
@property (retain, readonly) NSMutableArray *buckets;
|
||||
@end
|
||||
80
projects/mtg/iOS/asi-http-request/S3/ASIS3ServiceRequest.m
Normal file
80
projects/mtg/iOS/asi-http-request/S3/ASIS3ServiceRequest.m
Normal file
@@ -0,0 +1,80 @@
|
||||
//
|
||||
// ASIS3ServiceRequest.m
|
||||
// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest
|
||||
//
|
||||
// Created by Ben Copsey on 16/03/2010.
|
||||
// Copyright 2010 All-Seeing Interactive. All rights reserved.
|
||||
//
|
||||
|
||||
#import "ASIS3ServiceRequest.h"
|
||||
#import "ASIS3Bucket.h"
|
||||
|
||||
// Private stuff
|
||||
@interface ASIS3ServiceRequest ()
|
||||
@property (retain) NSMutableArray *buckets;
|
||||
@property (retain, nonatomic) ASIS3Bucket *currentBucket;
|
||||
@property (retain, nonatomic) NSString *ownerID;
|
||||
@property (retain, nonatomic) NSString *ownerName;
|
||||
@end
|
||||
|
||||
@implementation ASIS3ServiceRequest
|
||||
|
||||
+ (id)serviceRequest
|
||||
{
|
||||
ASIS3ServiceRequest *request = [[[self alloc] initWithURL:nil] autorelease];
|
||||
return request;
|
||||
}
|
||||
|
||||
- (id)initWithURL:(NSURL *)newURL
|
||||
{
|
||||
self = [super initWithURL:newURL];
|
||||
[self setBuckets:[[[NSMutableArray alloc] init] autorelease]];
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
[buckets release];
|
||||
[currentBucket release];
|
||||
[ownerID release];
|
||||
[ownerName release];
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (void)buildURL
|
||||
{
|
||||
[self setURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@://%@",[self requestScheme],[[self class] S3Host]]]];
|
||||
}
|
||||
|
||||
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict
|
||||
{
|
||||
if ([elementName isEqualToString:@"Bucket"]) {
|
||||
[self setCurrentBucket:[ASIS3Bucket bucketWithOwnerID:[self ownerID] ownerName:[self ownerName]]];
|
||||
}
|
||||
[super parser:parser didStartElement:elementName namespaceURI:namespaceURI qualifiedName:qName attributes:attributeDict];
|
||||
}
|
||||
|
||||
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
|
||||
{
|
||||
if ([elementName isEqualToString:@"Bucket"]) {
|
||||
[[self buckets] addObject:[self currentBucket]];
|
||||
[self setCurrentBucket:nil];
|
||||
} else if ([elementName isEqualToString:@"Name"]) {
|
||||
[[self currentBucket] setName:[self currentXMLElementContent]];
|
||||
} else if ([elementName isEqualToString:@"CreationDate"]) {
|
||||
[[self currentBucket] setCreationDate:[[ASIS3Request S3ResponseDateFormatter] dateFromString:[self currentXMLElementContent]]];
|
||||
} else if ([elementName isEqualToString:@"ID"]) {
|
||||
[self setOwnerID:[self currentXMLElementContent]];
|
||||
} else if ([elementName isEqualToString:@"DisplayName"]) {
|
||||
[self setOwnerName:[self currentXMLElementContent]];
|
||||
} else {
|
||||
// Let ASIS3Request look for error messages
|
||||
[super parser:parser didEndElement:elementName namespaceURI:namespaceURI qualifiedName:qName];
|
||||
}
|
||||
}
|
||||
|
||||
@synthesize buckets;
|
||||
@synthesize currentBucket;
|
||||
@synthesize ownerID;
|
||||
@synthesize ownerName;
|
||||
@end
|
||||
@@ -0,0 +1,18 @@
|
||||
//
|
||||
// ASICloudFilesRequestTests.h
|
||||
//
|
||||
// Created by Michael Mayo on 1/6/10.
|
||||
//
|
||||
|
||||
#import "ASITestCase.h"
|
||||
|
||||
@class ASINetworkQueue;
|
||||
|
||||
@interface ASICloudFilesRequestTests : ASITestCase {
|
||||
ASINetworkQueue *networkQueue;
|
||||
float progress;
|
||||
}
|
||||
|
||||
@property (retain,nonatomic) ASINetworkQueue *networkQueue;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,338 @@
|
||||
//
|
||||
// ASICloudFilesRequestTests.m
|
||||
//
|
||||
// Created by Michael Mayo on 1/6/10.
|
||||
//
|
||||
|
||||
#import "ASICloudFilesRequestTests.h"
|
||||
|
||||
// models
|
||||
#import "ASICloudFilesContainer.h"
|
||||
#import "ASICloudFilesObject.h"
|
||||
|
||||
// requests
|
||||
#import "ASICloudFilesRequest.h"
|
||||
#import "ASICloudFilesContainerRequest.h"
|
||||
#import "ASICloudFilesObjectRequest.h"
|
||||
#import "ASICloudFilesCDNRequest.h"
|
||||
|
||||
// Fill in these to run the tests that actually connect and manipulate objects on Cloud Files
|
||||
static NSString *username = @"";
|
||||
static NSString *apiKey = @"";
|
||||
|
||||
@implementation ASICloudFilesRequestTests
|
||||
|
||||
@synthesize networkQueue;
|
||||
|
||||
// Authenticate before any test if there's no auth token present
|
||||
- (void)authenticate {
|
||||
if (![ASICloudFilesRequest authToken]) {
|
||||
[ASICloudFilesRequest setUsername:username];
|
||||
[ASICloudFilesRequest setApiKey:apiKey];
|
||||
[ASICloudFilesRequest authenticate];
|
||||
}
|
||||
}
|
||||
|
||||
// ASICloudFilesRequest
|
||||
- (void)testAuthentication {
|
||||
[self authenticate];
|
||||
GHAssertNotNil([ASICloudFilesRequest authToken], @"Failed to authenticate and obtain authentication token");
|
||||
GHAssertNotNil([ASICloudFilesRequest storageURL], @"Failed to authenticate and obtain storage URL");
|
||||
GHAssertNotNil([ASICloudFilesRequest cdnManagementURL], @"Failed to authenticate and obtain CDN URL");
|
||||
}
|
||||
|
||||
- (void)testDateParser {
|
||||
ASICloudFilesRequest *request = [[[ASICloudFilesRequest alloc] init] autorelease];
|
||||
|
||||
NSDate *date = [request dateFromString:@"invalid date string"];
|
||||
GHAssertNil(date, @"Should have failed to parse an invalid date string");
|
||||
|
||||
date = [request dateFromString:@"2009-11-04T19:46:20.192723"];
|
||||
GHAssertNotNil(date, @"Failed to parse date string");
|
||||
|
||||
NSDateComponents *components = [[[NSDateComponents alloc] init] autorelease];
|
||||
[components setYear:2009];
|
||||
[components setMonth:11];
|
||||
[components setDay:4];
|
||||
[components setHour:19];
|
||||
[components setMinute:46];
|
||||
[components setSecond:20];
|
||||
NSCalendar *calendar = [[[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar] autorelease];
|
||||
NSDate *referenceDate = [calendar dateFromComponents:components];
|
||||
|
||||
// NSDateComponents has seconds as the smallest value, so we'll just check the created date is less than 1 second different from what we expect
|
||||
NSTimeInterval timeDifference = [date timeIntervalSinceDate:referenceDate];
|
||||
BOOL success = (timeDifference < 1.0);
|
||||
GHAssertTrue(success, @"Parsed date incorrectly");
|
||||
}
|
||||
|
||||
// ASICloudFilesContainerRequest
|
||||
- (void)testAccountInfo {
|
||||
[self authenticate];
|
||||
|
||||
ASICloudFilesContainerRequest *request = [ASICloudFilesContainerRequest accountInfoRequest];
|
||||
[request startSynchronous];
|
||||
|
||||
GHAssertTrue([request containerCount] > 0, @"Failed to retrieve account info");
|
||||
GHAssertTrue([request bytesUsed] > 0, @"Failed to retrieve account info");
|
||||
}
|
||||
|
||||
- (void)testContainerList {
|
||||
[self authenticate];
|
||||
|
||||
NSArray *containers = nil;
|
||||
|
||||
ASICloudFilesContainerRequest *containerListRequest = [ASICloudFilesContainerRequest listRequest];
|
||||
[containerListRequest startSynchronous];
|
||||
|
||||
containers = [containerListRequest containers];
|
||||
GHAssertTrue([containers count] > 0, @"Failed to list containers");
|
||||
NSUInteger i;
|
||||
for (i = 0; i < [containers count]; i++) {
|
||||
ASICloudFilesContainer *container = [containers objectAtIndex:i];
|
||||
GHAssertNotNil(container.name, @"Failed to parse container");
|
||||
}
|
||||
|
||||
ASICloudFilesContainerRequest *limitContainerListRequest = [ASICloudFilesContainerRequest listRequestWithLimit:2 marker:nil];
|
||||
[limitContainerListRequest startSynchronous];
|
||||
containers = [limitContainerListRequest containers];
|
||||
GHAssertTrue([containers count] == 2, @"Failed to limit container list");
|
||||
}
|
||||
|
||||
- (void)testContainerCreate {
|
||||
[self authenticate];
|
||||
|
||||
ASICloudFilesContainerRequest *createContainerRequest = [ASICloudFilesContainerRequest createContainerRequest:@"ASICloudFilesContainerTest"];
|
||||
[createContainerRequest startSynchronous];
|
||||
GHAssertTrue([createContainerRequest error] == nil, @"Failed to create container");
|
||||
}
|
||||
|
||||
- (void)testContainerDelete {
|
||||
[self authenticate];
|
||||
|
||||
ASICloudFilesContainerRequest *deleteContainerRequest = [ASICloudFilesContainerRequest deleteContainerRequest:@"ASICloudFilesContainerTest"];
|
||||
[deleteContainerRequest startSynchronous];
|
||||
GHAssertTrue([deleteContainerRequest error] == nil, @"Failed to delete container");
|
||||
}
|
||||
|
||||
// ASICloudFilesObjectRequest
|
||||
- (void)testContainerInfo {
|
||||
[self authenticate];
|
||||
|
||||
// create a file first
|
||||
ASICloudFilesContainerRequest *createContainerRequest = [ASICloudFilesContainerRequest createContainerRequest:@"ASICloudFilesTest"];
|
||||
[createContainerRequest startSynchronous];
|
||||
NSData *data = [@"this is a test" dataUsingEncoding:NSUTF8StringEncoding];
|
||||
ASICloudFilesObjectRequest *putRequest
|
||||
= [ASICloudFilesObjectRequest putObjectRequestWithContainer:@"ASICloudFilesTest"
|
||||
objectPath:@"infotestfile.txt" contentType:@"text/plain"
|
||||
objectData:data metadata:nil etag:nil];
|
||||
|
||||
[putRequest startSynchronous];
|
||||
|
||||
ASICloudFilesObjectRequest *request = [ASICloudFilesObjectRequest containerInfoRequest:@"ASICloudFilesTest"];
|
||||
[request startSynchronous];
|
||||
GHAssertTrue([request containerObjectCount] > 0, @"Failed to retrieve container info");
|
||||
GHAssertTrue([request containerBytesUsed] > 0, @"Failed to retrieve container info");
|
||||
}
|
||||
|
||||
- (void)testObjectInfo {
|
||||
[self authenticate];
|
||||
|
||||
ASICloudFilesObjectRequest *request = [ASICloudFilesObjectRequest objectInfoRequest:@"ASICloudFilesTest" objectPath:@"infotestfile.txt"];
|
||||
[request startSynchronous];
|
||||
|
||||
ASICloudFilesObject *object = [request object];
|
||||
GHAssertNotNil(object, @"Failed to retrieve object");
|
||||
GHAssertTrue([object.metadata count] > 0, @"Failed to parse metadata");
|
||||
|
||||
GHAssertTrue([object.metadata objectForKey:@"Test"] != nil, @"Failed to parse metadata");
|
||||
|
||||
}
|
||||
|
||||
- (void)testObjectList {
|
||||
[self authenticate];
|
||||
|
||||
ASICloudFilesObjectRequest *objectListRequest = [ASICloudFilesObjectRequest listRequestWithContainer:@"ASICloudFilesTest"];
|
||||
[objectListRequest startSynchronous];
|
||||
|
||||
NSArray *containers = [objectListRequest objects];
|
||||
GHAssertTrue([containers count] > 0, @"Failed to list objects");
|
||||
NSUInteger i;
|
||||
for (i = 0; i < [containers count]; i++) {
|
||||
ASICloudFilesObject *object = [containers objectAtIndex:i];
|
||||
GHAssertNotNil(object.name, @"Failed to parse object");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
- (void)testGetObject {
|
||||
[self authenticate];
|
||||
|
||||
ASICloudFilesObjectRequest *request = [ASICloudFilesObjectRequest getObjectRequestWithContainer:@"ASICloudFilesTest" objectPath:@"infotestfile.txt"];
|
||||
[request startSynchronous];
|
||||
|
||||
ASICloudFilesObject *object = [request object];
|
||||
GHAssertNotNil(object, @"Failed to retrieve object");
|
||||
|
||||
GHAssertNotNil(object.name, @"Failed to parse object name");
|
||||
GHAssertTrue(object.bytes > 0, @"Failed to parse object bytes");
|
||||
GHAssertNotNil(object.contentType, @"Failed to parse object content type");
|
||||
GHAssertNotNil(object.lastModified, @"Failed to parse object last modified");
|
||||
GHAssertNotNil(object.data, @"Failed to parse object data");
|
||||
}
|
||||
|
||||
- (void)testPutObject {
|
||||
[self authenticate];
|
||||
|
||||
ASICloudFilesContainerRequest *createContainerRequest
|
||||
= [ASICloudFilesContainerRequest createContainerRequest:@"ASICloudFilesTest"];
|
||||
[createContainerRequest startSynchronous];
|
||||
|
||||
NSData *data = [@"this is a test" dataUsingEncoding:NSUTF8StringEncoding];
|
||||
|
||||
ASICloudFilesObjectRequest *putRequest
|
||||
= [ASICloudFilesObjectRequest putObjectRequestWithContainer:@"ASICloudFilesTest"
|
||||
objectPath:@"puttestfile.txt" contentType:@"text/plain"
|
||||
objectData:data metadata:nil etag:nil];
|
||||
|
||||
[putRequest startSynchronous];
|
||||
|
||||
GHAssertNil([putRequest error], @"Failed to PUT object");
|
||||
|
||||
ASICloudFilesObjectRequest *getRequest = [ASICloudFilesObjectRequest getObjectRequestWithContainer:@"ASICloudFilesTest" objectPath:@"puttestfile.txt"];
|
||||
[getRequest startSynchronous];
|
||||
|
||||
ASICloudFilesObject *object = [getRequest object];
|
||||
NSString *string = [[NSString alloc] initWithData:object.data encoding:NSASCIIStringEncoding];
|
||||
|
||||
GHAssertNotNil(object, @"Failed to retrieve new object");
|
||||
GHAssertNotNil(object.name, @"Failed to parse object name");
|
||||
GHAssertEqualStrings(object.name, @"puttestfile.txt", @"Failed to parse object name", @"Failed to parse object name");
|
||||
GHAssertNotNil(object.data, @"Failed to parse object data");
|
||||
GHAssertEqualStrings(string, @"this is a test", @"Failed to parse object data", @"Failed to parse object data");
|
||||
|
||||
|
||||
ASICloudFilesContainerRequest *deleteContainerRequest = [ASICloudFilesContainerRequest deleteContainerRequest:@"ASICloudFilesTest"];
|
||||
[deleteContainerRequest startSynchronous];
|
||||
|
||||
// Now put the object from a file
|
||||
|
||||
createContainerRequest = [ASICloudFilesContainerRequest createContainerRequest:@"ASICloudFilesTest"];
|
||||
[createContainerRequest startSynchronous];
|
||||
|
||||
NSString *filePath = [[self filePathForTemporaryTestFiles] stringByAppendingPathComponent:@"cloudfile"];
|
||||
[data writeToFile:filePath atomically:NO];
|
||||
|
||||
putRequest = [ASICloudFilesObjectRequest putObjectRequestWithContainer:@"ASICloudFilesTest" objectPath:@"puttestfile.txt" contentType:@"text/plain" file:filePath metadata:nil etag:nil];
|
||||
|
||||
[putRequest startSynchronous];
|
||||
|
||||
GHAssertNil([putRequest error], @"Failed to PUT object");
|
||||
|
||||
getRequest = [ASICloudFilesObjectRequest getObjectRequestWithContainer:@"ASICloudFilesTest" objectPath:@"puttestfile.txt"];
|
||||
[getRequest startSynchronous];
|
||||
|
||||
object = [getRequest object];
|
||||
|
||||
GHAssertNotNil(object, @"Failed to retrieve new object");
|
||||
GHAssertNotNil(object.name, @"Failed to parse object name");
|
||||
GHAssertEqualStrings(object.name, @"puttestfile.txt", @"Failed to parse object name", @"Failed to parse object name");
|
||||
GHAssertNotNil(object.data, @"Failed to parse object data");
|
||||
GHAssertEqualStrings(string, @"this is a test", @"Failed to parse object data", @"Failed to parse object data");
|
||||
|
||||
[string release];
|
||||
|
||||
deleteContainerRequest = [ASICloudFilesContainerRequest deleteContainerRequest:@"ASICloudFilesTest"];
|
||||
[deleteContainerRequest startSynchronous];
|
||||
}
|
||||
|
||||
- (void)testPostObject {
|
||||
[self authenticate];
|
||||
|
||||
NSMutableDictionary *metadata = [[NSMutableDictionary alloc] initWithCapacity:2];
|
||||
[metadata setObject:@"test" forKey:@"Test"];
|
||||
[metadata setObject:@"test" forKey:@"ASITest"];
|
||||
|
||||
ASICloudFilesObject *object = [ASICloudFilesObject object];
|
||||
object.name = @"infotestfile.txt";
|
||||
object.metadata = metadata;
|
||||
|
||||
ASICloudFilesObjectRequest *request = [ASICloudFilesObjectRequest postObjectRequestWithContainer:@"ASICloudFilesTest" object:object];
|
||||
[request startSynchronous];
|
||||
|
||||
GHAssertTrue([request responseStatusCode] == 202, @"Failed to post object metadata");
|
||||
|
||||
[metadata release];
|
||||
|
||||
}
|
||||
|
||||
- (void)testDeleteObject {
|
||||
[self authenticate];
|
||||
|
||||
ASICloudFilesObjectRequest *deleteRequest = [ASICloudFilesObjectRequest deleteObjectRequestWithContainer:@"ASICloudFilesTest" objectPath:@"puttestfile.txt"];
|
||||
[deleteRequest startSynchronous];
|
||||
GHAssertTrue([deleteRequest responseStatusCode] == 204, @"Failed to delete object");
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark CDN Tests
|
||||
|
||||
- (void)testCDNContainerInfo {
|
||||
[self authenticate];
|
||||
|
||||
ASICloudFilesCDNRequest *request = [ASICloudFilesCDNRequest containerInfoRequest:@"ASICloudFilesTest"];
|
||||
[request startSynchronous];
|
||||
|
||||
GHAssertTrue([request responseStatusCode] == 204, @"Failed to retrieve CDN container info");
|
||||
GHAssertTrue([request cdnEnabled], @"Failed to retrieve CDN container info");
|
||||
GHAssertNotNil([request cdnURI], @"Failed to retrieve CDN container info");
|
||||
GHAssertTrue([request cdnTTL] > 0, @"Failed to retrieve CDN container info");
|
||||
}
|
||||
|
||||
- (void)testCDNContainerList {
|
||||
[self authenticate];
|
||||
|
||||
ASICloudFilesCDNRequest *request = [ASICloudFilesCDNRequest listRequest];
|
||||
[request startSynchronous];
|
||||
|
||||
GHAssertNotNil([request containers], @"Failed to retrieve CDN container list");
|
||||
}
|
||||
|
||||
- (void)testCDNContainerListWithParams {
|
||||
[self authenticate];
|
||||
|
||||
ASICloudFilesCDNRequest *request = [ASICloudFilesCDNRequest listRequestWithLimit:2 marker:nil enabledOnly:YES];
|
||||
[request startSynchronous];
|
||||
|
||||
GHAssertNotNil([request containers], @"Failed to retrieve CDN container list");
|
||||
GHAssertTrue([[request containers] count] == 2, @"Failed to retrieve limited CDN container list");
|
||||
}
|
||||
|
||||
- (void)testCDNPut {
|
||||
[self authenticate];
|
||||
|
||||
ASICloudFilesCDNRequest *request = [ASICloudFilesCDNRequest putRequestWithContainer:@"ASICloudFilesTest"];
|
||||
[request startSynchronous];
|
||||
|
||||
GHAssertNotNil([request cdnURI], @"Failed to PUT to CDN container");
|
||||
}
|
||||
|
||||
- (void)testCDNPost {
|
||||
[self authenticate];
|
||||
|
||||
ASICloudFilesCDNRequest *request = [ASICloudFilesCDNRequest postRequestWithContainer:@"ASICloudFilesTest" cdnEnabled:YES ttl:86600];
|
||||
[request startSynchronous];
|
||||
|
||||
GHAssertNotNil([request cdnURI], @"Failed to POST to CDN container");
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Memory Management
|
||||
|
||||
-(void)dealloc {
|
||||
[networkQueue release];
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,16 @@
|
||||
//
|
||||
// ASIDataCompressorTests.h
|
||||
// Mac
|
||||
//
|
||||
// Created by Ben Copsey on 17/08/2010.
|
||||
// Copyright 2010 All-Seeing Interactive. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "ASITestCase.h"
|
||||
|
||||
@interface ASIDataCompressorTests : ASITestCase {
|
||||
|
||||
}
|
||||
|
||||
@end
|
||||
179
projects/mtg/iOS/asi-http-request/Tests/ASIDataCompressorTests.m
Normal file
179
projects/mtg/iOS/asi-http-request/Tests/ASIDataCompressorTests.m
Normal file
@@ -0,0 +1,179 @@
|
||||
//
|
||||
// ASIDataCompressorTests.m
|
||||
// Mac
|
||||
//
|
||||
// Created by Ben Copsey on 17/08/2010.
|
||||
// Copyright 2010 All-Seeing Interactive. All rights reserved.
|
||||
//
|
||||
// Sadly these tests only work on Mac because of the dependency on NSTask, but I'm fairly sure this class should behave in the same way on iOS
|
||||
|
||||
#import "ASIDataCompressorTests.h"
|
||||
#import "ASIDataCompressor.h"
|
||||
#import "ASIDataDecompressor.h"
|
||||
#import "ASIHTTPRequest.h"
|
||||
|
||||
@implementation ASIDataCompressorTests
|
||||
|
||||
- (void)setUp
|
||||
{
|
||||
NSFileManager *fileManager = [[[NSFileManager alloc] init] autorelease];
|
||||
|
||||
// Download a 1.7MB text file
|
||||
NSString *filePath = [[self filePathForTemporaryTestFiles] stringByAppendingPathComponent:@"story.txt"];
|
||||
if (![fileManager fileExistsAtPath:filePath] || [[[fileManager attributesOfItemAtPath:filePath error:NULL] objectForKey:NSFileSize] unsignedLongLongValue] < 1693961) {
|
||||
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:@"http://allseeing-i.com/ASIHTTPRequest/tests/the_hound_of_the_baskervilles.text"]];
|
||||
[request setDownloadDestinationPath:[[self filePathForTemporaryTestFiles] stringByAppendingPathComponent:@"story.txt"]];
|
||||
[request startSynchronous];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)testInflateData
|
||||
{
|
||||
|
||||
NSString *originalString = [NSString stringWithContentsOfFile:[[self filePathForTemporaryTestFiles] stringByAppendingPathComponent:@"story.txt"] encoding:NSUTF8StringEncoding error:NULL];
|
||||
|
||||
// Test in-memory inflate using uncompressData:error:
|
||||
NSError *error = nil;
|
||||
NSString *filePath = [[self filePathForTemporaryTestFiles] stringByAppendingPathComponent:@"uncompressed_file.txt"];
|
||||
NSString *gzippedFilePath = [[self filePathForTemporaryTestFiles] stringByAppendingPathComponent:@"uncompressed_file.txt.gz"];
|
||||
[ASIHTTPRequest removeFileAtPath:gzippedFilePath error:&error];
|
||||
if (error) {
|
||||
GHFail(@"Failed to remove old file, cannot proceed with test");
|
||||
}
|
||||
[originalString writeToFile:filePath atomically:NO encoding:NSUTF8StringEncoding error:&error];
|
||||
if (error) {
|
||||
GHFail(@"Failed to write string, cannot proceed with test");
|
||||
}
|
||||
|
||||
NSTask *task = [[[NSTask alloc] init] autorelease];
|
||||
[task setLaunchPath:@"/usr/bin/gzip"];
|
||||
[task setArguments:[NSArray arrayWithObject:filePath]];
|
||||
[task launch];
|
||||
[task waitUntilExit];
|
||||
|
||||
NSData *deflatedData = [NSData dataWithContentsOfFile:gzippedFilePath];
|
||||
|
||||
NSData *inflatedData = [ASIDataDecompressor uncompressData:deflatedData error:&error];
|
||||
if (error) {
|
||||
GHFail(@"Inflate failed because %@",error);
|
||||
}
|
||||
|
||||
NSString *inflatedString = [[[NSString alloc] initWithBytes:[inflatedData bytes] length:[inflatedData length] encoding:NSUTF8StringEncoding] autorelease];
|
||||
|
||||
|
||||
BOOL success = [inflatedString isEqualToString:originalString];
|
||||
GHAssertTrue(success,@"inflated data is not the same as original");
|
||||
|
||||
// Test file to file inflate
|
||||
NSString *inflatedFilePath = [[self filePathForTemporaryTestFiles] stringByAppendingPathComponent:@"inflated_file.txt"];
|
||||
[ASIHTTPRequest removeFileAtPath:inflatedFilePath error:&error];
|
||||
if (error) {
|
||||
GHFail(@"Failed to remove old file, cannot proceed with test");
|
||||
}
|
||||
|
||||
if (![ASIDataDecompressor uncompressDataFromFile:gzippedFilePath toFile:inflatedFilePath error:&error]) {
|
||||
GHFail(@"Inflate failed because %@",error);
|
||||
}
|
||||
|
||||
originalString = [NSString stringWithContentsOfFile:inflatedFilePath encoding:NSUTF8StringEncoding error:&error];
|
||||
if (error) {
|
||||
GHFail(@"Failed to read the inflated data, cannot proceed with test");
|
||||
}
|
||||
|
||||
success = [inflatedString isEqualToString:originalString];
|
||||
GHAssertTrue(success,@"inflated data is not the same as original");
|
||||
|
||||
}
|
||||
|
||||
- (void)testDeflateData
|
||||
{
|
||||
|
||||
NSString *originalString = [NSString stringWithContentsOfFile:[[self filePathForTemporaryTestFiles] stringByAppendingPathComponent:@"story.txt"] encoding:NSUTF8StringEncoding error:NULL];
|
||||
|
||||
// Test in-memory deflate using compressData:error:
|
||||
NSError *error = nil;
|
||||
NSData *deflatedData = [ASIDataCompressor compressData:[originalString dataUsingEncoding:NSUTF8StringEncoding] error:&error];
|
||||
if (error) {
|
||||
GHFail(@"Failed to deflate the data");
|
||||
}
|
||||
|
||||
NSString *gzippedFilePath = [[self filePathForTemporaryTestFiles] stringByAppendingPathComponent:@"uncompressed_file.txt.gz"];
|
||||
[ASIHTTPRequest removeFileAtPath:gzippedFilePath error:&error];
|
||||
if (error) {
|
||||
GHFail(@"Failed to remove old file, cannot proceed with test");
|
||||
}
|
||||
|
||||
[deflatedData writeToFile:gzippedFilePath options:0 error:&error];
|
||||
if (error) {
|
||||
GHFail(@"Failed to write data, cannot proceed with test");
|
||||
}
|
||||
|
||||
NSString *filePath = [[self filePathForTemporaryTestFiles] stringByAppendingPathComponent:@"uncompressed_file.txt"];
|
||||
[ASIHTTPRequest removeFileAtPath:filePath error:&error];
|
||||
if (error) {
|
||||
GHFail(@"Failed to remove old file, cannot proceed with test");
|
||||
}
|
||||
|
||||
NSTask *task = [[[NSTask alloc] init] autorelease];
|
||||
[task setLaunchPath:@"/usr/bin/gzip"];
|
||||
[task setArguments:[NSArray arrayWithObjects:@"-d",gzippedFilePath,nil]];
|
||||
[task launch];
|
||||
[task waitUntilExit];
|
||||
|
||||
NSString *inflatedString = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:&error];
|
||||
if (error) {
|
||||
GHFail(@"Failed to read the inflated data, cannot proceed with test");
|
||||
}
|
||||
|
||||
BOOL success = [inflatedString isEqualToString:originalString];
|
||||
GHAssertTrue(success,@"inflated data is not the same as original");
|
||||
|
||||
|
||||
// Test file to file deflate
|
||||
[ASIHTTPRequest removeFileAtPath:gzippedFilePath error:&error];
|
||||
|
||||
if (![ASIDataCompressor compressDataFromFile:filePath toFile:gzippedFilePath error:&error]) {
|
||||
GHFail(@"Deflate failed because %@",error);
|
||||
}
|
||||
[ASIHTTPRequest removeFileAtPath:filePath error:&error];
|
||||
|
||||
task = [[[NSTask alloc] init] autorelease];
|
||||
[task setLaunchPath:@"/usr/bin/gzip"];
|
||||
[task setArguments:[NSArray arrayWithObjects:@"-d",gzippedFilePath,nil]];
|
||||
[task launch];
|
||||
[task waitUntilExit];
|
||||
|
||||
inflatedString = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:&error];
|
||||
|
||||
success = ([inflatedString isEqualToString:originalString]);
|
||||
GHAssertTrue(success,@"deflate data is not the same as that generated by gzip");
|
||||
|
||||
// Test for bug https://github.com/pokeb/asi-http-request/issues/147
|
||||
[ASIHTTPRequest removeFileAtPath:gzippedFilePath error:&error];
|
||||
[ASIHTTPRequest removeFileAtPath:filePath error:&error];
|
||||
|
||||
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:@"http://spaceharvest.com/i/screen6.png"]];
|
||||
[request setDownloadDestinationPath:filePath];
|
||||
[request startSynchronous];
|
||||
|
||||
if (![ASIDataCompressor compressDataFromFile:filePath toFile:gzippedFilePath error:&error]) {
|
||||
GHFail(@"Deflate failed because %@",error);
|
||||
}
|
||||
|
||||
unsigned long long originalFileSize = [[[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:&error] fileSize];
|
||||
[ASIHTTPRequest removeFileAtPath:filePath error:&error];
|
||||
|
||||
task = [[[NSTask alloc] init] autorelease];
|
||||
[task setLaunchPath:@"/usr/bin/gzip"];
|
||||
[task setArguments:[NSArray arrayWithObjects:@"-d",gzippedFilePath,nil]];
|
||||
[task launch];
|
||||
[task waitUntilExit];
|
||||
|
||||
unsigned long long inflatedFileSize = [[[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:&error] fileSize];
|
||||
|
||||
success = (originalFileSize == inflatedFileSize);
|
||||
GHAssertTrue(success,@"inflated data is not the same size as the original");
|
||||
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,17 @@
|
||||
//
|
||||
// ASIDownloadCacheTests.h
|
||||
// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest
|
||||
//
|
||||
// Created by Ben Copsey on 03/05/2010.
|
||||
// Copyright 2010 All-Seeing Interactive. All rights reserved.
|
||||
//
|
||||
|
||||
#import "ASITestCase.h"
|
||||
|
||||
|
||||
@interface ASIDownloadCacheTests : ASITestCase {
|
||||
NSUInteger requestsFinishedCount;
|
||||
BOOL requestRedirectedWasCalled;
|
||||
}
|
||||
|
||||
@end
|
||||
562
projects/mtg/iOS/asi-http-request/Tests/ASIDownloadCacheTests.m
Normal file
562
projects/mtg/iOS/asi-http-request/Tests/ASIDownloadCacheTests.m
Normal file
@@ -0,0 +1,562 @@
|
||||
//
|
||||
// ASIDownloadCacheTests.m
|
||||
// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest
|
||||
//
|
||||
// Created by Ben Copsey on 03/05/2010.
|
||||
// Copyright 2010 All-Seeing Interactive. All rights reserved.
|
||||
//
|
||||
|
||||
#import "ASIDownloadCacheTests.h"
|
||||
#import "ASIDownloadCache.h"
|
||||
#import "ASIHTTPRequest.h"
|
||||
|
||||
// Stop clang complaining about undeclared selectors
|
||||
@interface ASIDownloadCacheTests ()
|
||||
- (void)runCacheOnlyCallsRequestFinishedOnceTest;
|
||||
- (void)finishCached:(ASIHTTPRequest *)request;
|
||||
- (void)runRedirectTest;
|
||||
@end
|
||||
|
||||
|
||||
@implementation ASIDownloadCacheTests
|
||||
|
||||
- (void)testDownloadCache
|
||||
{
|
||||
[[ASIDownloadCache sharedCache] clearCachedResponsesForStoragePolicy:ASICacheForSessionDurationCacheStoragePolicy];
|
||||
[[ASIDownloadCache sharedCache] clearCachedResponsesForStoragePolicy:ASICachePermanentlyCacheStoragePolicy];
|
||||
[[ASIDownloadCache sharedCache] setDefaultCachePolicy:ASIUseDefaultCachePolicy];
|
||||
[ASIHTTPRequest setDefaultCache:nil];
|
||||
|
||||
// Ensure a request without a download cache does not pull from the cache
|
||||
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:@"http://allseeing-i.com/ASIHTTPRequest/tests/cache-away"]];
|
||||
[request startSynchronous];
|
||||
BOOL success = ![request didUseCachedResponse];
|
||||
GHAssertTrue(success,@"Used cached response when we shouldn't have");
|
||||
|
||||
// Make all requests use the cache
|
||||
[ASIHTTPRequest setDefaultCache:[ASIDownloadCache sharedCache]];
|
||||
|
||||
// Check a request isn't setting didUseCachedResponse when the data is not in the cache
|
||||
request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:@"http://allseeing-i.com/ASIHTTPRequest/tests/cache-away"]];
|
||||
[request startSynchronous];
|
||||
success = ![request didUseCachedResponse];
|
||||
GHAssertTrue(success,@"Cached response should not have been available");
|
||||
|
||||
// Test read from the cache
|
||||
request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:@"http://allseeing-i.com/ASIHTTPRequest/tests/cache-away"]];
|
||||
[request startSynchronous];
|
||||
success = [request didUseCachedResponse];
|
||||
GHAssertTrue(success,@"Failed to use cached response");
|
||||
|
||||
// Test preventing reads from the cache
|
||||
[[ASIDownloadCache sharedCache] setShouldRespectCacheControlHeaders:YES];
|
||||
request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:@"http://allseeing-i.com/ASIHTTPRequest/tests/cache-away"]];
|
||||
[request setCachePolicy:ASIAskServerIfModifiedWhenStaleCachePolicy|ASIDoNotReadFromCacheCachePolicy];
|
||||
[request startSynchronous];
|
||||
success = ![request didUseCachedResponse];
|
||||
GHAssertTrue(success,@"Used the cache when reads were not enabled");
|
||||
|
||||
//Empty the cache
|
||||
[[ASIDownloadCache sharedCache] clearCachedResponsesForStoragePolicy:ASICacheForSessionDurationCacheStoragePolicy];
|
||||
|
||||
// Test preventing writes to the cache
|
||||
request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:@"http://allseeing-i.com/ASIHTTPRequest/tests/cache-away"]];
|
||||
[request setCachePolicy:ASIAskServerIfModifiedWhenStaleCachePolicy|ASIDoNotWriteToCacheCachePolicy];
|
||||
[request startSynchronous];
|
||||
|
||||
request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:@"http://allseeing-i.com/ASIHTTPRequest/tests/cache-away"]];
|
||||
[request setCachePolicy:ASIAskServerIfModifiedWhenStaleCachePolicy];
|
||||
[request startSynchronous];
|
||||
success = ![request didUseCachedResponse];
|
||||
GHAssertTrue(success,@"Used cached response when the cache should have been empty");
|
||||
|
||||
// Test respecting etag
|
||||
request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:@"http://allseeing-i.com/ASIHTTPRequest/tests/content-always-new"]];
|
||||
[request startSynchronous];
|
||||
success = ![request didUseCachedResponse];
|
||||
GHAssertTrue(success,@"Used cached response when we shouldn't have");
|
||||
|
||||
// Etag will be different on the second request
|
||||
request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:@"http://allseeing-i.com/ASIHTTPRequest/tests/content-always-new"]];
|
||||
|
||||
// Note: we are forcing it to perform a conditional GET
|
||||
[request setCachePolicy:ASIDoNotReadFromCacheCachePolicy|ASIAskServerIfModifiedCachePolicy];
|
||||
[request startSynchronous];
|
||||
success = ![request didUseCachedResponse];
|
||||
GHAssertTrue(success,@"Used cached response when we shouldn't have");
|
||||
|
||||
// Test ignoring server headers
|
||||
request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:@"http://allseeing-i.com/ASIHTTPRequest/tests/no-cache"]];
|
||||
[[ASIDownloadCache sharedCache] setShouldRespectCacheControlHeaders:NO];
|
||||
[request startSynchronous];
|
||||
|
||||
request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:@"http://allseeing-i.com/ASIHTTPRequest/tests/no-cache"]];
|
||||
[[ASIDownloadCache sharedCache] setShouldRespectCacheControlHeaders:NO];
|
||||
[request startSynchronous];
|
||||
|
||||
success = [request didUseCachedResponse];
|
||||
GHAssertTrue(success,@"Failed to use cached response");
|
||||
|
||||
// Test ASIOnlyLoadIfNotCachedCachePolicy
|
||||
[[ASIDownloadCache sharedCache] setShouldRespectCacheControlHeaders:YES];
|
||||
request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:@"http://allseeing-i.com/ASIHTTPRequest/tests/content-always-new"]];
|
||||
[[ASIDownloadCache sharedCache] setDefaultCachePolicy:ASIOnlyLoadIfNotCachedCachePolicy];
|
||||
[request startSynchronous];
|
||||
success = [request didUseCachedResponse];
|
||||
GHAssertTrue(success,@"Failed to use cached response");
|
||||
|
||||
// Test clearing the cache
|
||||
[[ASIDownloadCache sharedCache] clearCachedResponsesForStoragePolicy:ASICacheForSessionDurationCacheStoragePolicy];
|
||||
|
||||
request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:@"http://allseeing-i.com/ASIHTTPRequest/tests/cache-away"]];
|
||||
[request startSynchronous];
|
||||
success = ![request didUseCachedResponse];
|
||||
GHAssertTrue(success,@"Cached response should not have been available");
|
||||
|
||||
// Test ASIAskServerIfModifiedWhenStaleCachePolicy
|
||||
[[ASIDownloadCache sharedCache] setDefaultCachePolicy:ASIUseDefaultCachePolicy];
|
||||
request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:@"http://allseeing-i.com/ASIHTTPRequest/tests/content-always-new"]];
|
||||
[request setSecondsToCache:2];
|
||||
[request startSynchronous];
|
||||
|
||||
// This request should not go to the network
|
||||
request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:@"http://allseeing-i.com/ASIHTTPRequest/tests/content-always-new"]];
|
||||
[request startSynchronous];
|
||||
success = [request didUseCachedResponse];
|
||||
GHAssertTrue(success,@"Failed to use cached response");
|
||||
|
||||
[NSThread sleepForTimeInterval:2];
|
||||
|
||||
// This request will perform a conditional GET
|
||||
request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:@"http://allseeing-i.com/ASIHTTPRequest/tests/content-always-new"]];
|
||||
[request setSecondsToCache:2];
|
||||
[request startSynchronous];
|
||||
success = ![request didUseCachedResponse];
|
||||
GHAssertTrue(success,@"Failed to use cached response");
|
||||
|
||||
// Test ASIFallbackToCacheIfLoadFailsCachePolicy
|
||||
// Store something in the cache
|
||||
[request setURL:[NSURL URLWithString:@"http://"]];
|
||||
[request setResponseHeaders:[NSDictionary dictionaryWithObject:@"test" forKey:@"test"]];
|
||||
[request setRawResponseData:(NSMutableData *)[@"test" dataUsingEncoding:NSUTF8StringEncoding]];
|
||||
[[ASIDownloadCache sharedCache] storeResponseForRequest:request maxAge:0];
|
||||
|
||||
request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:@"http://"]];
|
||||
[request setCachePolicy:ASIFallbackToCacheIfLoadFailsCachePolicy];
|
||||
[request startSynchronous];
|
||||
|
||||
success = [request didUseCachedResponse];
|
||||
GHAssertTrue(success,@"Failed to use cached response");
|
||||
|
||||
success = [[request responseString] isEqualToString:@"test"];
|
||||
GHAssertTrue(success,@"Failed to read cached response");
|
||||
|
||||
success = [[[request responseHeaders] valueForKey:@"test"] isEqualToString:@"test"];
|
||||
GHAssertTrue(success,@"Failed to read cached response headers");
|
||||
|
||||
// Remove the stuff from the cache, and try again
|
||||
[request setURL:[NSURL URLWithString:@"http://"]];
|
||||
[[ASIDownloadCache sharedCache] removeCachedDataForRequest:request];
|
||||
|
||||
request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:@"http://"]];
|
||||
[request setCachePolicy:ASIFallbackToCacheIfLoadFailsCachePolicy];
|
||||
[request startSynchronous];
|
||||
|
||||
success = ![request didUseCachedResponse];
|
||||
GHAssertTrue(success,@"Request says it used a cached response, but there wasn't one to use");
|
||||
|
||||
success = ([request error] != nil);
|
||||
GHAssertTrue(success,@"Request had no error set");
|
||||
|
||||
// Cache some data
|
||||
NSURL *url = [NSURL URLWithString:@"http://allseeing-i.com/ASIHTTPRequest/tests/cache-away"];
|
||||
request = [ASIHTTPRequest requestWithURL:url];
|
||||
[request startSynchronous];
|
||||
|
||||
NSString *path = [[ASIDownloadCache sharedCache] pathToStoreCachedResponseDataForRequest:request];
|
||||
success = (path != nil);
|
||||
GHAssertTrue(success,@"Cache failed to store data");
|
||||
|
||||
path = [[ASIDownloadCache sharedCache] pathToStoreCachedResponseHeadersForRequest:request];
|
||||
success = (path != nil);
|
||||
GHAssertTrue(success,@"Cache failed to store data");
|
||||
|
||||
// Make sure data gets removed
|
||||
[[ASIDownloadCache sharedCache] removeCachedDataForURL:url];
|
||||
|
||||
path = [[ASIDownloadCache sharedCache] pathToCachedResponseDataForURL:url];
|
||||
success = (path == nil);
|
||||
GHAssertTrue(success,@"Cache failed to remove data");
|
||||
|
||||
path = [[ASIDownloadCache sharedCache] pathToCachedResponseHeadersForURL:url];
|
||||
success = (path == nil);
|
||||
GHAssertTrue(success,@"Cache failed to remove data");
|
||||
|
||||
// Test ASIDontLoadCachePolicy
|
||||
[[ASIDownloadCache sharedCache] clearCachedResponsesForStoragePolicy:ASICacheForSessionDurationCacheStoragePolicy];
|
||||
request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:@"http://allseeing-i.com/ASIHTTPRequest/tests/content-always-new"]];
|
||||
[request setCachePolicy:ASIDontLoadCachePolicy];
|
||||
[request startSynchronous];
|
||||
success = ![request error];
|
||||
GHAssertTrue(success,@"Request had an error");
|
||||
success = ![request contentLength];
|
||||
GHAssertTrue(success,@"Request had a response");
|
||||
}
|
||||
|
||||
- (void)testDefaultPolicy
|
||||
{
|
||||
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:@"http://allseeing-i.com/ASIHTTPRequest/tests/the_great_american_novel_(abridged).txt"]];
|
||||
[request setDownloadCache:[ASIDownloadCache sharedCache]];
|
||||
[request startSynchronous];
|
||||
BOOL success = ([request cachePolicy] == [[ASIDownloadCache sharedCache] defaultCachePolicy]);
|
||||
GHAssertTrue(success,@"Failed to use the cache policy from the cache");
|
||||
|
||||
request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:@"http://allseeing-i.com/ASIHTTPRequest/tests/the_great_american_novel_(abridged).txt"]];
|
||||
[request setDownloadCache:[ASIDownloadCache sharedCache]];
|
||||
[request setCachePolicy:ASIOnlyLoadIfNotCachedCachePolicy];
|
||||
[request startSynchronous];
|
||||
success = ([request cachePolicy] == ASIOnlyLoadIfNotCachedCachePolicy);
|
||||
GHAssertTrue(success,@"Failed to use the cache policy from the cache");
|
||||
}
|
||||
|
||||
- (void)testNoCache
|
||||
{
|
||||
|
||||
// Test server no-cache headers
|
||||
[[ASIDownloadCache sharedCache] clearCachedResponsesForStoragePolicy:ASICacheForSessionDurationCacheStoragePolicy];
|
||||
NSArray *cacheHeaders = [NSArray arrayWithObjects:@"cache-control/no-cache",@"cache-control/no-store",@"pragma/no-cache",nil];
|
||||
for (NSString *cacheType in cacheHeaders) {
|
||||
NSString *url = [NSString stringWithFormat:@"http://allseeing-i.com/ASIHTTPRequest/tests/%@",cacheType];
|
||||
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:url]];
|
||||
[request setDownloadCache:[ASIDownloadCache sharedCache]];
|
||||
[request startSynchronous];
|
||||
|
||||
request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:url]];
|
||||
[request setDownloadCache:[ASIDownloadCache sharedCache]];
|
||||
[request startSynchronous];
|
||||
BOOL success = ![request didUseCachedResponse];
|
||||
GHAssertTrue(success,@"Data should not have been stored in the cache");
|
||||
}
|
||||
}
|
||||
|
||||
- (void)testSharedCache
|
||||
{
|
||||
[[ASIDownloadCache sharedCache] clearCachedResponsesForStoragePolicy:ASICacheForSessionDurationCacheStoragePolicy];
|
||||
|
||||
// Make using the cache automatic
|
||||
[ASIHTTPRequest setDefaultCache:[ASIDownloadCache sharedCache]];
|
||||
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:@"http://allseeing-i.com/ASIHTTPRequest/tests/the_great_american_novel_(abridged).txt"]];
|
||||
[request startSynchronous];
|
||||
|
||||
request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:@"http://allseeing-i.com/ASIHTTPRequest/tests/the_great_american_novel_(abridged).txt"]];
|
||||
[request startSynchronous];
|
||||
BOOL success = [request didUseCachedResponse];
|
||||
GHAssertTrue(success,@"Failed to use data cached in default cache");
|
||||
|
||||
[ASIHTTPRequest setDefaultCache:nil];
|
||||
request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:@"http://allseeing-i.com/ASIHTTPRequest/tests/the_great_american_novel_(abridged).txt"]];
|
||||
[request startSynchronous];
|
||||
success = ![request didUseCachedResponse];
|
||||
GHAssertTrue(success,@"Should not have used data cached in default cache");
|
||||
}
|
||||
|
||||
- (void)testExpiry
|
||||
{
|
||||
[[ASIDownloadCache sharedCache] clearCachedResponsesForStoragePolicy:ASICacheForSessionDurationCacheStoragePolicy];
|
||||
[[ASIDownloadCache sharedCache] setDefaultCachePolicy:ASIAskServerIfModifiedCachePolicy];
|
||||
|
||||
NSArray *headers = [NSArray arrayWithObjects:@"last-modified",@"etag",@"expires",@"max-age",nil];
|
||||
for (NSString *header in headers) {
|
||||
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"http://allseeing-i.com/ASIHTTPRequest/tests/content-always-new/%@",header]]];
|
||||
[request setDownloadCache:[ASIDownloadCache sharedCache]];
|
||||
[request startSynchronous];
|
||||
|
||||
if ([header isEqualToString:@"last-modified"]) {
|
||||
[NSThread sleepForTimeInterval:2];
|
||||
}
|
||||
|
||||
request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"http://allseeing-i.com/ASIHTTPRequest/tests/content-always-new/%@",header]]];
|
||||
[request setDownloadCache:[ASIDownloadCache sharedCache]];
|
||||
[request startSynchronous];
|
||||
BOOL success = ![request didUseCachedResponse];
|
||||
GHAssertTrue(success,@"Cached data should have expired");
|
||||
}
|
||||
}
|
||||
|
||||
- (void)testMaxAgeParsing
|
||||
{
|
||||
[[ASIDownloadCache sharedCache] clearCachedResponsesForStoragePolicy:ASICacheForSessionDurationCacheStoragePolicy];
|
||||
[[ASIDownloadCache sharedCache] setDefaultCachePolicy:ASIUseDefaultCachePolicy];
|
||||
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:@"http://allseeing-i.com/ASIHTTPRequest/tests/cache-control-max-age-parsing"]];
|
||||
[request setDownloadCache:[ASIDownloadCache sharedCache]];
|
||||
[request startSynchronous];
|
||||
|
||||
request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:@"http://allseeing-i.com/ASIHTTPRequest/tests/cache-control-max-age-parsing"]];
|
||||
[request setDownloadCache:[ASIDownloadCache sharedCache]];
|
||||
[request startSynchronous];
|
||||
BOOL success = [request didUseCachedResponse];
|
||||
GHAssertTrue(success,@"Failed to use cached response");
|
||||
}
|
||||
|
||||
- (void)testExtensionHandling
|
||||
{
|
||||
NSArray *extensions = [ASIDownloadCache fileExtensionsToHandleAsHTML];
|
||||
for (NSString *extension in extensions) {
|
||||
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"http://allseeing-i.com/file.%@",extension]];
|
||||
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
|
||||
NSString *path = [[ASIDownloadCache sharedCache] pathToStoreCachedResponseDataForRequest:request];
|
||||
BOOL success = [[path pathExtension] isEqualToString:@"html"];
|
||||
GHAssertTrue(success, @"Failed to use html extension on cached path for a resource we know a webview won't be able to open locally");
|
||||
}
|
||||
|
||||
NSURL *url = [NSURL URLWithString:@"http://allseeing-i.com/"];
|
||||
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
|
||||
NSString *path = [[ASIDownloadCache sharedCache] pathToStoreCachedResponseDataForRequest:request];
|
||||
BOOL success = [[path pathExtension] isEqualToString:@"html"];
|
||||
GHAssertTrue(success, @"Failed to use html extension on cached path for a url without an extension");
|
||||
|
||||
url = [NSURL URLWithString:@"http://allseeing-i.com/i/logo.png"];
|
||||
request = [ASIHTTPRequest requestWithURL:url];
|
||||
path = [[ASIDownloadCache sharedCache] pathToStoreCachedResponseDataForRequest:request];
|
||||
success = [[path pathExtension] isEqualToString:@"png"];
|
||||
GHAssertTrue(success, @"Failed to preserve file extension on cached path");
|
||||
}
|
||||
|
||||
- (void)testCustomExpiry
|
||||
{
|
||||
[[ASIDownloadCache sharedCache] clearCachedResponsesForStoragePolicy:ASICacheForSessionDurationCacheStoragePolicy];
|
||||
[[ASIDownloadCache sharedCache] setDefaultCachePolicy:ASIUseDefaultCachePolicy];
|
||||
[[ASIDownloadCache sharedCache] setShouldRespectCacheControlHeaders:YES];
|
||||
|
||||
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:@"http://allseeing-i.com/ASIHTTPRequest/tests/cache-away"]];
|
||||
[request setDownloadCache:[ASIDownloadCache sharedCache]];
|
||||
[request setSecondsToCache:-2];
|
||||
[request startSynchronous];
|
||||
|
||||
request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:@"http://allseeing-i.com/ASIHTTPRequest/tests/cache-away"]];
|
||||
[request setDownloadCache:[ASIDownloadCache sharedCache]];
|
||||
[request startSynchronous];
|
||||
|
||||
BOOL success = ![request didUseCachedResponse];
|
||||
GHAssertTrue(success,@"Cached data should have expired");
|
||||
|
||||
request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:@"http://allseeing-i.com/ASIHTTPRequest/tests/cache-away"]];
|
||||
[request setDownloadCache:[ASIDownloadCache sharedCache]];
|
||||
[request setSecondsToCache:20];
|
||||
[request startSynchronous];
|
||||
|
||||
request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:@"http://allseeing-i.com/ASIHTTPRequest/tests/cache-away"]];
|
||||
[request setDownloadCache:[ASIDownloadCache sharedCache]];
|
||||
[request startSynchronous];
|
||||
|
||||
success = [request didUseCachedResponse];
|
||||
GHAssertTrue(success,@"Cached data should have been used");
|
||||
}
|
||||
|
||||
- (void)test304
|
||||
{
|
||||
NSURL *url = [NSURL URLWithString:@"http://allseeing-i.com/ASIHTTPRequest/tests/the_great_american_novel_(abridged).txt"];
|
||||
|
||||
// Test default cache policy
|
||||
[[ASIDownloadCache sharedCache] clearCachedResponsesForStoragePolicy:ASICacheForSessionDurationCacheStoragePolicy];
|
||||
[[ASIDownloadCache sharedCache] setDefaultCachePolicy:ASIUseDefaultCachePolicy];
|
||||
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
|
||||
[request setDownloadCache:[ASIDownloadCache sharedCache]];
|
||||
[request startSynchronous];
|
||||
|
||||
request = [ASIHTTPRequest requestWithURL:url];
|
||||
[request setDownloadCache:[ASIDownloadCache sharedCache]];
|
||||
[request startSynchronous];
|
||||
BOOL success = ([request responseStatusCode] == 200);
|
||||
GHAssertTrue(success,@"Failed to perform a conditional get");
|
||||
|
||||
success = [request didUseCachedResponse];
|
||||
GHAssertTrue(success,@"Cached data should have been used");
|
||||
|
||||
success = ([[request responseData] length]);
|
||||
GHAssertTrue(success,@"Response was empty");
|
||||
|
||||
// Test 304 updates expiry date
|
||||
url = [NSURL URLWithString:@"http://allseeing-i.com/ASIHTTPRequest/tests/content_not_modified_but_expires_tomorrow"];
|
||||
request = [ASIHTTPRequest requestWithURL:url];
|
||||
[request setDownloadCache:[ASIDownloadCache sharedCache]];
|
||||
[request startSynchronous];
|
||||
|
||||
NSTimeInterval expiryTimestamp = [[[[ASIDownloadCache sharedCache] cachedResponseHeadersForURL:url] objectForKey:@"X-ASIHTTPRequest-Expires"] doubleValue];
|
||||
|
||||
// Wait to give the expiry date a chance to change
|
||||
sleep(2);
|
||||
|
||||
request = [ASIHTTPRequest requestWithURL:url];
|
||||
[request setCachePolicy:ASIAskServerIfModifiedCachePolicy];
|
||||
[request setDownloadCache:[ASIDownloadCache sharedCache]];
|
||||
[request startSynchronous];
|
||||
|
||||
success = [request didUseCachedResponse];
|
||||
GHAssertTrue(success, @"Cached data should have been used");
|
||||
|
||||
NSTimeInterval newExpiryTimestamp = [[[[ASIDownloadCache sharedCache] cachedResponseHeadersForURL:url] objectForKey:@"X-ASIHTTPRequest-Expires"] doubleValue];
|
||||
NSLog(@"%@",[request responseString]);
|
||||
success = (newExpiryTimestamp > expiryTimestamp);
|
||||
GHAssertTrue(success, @"Failed to update expiry timestamp on 304");
|
||||
}
|
||||
|
||||
- (void)testStringEncoding
|
||||
{
|
||||
[ASIHTTPRequest setDefaultCache:[ASIDownloadCache sharedCache]];
|
||||
[[ASIDownloadCache sharedCache] setShouldRespectCacheControlHeaders:NO];
|
||||
[[ASIDownloadCache sharedCache] clearCachedResponsesForStoragePolicy:ASICacheForSessionDurationCacheStoragePolicy];
|
||||
[[ASIDownloadCache sharedCache] setDefaultCachePolicy:ASIOnlyLoadIfNotCachedCachePolicy];
|
||||
|
||||
NSURL *url = [NSURL URLWithString:@"http://allseeing-i.com/ASIHTTPRequest/tests/Character-Encoding/UTF-16"];
|
||||
ASIHTTPRequest *request = [[[ASIHTTPRequest alloc] initWithURL:url] autorelease];
|
||||
[request startSynchronous];
|
||||
BOOL success = ([request responseEncoding] == NSUnicodeStringEncoding);
|
||||
GHAssertTrue(success,@"Got the wrong encoding back, cannot proceed with test");
|
||||
|
||||
request = [[[ASIHTTPRequest alloc] initWithURL:url] autorelease];
|
||||
[request startSynchronous];
|
||||
success = [request responseEncoding] == NSUnicodeStringEncoding;
|
||||
GHAssertTrue(success,@"Failed to set the correct encoding on the cached response");
|
||||
|
||||
[ASIHTTPRequest setDefaultCache:nil];
|
||||
}
|
||||
|
||||
- (void)testCookies
|
||||
{
|
||||
[ASIHTTPRequest setDefaultCache:[ASIDownloadCache sharedCache]];
|
||||
[[ASIDownloadCache sharedCache] setShouldRespectCacheControlHeaders:NO];
|
||||
[[ASIDownloadCache sharedCache] clearCachedResponsesForStoragePolicy:ASICacheForSessionDurationCacheStoragePolicy];
|
||||
[[ASIDownloadCache sharedCache] setDefaultCachePolicy:ASIOnlyLoadIfNotCachedCachePolicy];
|
||||
|
||||
NSURL *url = [NSURL URLWithString:@"http://allseeing-i.com/ASIHTTPRequest/tests/set_cookie"];
|
||||
ASIHTTPRequest *request = [[[ASIHTTPRequest alloc] initWithURL:url] autorelease];
|
||||
[request startSynchronous];
|
||||
NSArray *cookies = [request responseCookies];
|
||||
|
||||
BOOL success = ([cookies count]);
|
||||
GHAssertTrue(success,@"Got no cookies back, cannot proceed with test");
|
||||
|
||||
request = [[[ASIHTTPRequest alloc] initWithURL:url] autorelease];
|
||||
[request startSynchronous];
|
||||
|
||||
NSUInteger i;
|
||||
for (i=0; i<[cookies count]; i++) {
|
||||
if (![[[cookies objectAtIndex:i] name] isEqualToString:[[[request responseCookies] objectAtIndex:i] name]]) {
|
||||
GHAssertTrue(success,@"Failed to set response cookies correctly");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
[ASIHTTPRequest setDefaultCache:nil];
|
||||
}
|
||||
|
||||
// Text fix for a bug where the didFinishSelector would be called twice for a cached response using ASIReloadIfDifferentCachePolicy
|
||||
- (void)testCacheOnlyCallsRequestFinishedOnce
|
||||
{
|
||||
// Run this request on the main thread to force delegate calls to happen synchronously
|
||||
[self performSelectorOnMainThread:@selector(runCacheOnlyCallsRequestFinishedOnceTest) withObject:nil waitUntilDone:YES];
|
||||
}
|
||||
|
||||
- (void)runCacheOnlyCallsRequestFinishedOnceTest
|
||||
{
|
||||
[[ASIDownloadCache sharedCache] clearCachedResponsesForStoragePolicy:ASICacheForSessionDurationCacheStoragePolicy];
|
||||
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:@"http://allseeing-i.com/i/logo.png"]];
|
||||
[request setCachePolicy:ASIUseDefaultCachePolicy];
|
||||
[request setDownloadCache:[ASIDownloadCache sharedCache]];
|
||||
[request setDelegate:self];
|
||||
[request startSynchronous];
|
||||
|
||||
requestsFinishedCount = 0;
|
||||
request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:@"http://allseeing-i.com/i/logo.png"]];
|
||||
[request setCachePolicy:ASIUseDefaultCachePolicy];
|
||||
[request setDownloadCache:[ASIDownloadCache sharedCache]];
|
||||
[request setDidFinishSelector:@selector(finishCached:)];
|
||||
[request setDelegate:self];
|
||||
[request startSynchronous];
|
||||
|
||||
BOOL success = (requestsFinishedCount == 1);
|
||||
GHAssertTrue(success,@"didFinishSelector called more than once");
|
||||
}
|
||||
|
||||
- (void)finishCached:(ASIHTTPRequest *)request
|
||||
{
|
||||
requestsFinishedCount++;
|
||||
}
|
||||
|
||||
- (void)testRedirect
|
||||
{
|
||||
// Run this request on the main thread to force delegate calls to happen synchronously
|
||||
[self performSelectorOnMainThread:@selector(runRedirectTest) withObject:nil waitUntilDone:YES];
|
||||
}
|
||||
|
||||
- (void)runRedirectTest
|
||||
{
|
||||
[[ASIDownloadCache sharedCache] clearCachedResponsesForStoragePolicy:ASICacheForSessionDurationCacheStoragePolicy];
|
||||
[[ASIDownloadCache sharedCache] clearCachedResponsesForStoragePolicy:ASICachePermanentlyCacheStoragePolicy];
|
||||
[[ASIDownloadCache sharedCache] setDefaultCachePolicy:ASIUseDefaultCachePolicy];
|
||||
[ASIHTTPRequest setDefaultCache:[ASIDownloadCache sharedCache]];
|
||||
|
||||
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:@"http://allseeing-i.com/ASIHTTPRequest/tests/cached-redirect"]];
|
||||
[request startSynchronous];
|
||||
|
||||
BOOL success = ([[[request url] absoluteString] isEqualToString:@"http://allseeing-i.com/i/logo.png"]);
|
||||
GHAssertTrue(success,@"Request did not redirect correctly, cannot proceed with test");
|
||||
|
||||
requestRedirectedWasCalled = NO;
|
||||
request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:@"http://allseeing-i.com/ASIHTTPRequest/tests/cached-redirect"]];
|
||||
[request setDelegate:self];
|
||||
[request startSynchronous];
|
||||
|
||||
success = ([request didUseCachedResponse]);
|
||||
GHAssertTrue(success,@"Failed to cache final response");
|
||||
|
||||
GHAssertTrue(requestRedirectedWasCalled,@"Failed to call requestRedirected");
|
||||
}
|
||||
|
||||
- (void)requestRedirected:(ASIHTTPRequest *)redirected
|
||||
{
|
||||
requestRedirectedWasCalled = YES;
|
||||
}
|
||||
|
||||
- (void)request:(ASIHTTPRequest *)request willRedirectToURL:(NSURL *)newURL
|
||||
{
|
||||
BOOL success = ([[newURL absoluteString] isEqualToString:@"http://allseeing-i.com/i/logo.png"]);
|
||||
GHAssertTrue(success,@"Request did not redirect correctly, cannot proceed with test");
|
||||
|
||||
success = ([request didUseCachedResponse]);
|
||||
GHAssertTrue(success,@"Failed to cache redirect response");
|
||||
|
||||
[request redirectToURL:newURL];
|
||||
}
|
||||
|
||||
- (void)testCachedFileOverwritten
|
||||
{
|
||||
// Test for https://github.com/pokeb/asi-http-request/pull/211
|
||||
// This test ensures that items in the cache are correctly overwritten when a downloadDestinationPath is set,
|
||||
// and they need to be copied to the cache at the end of the request
|
||||
|
||||
// This url returns different content every time
|
||||
NSURL *url = [NSURL URLWithString:@"http://asi/ASIHTTPRequest/tests/random-content"];
|
||||
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
|
||||
[request setDownloadCache:[ASIDownloadCache sharedCache]];
|
||||
[request setSecondsToCache:0.5f];
|
||||
[request startSynchronous];
|
||||
|
||||
NSString *path = [[ASIDownloadCache sharedCache] pathToCachedResponseDataForURL:url];
|
||||
NSString *content = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:NULL];
|
||||
|
||||
sleep(1);
|
||||
|
||||
request = [ASIHTTPRequest requestWithURL:url];
|
||||
[request setDownloadCache:[ASIDownloadCache sharedCache]];
|
||||
[request setDownloadDestinationPath:[[self filePathForTemporaryTestFiles] stringByAppendingPathComponent:@"test.html"]];
|
||||
[request startSynchronous];
|
||||
|
||||
NSString *content2 = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:NULL];
|
||||
|
||||
BOOL success = ![content isEqualToString:content2];
|
||||
GHAssertTrue(success, @"Failed to overwrite response in cache");
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,24 @@
|
||||
//
|
||||
// ASIFormDataRequestTests.h
|
||||
// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest
|
||||
//
|
||||
// Created by Ben Copsey on 08/11/2008.
|
||||
// Copyright 2008 All-Seeing Interactive. All rights reserved.
|
||||
//
|
||||
|
||||
#import "ASITestCase.h"
|
||||
|
||||
@interface ASIFormDataRequestTests : ASITestCase {
|
||||
float progress;
|
||||
}
|
||||
|
||||
- (void)testDefaultMethod;
|
||||
- (void)testPostWithFileUpload;
|
||||
- (void)testEmptyData;
|
||||
- (void)testSubclass;
|
||||
- (void)testURLEncodedPost;
|
||||
- (void)testCharset;
|
||||
- (void)testPUT;
|
||||
- (void)testCopy;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,299 @@
|
||||
//
|
||||
// ASIFormDataRequestTests.m
|
||||
// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest
|
||||
//
|
||||
// Created by Ben Copsey on 08/11/2008.
|
||||
// Copyright 2008 All-Seeing Interactive. All rights reserved.
|
||||
//
|
||||
|
||||
#import "ASIFormDataRequestTests.h"
|
||||
#import "ASIFormDataRequest.h"
|
||||
|
||||
// Used for subclass test
|
||||
@interface ASIFormDataRequestSubclass : ASIFormDataRequest {}
|
||||
@end
|
||||
@implementation ASIFormDataRequestSubclass;
|
||||
@end
|
||||
|
||||
@implementation ASIFormDataRequestTests
|
||||
|
||||
|
||||
-(void)testDefaultMethod
|
||||
{
|
||||
ASIFormDataRequest *request = [[[ASIFormDataRequest alloc] initWithURL:[NSURL URLWithString:@"http://wedontcare.com"]] autorelease];
|
||||
|
||||
GHAssertTrue([[request requestMethod] isEqualToString:@"POST"], @"Default request method should be POST");
|
||||
}
|
||||
|
||||
- (void)testAddNilKeysAndValues
|
||||
{
|
||||
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:[NSURL URLWithString:@"http://allseeing-i.com/ASIHTTPRequest/tests/empty-post-value"]];
|
||||
[request setPostValue:nil forKey:@"key1"];
|
||||
[request setPostValue:@"value2" forKey:@"key2"];
|
||||
[request setData:nil forKey:@"file1"];
|
||||
[request setData:[@"hello" dataUsingEncoding:NSUTF8StringEncoding] forKey:@"file2"];
|
||||
[request startSynchronous];
|
||||
BOOL success = ([[request responseString] isEqualToString:@"key1: \r\nkey2: value2\r\nfile1: \r\nfile2: hello"]);
|
||||
GHAssertTrue(success, @"Sent wrong data");
|
||||
|
||||
// Test nil key (no key or value should be sent to the server)
|
||||
request = [ASIFormDataRequest requestWithURL:[NSURL URLWithString:@"http://allseeing-i.com"]];
|
||||
[request addPostValue:@"value1" forKey:nil];
|
||||
[request addPostValue:@"value2" forKey:@"key2"];
|
||||
[request buildPostBody];
|
||||
NSString *postBody = [[[NSString alloc] initWithData:[request postBody] encoding:NSUTF8StringEncoding] autorelease];
|
||||
success = ([postBody isEqualToString:@"key2=value2"]);
|
||||
GHAssertTrue(success, @"Sent wrong data");
|
||||
}
|
||||
|
||||
- (void)testPostWithFileUpload
|
||||
{
|
||||
NSURL *url = [NSURL URLWithString:@"http://allseeing-i.com/ASIHTTPRequest/tests/post"];
|
||||
|
||||
//Create a 32kb file
|
||||
unsigned int size = 1024*32;
|
||||
NSMutableData *data = [NSMutableData dataWithLength:size];
|
||||
NSString *path = [[self filePathForTemporaryTestFiles] stringByAppendingPathComponent:@"bigfile"];
|
||||
[data writeToFile:path atomically:NO];
|
||||
|
||||
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];
|
||||
|
||||
NSDate *d = [NSDate date];
|
||||
#if TARGET_OS_IPHONE
|
||||
NSValue *v = [NSValue valueWithCGRect:CGRectMake(0, 0, 200, 200)];
|
||||
#else
|
||||
NSValue *v = [NSValue valueWithRect:NSMakeRect(0, 0, 200, 200)];
|
||||
#endif
|
||||
[request setPostValue:@"foo" forKey:@"post_var"];
|
||||
[request setPostValue:d forKey:@"post_var2"];
|
||||
[request setPostValue:v forKey:@"post_var3"];
|
||||
[request setFile:path forKey:@"file"];
|
||||
[request startSynchronous];
|
||||
|
||||
BOOL success = ([[request responseString] isEqualToString:[NSString stringWithFormat:@"post_var: %@\r\npost_var2: %@\r\npost_var3: %@\r\nfile_name: %@\r\nfile_size: %hu\r\ncontent_type: %@",@"foo",d,v,@"bigfile",size,@"application/octet-stream"]]);
|
||||
GHAssertTrue(success,@"Failed to upload the correct data (using local file)");
|
||||
|
||||
//Try the same with the raw data
|
||||
request = [[[ASIFormDataRequest alloc] initWithURL:url] autorelease];
|
||||
[request setPostValue:@"foo" forKey:@"post_var"];
|
||||
[request setPostValue:d forKey:@"post_var2"];
|
||||
[request setPostValue:v forKey:@"post_var3"];
|
||||
[request setData:data forKey:@"file"];
|
||||
[request startSynchronous];
|
||||
|
||||
success = ([[request responseString] isEqualToString:[NSString stringWithFormat:@"post_var: %@\r\npost_var2: %@\r\npost_var3: %@\r\nfile_name: %@\r\nfile_size: %hu\r\ncontent_type: %@",@"foo",d,v,@"file",size,@"application/octet-stream"]]);
|
||||
GHAssertTrue(success,@"Failed to upload the correct data (using NSData)");
|
||||
|
||||
//Post with custom content-type and file name
|
||||
request = [[[ASIFormDataRequest alloc] initWithURL:url] autorelease];
|
||||
[request setPostValue:@"foo" forKey:@"post_var"];
|
||||
[request setPostValue:d forKey:@"post_var2"];
|
||||
[request setPostValue:v forKey:@"post_var3"];
|
||||
[request setFile:path withFileName:@"myfile" andContentType:@"text/plain" forKey:@"file"];
|
||||
[request startSynchronous];
|
||||
|
||||
success = ([[request responseString] isEqualToString:[NSString stringWithFormat:@"post_var: %@\r\npost_var2: %@\r\npost_var3: %@\r\nfile_name: %@\r\nfile_size: %hu\r\ncontent_type: %@",@"foo",d,v,@"myfile",size,@"text/plain"]]);
|
||||
GHAssertTrue(success,@"Failed to send the correct content-type / file name");
|
||||
|
||||
//Post raw data with custom content-type and file name
|
||||
request = [[[ASIFormDataRequest alloc] initWithURL:url] autorelease];
|
||||
[request setPostValue:@"foo" forKey:@"post_var"];
|
||||
[request setPostValue:d forKey:@"post_var2"];
|
||||
[request setPostValue:v forKey:@"post_var3"];
|
||||
[request setData:data withFileName:@"myfile" andContentType:@"text/plain" forKey:@"file"];
|
||||
[request startSynchronous];
|
||||
|
||||
success = ([[request responseString] isEqualToString:[NSString stringWithFormat:@"post_var: %@\r\npost_var2: %@\r\npost_var3: %@\r\nfile_name: %@\r\nfile_size: %hu\r\ncontent_type: %@",@"foo",d,v,@"myfile",size,@"text/plain"]]);
|
||||
GHAssertTrue(success,@"Failed to send the correct content-type / file name");
|
||||
|
||||
}
|
||||
|
||||
// Test fix for bug where setting an empty string for a form post value would cause the rest of the post body to be ignored (because an NSOutputStream won't like it if you try to write 0 bytes)
|
||||
- (void)testEmptyData
|
||||
{
|
||||
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:[NSURL URLWithString:@"http://allseeing-i.com/ASIHTTPRequest/tests/post-empty"]];
|
||||
[request setPostValue:@"hello" forKey:@"a_non_empty_string"];
|
||||
[request setPostValue:@"" forKey:@"zzz_empty_string"];
|
||||
[request setPostValue:@"there" forKey:@"xxx_non_empty_string"];
|
||||
[request setShouldStreamPostDataFromDisk:YES];
|
||||
[request buildPostBody];
|
||||
[request startSynchronous];
|
||||
|
||||
BOOL success = ([[request responseString] isEqualToString:@"a_non_empty_string: hello\r\nzzz_empty_string: \r\nxxx_non_empty_string: there"]);
|
||||
GHAssertTrue(success,@"Failed to send the correct post data");
|
||||
|
||||
}
|
||||
|
||||
// Ensure class convenience constructor returns an instance of our subclass
|
||||
- (void)testSubclass
|
||||
{
|
||||
ASIFormDataRequestSubclass *instance = [ASIFormDataRequestSubclass requestWithURL:[NSURL URLWithString:@"http://allseeing-i.com"]];
|
||||
BOOL success = [instance isKindOfClass:[ASIFormDataRequestSubclass class]];
|
||||
GHAssertTrue(success,@"Convenience constructor failed to return an instance of the correct class");
|
||||
}
|
||||
|
||||
- (void)testURLEncodedPost
|
||||
{
|
||||
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:[NSURL URLWithString:@"http://allseeing-i.com/ASIHTTPRequest/tests/url-encoded-post"]];
|
||||
[request setPostValue:@"value1" forKey:@"value1"];
|
||||
[request setPostValue:@"(%20 ? =)" forKey:@"value2"];
|
||||
[request setPostValue:@"£100.00" forKey:@"value3"];
|
||||
[request setPostValue:@"" forKey:@"value4"];
|
||||
[request setPostValue:@"&??aaa=//ciaoèèè" forKey:@"teskey&aa"];
|
||||
|
||||
[request setShouldStreamPostDataFromDisk:YES];
|
||||
[request setPostFormat:ASIURLEncodedPostFormat];
|
||||
[request startSynchronous];
|
||||
|
||||
|
||||
BOOL success = ([[request responseString] isEqualToString:@"value1: value1\r\nvalue2: (%20 ? =)\r\nvalue3: £100.00\r\nvalue4: \r\nteskey&aa: &??aaa=//ciaoèèè"]);
|
||||
GHAssertTrue(success,@"Failed to send the correct post data");
|
||||
}
|
||||
|
||||
- (void)testCharset
|
||||
{
|
||||
NSURL *url = [NSURL URLWithString:@"http://allseeing-i.com/ASIHTTPRequest/tests/formdata-charset"];
|
||||
NSString *testString = @"£££s don't seem to buy me many €€€s these days";
|
||||
|
||||
// Test the default (UTF-8) with a url-encoded request
|
||||
NSString *charset = @"utf-8";
|
||||
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];
|
||||
[request setPostValue:testString forKey:@"value"];
|
||||
[request startSynchronous];
|
||||
BOOL success = ([[request responseString] isEqualToString:[NSString stringWithFormat:@"Got data in %@: %@",charset,testString]]);
|
||||
GHAssertTrue(success,@"Failed to correctly encode the data");
|
||||
|
||||
// Test the default (UTF-8) with a multipart/form-data request
|
||||
request = [ASIFormDataRequest requestWithURL:url];
|
||||
[request setPostValue:testString forKey:@"value"];
|
||||
[request setPostFormat:ASIMultipartFormDataPostFormat];
|
||||
[request startSynchronous];
|
||||
success = ([[request responseString] isEqualToString:[NSString stringWithFormat:@"Got data in %@: %@",charset,testString]]);
|
||||
GHAssertTrue(success,@"Failed to correctly encode the data");
|
||||
|
||||
// Test a different charset
|
||||
testString = @"£££s don't seem to buy me many $$$s these days";
|
||||
charset = @"iso-8859-1";
|
||||
request = [ASIFormDataRequest requestWithURL:url];
|
||||
[request setPostValue:testString forKey:@"value"];
|
||||
[request setStringEncoding:NSISOLatin1StringEncoding];
|
||||
[request startSynchronous];
|
||||
success = ([[request responseString] isEqualToString:[NSString stringWithFormat:@"Got data in %@: %@",charset,testString]]);
|
||||
GHAssertTrue(success,@"Failed to correctly encode the data");
|
||||
|
||||
// And again with multipart/form-data request
|
||||
request = [ASIFormDataRequest requestWithURL:url];
|
||||
[request setPostValue:testString forKey:@"value"];
|
||||
[request setPostFormat:ASIMultipartFormDataPostFormat];
|
||||
[request setStringEncoding:NSISOLatin1StringEncoding];
|
||||
[request startSynchronous];
|
||||
success = ([[request responseString] isEqualToString:[NSString stringWithFormat:@"Got data in %@: %@",charset,testString]]);
|
||||
GHAssertTrue(success,@"Failed to correctly encode the data");
|
||||
|
||||
// Once more for luck
|
||||
charset = @"windows-1252";
|
||||
request = [ASIFormDataRequest requestWithURL:url];
|
||||
[request setPostValue:testString forKey:@"value"];
|
||||
[request setStringEncoding:NSWindowsCP1252StringEncoding];
|
||||
[request startSynchronous];
|
||||
success = ([[request responseString] isEqualToString:[NSString stringWithFormat:@"Got data in %@: %@",charset,testString]]);
|
||||
GHAssertTrue(success,@"Failed to correctly encode the data");
|
||||
|
||||
request = [ASIFormDataRequest requestWithURL:url];
|
||||
[request setPostValue:testString forKey:@"value"];
|
||||
[request setPostFormat:ASIMultipartFormDataPostFormat];
|
||||
[request setStringEncoding:NSWindowsCP1252StringEncoding];
|
||||
[request startSynchronous];
|
||||
success = ([[request responseString] isEqualToString:[NSString stringWithFormat:@"Got data in %@: %@",charset,testString]]);
|
||||
GHAssertTrue(success,@"Failed to correctly encode the data");
|
||||
|
||||
// Ensure charset isn't added to file post (GH issue 36)
|
||||
request = [ASIFormDataRequest requestWithURL:[NSURL URLWithString:@"http://allseeing-i.com/ASIHTTPRequest/Tests/return-raw-request"]];
|
||||
[request setData:[@"test 123" dataUsingEncoding:NSUTF8StringEncoding] forKey:@"file"];
|
||||
[request setRequestMethod:@"PUT"];
|
||||
[request startSynchronous];
|
||||
success = ([[request responseString] rangeOfString:@"charset=utf-8"].location == NSNotFound);
|
||||
GHAssertTrue(success,@"Sent a charset header for an uploaded file");
|
||||
|
||||
|
||||
}
|
||||
|
||||
- (void)testPUT
|
||||
{
|
||||
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:[NSURL URLWithString:@"http://allseeing-i.com/ASIHTTPRequest/Tests/put_form_data"]];
|
||||
[request setRequestMethod:@"PUT"];
|
||||
[request setPostValue:@"cheep cheep" forKey:@"hello"];
|
||||
[request startSynchronous];
|
||||
|
||||
NSString *expectedResponse = [[[NSString alloc] initWithBytes:[[request postBody] bytes] length:[[request postBody] length] encoding:[request stringEncoding]] autorelease];
|
||||
BOOL success = ([[request responseString] isEqualToString:expectedResponse]);
|
||||
GHAssertTrue(success,@"Failed to send form data using PUT");
|
||||
|
||||
// Ensure that other methods still default to POST
|
||||
request = [ASIFormDataRequest requestWithURL:[NSURL URLWithString:@"http://allseeing-i.com/ASIHTTPRequest/Tests/put_form_data"]];
|
||||
[request setRequestMethod:@"DELETE"];
|
||||
[request setPostValue:@"cheep cheep" forKey:@"hello"];
|
||||
[request startSynchronous];
|
||||
|
||||
success = ([[request responseString] isEqualToString:@"Got POST instead"]);
|
||||
GHAssertTrue(success,@"Failed to send form data using PUT");
|
||||
}
|
||||
|
||||
- (void)testCopy
|
||||
{
|
||||
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
|
||||
|
||||
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:[NSURL URLWithString:@"http://allseeing-i.com"]];
|
||||
ASIFormDataRequest *request2 = [request copy];
|
||||
GHAssertNotNil(request2,@"Failed to create a copy");
|
||||
|
||||
[pool release];
|
||||
BOOL success = ([request2 retainCount] == 1);
|
||||
GHAssertTrue(success,@"Failed to create a retained copy");
|
||||
success = ([request2 isKindOfClass:[ASIFormDataRequest class]]);
|
||||
GHAssertTrue(success,@"Copy is of wrong class");
|
||||
|
||||
[request2 release];
|
||||
}
|
||||
|
||||
- (void)testMultipleValuesForASingleKey
|
||||
{
|
||||
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:[NSURL URLWithString:@"http://allseeing-i.com/ASIHTTPRequest/tests/multiple-values"]];
|
||||
[request addPostValue:@"here" forKey:@"test_value[]"];
|
||||
[request addPostValue:@"are" forKey:@"test_value[]"];
|
||||
[request addPostValue:@"some" forKey:@"test_value[]"];
|
||||
[request addPostValue:@"values" forKey:@"test_value[]"];
|
||||
|
||||
NSString *path1 = [[self filePathForTemporaryTestFiles] stringByAppendingPathComponent:@"file1.txt"];
|
||||
NSString *path2 = [[self filePathForTemporaryTestFiles] stringByAppendingPathComponent:@"file2.txt"];
|
||||
[@"hello" writeToFile:path1 atomically:NO encoding:NSUTF8StringEncoding error:nil];
|
||||
[@"there" writeToFile:path2 atomically:NO encoding:NSUTF8StringEncoding error:nil];
|
||||
[request addFile:path1 forKey:@"test_file[]"];
|
||||
[request addFile:path2 forKey:@"test_file[]"];
|
||||
|
||||
[request startSynchronous];
|
||||
NSString *expectedOutput = @"here\r\nare\r\nsome\r\nvalues\r\nfile1.txt\r\nfile2.txt\r\n";
|
||||
BOOL success = [[request responseString] isEqualToString:expectedOutput];
|
||||
GHAssertTrue(success,@"Failed to send the correct data");
|
||||
|
||||
// Check data replaces older data
|
||||
request = [ASIFormDataRequest requestWithURL:[NSURL URLWithString:@"http://allseeing-i.com/ASIHTTPRequest/tests/single-values"]];
|
||||
[request addPostValue:@"here" forKey:@"test_value[]"];
|
||||
[request addPostValue:@"are" forKey:@"test_value[]"];
|
||||
[request addPostValue:@"some" forKey:@"test_value[]"];
|
||||
[request addPostValue:@"values" forKey:@"test_value[]"];
|
||||
|
||||
[request setPostValue:@"this is new data" forKey:@"test_value[]"];
|
||||
|
||||
[request addFile:path1 forKey:@"test_file[]"];
|
||||
[request addFile:path2 forKey:@"test_file[]"];
|
||||
|
||||
[request setData:[@"this is new data" dataUsingEncoding:NSUTF8StringEncoding] forKey:@"test_file[]"];
|
||||
|
||||
[request startSynchronous];
|
||||
expectedOutput = @"this is new data\r\nfile\r\n";
|
||||
success = [[request responseString] isEqualToString:expectedOutput];
|
||||
GHAssertTrue(success,@"Failed to send the correct data");
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,64 @@
|
||||
//
|
||||
// ASIHTTPRequestTests.h
|
||||
// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest
|
||||
//
|
||||
// Created by Ben Copsey on 01/08/2008.
|
||||
// Copyright 2008 All-Seeing Interactive. All rights reserved.
|
||||
//
|
||||
|
||||
#import "ASITestCase.h"
|
||||
|
||||
@class ASIHTTPRequest;
|
||||
|
||||
@interface ASIHTTPRequestTests : ASITestCase {
|
||||
float progress;
|
||||
BOOL started;
|
||||
BOOL finished;
|
||||
BOOL failed;
|
||||
BOOL receivedResponseHeaders;
|
||||
NSMutableData *responseData;
|
||||
}
|
||||
|
||||
- (void)testBasicDownload;
|
||||
- (void)testBase64Encode;
|
||||
- (void)testDelegateMethods;
|
||||
- (void)testConditionalGET;
|
||||
- (void)testException;
|
||||
- (void)testTimeOut;
|
||||
- (void)testRequestMethod;
|
||||
- (void)testHTTPVersion;
|
||||
- (void)testUserAgent;
|
||||
- (void)testAutomaticRedirection;
|
||||
- (void)test30xCrash;
|
||||
- (void)testUploadContentLength;
|
||||
- (void)testDownloadContentLength;
|
||||
- (void)testFileDownload;
|
||||
- (void)testDownloadProgress;
|
||||
- (void)testUploadProgress;
|
||||
- (void)testCookies;
|
||||
- (void)testRemoveCredentialsFromKeychain;
|
||||
- (void)testBasicAuthentication;
|
||||
- (void)testDigestAuthentication;
|
||||
- (void)testNTLMHandshake;
|
||||
- (void)testCharacterEncoding;
|
||||
- (void)testCompressedResponse;
|
||||
- (void)testCompressedResponseDownloadToFile;
|
||||
- (void)test000SSL;
|
||||
- (void)testRedirectPreservesSession;
|
||||
- (void)testTooMuchRedirection;
|
||||
- (void)testRedirectToNewDomain;
|
||||
- (void)test303Redirect;
|
||||
- (void)testSubclass;
|
||||
- (void)testTimeOutWithoutDownloadDelegate;
|
||||
- (void)testThrottlingDownloadBandwidth;
|
||||
- (void)testThrottlingUploadBandwidth;
|
||||
#if TARGET_OS_IPHONE
|
||||
- (void)testReachability;
|
||||
#endif
|
||||
- (void)testAutomaticRetry;
|
||||
- (void)testCloseConnection;
|
||||
- (void)testPersistentConnections;
|
||||
- (void)testNilPortCredentialsMatching;
|
||||
|
||||
@property (retain, nonatomic) NSMutableData *responseData;
|
||||
@end
|
||||
2001
projects/mtg/iOS/asi-http-request/Tests/ASIHTTPRequestTests.m
Normal file
2001
projects/mtg/iOS/asi-http-request/Tests/ASIHTTPRequestTests.m
Normal file
File diff suppressed because it is too large
Load Diff
83
projects/mtg/iOS/asi-http-request/Tests/ASINetworkQueueTests.h
Executable file
83
projects/mtg/iOS/asi-http-request/Tests/ASINetworkQueueTests.h
Executable file
@@ -0,0 +1,83 @@
|
||||
//
|
||||
// ASINetworkQueueTests.h
|
||||
// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest
|
||||
//
|
||||
// Created by Ben Copsey on 08/11/2008.
|
||||
// Copyright 2008 All-Seeing Interactive. All rights reserved.
|
||||
//
|
||||
|
||||
#import "ASITestCase.h"
|
||||
|
||||
/*
|
||||
IMPORTANT
|
||||
Code that appears in these tests is not for general purpose use.
|
||||
You should not use [networkQueue waitUntilAllOperationsAreFinished] or [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.25]] in your own software.
|
||||
They are used here to force a queue to operate synchronously to simplify writing the tests.
|
||||
IMPORTANT
|
||||
*/
|
||||
|
||||
@class ASIHTTPRequest;
|
||||
@class ASINetworkQueue;
|
||||
|
||||
@interface ASINetworkQueueTests : ASITestCase {
|
||||
ASIHTTPRequest *requestThatShouldFail;
|
||||
BOOL complete;
|
||||
BOOL request_didfail;
|
||||
BOOL request_succeeded;
|
||||
float progress;
|
||||
int addedRequests;
|
||||
|
||||
|
||||
NSOperationQueue *immediateCancelQueue;
|
||||
NSMutableArray *failedRequests;
|
||||
NSMutableArray *finishedRequests;
|
||||
|
||||
ASINetworkQueue *releaseTestQueue;
|
||||
ASINetworkQueue *cancelQueue;
|
||||
|
||||
int authenticationPromptCount;
|
||||
|
||||
ASINetworkQueue *postQueue;
|
||||
|
||||
ASINetworkQueue *testNTLMQueue;
|
||||
|
||||
ASINetworkQueue *addMoreRequestsQueue;
|
||||
int requestsFinishedCount;
|
||||
|
||||
BOOL started;
|
||||
BOOL finished;
|
||||
BOOL failed;
|
||||
BOOL headFailed;
|
||||
BOOL receivedResponseHeaders;
|
||||
|
||||
int queueFinishedCallCount;
|
||||
}
|
||||
- (void)testFailure;
|
||||
- (void)testFailureCancelsOtherRequests;
|
||||
- (void)testDownloadProgress;
|
||||
- (void)testUploadProgress;
|
||||
- (void)testProgressWithAuthentication;
|
||||
- (void)testWithNoListener;
|
||||
- (void)testPartialResume;
|
||||
- (void)testImmediateCancel;
|
||||
|
||||
- (void)setProgress:(float)newProgress;
|
||||
- (void)testSubclass;
|
||||
- (void)testQueueReleaseOnRequestComplete;
|
||||
- (void)testQueueReleaseOnQueueComplete;
|
||||
|
||||
- (void)testMultipleDownloadsThrottlingBandwidth;
|
||||
- (void)testMultipleUploadsThrottlingBandwidth;
|
||||
|
||||
- (void)testDelegateAuthenticationCredentialsReuse;
|
||||
- (void)testPOSTWithAuthentication;
|
||||
- (void)testHEADFailure;
|
||||
@property (retain) NSOperationQueue *immediateCancelQueue;
|
||||
@property (retain) NSMutableArray *failedRequests;
|
||||
@property (retain) NSMutableArray *finishedRequests;
|
||||
@property (retain) ASINetworkQueue *releaseTestQueue;
|
||||
@property (retain) ASINetworkQueue *cancelQueue;
|
||||
@property (retain) ASINetworkQueue *postQueue;
|
||||
@property (retain) ASINetworkQueue *testNTLMQueue;
|
||||
@property (retain) ASINetworkQueue *addMoreRequestsQueue;
|
||||
@end
|
||||
1274
projects/mtg/iOS/asi-http-request/Tests/ASINetworkQueueTests.m
Executable file
1274
projects/mtg/iOS/asi-http-request/Tests/ASINetworkQueueTests.m
Executable file
File diff suppressed because it is too large
Load Diff
28
projects/mtg/iOS/asi-http-request/Tests/ASIS3RequestTests.h
Normal file
28
projects/mtg/iOS/asi-http-request/Tests/ASIS3RequestTests.h
Normal file
@@ -0,0 +1,28 @@
|
||||
//
|
||||
// ASIS3RequestTests.h
|
||||
// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest
|
||||
//
|
||||
// Created by Ben Copsey on 12/07/2009.
|
||||
// Copyright 2009 All-Seeing Interactive. All rights reserved.
|
||||
//
|
||||
|
||||
#import "ASITestCase.h"
|
||||
|
||||
@class ASINetworkQueue;
|
||||
|
||||
@interface ASIS3RequestTests : ASITestCase {
|
||||
ASINetworkQueue *networkQueue;
|
||||
float progress;
|
||||
}
|
||||
|
||||
- (void)testAuthenticationHeaderGeneration;
|
||||
- (void)testREST;
|
||||
- (void)testFailure;
|
||||
- (void)testListRequest;
|
||||
- (void)testSubclasses;
|
||||
- (void)createTestBucket;
|
||||
- (void)testCopy;
|
||||
- (void)testHTTPS;
|
||||
|
||||
@property (retain,nonatomic) ASINetworkQueue *networkQueue;
|
||||
@end
|
||||
860
projects/mtg/iOS/asi-http-request/Tests/ASIS3RequestTests.m
Normal file
860
projects/mtg/iOS/asi-http-request/Tests/ASIS3RequestTests.m
Normal file
@@ -0,0 +1,860 @@
|
||||
//
|
||||
// ASIS3RequestTests.m
|
||||
// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest
|
||||
//
|
||||
// Created by Ben Copsey on 12/07/2009.
|
||||
// Copyright 2009 All-Seeing Interactive. All rights reserved.
|
||||
//
|
||||
|
||||
#import "ASIS3RequestTests.h"
|
||||
#import "ASINetworkQueue.h"
|
||||
#import "ASIS3BucketObject.h"
|
||||
#import "ASIS3ObjectRequest.h"
|
||||
#import "ASIS3BucketRequest.h"
|
||||
#import "ASIS3ServiceRequest.h"
|
||||
|
||||
// Fill in these to run the tests that actually connect and manipulate objects on S3
|
||||
static NSString *secretAccessKey = @"";
|
||||
static NSString *accessKey = @"";
|
||||
|
||||
// You should run these tests on a bucket that does not yet exist
|
||||
static NSString *bucket = @"";
|
||||
|
||||
// Used for subclass test
|
||||
@interface ASIS3ObjectRequestSubclass : ASIS3ObjectRequest {}
|
||||
@end
|
||||
@implementation ASIS3ObjectRequestSubclass;
|
||||
@end
|
||||
@interface ASIS3BucketRequestSubclass : ASIS3BucketRequest {}
|
||||
@end
|
||||
@implementation ASIS3BucketRequestSubclass;
|
||||
@end
|
||||
@interface ASIS3BucketObjectSubclass : ASIS3BucketObject {}
|
||||
@end
|
||||
@implementation ASIS3BucketObjectSubclass;
|
||||
@end
|
||||
|
||||
// Stop clang complaining about undeclared selectors
|
||||
@interface ASIS3RequestTests ()
|
||||
- (void)GETRequestDone:(ASIHTTPRequest *)request;
|
||||
- (void)GETRequestFailed:(ASIHTTPRequest *)request;
|
||||
- (void)PUTRequestDone:(ASIHTTPRequest *)request;
|
||||
- (void)PUTRequestFailed:(ASIHTTPRequest *)request;
|
||||
- (void)DELETERequestDone:(ASIHTTPRequest *)request;
|
||||
- (void)DELETERequestFailed:(ASIHTTPRequest *)request;
|
||||
@end
|
||||
|
||||
@implementation ASIS3RequestTests
|
||||
|
||||
// All these tests are based on Amazon's examples at: http://docs.amazonwebservices.com/AmazonS3/2006-03-01/
|
||||
- (void)testAuthenticationHeaderGeneration
|
||||
{
|
||||
NSString *exampleSecretAccessKey = @"uV3F3YluFJax1cknvbcGwgjvx4QpvB+leU8dUj2o";
|
||||
NSString *exampleAccessKey = @"0PN5J17HBGZHT7JJ3X82";
|
||||
NSString *exampleBucket = @"johnsmith";
|
||||
|
||||
// Test list all my buckets
|
||||
NSString *dateString = @"Wed, 28 Mar 2007 01:29:59 +0000";
|
||||
ASIS3ServiceRequest *serviceRequest = [ASIS3ServiceRequest serviceRequest];
|
||||
[serviceRequest setSecretAccessKey:exampleSecretAccessKey];
|
||||
[serviceRequest setAccessKey:exampleAccessKey];
|
||||
[serviceRequest setDateString:dateString];
|
||||
[serviceRequest buildRequestHeaders];
|
||||
BOOL success = [[[serviceRequest requestHeaders] valueForKey:@"Authorization"] isEqualToString:@"AWS 0PN5J17HBGZHT7JJ3X82:Db+gepJSUbZKwpx1FR0DLtEYoZA="];
|
||||
GHAssertTrue(success,@"Failed to generate the correct authorisation header for a GET service request");
|
||||
|
||||
// Test GET
|
||||
NSString *key = @"photos/puppy.jpg";
|
||||
dateString = @"Tue, 27 Mar 2007 19:36:42 +0000";
|
||||
ASIS3ObjectRequest *request = [ASIS3ObjectRequest requestWithBucket:exampleBucket key:key];
|
||||
[request setDateString:dateString];
|
||||
[request setSecretAccessKey:exampleSecretAccessKey];
|
||||
[request setAccessKey:exampleAccessKey];
|
||||
[request buildRequestHeaders];
|
||||
success = [[[request requestHeaders] valueForKey:@"Authorization"] isEqualToString:@"AWS 0PN5J17HBGZHT7JJ3X82:xXjDGYUmKxnwqr5KXNPGldn5LbA="];
|
||||
GHAssertTrue(success,@"Failed to generate the correct authorisation header for a GET request");
|
||||
|
||||
// Test PUT
|
||||
key = @"photos/puppy.jpg";
|
||||
dateString = @"Tue, 27 Mar 2007 21:15:45 +0000";
|
||||
request = [ASIS3ObjectRequest requestWithBucket:exampleBucket key:key];
|
||||
[request setRequestMethod:@"PUT"];
|
||||
[request setMimeType:@"image/jpeg"];
|
||||
[request setDateString:dateString];
|
||||
[request setSecretAccessKey:exampleSecretAccessKey];
|
||||
[request setAccessKey:exampleAccessKey];
|
||||
[request buildRequestHeaders];
|
||||
success = [[[request requestHeaders] valueForKey:@"Authorization"] isEqualToString:@"AWS 0PN5J17HBGZHT7JJ3X82:hcicpDDvL9SsO6AkvxqmIWkmOuQ="];
|
||||
GHAssertTrue(success,@"Failed to generate the correct authorisation header for a PUT request");
|
||||
|
||||
// Test List
|
||||
dateString = @"Tue, 27 Mar 2007 19:42:41 +0000";
|
||||
ASIS3BucketRequest *listRequest = [ASIS3BucketRequest requestWithBucket:exampleBucket];
|
||||
[listRequest setPrefix:@"photos"];
|
||||
[listRequest setMaxResultCount:50];
|
||||
[listRequest setMarker:@"puppy"];
|
||||
[listRequest setDateString:dateString];
|
||||
[listRequest setSecretAccessKey:exampleSecretAccessKey];
|
||||
[listRequest setAccessKey:exampleAccessKey];
|
||||
[listRequest buildRequestHeaders];
|
||||
success = [[[listRequest requestHeaders] valueForKey:@"Authorization"] isEqualToString:@"AWS 0PN5J17HBGZHT7JJ3X82:jsRt/rhG+Vtp88HrYL706QhE4w4="];
|
||||
GHAssertTrue(success,@"Failed to generate the correct authorisation header for a list request");
|
||||
|
||||
// Test fetch ACL
|
||||
dateString = @"Tue, 27 Mar 2007 19:44:46 +0000";
|
||||
listRequest = [ASIS3BucketRequest requestWithBucket:exampleBucket subResource:@"acl"];
|
||||
[listRequest setDateString:dateString];
|
||||
[listRequest setSecretAccessKey:exampleSecretAccessKey];
|
||||
[listRequest setAccessKey:exampleAccessKey];
|
||||
[listRequest buildRequestHeaders];
|
||||
success = [[[listRequest requestHeaders] valueForKey:@"Authorization"] isEqualToString:@"AWS 0PN5J17HBGZHT7JJ3X82:thdUi9VAkzhkniLj96JIrOPGi0g="];
|
||||
GHAssertTrue(success,@"Failed to generate the correct authorisation header for a list request");
|
||||
|
||||
// Test Unicode keys
|
||||
// Comment out this test for now, as the S3 example is relying on mixed-case hex-encoded characters in the url, which isn't going to be easy to replicate
|
||||
// exampleBucket = @"dictionary";
|
||||
// key = @"français/préfère";
|
||||
// dateString = @"Wed, 28 Mar 2007 01:49:49 +0000";
|
||||
// request = [ASIS3ObjectRequest requestWithBucket:exampleBucket key:key];
|
||||
// [request setDateString:dateString];
|
||||
// [request setSecretAccessKey:exampleSecretAccessKey];
|
||||
// [request setAccessKey:exampleAccessKey];
|
||||
// [request buildRequestHeaders];
|
||||
// success = [[[request requestHeaders] valueForKey:@"Authorization"] isEqualToString:@"AWS 0PN5J17HBGZHT7JJ3X82:dxhSBHoI6eVSPcXJqEghlUzZMnY="];
|
||||
//GHAssertTrue(success,@"Failed to generate the correct authorisation header for a list request");
|
||||
}
|
||||
|
||||
- (void)testFailure
|
||||
{
|
||||
// Needs expanding to cover more failure states - this is just a test to ensure Amazon's error description is being added to the error
|
||||
|
||||
// We're actually going to try with the Amazon example details, but the request will fail because the date is old
|
||||
NSString *exampleSecretAccessKey = @"uV3F3YluFJax1cknvbcGwgjvx4QpvB+leU8dUj2o";
|
||||
NSString *exampleAccessKey = @"0PN5J17HBGZHT7JJ3X82";
|
||||
NSString *exampleBucket = @"johnsmith";
|
||||
NSString *key = @"photos/puppy.jpg";
|
||||
NSString *dateString = @"Tue, 27 Mar 2007 19:36:42 +0000";
|
||||
ASIS3Request *request = [ASIS3ObjectRequest requestWithBucket:exampleBucket key:key];
|
||||
[request setDateString:dateString];
|
||||
[request setSecretAccessKey:exampleSecretAccessKey];
|
||||
[request setAccessKey:exampleAccessKey];
|
||||
[request startSynchronous];
|
||||
GHAssertNotNil([request error],@"Failed to generate an error when the request was not correctly signed");
|
||||
|
||||
BOOL success = ([[request error] code] == ASIS3ResponseErrorType);
|
||||
GHAssertTrue(success,@"Generated error had the wrong error code");
|
||||
|
||||
success = ([[[request error] localizedDescription] isEqualToString:@"The difference between the request time and the current time is too large."]);
|
||||
GHAssertTrue(success,@"Generated error had the wrong description");
|
||||
|
||||
// Ensure a bucket request will correctly parse an error from S3
|
||||
request = [ASIS3BucketRequest requestWithBucket:exampleBucket];
|
||||
[request setDateString:dateString];
|
||||
[request setSecretAccessKey:exampleSecretAccessKey];
|
||||
[request setAccessKey:exampleAccessKey];
|
||||
[request startSynchronous];
|
||||
GHAssertNotNil([request error],@"Failed to generate an error when the request was not correctly signed");
|
||||
|
||||
success = ([[request error] code] == ASIS3ResponseErrorType);
|
||||
GHAssertTrue(success,@"Generated error had the wrong error code");
|
||||
|
||||
success = ([[[request error] localizedDescription] isEqualToString:@"The difference between the request time and the current time is too large."]);
|
||||
GHAssertTrue(success,@"Generated error had the wrong description");
|
||||
|
||||
// Ensure a service request will correctly parse an error from S3
|
||||
request = [ASIS3ServiceRequest serviceRequest];
|
||||
[request setDateString:dateString];
|
||||
[request setSecretAccessKey:exampleSecretAccessKey];
|
||||
[request setAccessKey:exampleAccessKey];
|
||||
[request startSynchronous];
|
||||
GHAssertNotNil([request error],@"Failed to generate an error when the request was not correctly signed");
|
||||
|
||||
success = ([[request error] code] == ASIS3ResponseErrorType);
|
||||
GHAssertTrue(success,@"Generated error had the wrong error code");
|
||||
|
||||
success = ([[[request error] localizedDescription] isEqualToString:@"The difference between the request time and the current time is too large."]);
|
||||
GHAssertTrue(success,@"Generated error had the wrong description");
|
||||
}
|
||||
|
||||
- (void)createTestBucket
|
||||
{
|
||||
// Test creating a bucket
|
||||
ASIS3BucketRequest *bucketRequest = [ASIS3BucketRequest PUTRequestWithBucket:bucket];
|
||||
[bucketRequest setSecretAccessKey:secretAccessKey];
|
||||
[bucketRequest setAccessKey:accessKey];
|
||||
[bucketRequest startSynchronous];
|
||||
GHAssertNil([bucketRequest error],@"Failed to create a bucket");
|
||||
}
|
||||
|
||||
// To run this test, uncomment and fill in your S3 access details
|
||||
- (void)testREST
|
||||
{
|
||||
[self createTestBucket];
|
||||
|
||||
BOOL success = (![secretAccessKey isEqualToString:@""] && ![accessKey isEqualToString:@""] && ![bucket isEqualToString:@""]);
|
||||
GHAssertTrue(success,@"You need to supply your S3 access details to run the REST test (see the top of ASIS3RequestTests.m)");
|
||||
|
||||
// Test creating a bucket
|
||||
ASIS3BucketRequest *bucketRequest = [ASIS3BucketRequest PUTRequestWithBucket:bucket];
|
||||
[bucketRequest setSecretAccessKey:secretAccessKey];
|
||||
[bucketRequest setAccessKey:accessKey];
|
||||
[bucketRequest startSynchronous];
|
||||
GHAssertNil([bucketRequest error],@"Failed to create a bucket");
|
||||
|
||||
// List buckets to make sure the bucket is there
|
||||
ASIS3ServiceRequest *serviceRequest = [ASIS3ServiceRequest serviceRequest];
|
||||
[serviceRequest setSecretAccessKey:secretAccessKey];
|
||||
[serviceRequest setAccessKey:accessKey];
|
||||
[serviceRequest startSynchronous];
|
||||
GHAssertNil([serviceRequest error],@"Failed to fetch the list of buckets from S3");
|
||||
|
||||
BOOL foundBucket = NO;
|
||||
for (ASIS3Bucket *theBucket in [serviceRequest buckets]) {
|
||||
if ([[theBucket name] isEqualToString:bucket]) {
|
||||
foundBucket = YES;
|
||||
break;
|
||||
}
|
||||
}
|
||||
GHAssertTrue(foundBucket,@"Failed to retrive the newly-created bucket in a list of buckets");
|
||||
|
||||
NSString *key = @"test";
|
||||
|
||||
// Create the file
|
||||
NSString *text = @"This is my content";
|
||||
NSString *filePath = [[self filePathForTemporaryTestFiles] stringByAppendingPathComponent:@"testfile.txt"];
|
||||
[[text dataUsingEncoding:NSUTF8StringEncoding] writeToFile:filePath atomically:NO];
|
||||
|
||||
// PUT the file
|
||||
ASIS3ObjectRequest *request = [ASIS3ObjectRequest PUTRequestForFile:filePath withBucket:bucket key:key];
|
||||
[request setSecretAccessKey:secretAccessKey];
|
||||
[request setAccessKey:accessKey];
|
||||
[request setStorageClass:ASIS3StorageClassReducedRedundancy];
|
||||
[request startSynchronous];
|
||||
success = [[request responseString] isEqualToString:@""];
|
||||
GHAssertTrue(success,@"Failed to PUT a file to S3");
|
||||
|
||||
// GET the file
|
||||
request = [ASIS3ObjectRequest requestWithBucket:bucket key:key];
|
||||
[request setSecretAccessKey:secretAccessKey];
|
||||
[request setAccessKey:accessKey];
|
||||
[request startSynchronous];
|
||||
success = [[request responseString] isEqualToString:@"This is my content"];
|
||||
GHAssertTrue(success,@"Failed to GET the correct data from S3");
|
||||
|
||||
// Test fetch subresource
|
||||
request = [ASIS3ObjectRequest requestWithBucket:bucket key:key subResource:@"acl"];
|
||||
[request setSecretAccessKey:secretAccessKey];
|
||||
[request setAccessKey:accessKey];
|
||||
[request startSynchronous];
|
||||
success = ([[request responseString] rangeOfString:@"<AccessControlPolicy"].location != NSNotFound);
|
||||
GHAssertTrue(success,@"Failed to GET a subresource");
|
||||
|
||||
// COPY the file
|
||||
request = [ASIS3ObjectRequest COPYRequestFromBucket:bucket key:key toBucket:bucket key:@"test-copy"];
|
||||
[request setSecretAccessKey:secretAccessKey];
|
||||
[request setAccessKey:accessKey];
|
||||
[request startSynchronous];
|
||||
GHAssertNil([request error],@"Failed to COPY a file");
|
||||
|
||||
// GET the copy
|
||||
request = [ASIS3ObjectRequest requestWithBucket:bucket key:@"test-copy"];
|
||||
[request setSecretAccessKey:secretAccessKey];
|
||||
[request setAccessKey:accessKey];
|
||||
[request startSynchronous];
|
||||
success = [[request responseString] isEqualToString:@"This is my content"];
|
||||
GHAssertTrue(success,@"Failed to GET the correct data from S3");
|
||||
|
||||
// HEAD the copy
|
||||
request = [ASIS3ObjectRequest HEADRequestWithBucket:bucket key:@"test-copy"];
|
||||
[request setSecretAccessKey:secretAccessKey];
|
||||
[request setAccessKey:accessKey];
|
||||
[request startSynchronous];
|
||||
success = [[request responseString] isEqualToString:@""];
|
||||
GHAssertTrue(success,@"Got a response body for a HEAD request");
|
||||
|
||||
// Test listing the objects in this bucket
|
||||
ASIS3BucketRequest *listRequest = [ASIS3BucketRequest requestWithBucket:bucket];
|
||||
[listRequest setSecretAccessKey:secretAccessKey];
|
||||
[listRequest setAccessKey:accessKey];
|
||||
[listRequest startSynchronous];
|
||||
GHAssertNil([listRequest error],@"Failed to download a list from S3");
|
||||
success = [[listRequest objects] count];
|
||||
GHAssertTrue(success,@"The file didn't show up in the list");
|
||||
|
||||
// Test again with a prefix query
|
||||
listRequest = [ASIS3BucketRequest requestWithBucket:bucket];
|
||||
[listRequest setPrefix:@"test"];
|
||||
[listRequest setSecretAccessKey:secretAccessKey];
|
||||
[listRequest setAccessKey:accessKey];
|
||||
[listRequest startSynchronous];
|
||||
GHAssertNil([listRequest error],@"Failed to download a list from S3");
|
||||
success = [[listRequest objects] count];
|
||||
GHAssertTrue(success,@"The file didn't show up in the list");
|
||||
|
||||
// DELETE the file
|
||||
request = [ASIS3ObjectRequest requestWithBucket:bucket key:key];
|
||||
[request setSecretAccessKey:secretAccessKey];
|
||||
[request setRequestMethod:@"DELETE"];
|
||||
[request setAccessKey:accessKey];
|
||||
[request startSynchronous];
|
||||
success = [[request responseString] isEqualToString:@""];
|
||||
GHAssertTrue(success,@"Failed to DELETE the file from S3");
|
||||
|
||||
// (Also DELETE the copy we made)
|
||||
request = [ASIS3ObjectRequest requestWithBucket:bucket key:@"test-copy"];
|
||||
[request setSecretAccessKey:secretAccessKey];
|
||||
[request setRequestMethod:@"DELETE"];
|
||||
[request setAccessKey:accessKey];
|
||||
[request startSynchronous];
|
||||
success = [[request responseString] isEqualToString:@""];
|
||||
GHAssertTrue(success,@"Failed to DELETE the copy from S3");
|
||||
|
||||
// Attempt to COPY the file, even though it is no longer there
|
||||
request = [ASIS3ObjectRequest COPYRequestFromBucket:bucket key:key toBucket:bucket key:@"test-copy"];
|
||||
[request setSecretAccessKey:secretAccessKey];
|
||||
[request setAccessKey:accessKey];
|
||||
[request startSynchronous];
|
||||
GHAssertNotNil([request error],@"Failed generate an error for what should have been a failed COPY");
|
||||
|
||||
success = [[[request error] localizedDescription] isEqualToString:@"The specified key does not exist."];
|
||||
GHAssertTrue(success, @"Got the wrong error message");
|
||||
|
||||
// PUT some data
|
||||
NSData *data = [@"Hello" dataUsingEncoding:NSUTF8StringEncoding];
|
||||
request = [ASIS3ObjectRequest PUTRequestForData:data withBucket:bucket key:key];
|
||||
[request setMimeType:@"text/plain"];
|
||||
[request setSecretAccessKey:secretAccessKey];
|
||||
[request setAccessKey:accessKey];
|
||||
[request startSynchronous];
|
||||
success = [[request responseString] isEqualToString:@""];
|
||||
GHAssertTrue(success,@"Failed to PUT data to S3");
|
||||
|
||||
// GET the data to check it uploaded properly
|
||||
request = [ASIS3ObjectRequest requestWithBucket:bucket key:key];
|
||||
[request setSecretAccessKey:secretAccessKey];
|
||||
[request setAccessKey:accessKey];
|
||||
[request startSynchronous];
|
||||
success = [[request responseString] isEqualToString:@"Hello"];
|
||||
GHAssertTrue(success,@"Failed to GET the correct data from S3");
|
||||
|
||||
// clean up (Delete it)
|
||||
request = [ASIS3ObjectRequest requestWithBucket:bucket key:key];
|
||||
[request setSecretAccessKey:secretAccessKey];
|
||||
[request setRequestMethod:@"DELETE"];
|
||||
[request setAccessKey:accessKey];
|
||||
[request startSynchronous];
|
||||
success = [[request responseString] isEqualToString:@""];
|
||||
GHAssertTrue(success,@"Failed to DELETE the file from S3");
|
||||
|
||||
// Delete the bucket
|
||||
bucketRequest = [ASIS3BucketRequest DELETERequestWithBucket:bucket];
|
||||
[bucketRequest setSecretAccessKey:secretAccessKey];
|
||||
[bucketRequest setAccessKey:accessKey];
|
||||
[bucketRequest startSynchronous];
|
||||
GHAssertNil([bucketRequest error],@"Failed to delete a bucket");
|
||||
|
||||
|
||||
}
|
||||
|
||||
// Will upload a file to S3, gzipping it before uploading
|
||||
// The file will be stored deflate, and automatically inflated when downloaded
|
||||
// This means the file will take up less storage space, and will upload and download faster
|
||||
// The file should still be accessible by any HTTP client that supports gzipped responses (eg browsers, NSURLConnection, etc)
|
||||
- (void)testGZippedContent
|
||||
{
|
||||
[self createTestBucket];
|
||||
|
||||
BOOL success = (![secretAccessKey isEqualToString:@""] && ![accessKey isEqualToString:@""] && ![bucket isEqualToString:@""]);
|
||||
GHAssertTrue(success,@"You need to supply your S3 access details to run the gzipped put test (see the top of ASIS3RequestTests.m)");
|
||||
|
||||
// Create the file
|
||||
NSString *text = @"This is my content This is my content This is my content This is my content This is my content This is my content";
|
||||
NSString *filePath = [[self filePathForTemporaryTestFiles] stringByAppendingPathComponent:@"testfile.txt"];
|
||||
[[text dataUsingEncoding:NSUTF8StringEncoding] writeToFile:filePath atomically:NO];
|
||||
|
||||
NSString *key = @"gzipped-data";
|
||||
ASIS3ObjectRequest *request = [ASIS3ObjectRequest PUTRequestForFile:filePath withBucket:bucket key:key];
|
||||
[request setSecretAccessKey:secretAccessKey];
|
||||
[request setAccessKey:accessKey];
|
||||
[request setShouldCompressRequestBody:YES];
|
||||
[request setAccessPolicy:ASIS3AccessPolicyPublicRead]; // We'll make it public
|
||||
[request startSynchronous];
|
||||
success = [[request responseString] isEqualToString:@""];
|
||||
GHAssertTrue(success,@"Failed to PUT the gzipped file");
|
||||
|
||||
// GET the file
|
||||
request = [ASIS3ObjectRequest requestWithBucket:bucket key:key];
|
||||
[request setSecretAccessKey:secretAccessKey];
|
||||
[request setAccessKey:accessKey];
|
||||
[request startSynchronous];
|
||||
success = [[request responseString] isEqualToString:text];
|
||||
GHAssertTrue(success,@"Failed to GET the correct data from S3");
|
||||
|
||||
success = [[[request responseHeaders] valueForKey:@"Content-Encoding"] isEqualToString:@"gzip"];
|
||||
GHAssertTrue(success,@"Failed to GET the correct data from S3");
|
||||
|
||||
// Now grab the data using something other than ASIHTTPRequest to ensure other HTTP clients can parse the gzipped content
|
||||
NSData *data = [NSURLConnection sendSynchronousRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"http://%@.s3.amazonaws.com/gzipped-data",bucket]]] returningResponse:NULL error:NULL];
|
||||
NSString *string = [[[NSString alloc] initWithBytes:[data bytes] length:[data length] encoding:NSUTF8StringEncoding] autorelease];
|
||||
success = [string isEqualToString:text];
|
||||
GHAssertTrue(success,@"Failed to GET the correct data from S3");
|
||||
|
||||
// Cleanup
|
||||
request = [ASIS3ObjectRequest DELETERequestWithBucket:bucket key:key];
|
||||
[request setSecretAccessKey:secretAccessKey];
|
||||
[request setAccessKey:accessKey];
|
||||
[request startSynchronous];
|
||||
|
||||
}
|
||||
|
||||
|
||||
- (void)testListRequest
|
||||
{
|
||||
[self createTestBucket];
|
||||
|
||||
BOOL success = (![secretAccessKey isEqualToString:@""] && ![accessKey isEqualToString:@""] && ![bucket isEqualToString:@""]);
|
||||
GHAssertTrue(success,@"You need to supply your S3 access details to run the list test (see the top of ASIS3RequestTests.m)");
|
||||
|
||||
// Firstly, create and upload 5 files
|
||||
int i;
|
||||
for (i=0; i<5; i++) {
|
||||
NSString *text = [NSString stringWithFormat:@"This is the content of file #%hi",i];
|
||||
NSString *filePath = [[self filePathForTemporaryTestFiles] stringByAppendingPathComponent:[NSString stringWithFormat:@"%hi.txt",i]];
|
||||
[[text dataUsingEncoding:NSUTF8StringEncoding] writeToFile:filePath atomically:NO];
|
||||
NSString *key = [NSString stringWithFormat:@"test-file/%hi",i];
|
||||
ASIS3ObjectRequest *request = [ASIS3ObjectRequest PUTRequestForFile:filePath withBucket:bucket key:key];
|
||||
[request setSecretAccessKey:secretAccessKey];
|
||||
[request setAccessKey:accessKey];
|
||||
[request startSynchronous];
|
||||
GHAssertNil([request error],@"Give up on list request test - failed to upload a file");
|
||||
}
|
||||
|
||||
// Test common prefixes
|
||||
ASIS3BucketRequest *listRequest = [ASIS3BucketRequest requestWithBucket:bucket];
|
||||
[listRequest setSecretAccessKey:secretAccessKey];
|
||||
[listRequest setAccessKey:accessKey];
|
||||
[listRequest setDelimiter:@"/"];
|
||||
[listRequest startSynchronous];
|
||||
GHAssertNil([listRequest error],@"Failed to download a list from S3");
|
||||
success = NO;
|
||||
for (NSString *prefix in [listRequest commonPrefixes]) {
|
||||
if ([prefix isEqualToString:@"test-file/"]) {
|
||||
success = YES;
|
||||
}
|
||||
}
|
||||
GHAssertTrue(success,@"Failed to obtain a list of common prefixes");
|
||||
|
||||
|
||||
// Test truncation
|
||||
listRequest = [ASIS3BucketRequest requestWithBucket:bucket];
|
||||
[listRequest setSecretAccessKey:secretAccessKey];
|
||||
[listRequest setAccessKey:accessKey];
|
||||
[listRequest setMaxResultCount:1];
|
||||
[listRequest startSynchronous];
|
||||
GHAssertTrue([listRequest isTruncated],@"Failed to identify what should be a truncated list of results");
|
||||
|
||||
// Test urls are built correctly when requesting a subresource
|
||||
listRequest = [ASIS3BucketRequest requestWithBucket:bucket subResource:@"acl"];
|
||||
[listRequest setSecretAccessKey:secretAccessKey];
|
||||
[listRequest setAccessKey:accessKey];
|
||||
[listRequest setDelimiter:@"/"];
|
||||
[listRequest setPrefix:@"foo"];
|
||||
[listRequest setMarker:@"bar"];
|
||||
[listRequest setMaxResultCount:5];
|
||||
[listRequest buildURL];
|
||||
NSString *expectedURL = [NSString stringWithFormat:@"http://%@.s3.amazonaws.com/?acl&prefix=foo&marker=bar&delimiter=/&max-keys=5",bucket];
|
||||
success = ([[[listRequest url] absoluteString] isEqualToString:expectedURL]);
|
||||
GHAssertTrue(success,@"Generated the wrong url when requesting a subresource");
|
||||
|
||||
|
||||
// Now get a list of the files
|
||||
listRequest = [ASIS3BucketRequest requestWithBucket:bucket];
|
||||
[listRequest setPrefix:@"test-file"];
|
||||
[listRequest setSecretAccessKey:secretAccessKey];
|
||||
[listRequest setAccessKey:accessKey];
|
||||
[listRequest startSynchronous];
|
||||
GHAssertNil([listRequest error],@"Failed to download a list from S3");
|
||||
success = ([[listRequest objects] count] == 5);
|
||||
GHAssertTrue(success,@"List did not contain all files");
|
||||
|
||||
// Please don't use an autoreleased operation queue with waitUntilAllOperationsAreFinished in your own code unless you're writing a test like this one
|
||||
// (The end result is no better than using synchronous requests) thx - Ben :)
|
||||
ASINetworkQueue *queue = [[[ASINetworkQueue alloc] init] autorelease];
|
||||
|
||||
// Test fetching all the items
|
||||
[queue setRequestDidFinishSelector:@selector(GETRequestDone:)];
|
||||
[queue setRequestDidFailSelector:@selector(GETRequestFailed:)];
|
||||
[queue setDelegate:self];
|
||||
for (ASIS3BucketObject *object in [listRequest objects]) {
|
||||
ASIS3ObjectRequest *request = [object GETRequest];
|
||||
[request setAccessKey:accessKey];
|
||||
[request setSecretAccessKey:secretAccessKey];
|
||||
[queue addOperation:request];
|
||||
}
|
||||
[queue go];
|
||||
[queue waitUntilAllOperationsAreFinished];
|
||||
|
||||
|
||||
// Test uploading new files for all the items
|
||||
[queue setRequestDidFinishSelector:@selector(PUTRequestDone:)];
|
||||
[queue setRequestDidFailSelector:@selector(PUTRequestFailed:)];
|
||||
[queue setDelegate:self];
|
||||
i=0;
|
||||
// For each one, we'll just upload the same content again
|
||||
for (ASIS3BucketObject *object in [listRequest objects]) {
|
||||
NSString *oldFilePath = [[self filePathForTemporaryTestFiles] stringByAppendingPathComponent:[NSString stringWithFormat:@"%hi.txt",i]];;
|
||||
ASIS3Request *request = [object PUTRequestWithFile:oldFilePath];
|
||||
[request setAccessKey:accessKey];
|
||||
[request setSecretAccessKey:secretAccessKey];
|
||||
[queue addOperation:request];
|
||||
i++;
|
||||
}
|
||||
[queue go];
|
||||
[queue waitUntilAllOperationsAreFinished];
|
||||
|
||||
|
||||
// Test deleting all the items
|
||||
[queue setRequestDidFinishSelector:@selector(DELETERequestDone:)];
|
||||
[queue setRequestDidFailSelector:@selector(DELETERequestFailed:)];
|
||||
[queue setDelegate:self];
|
||||
i=0;
|
||||
|
||||
for (ASIS3BucketObject *object in [listRequest objects]) {
|
||||
ASIS3ObjectRequest *request = [object DELETERequest];
|
||||
[request setAccessKey:accessKey];
|
||||
[request setSecretAccessKey:secretAccessKey];
|
||||
[queue addOperation:request];
|
||||
i++;
|
||||
}
|
||||
[queue go];
|
||||
[queue waitUntilAllOperationsAreFinished];
|
||||
|
||||
// Grab the list again, it should be empty now
|
||||
listRequest = [ASIS3BucketRequest requestWithBucket:bucket];
|
||||
[listRequest setPrefix:@"test-file"];
|
||||
[listRequest setSecretAccessKey:secretAccessKey];
|
||||
[listRequest setAccessKey:accessKey];
|
||||
[listRequest startSynchronous];
|
||||
GHAssertNil([listRequest error],@"Failed to download a list from S3");
|
||||
success = ([[listRequest objects] count] == 0);
|
||||
GHAssertTrue(success,@"List contained files that should have been deleted");
|
||||
|
||||
}
|
||||
|
||||
- (void)GETRequestDone:(ASIS3Request *)request
|
||||
{
|
||||
NSString *expectedContent = [NSString stringWithFormat:@"This is the content of file #%@",[[[request url] absoluteString] lastPathComponent]];
|
||||
BOOL success = ([[request responseString] isEqualToString:expectedContent]);
|
||||
GHAssertTrue(success,@"Got the wrong content when downloading one of the files");
|
||||
|
||||
}
|
||||
|
||||
- (void)GETRequestFailed:(ASIS3Request *)request
|
||||
{
|
||||
GHAssertTrue(NO,@"GET request failed for one of the items in the list");
|
||||
}
|
||||
|
||||
- (void)PUTRequestDone:(ASIS3Request *)request
|
||||
{
|
||||
}
|
||||
|
||||
- (void)PUTRequestFailed:(ASIS3Request *)request
|
||||
{
|
||||
GHAssertTrue(NO,@"PUT request failed for one of the items in the list");
|
||||
}
|
||||
|
||||
- (void)DELETERequestDone:(ASIS3Request *)request
|
||||
{
|
||||
}
|
||||
|
||||
- (void)DELETERequestFailed:(ASIS3Request *)request
|
||||
{
|
||||
GHAssertTrue(NO,@"DELETE request failed for one of the items in the list");
|
||||
}
|
||||
|
||||
|
||||
// Ensure class convenience constructors return an instance of our subclass
|
||||
- (void)testSubclasses
|
||||
{
|
||||
ASIS3ObjectRequestSubclass *instance = [ASIS3ObjectRequestSubclass requestWithBucket:@"bucket" key:@"key"];
|
||||
BOOL success = [instance isKindOfClass:[ASIS3ObjectRequestSubclass class]];
|
||||
GHAssertTrue(success,@"Convenience constructor failed to return an instance of the correct class");
|
||||
|
||||
instance = [ASIS3ObjectRequestSubclass PUTRequestForFile:@"/file" withBucket:@"bucket" key:@"key"];
|
||||
success = [instance isKindOfClass:[ASIS3ObjectRequestSubclass class]];
|
||||
GHAssertTrue(success,@"Convenience constructor failed to return an instance of the correct class");
|
||||
|
||||
instance = [ASIS3ObjectRequestSubclass DELETERequestWithBucket:@"bucket" key:@"key"];
|
||||
success = [instance isKindOfClass:[ASIS3ObjectRequestSubclass class]];
|
||||
GHAssertTrue(success,@"Convenience constructor failed to return an instance of the correct class");
|
||||
|
||||
instance = [ASIS3ObjectRequestSubclass COPYRequestFromBucket:@"bucket" key:@"key" toBucket:@"bucket" key:@"key2"];
|
||||
success = [instance isKindOfClass:[ASIS3ObjectRequestSubclass class]];
|
||||
GHAssertTrue(success,@"Convenience constructor failed to return an instance of the correct class");
|
||||
|
||||
instance = [ASIS3ObjectRequestSubclass HEADRequestWithBucket:@"bucket" key:@"key"];
|
||||
success = [instance isKindOfClass:[ASIS3ObjectRequestSubclass class]];
|
||||
GHAssertTrue(success,@"Convenience constructor failed to return an instance of the correct class");
|
||||
|
||||
ASIS3BucketRequestSubclass *instance2 = [ASIS3BucketRequestSubclass requestWithBucket:@"bucket"];
|
||||
success = [instance2 isKindOfClass:[ASIS3BucketRequestSubclass class]];
|
||||
GHAssertTrue(success,@"Convenience constructor failed to return an instance of the correct class");
|
||||
|
||||
ASIS3BucketObjectSubclass *instance3 = [ASIS3BucketObjectSubclass objectWithBucket:@"bucket"];
|
||||
success = [instance3 isKindOfClass:[ASIS3BucketObjectSubclass class]];
|
||||
GHAssertTrue(success,@"Convenience constructor failed to return an instance of the correct class");
|
||||
}
|
||||
|
||||
|
||||
- (void)s3RequestFailed:(ASIHTTPRequest *)request
|
||||
{
|
||||
GHFail(@"Request failed - cannot continue with test");
|
||||
[[self networkQueue] cancelAllOperations];
|
||||
}
|
||||
|
||||
- (void)s3QueueFinished:(ASINetworkQueue *)queue
|
||||
{
|
||||
// BOOL success = (progress == 1.0);
|
||||
// GHAssertTrue(success,@"Failed to update progress properly");
|
||||
}
|
||||
|
||||
|
||||
- (void)testQueueProgress
|
||||
{
|
||||
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:@"http://allseeing-i.com/i/logo.png"]];
|
||||
[request startSynchronous];
|
||||
NSData *data = [request responseData];
|
||||
|
||||
[self createTestBucket];
|
||||
|
||||
// Upload objects
|
||||
progress = 0;
|
||||
[[self networkQueue] cancelAllOperations];
|
||||
[self setNetworkQueue:[ASINetworkQueue queue]];
|
||||
[[self networkQueue] setDelegate:self];
|
||||
[[self networkQueue] setRequestDidFailSelector:@selector(s3RequestFailed:)];
|
||||
[[self networkQueue] setQueueDidFinishSelector:@selector(s3QueueFinished:)];
|
||||
[[self networkQueue] setUploadProgressDelegate:self];
|
||||
[[self networkQueue] setShowAccurateProgress:YES];
|
||||
[[self networkQueue] setMaxConcurrentOperationCount:1];
|
||||
|
||||
int i;
|
||||
for (i=0; i<5; i++) {
|
||||
|
||||
NSString *key = [NSString stringWithFormat:@"stuff/file%hi.txt",i+1];
|
||||
|
||||
ASIS3ObjectRequest *s3Request = [ASIS3ObjectRequest PUTRequestForData:data withBucket:bucket key:key];
|
||||
[s3Request setSecretAccessKey:secretAccessKey];
|
||||
[s3Request setAccessKey:accessKey];
|
||||
[s3Request setTimeOutSeconds:20];
|
||||
[s3Request setNumberOfTimesToRetryOnTimeout:3];
|
||||
[s3Request setMimeType:@"image/png"];
|
||||
[[self networkQueue] addOperation:s3Request];
|
||||
}
|
||||
[[self networkQueue] go];
|
||||
[[self networkQueue] waitUntilAllOperationsAreFinished];
|
||||
|
||||
|
||||
// Download objects
|
||||
progress = 0;
|
||||
[[self networkQueue] cancelAllOperations];
|
||||
[self setNetworkQueue:[ASINetworkQueue queue]];
|
||||
[[self networkQueue] setDelegate:self];
|
||||
[[self networkQueue] setRequestDidFailSelector:@selector(s3RequestFailed:)];
|
||||
[[self networkQueue] setQueueDidFinishSelector:@selector(s3QueueFinished:)];
|
||||
[[self networkQueue] setDownloadProgressDelegate:self];
|
||||
[[self networkQueue] setShowAccurateProgress:YES];
|
||||
|
||||
for (i=0; i<5; i++) {
|
||||
|
||||
NSString *key = [NSString stringWithFormat:@"stuff/file%hi.txt",i+1];
|
||||
|
||||
ASIS3ObjectRequest *s3Request = [ASIS3ObjectRequest requestWithBucket:bucket key:key];
|
||||
[s3Request setSecretAccessKey:secretAccessKey];
|
||||
[s3Request setAccessKey:accessKey];
|
||||
NSString *downloadPath = [[self filePathForTemporaryTestFiles] stringByAppendingPathComponent:[NSString stringWithFormat:@"%hi.jpg",i+1]];
|
||||
[s3Request setDownloadDestinationPath:downloadPath];
|
||||
[[self networkQueue] addOperation:s3Request];
|
||||
}
|
||||
|
||||
[[self networkQueue] go];
|
||||
[[self networkQueue] waitUntilAllOperationsAreFinished];
|
||||
progress = 0;
|
||||
|
||||
// Delete objects
|
||||
progress = 0;
|
||||
|
||||
[[self networkQueue] cancelAllOperations];
|
||||
[self setNetworkQueue:[ASINetworkQueue queue]];
|
||||
[[self networkQueue] setDelegate:self];
|
||||
[[self networkQueue] setRequestDidFailSelector:@selector(s3RequestFailed:)];
|
||||
[[self networkQueue] setShowAccurateProgress:YES];
|
||||
|
||||
|
||||
for (i=0; i<5; i++) {
|
||||
|
||||
NSString *key = [NSString stringWithFormat:@"stuff/file%hi.txt",i+1];
|
||||
|
||||
ASIS3ObjectRequest *s3Request = [ASIS3ObjectRequest DELETERequestWithBucket:bucket key:key];
|
||||
[s3Request setSecretAccessKey:secretAccessKey];
|
||||
[s3Request setAccessKey:accessKey];
|
||||
[[self networkQueue] addOperation:s3Request];
|
||||
}
|
||||
[[self networkQueue] go];
|
||||
[[self networkQueue] waitUntilAllOperationsAreFinished];
|
||||
|
||||
}
|
||||
|
||||
// Will be called on Mac OS
|
||||
- (void)setDoubleValue:(double)newProgress;
|
||||
{
|
||||
progress = (float)newProgress;
|
||||
}
|
||||
|
||||
- (void)setProgress:(float)newProgress;
|
||||
{
|
||||
progress = newProgress;
|
||||
}
|
||||
|
||||
|
||||
- (void)testCopy
|
||||
{
|
||||
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
|
||||
|
||||
ASIS3ObjectRequest *request = [ASIS3ObjectRequest requestWithBucket:@"foo" key:@"eep"];
|
||||
ASIS3ObjectRequest *request2 = [request copy];
|
||||
GHAssertNotNil(request2,@"Failed to create a copy");
|
||||
|
||||
[pool release];
|
||||
|
||||
BOOL success = ([request2 retainCount] > 0);
|
||||
GHAssertTrue(success,@"Failed to create a retained copy");
|
||||
success = ([request2 isKindOfClass:[ASIS3Request class]]);
|
||||
GHAssertTrue(success,@"Copy is of wrong class");
|
||||
|
||||
[request2 release];
|
||||
|
||||
pool = [[NSAutoreleasePool alloc] init];
|
||||
|
||||
|
||||
ASIS3BucketRequest *request3 = [ASIS3BucketRequest requestWithBucket:@"foo"];
|
||||
ASIS3BucketRequest *request4 = [request3 copy];
|
||||
GHAssertNotNil(request4,@"Failed to create a copy");
|
||||
|
||||
[pool release];
|
||||
|
||||
success = ([request4 retainCount] > 0);
|
||||
GHAssertTrue(success,@"Failed to create a retained copy");
|
||||
success = ([request4 isKindOfClass:[ASIS3BucketRequest class]]);
|
||||
GHAssertTrue(success,@"Copy is of wrong class");
|
||||
|
||||
[request4 release];
|
||||
|
||||
pool = [[NSAutoreleasePool alloc] init];
|
||||
|
||||
|
||||
ASIS3BucketObject *bucketObject = [ASIS3BucketObject objectWithBucket:@"foo"];
|
||||
ASIS3BucketObject *bucketObject2 = [bucketObject copy];
|
||||
GHAssertNotNil(bucketObject2,@"Failed to create a copy");
|
||||
|
||||
[pool release];
|
||||
|
||||
success = ([bucketObject2 retainCount] > 0);
|
||||
GHAssertTrue(success,@"Failed to create a retained copy");
|
||||
|
||||
[bucketObject2 release];
|
||||
}
|
||||
|
||||
|
||||
- (void)testHTTPS
|
||||
{
|
||||
[ASIS3Request setSharedAccessKey:accessKey];
|
||||
[ASIS3Request setSharedSecretAccessKey:secretAccessKey];
|
||||
|
||||
// Create a bucket
|
||||
ASIS3Request *request = [ASIS3BucketRequest PUTRequestWithBucket:bucket];
|
||||
[request setRequestScheme:ASIS3RequestSchemeHTTPS];
|
||||
[request startSynchronous];
|
||||
GHAssertNil([request error],@"Failed to create a bucket");
|
||||
|
||||
// PUT something in it
|
||||
NSString *key = @"king";
|
||||
request = [ASIS3ObjectRequest PUTRequestForData:[@"fink" dataUsingEncoding:NSUTF8StringEncoding] withBucket:bucket key:key];
|
||||
[request setRequestScheme:ASIS3RequestSchemeHTTPS];
|
||||
[request startSynchronous];
|
||||
BOOL success = [[request responseString] isEqualToString:@""];
|
||||
GHAssertTrue(success,@"Failed to PUT some data into S3");
|
||||
|
||||
// GET it
|
||||
request = [ASIS3ObjectRequest requestWithBucket:bucket key:key];
|
||||
[request setRequestScheme:ASIS3RequestSchemeHTTPS];
|
||||
[request startSynchronous];
|
||||
success = [[request responseString] isEqualToString:@"fink"];
|
||||
GHAssertTrue(success,@"Failed to GET the correct data from S3");
|
||||
|
||||
// DELETE it
|
||||
request = [ASIS3ObjectRequest DELETERequestWithBucket:bucket key:@"king"];
|
||||
[request setRequestScheme:ASIS3RequestSchemeHTTPS];
|
||||
[request startSynchronous];
|
||||
success = [[request responseString] isEqualToString:@""];
|
||||
GHAssertTrue(success,@"Failed to DELETE the object from S3");
|
||||
|
||||
// Delete the bucket
|
||||
request = [ASIS3BucketRequest DELETERequestWithBucket:bucket];
|
||||
[request setRequestScheme:ASIS3RequestSchemeHTTPS];
|
||||
[request startSynchronous];
|
||||
GHAssertNil([request error],@"Failed to delete a bucket");
|
||||
|
||||
[ASIS3Request setSharedAccessKey:nil];
|
||||
[ASIS3Request setSharedSecretAccessKey:nil];
|
||||
}
|
||||
|
||||
// Ideally this test would actually parse the ACL XML and check it, but for now it just makes sure S3 doesn't return an error
|
||||
- (void)testCannedACLs
|
||||
{
|
||||
[ASIS3Request setSharedAccessKey:accessKey];
|
||||
[ASIS3Request setSharedSecretAccessKey:secretAccessKey];
|
||||
|
||||
// Create a bucket
|
||||
ASIS3Request *request = [ASIS3BucketRequest PUTRequestWithBucket:bucket];
|
||||
[request setRequestScheme:ASIS3RequestSchemeHTTPS];
|
||||
[request startSynchronous];
|
||||
GHAssertNil([request error],@"Failed to create a bucket");
|
||||
|
||||
NSArray *ACLs = [NSArray arrayWithObjects:ASIS3AccessPolicyPrivate,ASIS3AccessPolicyPublicRead,ASIS3AccessPolicyPublicReadWrite,ASIS3AccessPolicyAuthenticatedRead,ASIS3AccessPolicyBucketOwnerRead,ASIS3AccessPolicyBucketOwnerFullControl,nil];
|
||||
|
||||
for (NSString *cannedACL in ACLs) {
|
||||
// PUT object
|
||||
NSString *key = @"king";
|
||||
request = [ASIS3ObjectRequest PUTRequestForData:[@"fink" dataUsingEncoding:NSUTF8StringEncoding] withBucket:bucket key:key];
|
||||
[request setAccessPolicy:cannedACL];
|
||||
[request startSynchronous];
|
||||
GHAssertNil([request error],@"Failed to PUT some data into S3");
|
||||
|
||||
// GET object ACL
|
||||
request = [ASIS3ObjectRequest requestWithBucket:bucket key:key subResource:@"acl"];
|
||||
[request startSynchronous];
|
||||
GHAssertNil([request error],@"Failed to fetch the object");
|
||||
}
|
||||
|
||||
// DELETE it
|
||||
request = [ASIS3ObjectRequest DELETERequestWithBucket:bucket key:@"king"];
|
||||
[request setRequestScheme:ASIS3RequestSchemeHTTPS];
|
||||
[request startSynchronous];
|
||||
BOOL success = [[request responseString] isEqualToString:@""];
|
||||
GHAssertTrue(success,@"Failed to DELETE the object from S3");
|
||||
|
||||
// Delete the bucket
|
||||
request = [ASIS3BucketRequest DELETERequestWithBucket:bucket];
|
||||
[request setRequestScheme:ASIS3RequestSchemeHTTPS];
|
||||
[request startSynchronous];
|
||||
GHAssertNil([request error],@"Failed to delete a bucket");
|
||||
|
||||
[ASIS3Request setSharedAccessKey:nil];
|
||||
[ASIS3Request setSharedSecretAccessKey:nil];
|
||||
}
|
||||
|
||||
|
||||
@synthesize networkQueue;
|
||||
|
||||
@end
|
||||
20
projects/mtg/iOS/asi-http-request/Tests/ASITestCase.h
Normal file
20
projects/mtg/iOS/asi-http-request/Tests/ASITestCase.h
Normal file
@@ -0,0 +1,20 @@
|
||||
//
|
||||
// ASITestCase.h
|
||||
// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest
|
||||
//
|
||||
// Created by Ben Copsey on 26/07/2009.
|
||||
// Copyright 2009 All-Seeing Interactive. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#if TARGET_OS_IPHONE
|
||||
#import <GHUnitIOS/GHUnit.h>
|
||||
#else
|
||||
#import <GHUnit/GHUnit.h>
|
||||
#endif
|
||||
|
||||
@interface ASITestCase : GHTestCase {
|
||||
}
|
||||
- (NSString *)filePathForTemporaryTestFiles;
|
||||
@end
|
||||
23
projects/mtg/iOS/asi-http-request/Tests/ASITestCase.m
Normal file
23
projects/mtg/iOS/asi-http-request/Tests/ASITestCase.m
Normal file
@@ -0,0 +1,23 @@
|
||||
//
|
||||
// ASITestCase.m
|
||||
// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest
|
||||
//
|
||||
// Created by Ben Copsey on 26/07/2009.
|
||||
// Copyright 2009 All-Seeing Interactive. All rights reserved.
|
||||
//
|
||||
|
||||
#import "ASITestCase.h"
|
||||
|
||||
|
||||
@implementation ASITestCase
|
||||
|
||||
- (NSString *)filePathForTemporaryTestFiles
|
||||
{
|
||||
NSString *path = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:@"ASIHTTPRequest Test Files"];
|
||||
if (![[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:NULL]) {
|
||||
[[NSFileManager defaultManager] createDirectoryAtPath:path withIntermediateDirectories:NO attributes:nil error:NULL];
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,16 @@
|
||||
//
|
||||
// ASIWebPageRequestTests.h
|
||||
// Mac
|
||||
//
|
||||
// Created by Ben Copsey on 06/01/2011.
|
||||
// Copyright 2011 All-Seeing Interactive. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "ASITestCase.h"
|
||||
|
||||
@interface ASIWebPageRequestTests : ASITestCase {
|
||||
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,41 @@
|
||||
//
|
||||
// ASIWebPageRequestTests.m
|
||||
// Mac
|
||||
//
|
||||
// Created by Ben Copsey on 06/01/2011.
|
||||
// Copyright 2011 All-Seeing Interactive. All rights reserved.
|
||||
//
|
||||
|
||||
#import "ASIWebPageRequestTests.h"
|
||||
#import "ASIWebPageRequest.h"
|
||||
|
||||
@implementation ASIWebPageRequestTests
|
||||
|
||||
- (void)testEncoding
|
||||
{
|
||||
NSArray *encodings = [NSArray arrayWithObjects:@"us-ascii",@"iso-8859-1",@"utf-16",@"utf-8",nil];
|
||||
NSArray *expectedResponses = [NSArray arrayWithObjects:@"Hi there",@"Olá",@"你好",@"今日は",nil];
|
||||
NSUInteger i;
|
||||
for (i=0; i<[encodings count]; i++) {
|
||||
ASIWebPageRequest *request = [ASIWebPageRequest requestWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"http://allseeing-i.com/ASIHTTPRequest/tests/asiwebpagerequest/character-encoding/%@",[encodings objectAtIndex:i]]]];
|
||||
[request setUserInfo:[NSDictionary dictionaryWithObject:[expectedResponses objectAtIndex:i] forKey:@"expected-response"]];
|
||||
[request setDelegate:self];
|
||||
[request setUrlReplacementMode:ASIReplaceExternalResourcesWithLocalURLs];
|
||||
[request startAsynchronous];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)requestFinished:(ASIHTTPRequest *)request
|
||||
{
|
||||
if ([[request userInfo] objectForKey:@"expected-response"]) {
|
||||
BOOL success = ([[request responseString] rangeOfString:[[request userInfo] objectForKey:@"expected-response"]].location != NSNotFound);
|
||||
GHAssertTrue(success,@"Response HTML used wrong encoding");
|
||||
}
|
||||
}
|
||||
|
||||
- (void)requestFailed:(ASIHTTPRequest *)request
|
||||
{
|
||||
GHAssertNil([request error],@"Request failed, cannot proceed with test");
|
||||
}
|
||||
|
||||
@end
|
||||
16
projects/mtg/iOS/asi-http-request/Tests/BlocksTests.h
Normal file
16
projects/mtg/iOS/asi-http-request/Tests/BlocksTests.h
Normal file
@@ -0,0 +1,16 @@
|
||||
//
|
||||
// BlocksTests.h
|
||||
// Mac
|
||||
//
|
||||
// Created by Ben Copsey on 18/10/2010.
|
||||
// Copyright 2010 All-Seeing Interactive. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "ASITestCase.h"
|
||||
|
||||
@interface BlocksTests : ASITestCase {
|
||||
|
||||
}
|
||||
|
||||
@end
|
||||
105
projects/mtg/iOS/asi-http-request/Tests/BlocksTests.m
Normal file
105
projects/mtg/iOS/asi-http-request/Tests/BlocksTests.m
Normal file
@@ -0,0 +1,105 @@
|
||||
//
|
||||
// BlocksTests.m
|
||||
// Mac
|
||||
//
|
||||
// Created by Ben Copsey on 18/10/2010.
|
||||
// Copyright 2010 All-Seeing Interactive. All rights reserved.
|
||||
//
|
||||
|
||||
#import "BlocksTests.h"
|
||||
#import "ASIHTTPRequest.h"
|
||||
|
||||
|
||||
@implementation BlocksTests
|
||||
|
||||
// ASIHTTPRequest always calls blocks on the main thread (just like it does with delegate methods)
|
||||
// So, we'll force this request to run on the main thread so we can rely on blocks having been called before the request returns
|
||||
- (BOOL)shouldRunOnMainThread { return YES; }
|
||||
|
||||
#if NS_BLOCKS_AVAILABLE
|
||||
#if TARGET_OS_IPHONE
|
||||
// It isn't safe to allow the view to deallocate on a thread other than the main thread / web thread, so this test is designed to cause a crash semi-reliably
|
||||
- (void)testBlockMainThreadSafety
|
||||
{
|
||||
NSURL *url = [NSURL URLWithString:@"http://allseeing-i.com"];
|
||||
UIWebView *webView = [[[UIWebView alloc] initWithFrame:CGRectMake(0,0,200,200)] autorelease];
|
||||
__block ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
|
||||
[request setCompletionBlock:^ {[webView loadHTMLString:[request responseString] baseURL:url]; }];
|
||||
[request startAsynchronous];
|
||||
}
|
||||
#endif
|
||||
|
||||
- (void)testBlocks
|
||||
{
|
||||
NSData *dataToSend = [@"This is my post body" dataUsingEncoding:NSUTF8StringEncoding];
|
||||
|
||||
__block BOOL started = NO;
|
||||
__block BOOL receivedHeaders = NO;
|
||||
__block BOOL complete = NO;
|
||||
__block BOOL failed = NO;
|
||||
__block unsigned long long totalBytesReceived = 0;
|
||||
__block unsigned long long totalDownloadSize = 0;
|
||||
__block unsigned long long totalBytesSent = 0;
|
||||
__block unsigned long long totalUploadSize = 0;
|
||||
NSMutableData *dataReceived = [NSMutableData data];
|
||||
|
||||
// There's actually no need for us to use '__block' here, because we aren't using the request inside any of our blocks, but it's good to get into the habit of doing this anyway.
|
||||
__block ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:@"http://allseeing-i.com/ASIHTTPRequest/tests/blocks"]];
|
||||
[request setStartedBlock:^{
|
||||
started = YES;
|
||||
}];
|
||||
[request setHeadersReceivedBlock:^(NSDictionary *headers) {
|
||||
receivedHeaders = YES;
|
||||
}];
|
||||
[request setCompletionBlock:^{
|
||||
complete = YES;
|
||||
}];
|
||||
[request setFailedBlock:^{
|
||||
failed = YES;
|
||||
}];
|
||||
[request setBytesReceivedBlock:^(unsigned long long length, unsigned long long total) {
|
||||
totalBytesReceived += length;
|
||||
}];
|
||||
[request setDownloadSizeIncrementedBlock:^(long long length){
|
||||
totalDownloadSize += length;
|
||||
}];
|
||||
[request setBytesSentBlock:^(unsigned long long length, unsigned long long total) {
|
||||
totalBytesSent += length;
|
||||
}];
|
||||
[request setUploadSizeIncrementedBlock:^(long long length){
|
||||
totalUploadSize += length;
|
||||
}];
|
||||
[request setDataReceivedBlock:^(NSData *data){
|
||||
[dataReceived appendData:data];
|
||||
}];
|
||||
|
||||
[request setRequestMethod:@"PUT"];
|
||||
[request appendPostData:dataToSend];
|
||||
[request startSynchronous];
|
||||
|
||||
GHAssertFalse(failed,@"Request failed, cannot proceed with test");
|
||||
GHAssertTrue(started,@"Failed to call started block");
|
||||
GHAssertTrue(receivedHeaders,@"Failed to call received headers block");
|
||||
GHAssertTrue(complete,@"Failed to call completed block");
|
||||
|
||||
BOOL success = (totalBytesReceived == 457);
|
||||
GHAssertTrue(success,@"Failed to call bytes received block, or got wrong amount of data");
|
||||
success = (totalDownloadSize == 457);
|
||||
GHAssertTrue(success,@"Failed to call download size increment block");
|
||||
|
||||
success = (totalBytesSent == [dataToSend length]);
|
||||
GHAssertTrue(success,@"Failed to call bytes sent block");
|
||||
success = (totalUploadSize == [dataToSend length]);
|
||||
GHAssertTrue(success,@"Failed to call upload size increment block");
|
||||
|
||||
|
||||
request = [ASIHTTPRequest requestWithURL:nil];
|
||||
[request setFailedBlock:^{
|
||||
failed = YES;
|
||||
}];
|
||||
[request startSynchronous];
|
||||
GHAssertTrue(failed,@"Failed to call request failure block");
|
||||
}
|
||||
#endif
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,21 @@
|
||||
//
|
||||
// ClientCertificateTests.h
|
||||
// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest
|
||||
//
|
||||
// Created by Ben Copsey on 18/08/2010.
|
||||
// Copyright 2010 All-Seeing Interactive. All rights reserved.
|
||||
//
|
||||
|
||||
// Currently, these tests only work on iOS - it looks like the method for parsing the PKCS12 file would need to be ported
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <Security/Security.h>
|
||||
#import "ASITestCase.h"
|
||||
|
||||
@interface ClientCertificateTests : ASITestCase {
|
||||
|
||||
}
|
||||
- (void)testClientCertificate;
|
||||
+ (BOOL)extractIdentity:(SecIdentityRef *)outIdentity andTrust:(SecTrustRef*)outTrust fromPKCS12Data:(NSData *)inPKCS12Data;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,76 @@
|
||||
//
|
||||
// ClientCertificateTests.m
|
||||
// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest
|
||||
//
|
||||
// Created by Ben Copsey on 18/08/2010.
|
||||
// Copyright 2010 All-Seeing Interactive. All rights reserved.
|
||||
//
|
||||
|
||||
#import "ClientCertificateTests.h"
|
||||
#import "ASIHTTPRequest.h"
|
||||
|
||||
@implementation ClientCertificateTests
|
||||
|
||||
- (void)testClientCertificate
|
||||
{
|
||||
// This test will fail the second time it is run, I presume the certificate is being cached somewhere
|
||||
|
||||
// This url requires we present a client certificate to connect to it
|
||||
NSURL *url = [NSURL URLWithString:@"https://clientcertificate.allseeing-i.com:8080/ASIHTTPRequest/tests/first"];
|
||||
|
||||
// First, let's attempt to connect to the url without supplying a certificate
|
||||
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
|
||||
|
||||
// We have to turn off validation for these tests, as the server has a self-signed certificate
|
||||
[request setValidatesSecureCertificate:NO];
|
||||
[request startSynchronous];
|
||||
|
||||
GHAssertNotNil([request error],@"Request succeeded even though we presented no certificate, cannot proceed with test");
|
||||
|
||||
// Now, let's grab the certificate (included in the resources of the test app)
|
||||
SecIdentityRef identity = NULL;
|
||||
SecTrustRef trust = NULL;
|
||||
NSData *PKCS12Data = [NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"client" ofType:@"p12"]];
|
||||
[ClientCertificateTests extractIdentity:&identity andTrust:&trust fromPKCS12Data:PKCS12Data];
|
||||
|
||||
request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:@"https://clientcertificate.allseeing-i.com:8080/ASIHTTPRequest/tests/first"]];
|
||||
|
||||
// In this case, we have no need to add extra certificates, just the one inside the indentity will be used
|
||||
[request setClientCertificateIdentity:identity];
|
||||
[request setValidatesSecureCertificate:NO];
|
||||
[request startSynchronous];
|
||||
|
||||
// Make sure the request got the correct content
|
||||
GHAssertNil([request error],@"Request failed with error %@",[request error]);
|
||||
BOOL success = [[request responseString] isEqualToString:@"This is the expected content for the first string"];
|
||||
GHAssertTrue(success,@"Request failed to download the correct content");
|
||||
}
|
||||
|
||||
// Based on code from http://developer.apple.com/mac/library/documentation/Security/Conceptual/CertKeyTrustProgGuide/iPhone_Tasks/iPhone_Tasks.html
|
||||
|
||||
+ (BOOL)extractIdentity:(SecIdentityRef *)outIdentity andTrust:(SecTrustRef*)outTrust fromPKCS12Data:(NSData *)inPKCS12Data
|
||||
{
|
||||
OSStatus securityError = errSecSuccess;
|
||||
|
||||
NSDictionary *optionsDictionary = [NSDictionary dictionaryWithObject:@"" forKey:(id)kSecImportExportPassphrase];
|
||||
|
||||
CFArrayRef items = CFArrayCreate(NULL, 0, 0, NULL);
|
||||
securityError = SecPKCS12Import((CFDataRef)inPKCS12Data,(CFDictionaryRef)optionsDictionary,&items);
|
||||
|
||||
if (securityError == 0) {
|
||||
CFDictionaryRef myIdentityAndTrust = CFArrayGetValueAtIndex (items, 0);
|
||||
const void *tempIdentity = NULL;
|
||||
tempIdentity = CFDictionaryGetValue (myIdentityAndTrust, kSecImportItemIdentity);
|
||||
*outIdentity = (SecIdentityRef)tempIdentity;
|
||||
const void *tempTrust = NULL;
|
||||
tempTrust = CFDictionaryGetValue (myIdentityAndTrust, kSecImportItemTrust);
|
||||
*outTrust = (SecTrustRef)tempTrust;
|
||||
} else {
|
||||
NSLog(@"Failed with error code %d",(int)securityError);
|
||||
return NO;
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
89
projects/mtg/iOS/asi-http-request/Tests/GHUnitTestMain.m
Normal file
89
projects/mtg/iOS/asi-http-request/Tests/GHUnitTestMain.m
Normal file
@@ -0,0 +1,89 @@
|
||||
//
|
||||
// GHUnitTestMain.m
|
||||
// GHUnit
|
||||
//
|
||||
// Created by Gabriel Handford on 2/22/09.
|
||||
// Copyright 2009. All rights reserved.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person
|
||||
// obtaining a copy of this software and associated documentation
|
||||
// files (the "Software"), to deal in the Software without
|
||||
// restriction, including without limitation the rights to use,
|
||||
// copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the
|
||||
// Software is furnished to do so, subject to the following
|
||||
// conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
// OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import <GHUnit/GHUnit.h>
|
||||
#import <GHUnit/GHTestApp.h>
|
||||
#import <GHUnit/GHTesting.h>
|
||||
|
||||
// Default exception handler
|
||||
void exceptionHandler(NSException *exception) {
|
||||
NSLog(@"%@\n%@", [exception reason], GHUStackTraceFromException(exception));
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
|
||||
/*!
|
||||
For debugging:
|
||||
Go into the "Get Info" contextual menu of your (test) executable (inside the "Executables" group in the left panel of XCode).
|
||||
Then go in the "Arguments" tab. You can add the following environment variables:
|
||||
|
||||
Default: Set to:
|
||||
NSDebugEnabled NO "YES"
|
||||
NSZombieEnabled NO "YES"
|
||||
NSDeallocateZombies NO "YES"
|
||||
NSHangOnUncaughtException NO "YES"
|
||||
|
||||
NSEnableAutoreleasePool YES "NO"
|
||||
NSAutoreleaseFreedObjectCheckEnabled NO "YES"
|
||||
NSAutoreleaseHighWaterMark 0 non-negative integer
|
||||
NSAutoreleaseHighWaterResolution 0 non-negative integer
|
||||
|
||||
For info on these varaiables see NSDebug.h; http://theshadow.uw.hu/iPhoneSDKdoc/Foundation.framework/NSDebug.h.html
|
||||
|
||||
For malloc debugging see: http://developer.apple.com/mac/library/documentation/Performance/Conceptual/ManagingMemory/Articles/MallocDebug.html
|
||||
*/
|
||||
|
||||
NSSetUncaughtExceptionHandler(&exceptionHandler);
|
||||
|
||||
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
|
||||
|
||||
// Register any special test case classes
|
||||
//[[GHTesting sharedInstance] registerClassName:@"GHSpecialTestCase"];
|
||||
|
||||
int retVal = 0;
|
||||
// If GHUNIT_CLI is set we are using the command line interface and run the tests
|
||||
// Otherwise load the GUI app
|
||||
if (getenv("GHUNIT_CLI")) {
|
||||
retVal = [GHTestRunner run];
|
||||
} else {
|
||||
// To run all tests (from ENV)
|
||||
GHTestApp *app = [[GHTestApp alloc] init];
|
||||
// To run a different test suite:
|
||||
//GHTestSuite *suite = [GHTestSuite suiteWithTestFilter:@"GHSlowTest,GHAsyncTestCaseTest"];
|
||||
//GHTestApp *app = [[GHTestApp alloc] initWithSuite:suite];
|
||||
// Or set global:
|
||||
//GHUnitTest = @"GHSlowTest";
|
||||
[NSApp run];
|
||||
[app release];
|
||||
}
|
||||
[pool release];
|
||||
return retVal;
|
||||
}
|
||||
28
projects/mtg/iOS/asi-http-request/Tests/PerformanceTests.h
Normal file
28
projects/mtg/iOS/asi-http-request/Tests/PerformanceTests.h
Normal file
@@ -0,0 +1,28 @@
|
||||
//
|
||||
// PerformanceTests.h
|
||||
// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest
|
||||
//
|
||||
// Created by Ben Copsey on 17/12/2009.
|
||||
// Copyright 2009 All-Seeing Interactive. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "ASITestCase.h"
|
||||
|
||||
@interface PerformanceTests : ASITestCase {
|
||||
NSURL *testURL;
|
||||
|
||||
NSDate *testStartDate;
|
||||
int requestsComplete;
|
||||
NSMutableArray *responseData;
|
||||
unsigned long bytesDownloaded;
|
||||
}
|
||||
|
||||
- (void)testASIHTTPRequestAsyncPerformance;
|
||||
- (void)testNSURLConnectionAsyncPerformance;
|
||||
|
||||
@property (retain,nonatomic) NSURL *testURL;
|
||||
@property (retain,nonatomic) NSDate *testStartDate;
|
||||
@property (assign,nonatomic) int requestsComplete;
|
||||
@property (retain,nonatomic) NSMutableArray *responseData;
|
||||
@end
|
||||
234
projects/mtg/iOS/asi-http-request/Tests/PerformanceTests.m
Normal file
234
projects/mtg/iOS/asi-http-request/Tests/PerformanceTests.m
Normal file
@@ -0,0 +1,234 @@
|
||||
//
|
||||
// PerformanceTests.m
|
||||
// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest
|
||||
//
|
||||
// Created by Ben Copsey on 17/12/2009.
|
||||
// Copyright 2009 All-Seeing Interactive. All rights reserved.
|
||||
//
|
||||
|
||||
#import "PerformanceTests.h"
|
||||
#import "ASIHTTPRequest.h"
|
||||
|
||||
// IMPORTANT - these tests need to be run one at a time!
|
||||
|
||||
@interface NSURLConnectionSubclass : NSURLConnection {
|
||||
int tag;
|
||||
}
|
||||
@property (assign) int tag;
|
||||
@end
|
||||
@implementation NSURLConnectionSubclass
|
||||
@synthesize tag;
|
||||
@end
|
||||
|
||||
// Stop clang complaining about undeclared selectors
|
||||
@interface PerformanceTests ()
|
||||
- (void)runSynchronousASIHTTPRequests;
|
||||
- (void)runSynchronousNSURLConnections;
|
||||
- (void)startASIHTTPRequests;
|
||||
- (void)startASIHTTPRequestsWithQueue;
|
||||
- (void)startNSURLConnections;
|
||||
@end
|
||||
|
||||
|
||||
@implementation PerformanceTests
|
||||
|
||||
- (void)setUp
|
||||
{
|
||||
[self setTestURL:[NSURL URLWithString:@"http://allseeing-i.com/ASIHTTPRequest/tests/the_great_american_novel_%28abridged%29.txt"]];
|
||||
//[self setTestURL:[NSURL URLWithString:@"http://allseeing-i.com"]];
|
||||
}
|
||||
|
||||
- (void)testASIHTTPRequestSynchronousPerformance
|
||||
{
|
||||
[self performSelectorOnMainThread:@selector(runSynchronousASIHTTPRequests) withObject:nil waitUntilDone:YES];
|
||||
}
|
||||
|
||||
|
||||
- (void)runSynchronousASIHTTPRequests
|
||||
{
|
||||
int runTimes = 10;
|
||||
NSTimeInterval times[runTimes];
|
||||
int i;
|
||||
for (i=0; i<runTimes; i++) {
|
||||
NSDate *startTime = [NSDate date];
|
||||
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:testURL];
|
||||
//Send the same headers as NSURLRequest
|
||||
[request addRequestHeader:@"Pragma" value:@"no-cache"];
|
||||
[request addRequestHeader:@"Accept" value:@"*/*"];
|
||||
[request addRequestHeader:@"Accept-Language" value:@"en/us"];
|
||||
[request startSynchronous];
|
||||
if ([request error]) {
|
||||
NSLog(@"Request failed - cannot proceed with test");
|
||||
return;
|
||||
}
|
||||
times[i] = [[NSDate date] timeIntervalSinceDate:startTime];
|
||||
}
|
||||
NSTimeInterval bestTime = 1000;
|
||||
NSTimeInterval worstTime = 0;
|
||||
NSTimeInterval totalTime = 0;
|
||||
for (i=0; i<runTimes; i++) {
|
||||
if (times[i] < bestTime) {
|
||||
bestTime = times[i];
|
||||
}
|
||||
if (times[i] > worstTime) {
|
||||
worstTime = times[i];
|
||||
}
|
||||
totalTime += times[i];
|
||||
}
|
||||
NSLog(@"Ran %i requests in %f seconds (average time: %f secs / best time: %f secs / worst time: %f secs)",runTimes,totalTime,totalTime/runTimes,bestTime,worstTime);
|
||||
}
|
||||
|
||||
|
||||
- (void)testNSURLConnectionSynchronousPerformance
|
||||
{
|
||||
[self performSelectorOnMainThread:@selector(runSynchronousNSURLConnections) withObject:nil waitUntilDone:YES];
|
||||
}
|
||||
|
||||
|
||||
- (void)runSynchronousNSURLConnections
|
||||
{
|
||||
int runTimes = 10;
|
||||
NSTimeInterval times[runTimes];
|
||||
int i;
|
||||
for (i=0; i<runTimes; i++) {
|
||||
NSDate *startTime = [NSDate date];
|
||||
|
||||
NSURLResponse *response = nil;
|
||||
NSError *error = nil;
|
||||
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:testURL cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:10];
|
||||
[NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
|
||||
if (error) {
|
||||
NSLog(@"Request failed - cannot proceed with test");
|
||||
return;
|
||||
}
|
||||
times[i] = [[NSDate date] timeIntervalSinceDate:startTime];
|
||||
}
|
||||
NSTimeInterval bestTime = 1000;
|
||||
NSTimeInterval worstTime = 0;
|
||||
NSTimeInterval totalTime = 0;
|
||||
for (i=0; i<runTimes; i++) {
|
||||
if (times[i] < bestTime) {
|
||||
bestTime = times[i];
|
||||
}
|
||||
if (times[i] > worstTime) {
|
||||
worstTime = times[i];
|
||||
}
|
||||
totalTime += times[i];
|
||||
}
|
||||
NSLog(@"Ran %i requests in %f seconds (average time: %f secs / best time: %f secs / worst time: %f secs)",runTimes,totalTime,totalTime/runTimes,bestTime,worstTime);
|
||||
}
|
||||
|
||||
|
||||
- (void)testASIHTTPRequestAsyncPerformance
|
||||
{
|
||||
[self performSelectorOnMainThread:@selector(startASIHTTPRequests) withObject:nil waitUntilDone:NO];
|
||||
}
|
||||
|
||||
- (void)testQueuedASIHTTPRequestAsyncPerformance
|
||||
{
|
||||
[self performSelectorOnMainThread:@selector(startASIHTTPRequestsWithQueue) withObject:nil waitUntilDone:NO];
|
||||
}
|
||||
|
||||
|
||||
- (void)startASIHTTPRequests
|
||||
{
|
||||
bytesDownloaded = 0;
|
||||
[self setRequestsComplete:0];
|
||||
[self setTestStartDate:[NSDate date]];
|
||||
int i;
|
||||
for (i=0; i<10; i++) {
|
||||
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:testURL];
|
||||
//Send the same headers as NSURLRequest
|
||||
[request addRequestHeader:@"Pragma" value:@"no-cache"];
|
||||
[request addRequestHeader:@"Accept" value:@"*/*"];
|
||||
[request addRequestHeader:@"Accept-Language" value:@"en/us"];
|
||||
[request setDelegate:self];
|
||||
[request startAsynchronous];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)startASIHTTPRequestsWithQueue
|
||||
{
|
||||
bytesDownloaded = 0;
|
||||
[self setRequestsComplete:0];
|
||||
[self setTestStartDate:[NSDate date]];
|
||||
int i;
|
||||
NSOperationQueue *queue = [[[NSOperationQueue alloc] init] autorelease];
|
||||
[queue setMaxConcurrentOperationCount:4];
|
||||
for (i=0; i<10; i++) {
|
||||
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:testURL];
|
||||
//Send the same headers as NSURLRequest
|
||||
[request addRequestHeader:@"Pragma" value:@"no-cache"];
|
||||
[request addRequestHeader:@"Accept" value:@"*/*"];
|
||||
[request addRequestHeader:@"Accept-Language" value:@"en/us"];
|
||||
[request setUseCookiePersistence:NO];
|
||||
[request setUseSessionPersistence:NO];
|
||||
[request setDelegate:self];
|
||||
[queue addOperation:request];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)requestFailed:(ASIHTTPRequest *)request
|
||||
{
|
||||
GHFail(@"Cannot proceed with ASIHTTPRequest test - a request failed");
|
||||
}
|
||||
|
||||
- (void)requestFinished:(ASIHTTPRequest *)request
|
||||
{
|
||||
bytesDownloaded += [[request responseData] length];
|
||||
requestsComplete++;
|
||||
if (requestsComplete == 10) {
|
||||
NSLog(@"ASIHTTPRequest: Completed 10 (downloaded %lu bytes) requests in %f seconds",bytesDownloaded,[[NSDate date] timeIntervalSinceDate:[self testStartDate]]);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)testNSURLConnectionAsyncPerformance
|
||||
{
|
||||
[self performSelectorOnMainThread:@selector(startNSURLConnections) withObject:nil waitUntilDone:NO];
|
||||
}
|
||||
|
||||
- (void)startNSURLConnections
|
||||
{
|
||||
bytesDownloaded = 0;
|
||||
[self setRequestsComplete:0];
|
||||
[self setTestStartDate:[NSDate date]];
|
||||
[self setResponseData:[NSMutableArray arrayWithCapacity:5]];
|
||||
|
||||
int i;
|
||||
for (i=0; i<10; i++) {
|
||||
NSURLRequest *request = [NSURLRequest requestWithURL:testURL cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:10];
|
||||
[[self responseData] addObject:[NSMutableData data]];
|
||||
NSURLConnectionSubclass *connection = [[[NSURLConnectionSubclass alloc] initWithRequest:request delegate:self startImmediately:YES] autorelease];
|
||||
[connection setTag:i];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)connection:(NSURLConnectionSubclass *)connection didReceiveResponse:(NSURLResponse *)response
|
||||
{
|
||||
}
|
||||
|
||||
- (void)connection:(NSURLConnectionSubclass *)connection didFailWithError:(NSError *)error
|
||||
{
|
||||
GHFail(@"Cannot proceed with NSURLConnection test - a request failed");
|
||||
}
|
||||
|
||||
- (void)connection:(NSURLConnectionSubclass *)connection didReceiveData:(NSData *)data
|
||||
{
|
||||
[[[self responseData] objectAtIndex:[connection tag]] appendData:data];
|
||||
|
||||
}
|
||||
|
||||
- (void)connectionDidFinishLoading:(NSURLConnectionSubclass *)connection
|
||||
{
|
||||
bytesDownloaded += [[responseData objectAtIndex:[connection tag]] length];
|
||||
requestsComplete++;
|
||||
if (requestsComplete == 10) {
|
||||
NSLog(@"NSURLConnection: Completed 10 (downloaded %lu bytes) requests in %f seconds",bytesDownloaded,[[NSDate date] timeIntervalSinceDate:[self testStartDate]]);
|
||||
}
|
||||
}
|
||||
|
||||
@synthesize testURL;
|
||||
@synthesize requestsComplete;
|
||||
@synthesize testStartDate;
|
||||
@synthesize responseData;
|
||||
@end
|
||||
29
projects/mtg/iOS/asi-http-request/Tests/ProxyTests.h
Normal file
29
projects/mtg/iOS/asi-http-request/Tests/ProxyTests.h
Normal file
@@ -0,0 +1,29 @@
|
||||
//
|
||||
// ProxyTests.h
|
||||
// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest
|
||||
//
|
||||
// Created by Ben Copsey on 02/08/2009.
|
||||
// Copyright 2009 All-Seeing Interactive. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "ASITestCase.h"
|
||||
@class ASINetworkQueue;
|
||||
|
||||
// Proxy tests must be run separately from other tests, using the proxy you specify
|
||||
// Some tests require an authenticating proxy to function
|
||||
|
||||
@interface ProxyTests : ASITestCase {
|
||||
ASINetworkQueue *queue;
|
||||
BOOL complete;
|
||||
}
|
||||
- (void)testProxy;
|
||||
- (void)testProxyAutodetect;
|
||||
- (void)testProxyWithSuppliedAuthenticationCredentials;
|
||||
- (void)testDoubleAuthentication;
|
||||
- (void)testProxyForHTTPS;
|
||||
|
||||
@property (retain) ASINetworkQueue *queue;
|
||||
@property (assign) BOOL complete;
|
||||
|
||||
@end
|
||||
203
projects/mtg/iOS/asi-http-request/Tests/ProxyTests.m
Normal file
203
projects/mtg/iOS/asi-http-request/Tests/ProxyTests.m
Normal file
@@ -0,0 +1,203 @@
|
||||
//
|
||||
// ProxyTests.m
|
||||
// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest
|
||||
//
|
||||
// Created by Ben Copsey on 02/08/2009.
|
||||
// Copyright 2009 All-Seeing Interactive. All rights reserved.
|
||||
//
|
||||
|
||||
#import "ProxyTests.h"
|
||||
#import "ASIHTTPRequest.h"
|
||||
#import "ASINetworkQueue.h"
|
||||
|
||||
// Fill in these to run the proxy tests
|
||||
static NSString *proxyHost = @"";
|
||||
static int proxyPort = 0;
|
||||
static NSString *proxyUsername = @"";
|
||||
static NSString *proxyPassword = @"";
|
||||
|
||||
// Stop clang complaining about undeclared selectors
|
||||
@interface ProxyTests ()
|
||||
- (void)requestDone:(ASIHTTPRequest *)request;
|
||||
- (void)requestFailed:(ASIHTTPRequest *)request;
|
||||
@end
|
||||
|
||||
|
||||
@implementation ProxyTests
|
||||
|
||||
- (void)testProxyForHTTPS
|
||||
{
|
||||
// Also test we are case-insensitive comparing our scheme
|
||||
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:@"HTTPS://selfsigned.allseeing-i.com/"]];
|
||||
[request setValidatesSecureCertificate:NO];
|
||||
[request startSynchronous];
|
||||
BOOL success = ([[request responseString] rangeOfString:@"All-Seeing Interactive"].location != NSNotFound);
|
||||
GHAssertTrue(success,@"Failed to connect to an HTTPS URL using a proxy");
|
||||
}
|
||||
|
||||
- (void)testAutoConfigureWithPAC
|
||||
{
|
||||
|
||||
NSString *pacurl = @"file:///non-existent.pac";
|
||||
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:@"http://allseeing-i.com"]];
|
||||
[request setPACurl:[NSURL URLWithString:pacurl]];
|
||||
[request startSynchronous];
|
||||
GHAssertNil([request proxyHost],@"Shouldn't use a proxy here");
|
||||
GHAssertNil([request error],@"Request failed when unable to fetch PAC (should assume no proxy instead)");
|
||||
|
||||
// To run this test, specify the location of the pac script that is available at http://developer.apple.com/samplecode/CFProxySupportTool/listing1.html
|
||||
pacurl = @"file:///Users/ben/Desktop/test.pac";
|
||||
request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:@"http://allseeing-i.com"]];
|
||||
[request setPACurl:[NSURL URLWithString:pacurl]];
|
||||
[request startSynchronous];
|
||||
|
||||
BOOL success = [[request proxyHost] isEqualToString:@"proxy1.apple.com"];
|
||||
GHAssertTrue(success,@"Failed to use the correct proxy");
|
||||
|
||||
request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:@"http://www.apple.com"]];
|
||||
[request setPACurl:[NSURL URLWithString:pacurl]];
|
||||
[request startSynchronous];
|
||||
GHAssertNil([request proxyHost],@"Used a proxy when the script told us to go direct");
|
||||
}
|
||||
|
||||
- (void)testAutoConfigureWithSystemPAC
|
||||
{
|
||||
// To run this test, specify the pac script above in your network settings
|
||||
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:@"http://allseeing-i.com"]];
|
||||
[request startSynchronous];
|
||||
|
||||
BOOL success = [[request proxyHost] isEqualToString:@"proxy1.apple.com"];
|
||||
GHAssertTrue(success,@"Failed to use the correct proxy");
|
||||
|
||||
request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:@"http://www.apple.com"]];
|
||||
[request startSynchronous];
|
||||
GHAssertNil([request proxyHost],@"Used a proxy when the script told us to go direct");
|
||||
}
|
||||
|
||||
- (void)testProxy
|
||||
{
|
||||
BOOL success = (![proxyHost isEqualToString:@""] && proxyPort > 0);
|
||||
GHAssertTrue(success,@"You need to supply the details of your proxy to run the proxy autodetect test");
|
||||
|
||||
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:@"http://allseeing-i.com"]];
|
||||
[request setProxyHost:proxyHost];
|
||||
[request setProxyPort:proxyPort];
|
||||
[request startSynchronous];
|
||||
|
||||
// Check data is as expected
|
||||
NSRange notFound = NSMakeRange(NSNotFound, 0);
|
||||
success = !NSEqualRanges([[request responseString] rangeOfString:@"All-Seeing Interactive"],notFound);
|
||||
GHAssertTrue(success,@"Failed to download the correct data, navigating the proxy");
|
||||
}
|
||||
|
||||
- (void)testProxyAutodetect
|
||||
{
|
||||
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:@"http://allseeing-i.com"]];
|
||||
[request startSynchronous];
|
||||
|
||||
BOOL success = ([request proxyHost] && [request proxyPort]);
|
||||
GHAssertTrue(success,@"Failed to detect the proxy");
|
||||
}
|
||||
|
||||
|
||||
- (void)testProxyWithSuppliedAuthenticationCredentials
|
||||
{
|
||||
BOOL success = (![proxyHost isEqualToString:@""] && proxyPort > 0 && ![proxyUsername isEqualToString:@""] && ![proxyPassword isEqualToString:@""]);
|
||||
GHAssertTrue(success,@"You need to supply the details of your authenticating proxy to run the proxy authentication test");
|
||||
|
||||
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:@"http://allseeing-i.com"]];
|
||||
[request setProxyHost:proxyHost];
|
||||
[request setProxyPort:proxyPort];
|
||||
[request setProxyUsername:proxyUsername];
|
||||
[request setProxyPassword:proxyPassword];
|
||||
[request startSynchronous];
|
||||
|
||||
// Check data is as expected
|
||||
NSRange notFound = NSMakeRange(NSNotFound, 0);
|
||||
success = !NSEqualRanges([[request responseString] rangeOfString:@"All-Seeing Interactive"],notFound);
|
||||
GHAssertTrue(success,@"Failed to download the correct data, navigating the proxy");
|
||||
}
|
||||
|
||||
- (void)testProxyWithDelegateSupplyingCredentials
|
||||
{
|
||||
[self setComplete:NO];
|
||||
BOOL success = (![proxyHost isEqualToString:@""] && proxyPort > 0 && ![proxyUsername isEqualToString:@""] && ![proxyPassword isEqualToString:@""]);
|
||||
GHAssertTrue(success,@"You need to supply the details of your authenticating proxy to run the proxy authentication test");
|
||||
|
||||
[[self queue] cancelAllOperations];
|
||||
[self setQueue:[ASINetworkQueue queue]];
|
||||
[[self queue] setDelegate:self];
|
||||
[[self queue] setRequestDidFinishSelector:@selector(requestFinished:)];
|
||||
[[self queue] setRequestDidFailSelector:@selector(requestFailed:)];
|
||||
|
||||
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:@"http://allseeing-i.com"]];
|
||||
[[self queue] addOperation:request];
|
||||
|
||||
[queue go];
|
||||
|
||||
while (![self complete]) {
|
||||
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.25]];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)requestFinished:(ASIHTTPRequest *)request
|
||||
{
|
||||
[self setComplete:YES];
|
||||
// Check data is as expected
|
||||
NSRange notFound = NSMakeRange(NSNotFound, 0);
|
||||
BOOL success = !NSEqualRanges([[request responseString] rangeOfString:@"All-Seeing Interactive"],notFound);
|
||||
GHAssertTrue(success,@"Failed to download the correct data, navigating the proxy");
|
||||
}
|
||||
|
||||
- (void)requestFailed:(ASIHTTPRequest *)request
|
||||
{
|
||||
[self setComplete:YES];
|
||||
GHAssertTrue(0,@"Request failed when it shouldn't have done so");
|
||||
}
|
||||
|
||||
- (void)proxyAuthenticationNeededForRequest:(ASIHTTPRequest *)request
|
||||
{
|
||||
[request setProxyUsername:proxyUsername];
|
||||
[request setProxyPassword:proxyPassword];
|
||||
[request retryUsingSuppliedCredentials];
|
||||
}
|
||||
|
||||
|
||||
- (void)testDoubleAuthentication
|
||||
{
|
||||
[self setComplete:NO];
|
||||
BOOL success = (![proxyHost isEqualToString:@""] && proxyPort > 0 && ![proxyUsername isEqualToString:@""] && ![proxyPassword isEqualToString:@""]);
|
||||
GHAssertTrue(success,@"You need to supply the details of your authenticating proxy to run the proxy authentication test");
|
||||
|
||||
[[self queue] cancelAllOperations];
|
||||
[self setQueue:[ASINetworkQueue queue]];
|
||||
[[self queue] setDelegate:self];
|
||||
[[self queue] setRequestDidFinishSelector:@selector(requestDone:)];
|
||||
[[self queue] setRequestDidFailSelector:@selector(requestFailed:)];
|
||||
|
||||
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:@"http://allseeing-i.com/ASIHTTPRequest/tests/basic-authentication"]];
|
||||
[[self queue] addOperation:request];
|
||||
|
||||
[queue go];
|
||||
|
||||
while (![self complete]) {
|
||||
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.25]];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)requestDone:(ASIHTTPRequest *)request
|
||||
{
|
||||
[self setComplete:YES];
|
||||
}
|
||||
|
||||
- (void)authenticationNeededForRequest:(ASIHTTPRequest *)request
|
||||
{
|
||||
[request setUsername:@"secret_username"];
|
||||
[request setPassword:@"secret_password"];
|
||||
[request retryUsingSuppliedCredentials];
|
||||
}
|
||||
|
||||
|
||||
@synthesize queue;
|
||||
@synthesize complete;
|
||||
@end
|
||||
46
projects/mtg/iOS/asi-http-request/Tests/StressTests.h
Normal file
46
projects/mtg/iOS/asi-http-request/Tests/StressTests.h
Normal file
@@ -0,0 +1,46 @@
|
||||
//
|
||||
// StressTests.h
|
||||
// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest
|
||||
//
|
||||
// Created by Ben Copsey on 30/10/2009.
|
||||
// Copyright 2009 All-Seeing Interactive. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "ASITestCase.h"
|
||||
|
||||
@class ASIHTTPRequest;
|
||||
|
||||
|
||||
@interface MyDelegate : NSObject {
|
||||
ASIHTTPRequest *request;
|
||||
}
|
||||
@property (retain) ASIHTTPRequest *request;
|
||||
@end
|
||||
|
||||
@interface StressTests : ASITestCase {
|
||||
float progress;
|
||||
ASIHTTPRequest *cancelRequest;
|
||||
NSDate *cancelStartDate;
|
||||
MyDelegate *delegate;
|
||||
NSLock *createRequestLock;
|
||||
}
|
||||
|
||||
- (void)testCancelQueue;
|
||||
|
||||
- (void)testCancelStressTest;
|
||||
- (void)performCancelRequest;
|
||||
|
||||
- (void)testRedirectStressTest;
|
||||
- (void)performRedirectRequest;
|
||||
|
||||
- (void)testSetDelegate;
|
||||
- (void)performSetDelegateRequest;
|
||||
|
||||
- (void)setProgress:(float)newProgress;
|
||||
|
||||
@property (retain) ASIHTTPRequest *cancelRequest;
|
||||
@property (retain) NSDate *cancelStartDate;
|
||||
@property (retain) MyDelegate *delegate;
|
||||
@property (retain) NSLock *createRequestLock;
|
||||
@end
|
||||
192
projects/mtg/iOS/asi-http-request/Tests/StressTests.m
Normal file
192
projects/mtg/iOS/asi-http-request/Tests/StressTests.m
Normal file
@@ -0,0 +1,192 @@
|
||||
//
|
||||
// StressTests.m
|
||||
// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest
|
||||
//
|
||||
// Created by Ben Copsey on 30/10/2009.
|
||||
// Copyright 2009 All-Seeing Interactive. All rights reserved.
|
||||
//
|
||||
|
||||
/*
|
||||
IMPORTANT
|
||||
All these tests depend on you running a local webserver on port 80
|
||||
These tests create 1000s of requests in a very short space of time - DO NOT RUN THESE TESTS ON A REMOTE WEBSERVER
|
||||
IMPORTANT
|
||||
*/
|
||||
|
||||
#import "StressTests.h"
|
||||
#import "ASIHTTPRequest.h"
|
||||
#import "ASINetworkQueue.h"
|
||||
|
||||
|
||||
|
||||
@implementation MyDelegate;
|
||||
- (void)dealloc
|
||||
{
|
||||
[request setDelegate:nil];
|
||||
[request release];
|
||||
[super dealloc];
|
||||
}
|
||||
@synthesize request;
|
||||
@end
|
||||
|
||||
// Stop clang complaining about undeclared selectors
|
||||
@interface StressTests ()
|
||||
- (void)cancelRedirectRequest;
|
||||
- (void)cancelSetDelegateRequest;
|
||||
@end
|
||||
|
||||
|
||||
|
||||
@implementation StressTests
|
||||
|
||||
// A test for a potential crasher that used to exist when requests were cancelled
|
||||
// We aren't testing a specific condition here, but rather attempting to trigger a crash
|
||||
- (void)testCancelQueue
|
||||
{
|
||||
ASINetworkQueue *queue = [ASINetworkQueue queue];
|
||||
|
||||
// Increase the risk of this crash
|
||||
[queue setMaxConcurrentOperationCount:25];
|
||||
int i;
|
||||
for (i=0; i<100; i++) {
|
||||
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:@"http://127.0.0.1"]];
|
||||
[queue addOperation:request];
|
||||
}
|
||||
[queue go];
|
||||
[queue cancelAllOperations];
|
||||
|
||||
// Run the test again with requests running on a background thread
|
||||
queue = [ASINetworkQueue queue];
|
||||
|
||||
[queue setMaxConcurrentOperationCount:25];
|
||||
for (i=0; i<100; i++) {
|
||||
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:@"http://127.0.0.1"]];
|
||||
[queue addOperation:request];
|
||||
}
|
||||
[queue go];
|
||||
[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:2]];
|
||||
[queue cancelAllOperations];
|
||||
|
||||
}
|
||||
|
||||
// This test looks for thread-safety problems with cancelling requests
|
||||
// It will run for 30 seconds, creating a request, then cancelling it and creating another as soon as it gets some indication of progress
|
||||
|
||||
- (void)testCancelStressTest
|
||||
{
|
||||
[self setCancelStartDate:[NSDate dateWithTimeIntervalSinceNow:30]];
|
||||
[self performCancelRequest];
|
||||
while ([[self cancelStartDate] timeIntervalSinceNow] > 0) {
|
||||
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.25]];
|
||||
}
|
||||
NSLog(@"Stress test: DONE");
|
||||
}
|
||||
|
||||
- (void)performCancelRequest
|
||||
{
|
||||
[self setCancelRequest:[ASIHTTPRequest requestWithURL:[NSURL URLWithString:@"http://127.0.0.1/ASIHTTPRequest/tests/the_great_american_novel.txt"]]];
|
||||
if ([[self cancelStartDate] timeIntervalSinceNow] > 0) {
|
||||
[[self cancelRequest] setDownloadProgressDelegate:self];
|
||||
[[self cancelRequest] setShowAccurateProgress:YES];
|
||||
NSLog(@"Stress test: Start request %@",[self cancelRequest]);
|
||||
[[self cancelRequest] startAsynchronous];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Another stress test that looks from problems when redirecting
|
||||
|
||||
- (void)testRedirectStressTest
|
||||
{
|
||||
[self setCancelStartDate:[NSDate dateWithTimeIntervalSinceNow:30]];
|
||||
[self performRedirectRequest];
|
||||
while ([[self cancelStartDate] timeIntervalSinceNow] > 0) {
|
||||
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.25]];
|
||||
}
|
||||
NSLog(@"Redirect stress test: DONE");
|
||||
}
|
||||
|
||||
- (void)performRedirectRequest
|
||||
{
|
||||
[self setCancelRequest:[ASIHTTPRequest requestWithURL:[NSURL URLWithString:@"http://127.0.0.1/ASIHTTPRequest/tests/one_infinite_loop"]]];
|
||||
if ([[self cancelStartDate] timeIntervalSinceNow] > 0) {
|
||||
NSLog(@"Redirect stress test: Start request %@",[self cancelRequest]);
|
||||
[[self cancelRequest] startAsynchronous];
|
||||
[self performSelector:@selector(cancelRedirectRequest) withObject:nil afterDelay:0.2];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)cancelRedirectRequest
|
||||
{
|
||||
NSLog(@"Redirect stress test: Cancel request %@",[self cancelRequest]);
|
||||
[[self cancelRequest] cancel];
|
||||
[self performRedirectRequest];
|
||||
}
|
||||
|
||||
// Ensures we can set the delegate while the request is running without problems
|
||||
- (void)testSetDelegate
|
||||
{
|
||||
[self setCreateRequestLock:[[[NSLock alloc] init] autorelease]];
|
||||
[self setCancelStartDate:[NSDate dateWithTimeIntervalSinceNow:30]];
|
||||
[self performSetDelegateRequest];
|
||||
while ([[self cancelStartDate] timeIntervalSinceNow] > 0) {
|
||||
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.25]];
|
||||
}
|
||||
NSLog(@"Set delegate stress test: DONE");
|
||||
}
|
||||
|
||||
- (void)performSetDelegateRequest
|
||||
{
|
||||
[self setDelegate:nil];
|
||||
|
||||
[createRequestLock lock];
|
||||
[self setCancelRequest:[ASIHTTPRequest requestWithURL:[NSURL URLWithString:@"http://127.0.0.1/ASIHTTPRequest/tests/the_great_american_novel.txt"]]];
|
||||
if ([[self cancelStartDate] timeIntervalSinceNow] > 0) {
|
||||
[self setDelegate:[[[MyDelegate alloc] init] autorelease]];
|
||||
[[self delegate] setRequest:[self cancelRequest]];
|
||||
[[self cancelRequest] setDelegate:delegate];
|
||||
[[self cancelRequest] setShowAccurateProgress:YES];
|
||||
NSLog(@"Set delegate stress test: Start request %@",[self cancelRequest]);
|
||||
[[self cancelRequest] startAsynchronous];
|
||||
[self performSelectorInBackground:@selector(cancelSetDelegateRequest) withObject:nil];
|
||||
}
|
||||
[createRequestLock unlock];
|
||||
}
|
||||
|
||||
- (void)cancelSetDelegateRequest
|
||||
{
|
||||
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
|
||||
[self performSetDelegateRequest];
|
||||
[pool release];
|
||||
}
|
||||
|
||||
|
||||
- (void)setDoubleValue:(double)newProgress
|
||||
{
|
||||
[self setProgress:(float)newProgress];
|
||||
}
|
||||
|
||||
- (void)setProgress:(float)newProgress
|
||||
{
|
||||
progress = newProgress;
|
||||
|
||||
// For cancel test
|
||||
if (newProgress > 0 && [self cancelRequest]) {
|
||||
|
||||
NSLog(@"Stress test: Cancel request %@",[self cancelRequest]);
|
||||
[[self cancelRequest] cancel];
|
||||
|
||||
[self performSelector:@selector(performCancelRequest) withObject:nil afterDelay:0.2];
|
||||
[self setCancelRequest:nil];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@synthesize cancelRequest;
|
||||
@synthesize cancelStartDate;
|
||||
@synthesize delegate;
|
||||
@synthesize createRequestLock;
|
||||
@end
|
||||
Reference in New Issue
Block a user