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:
techdragon.nguyen@gmail.com
2011-12-11 07:40:22 +00:00
parent 071dca6b0a
commit 128c60bc2b
131 changed files with 27956 additions and 10 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 235 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 234 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 233 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 234 KiB

BIN
projects/mtg/Default.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 235 KiB

BIN
projects/mtg/Default@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 235 KiB

405
projects/mtg/ZipArchive.mm Normal file
View File

@@ -0,0 +1,405 @@
//
// ZipArchive.mm
//
//
// Created by aish on 08-9-11.
// acsolu@gmail.com
// Copyright 2008 Inc. All rights reserved.
//
#import "ZipArchive.h"
#import "zlib.h"
#import "zconf.h"
@interface ZipArchive (Private)
-(void) OutputErrorMessage:(NSString*) msg;
-(BOOL) OverWrite:(NSString*) file;
-(void) DoUnzipProgress:(uLong)myCurrentFileIndex;
-(NSDate*) Date1980;
@end
@implementation ZipArchive
@synthesize delegate = _delegate;
-(id) init
{
if( (self=[super init]))
{
_zipFile = NULL ;
_totalFileCount = 0;
}
return self;
}
-(void) dealloc
{
[self CloseZipFile2];
[super dealloc];
}
-(BOOL) CreateZipFile2:(NSString*) zipFile
{
_zipFile = zipOpen( (const char*)[zipFile UTF8String], 0 );
if( !_zipFile )
return NO;
return YES;
}
-(BOOL) CreateZipFile2:(NSString*) zipFile Password:(NSString*) password
{
_password = password;
return [self CreateZipFile2:zipFile];
}
-(BOOL) addFileToZip:(NSString*) file newname:(NSString*) newname;
{
if( !_zipFile )
return NO;
// tm_zip filetime;
time_t current;
time( &current );
zip_fileinfo zipInfo = {0};
// zipInfo.dosDate = (unsigned long) current;
NSDictionary* attr = [[NSFileManager defaultManager] attributesOfItemAtPath:file error:nil];//[[NSFileManager defaultManager] fileAttributesAtPath:file traverseLink:YES];
if( attr )
{
NSDate* fileDate = (NSDate*)[attr objectForKey:NSFileModificationDate];
if( fileDate )
{
// some application does use dosDate, but tmz_date instead
// zipInfo.dosDate = [fileDate timeIntervalSinceDate:[self Date1980] ];
NSCalendar* currCalendar = [NSCalendar currentCalendar];
uint flags = NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit |
NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit ;
NSDateComponents* dc = [currCalendar components:flags fromDate:fileDate];
zipInfo.tmz_date.tm_sec = [dc second];
zipInfo.tmz_date.tm_min = [dc minute];
zipInfo.tmz_date.tm_hour = [dc hour];
zipInfo.tmz_date.tm_mday = [dc day];
zipInfo.tmz_date.tm_mon = [dc month] - 1;
zipInfo.tmz_date.tm_year = [dc year];
}
}
int ret ;
NSData* data = nil;
if( [_password length] == 0 )
{
ret = zipOpenNewFileInZip( _zipFile,
(const char*) [newname UTF8String],
&zipInfo,
NULL,0,
NULL,0,
NULL,//comment
Z_DEFLATED,
Z_DEFAULT_COMPRESSION );
}
else
{
data = [ NSData dataWithContentsOfFile:file];
uLong crcValue = crc32( 0L,NULL, 0L );
crcValue = crc32( crcValue, (const Bytef*)[data bytes], [data length] );
ret = zipOpenNewFileInZip3( _zipFile,
(const char*) [newname UTF8String],
&zipInfo,
NULL,0,
NULL,0,
NULL,//comment
Z_DEFLATED,
Z_DEFAULT_COMPRESSION,
0,
15,
8,
Z_DEFAULT_STRATEGY,
[_password cStringUsingEncoding:NSASCIIStringEncoding],
crcValue );
}
if( ret!=Z_OK )
{
return NO;
}
if( data==nil )
{
data = [ NSData dataWithContentsOfFile:file];
}
unsigned int dataLen = [data length];
ret = zipWriteInFileInZip( _zipFile, (const void*)[data bytes], dataLen);
if( ret!=Z_OK )
{
return NO;
}
ret = zipCloseFileInZip( _zipFile );
if( ret!=Z_OK )
return NO;
return YES;
}
-(BOOL) CloseZipFile2
{
_totalFileCount = 0;
_password = nil;
if( _zipFile==NULL )
return NO;
BOOL ret = zipClose( _zipFile,NULL )==Z_OK?YES:NO;
_zipFile = NULL;
return ret;
}
-(BOOL) UnzipOpenFile:(NSString*) zipFile
{
_unzFile = unzOpen( (const char*)[zipFile UTF8String] );
if( _unzFile )
{
unz_global_info globalInfo = {0};
if( unzGetGlobalInfo(_unzFile, &globalInfo )==UNZ_OK )
{
NSLog(@"%lu entries in the zip file", globalInfo.number_entry);
_totalFileCount = globalInfo.number_entry;
}
}
return _unzFile!=NULL;
}
-(BOOL) UnzipOpenFile:(NSString*) zipFile Password:(NSString*) password
{
_password = password;
return [self UnzipOpenFile:zipFile];
}
-(BOOL) UnzipFileTo:(NSString*) path overWrite:(BOOL) overwrite
{
BOOL success = YES;
int ret = unzGoToFirstFile( _unzFile );
unsigned char buffer[4096] = {0};
NSFileManager* fman = [NSFileManager defaultManager];
if( ret!=UNZ_OK )
{
[self OutputErrorMessage:@"Failed"];
}
uLong kFileCount = 0;
do{
if( [_password length]==0 )
ret = unzOpenCurrentFile( _unzFile );
else
ret = unzOpenCurrentFilePassword( _unzFile, [_password cStringUsingEncoding:NSASCIIStringEncoding] );
if( ret!=UNZ_OK )
{
[self OutputErrorMessage:@"Error occurs"];
success = NO;
break;
}
// reading data and write to file
int read ;
unz_file_info fileInfo ={0};
ret = unzGetCurrentFileInfo(_unzFile, &fileInfo, NULL, 0, NULL, 0, NULL, 0);
if( ret!=UNZ_OK )
{
[self OutputErrorMessage:@"Error occurs while getting file info"];
success = NO;
unzCloseCurrentFile( _unzFile );
break;
}
char* filename = (char*) malloc( fileInfo.size_filename +1 );
unzGetCurrentFileInfo(_unzFile, &fileInfo, filename, fileInfo.size_filename + 1, NULL, 0, NULL, 0);
filename[fileInfo.size_filename] = '\0';
// check if it contains directory
NSString * strPath = [NSString stringWithFormat:@"%s", filename];//[NSString stringWithCString:filename];
BOOL isDirectory = NO;
if( filename[fileInfo.size_filename-1]=='/' || filename[fileInfo.size_filename-1]=='\\')
isDirectory = YES;
free( filename );
if( [strPath rangeOfCharacterFromSet:[NSCharacterSet characterSetWithCharactersInString:@"/\\"]].location!=NSNotFound )
{// contains a path
strPath = [strPath stringByReplacingOccurrencesOfString:@"\\" withString:@"/"];
}
NSString* fullPath = [path stringByAppendingPathComponent:strPath];
if( isDirectory )
[fman createDirectoryAtPath:fullPath withIntermediateDirectories:YES attributes:nil error:nil];
else
[fman createDirectoryAtPath:[fullPath stringByDeletingLastPathComponent] withIntermediateDirectories:YES attributes:nil error:nil];
if( [fman fileExistsAtPath:fullPath] && !isDirectory && !overwrite )
{
if( ![self OverWrite:fullPath] )
{
unzCloseCurrentFile( _unzFile );
ret = unzGoToNextFile( _unzFile );
continue;
}
}
FILE* fp = fopen( (const char*)[fullPath UTF8String], "wb");
while( fp )
{
read=unzReadCurrentFile(_unzFile, buffer, 4096);
if( read > 0 )
{
fwrite(buffer, read, 1, fp );
}
else if( read<0 )
{
[self OutputErrorMessage:@"Failed to reading zip file"];
break;
}
else
break;
}
if( fp )
{
fclose( fp );
// set the orignal datetime property
NSDate* orgDate = nil;
//{{ thanks to brad.eaton for the solution
NSDateComponents *dc = [[NSDateComponents alloc] init];
dc.second = fileInfo.tmu_date.tm_sec;
dc.minute = fileInfo.tmu_date.tm_min;
dc.hour = fileInfo.tmu_date.tm_hour;
dc.day = fileInfo.tmu_date.tm_mday;
dc.month = fileInfo.tmu_date.tm_mon+1;
dc.year = fileInfo.tmu_date.tm_year;
NSCalendar *gregorian = [[NSCalendar alloc]
initWithCalendarIdentifier:NSGregorianCalendar];
orgDate = [gregorian dateFromComponents:dc] ;
[dc release];
[gregorian release];
//}}
NSDictionary* attr = [NSDictionary dictionaryWithObject:orgDate forKey:NSFileModificationDate]; //[[NSFileManager defaultManager] fileAttributesAtPath:fullPath traverseLink:YES];
if( attr )
{
// [attr setValue:orgDate forKey:NSFileCreationDate];
if( ![[NSFileManager defaultManager] setAttributes:attr ofItemAtPath:fullPath error:nil] )
{
// cann't set attributes
NSLog(@"Failed to set attributes");
}
}
kFileCount++;
[self DoUnzipProgress:kFileCount];
}
unzCloseCurrentFile( _unzFile );
ret = unzGoToNextFile( _unzFile );
}while( ret==UNZ_OK && UNZ_OK!=UNZ_END_OF_LIST_OF_FILE );
return success;
}
-(NSMutableArray *) getZipFileContents
{
BOOL success = YES;
int ret = unzGoToFirstFile( _unzFile );
NSMutableArray * AllFilenames = [[NSMutableArray alloc] initWithCapacity:40];
if( ret!=UNZ_OK )
{
[self OutputErrorMessage:@"Failed"];
}
do{
if( [_password length]==0 )
ret = unzOpenCurrentFile( _unzFile );
else
ret = unzOpenCurrentFilePassword( _unzFile, [_password cStringUsingEncoding:NSASCIIStringEncoding] );
if( ret!=UNZ_OK )
{
[self OutputErrorMessage:@"Error occured"];
success = NO;
break;
}
// reading data and write to file
unz_file_info fileInfo ={0};
ret = unzGetCurrentFileInfo(_unzFile, &fileInfo, NULL, 0, NULL, 0, NULL, 0);
if( ret!=UNZ_OK )
{
[self OutputErrorMessage:@"Error occurs while getting file info"];
success = NO;
unzCloseCurrentFile( _unzFile );
break;
}
char* filename = (char*) malloc( fileInfo.size_filename +1 );
unzGetCurrentFileInfo(_unzFile, &fileInfo, filename, fileInfo.size_filename + 1, NULL, 0, NULL, 0);
filename[fileInfo.size_filename] = '\0';
// check if it contains directory
NSString * strPath = [NSString stringWithFormat:@"%s", filename];//[NSString stringWithCString:filename];
BOOL isDirectory = NO;
if( filename[fileInfo.size_filename-1]=='/' || filename[fileInfo.size_filename-1]=='\\')
isDirectory = YES;
free( filename );
if( [strPath rangeOfCharacterFromSet:[NSCharacterSet characterSetWithCharactersInString:@"/\\"]].location!=NSNotFound )
{// contains a path
strPath = [strPath stringByReplacingOccurrencesOfString:@"\\" withString:@"/"];
}
// Copy name to array
[AllFilenames addObject:[strPath copy]];
unzCloseCurrentFile( _unzFile );
ret = unzGoToNextFile( _unzFile );
} while( ret==UNZ_OK && UNZ_OK!=UNZ_END_OF_LIST_OF_FILE );
return [AllFilenames autorelease];
}
-(BOOL) UnzipCloseFile
{
_totalFileCount = 0;
_password = nil;
if( _unzFile )
return unzClose( _unzFile )==UNZ_OK;
return YES;
}
#pragma mark wrapper for delegate
-(void) OutputErrorMessage:(NSString*) msg
{
if( _delegate && [_delegate respondsToSelector:@selector(ErrorMessage)] )
[_delegate ErrorMessage:msg];
}
-(BOOL) OverWrite:(NSString*) file
{
if( _delegate && [_delegate respondsToSelector:@selector(OverWriteOperation)] )
return [_delegate OverWriteOperation:file];
return YES;
}
-(void) DoUnzipProgress:(uLong)myCurrentFileIndex
{
if( _delegate && [_delegate respondsToSelector:@selector(UnzipProgress:total:)] )
[_delegate UnzipProgress:myCurrentFileIndex total:_totalFileCount];
}
#pragma mark get NSDate object for 1980-01-01
-(NSDate*) Date1980
{
NSDateComponents *comps = [[NSDateComponents alloc] init];
[comps setDay:1];
[comps setMonth:1];
[comps setYear:1980];
NSCalendar *gregorian = [[NSCalendar alloc]
initWithCalendarIdentifier:NSGregorianCalendar];
NSDate *date = [gregorian dateFromComponents:comps];
[comps release];
[gregorian release];
return date;
}
@end

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -0,0 +1,70 @@
/*
File: ReachabilityAppDelegate.h
Abstract: The application's controller.
Version: 2.0
Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Inc.
("Apple") in consideration of your agreement to the following terms, and your
use, installation, modification or redistribution of this Apple software
constitutes acceptance of these terms. If you do not agree with these terms,
please do not use, install, modify or redistribute this Apple software.
In consideration of your agreement to abide by the following terms, and subject
to these terms, Apple grants you a personal, non-exclusive license, under
Apple's copyrights in this original Apple software (the "Apple Software"), to
use, reproduce, modify and redistribute the Apple Software, with or without
modifications, in source and/or binary forms; provided that if you redistribute
the Apple Software in its entirety and without modifications, you must retain
this notice and the following text and disclaimers in all such redistributions
of the Apple Software.
Neither the name, trademarks, service marks or logos of Apple Inc. may be used
to endorse or promote products derived from the Apple Software without specific
prior written permission from Apple. Except as expressly stated in this notice,
no other rights or licenses, express or implied, are granted by Apple herein,
including but not limited to any patent rights that may be infringed by your
derivative works or by other works in which the Apple Software may be
incorporated.
The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO
WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED
WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN
COMBINATION WITH YOUR PRODUCTS.
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR
DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF
CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF
APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Copyright (C) 2009 Apple Inc. All Rights Reserved.
*/
#import <UIKit/UIKit.h>
@class Reachability;
@interface ReachabilityAppDelegate: NSObject {
IBOutlet UIWindow* window;
IBOutlet UIView* contentView;
IBOutlet UILabel* summaryLabel;
IBOutlet UITextField* remoteHostLabel;
IBOutlet UIImageView* remoteHostIcon;
IBOutlet UITextField* remoteHostStatusField;
IBOutlet UIImageView* internetConnectionIcon;
IBOutlet UITextField* internetConnectionStatusField;
IBOutlet UIImageView* localWiFiConnectionIcon;
IBOutlet UITextField* localWiFiConnectionStatusField;
Reachability* hostReach;
Reachability* internetReach;
Reachability* wifiReach;
}
@end

View File

@@ -0,0 +1,157 @@
/*
File: ReachabilityAppDelegate.m
Abstract: The application's controller.
Version: 2.0
Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Inc.
("Apple") in consideration of your agreement to the following terms, and your
use, installation, modification or redistribution of this Apple software
constitutes acceptance of these terms. If you do not agree with these terms,
please do not use, install, modify or redistribute this Apple software.
In consideration of your agreement to abide by the following terms, and subject
to these terms, Apple grants you a personal, non-exclusive license, under
Apple's copyrights in this original Apple software (the "Apple Software"), to
use, reproduce, modify and redistribute the Apple Software, with or without
modifications, in source and/or binary forms; provided that if you redistribute
the Apple Software in its entirety and without modifications, you must retain
this notice and the following text and disclaimers in all such redistributions
of the Apple Software.
Neither the name, trademarks, service marks or logos of Apple Inc. may be used
to endorse or promote products derived from the Apple Software without specific
prior written permission from Apple. Except as expressly stated in this notice,
no other rights or licenses, express or implied, are granted by Apple herein,
including but not limited to any patent rights that may be infringed by your
derivative works or by other works in which the Apple Software may be
incorporated.
The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO
WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED
WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN
COMBINATION WITH YOUR PRODUCTS.
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR
DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF
CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF
APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Copyright (C) 2009 Apple Inc. All Rights Reserved.
*/
#import "ReachabilityAppDelegate.h"
#import "Reachability.h"
@implementation ReachabilityAppDelegate
- (void) configureTextField: (UITextField*) textField imageView: (UIImageView*) imageView reachability: (Reachability*) curReach
{
NetworkStatus netStatus = [curReach currentReachabilityStatus];
BOOL connectionRequired= [curReach connectionRequired];
NSString* statusString= @"";
switch (netStatus)
{
case NotReachable:
{
statusString = @"Access Not Available";
imageView.image = [UIImage imageNamed: @"stop-32.png"] ;
//Minor interface detail- connectionRequired may return yes, even when the host is unreachable. We cover that up here...
connectionRequired= NO;
break;
}
case ReachableViaWWAN:
{
statusString = @"Reachable WWAN";
imageView.image = [UIImage imageNamed: @"WWAN5.png"];
break;
}
case ReachableViaWiFi:
{
statusString= @"Reachable WiFi";
imageView.image = [UIImage imageNamed: @"Airport.png"];
break;
}
}
if(connectionRequired)
{
statusString= [NSString stringWithFormat: @"%@, Connection Required", statusString];
}
textField.text= statusString;
}
- (void) updateInterfaceWithReachability: (Reachability*) curReach
{
if(curReach == hostReach)
{
[self configureTextField: remoteHostStatusField imageView: remoteHostIcon reachability: curReach];
NetworkStatus netStatus = [curReach currentReachabilityStatus];
BOOL connectionRequired= [curReach connectionRequired];
summaryLabel.hidden = (netStatus != ReachableViaWWAN);
NSString* baseLabel= @"";
if(connectionRequired)
{
baseLabel= @"Cellular data network is available.\n Internet traffic will be routed through it after a connection is established.";
}
else
{
baseLabel= @"Cellular data network is active.\n Internet traffic will be routed through it.";
}
summaryLabel.text= baseLabel;
}
if(curReach == internetReach)
{
[self configureTextField: internetConnectionStatusField imageView: internetConnectionIcon reachability: curReach];
}
if(curReach == wifiReach)
{
[self configureTextField: localWiFiConnectionStatusField imageView: localWiFiConnectionIcon reachability: curReach];
}
}
//Called by Reachability whenever status changes.
- (void) reachabilityChanged: (NSNotification* )note
{
Reachability* curReach = [note object];
NSParameterAssert([curReach isKindOfClass: [Reachability class]]);
[self updateInterfaceWithReachability: curReach];
}
- (void) applicationDidFinishLaunching: (UIApplication* )application
{
contentView.backgroundColor = [UIColor groupTableViewBackgroundColor];
summaryLabel.hidden = YES;
// Observe the kNetworkReachabilityChangedNotification. When that notification is posted, the
// method "reachabilityChanged" will be called.
[[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(reachabilityChanged:) name: kReachabilityChangedNotification object: nil];
//Change the host name here to change the server your monitoring
remoteHostLabel.text = [NSString stringWithFormat: @"Remote Host: %@", @"www.apple.com"];
hostReach = [[Reachability reachabilityWithHostName: @"www.apple.com"] retain];
[hostReach startNotifier];
[self updateInterfaceWithReachability: hostReach];
internetReach = [[Reachability reachabilityForInternetConnection] retain];
[internetReach startNotifier];
[self updateInterfaceWithReachability: internetReach];
wifiReach = [[Reachability reachabilityForLocalWiFi] retain];
[wifiReach startNotifier];
[self updateInterfaceWithReachability: wifiReach];
[window makeKeyAndVisible];
}
@end

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

View File

@@ -0,0 +1,568 @@
<?xml version="1.0" encoding="UTF-8"?>
<archive type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="7.03">
<data>
<int key="IBDocument.SystemTarget">512</int>
<string key="IBDocument.SystemVersion">9J61</string>
<string key="IBDocument.InterfaceBuilderVersion">677</string>
<string key="IBDocument.AppKitVersion">949.46</string>
<string key="IBDocument.HIToolboxVersion">353.00</string>
<object class="NSMutableArray" key="IBDocument.EditedObjectIDs">
<bool key="EncodedWithXMLCoder">YES</bool>
<integer value="7"/>
</object>
<object class="NSArray" key="IBDocument.PluginDependencies">
<bool key="EncodedWithXMLCoder">YES</bool>
<string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
</object>
<object class="NSMutableDictionary" key="IBDocument.Metadata">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="NSArray" key="dict.sortedKeys">
<bool key="EncodedWithXMLCoder">YES</bool>
</object>
<object class="NSMutableArray" key="dict.values">
<bool key="EncodedWithXMLCoder">YES</bool>
</object>
</object>
<object class="NSMutableArray" key="IBDocument.RootObjects" id="1000">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="IBProxyObject" id="841351856">
<string key="IBProxiedObjectIdentifier">IBFilesOwner</string>
</object>
<object class="IBProxyObject" id="841400030">
<string key="IBProxiedObjectIdentifier">IBFirstResponder</string>
</object>
<object class="IBUICustomObject" id="987256611"/>
<object class="IBUIWindow" id="380026005">
<reference key="NSNextResponder"/>
<int key="NSvFlags">1292</int>
<object class="NSMutableArray" key="NSSubviews">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="IBUIView" id="388289409">
<reference key="NSNextResponder" ref="380026005"/>
<int key="NSvFlags">1298</int>
<object class="NSMutableArray" key="NSSubviews">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="IBUILabel" id="1041619883">
<reference key="NSNextResponder" ref="388289409"/>
<int key="NSvFlags">1306</int>
<string key="NSFrame">{{8, 318}, {303, 99}}</string>
<reference key="NSSuperview" ref="388289409"/>
<bool key="IBUIOpaque">NO</bool>
<bool key="IBUIClipsSubviews">YES</bool>
<bool key="IBUIUserInteractionEnabled">NO</bool>
<string type="base64-UTF8" key="IBUIText">Q2VsbHVsYXIgZGF0YSBuZXR3b3JrIGlzIGF2YWlsYWJsZS4KSW50ZXJuZXQgdHJhZmZpYyB3aWxsIGJl
IHJvdXRlZCB0aHJvdWdoIGl0Lg</string>
<object class="NSColor" key="IBUITextColor">
<int key="NSColorSpace">1</int>
<bytes key="NSRGB">MSAwIDAAA</bytes>
</object>
<nil key="IBUIHighlightedColor"/>
<int key="IBUIBaselineAdjustment">1</int>
<bool key="IBUIAdjustsFontSizeToFit">NO</bool>
<float key="IBUIMinimumFontSize">1.400000e+01</float>
<int key="IBUINumberOfLines">0</int>
<int key="IBUITextAlignment">1</int>
</object>
<object class="IBUIImageView" id="355548833">
<reference key="NSNextResponder" ref="388289409"/>
<int key="NSvFlags">1316</int>
<string key="NSFrame">{{20, 61}, {32, 32}}</string>
<reference key="NSSuperview" ref="388289409"/>
<bool key="IBUIOpaque">NO</bool>
<bool key="IBUIClearsContextBeforeDrawing">NO</bool>
<int key="IBUIContentMode">4</int>
<bool key="IBUIUserInteractionEnabled">NO</bool>
</object>
<object class="IBUIImageView" id="917119444">
<reference key="NSNextResponder" ref="388289409"/>
<int key="NSvFlags">1316</int>
<string key="NSFrame">{{20, 155}, {32, 32}}</string>
<reference key="NSSuperview" ref="388289409"/>
<bool key="IBUIOpaque">NO</bool>
<bool key="IBUIClearsContextBeforeDrawing">NO</bool>
<int key="IBUIContentMode">4</int>
<bool key="IBUIUserInteractionEnabled">NO</bool>
</object>
<object class="IBUITextField" id="167828665">
<reference key="NSNextResponder" ref="388289409"/>
<int key="NSvFlags">1316</int>
<string key="NSFrame">{{60, 62}, {240, 31}}</string>
<reference key="NSSuperview" ref="388289409"/>
<bool key="IBUIOpaque">NO</bool>
<bool key="IBUIClearsContextBeforeDrawing">NO</bool>
<int key="IBUIContentVerticalAlignment">0</int>
<string key="IBUIText"/>
<int key="IBUIBorderStyle">3</int>
<object class="NSColor" key="IBUITextColor">
<int key="NSColorSpace">3</int>
<bytes key="NSWhite">MAA</bytes>
<object class="NSColorSpace" key="NSCustomColorSpace" id="719928199">
<int key="NSID">2</int>
</object>
</object>
<bool key="IBUIClearsOnBeginEditing">YES</bool>
<bool key="IBUIAdjustsFontSizeToFit">YES</bool>
<float key="IBUIMinimumFontSize">1.200000e+01</float>
<object class="IBUITextInputTraits" key="IBUITextInputTraits"/>
</object>
<object class="IBUITextField" id="503258438">
<reference key="NSNextResponder" ref="388289409"/>
<int key="NSvFlags">1316</int>
<string key="NSFrame">{{20, 25}, {280, 29}}</string>
<reference key="NSSuperview" ref="388289409"/>
<bool key="IBUIOpaque">NO</bool>
<bool key="IBUIClearsContextBeforeDrawing">NO</bool>
<int key="IBUIContentVerticalAlignment">0</int>
<string key="IBUIText">Remote Host:</string>
<object class="NSColor" key="IBUITextColor">
<int key="NSColorSpace">3</int>
<bytes key="NSWhite">MAA</bytes>
<reference key="NSCustomColorSpace" ref="719928199"/>
</object>
<object class="NSFont" key="IBUIFont" id="453874746">
<string key="NSName">Helvetica</string>
<double key="NSSize">1.900000e+01</double>
<int key="NSfFlags">16</int>
</object>
<bool key="IBUIClearsOnBeginEditing">YES</bool>
<bool key="IBUIAdjustsFontSizeToFit">YES</bool>
<float key="IBUIMinimumFontSize">2.900000e+01</float>
<object class="IBUITextInputTraits" key="IBUITextInputTraits"/>
</object>
<object class="IBUITextField" id="898416413">
<reference key="NSNextResponder" ref="388289409"/>
<int key="NSvFlags">1316</int>
<string key="NSFrame">{{60, 155}, {240, 31}}</string>
<reference key="NSSuperview" ref="388289409"/>
<bool key="IBUIOpaque">NO</bool>
<bool key="IBUIClearsContextBeforeDrawing">NO</bool>
<int key="IBUIContentVerticalAlignment">0</int>
<string key="IBUIText"/>
<int key="IBUIBorderStyle">3</int>
<object class="NSColor" key="IBUITextColor">
<int key="NSColorSpace">3</int>
<bytes key="NSWhite">MAA</bytes>
<reference key="NSCustomColorSpace" ref="719928199"/>
</object>
<bool key="IBUIClearsOnBeginEditing">YES</bool>
<bool key="IBUIAdjustsFontSizeToFit">YES</bool>
<float key="IBUIMinimumFontSize">1.200000e+01</float>
<object class="IBUITextInputTraits" key="IBUITextInputTraits"/>
</object>
<object class="IBUITextField" id="154536692">
<reference key="NSNextResponder" ref="388289409"/>
<int key="NSvFlags">1316</int>
<string key="NSFrame">{{20, 117}, {280, 29}}</string>
<reference key="NSSuperview" ref="388289409"/>
<bool key="IBUIOpaque">NO</bool>
<bool key="IBUIClearsContextBeforeDrawing">NO</bool>
<int key="IBUIContentVerticalAlignment">0</int>
<string key="IBUIText">TCP/IP Routing Available</string>
<object class="NSColor" key="IBUITextColor">
<int key="NSColorSpace">3</int>
<bytes key="NSWhite">MAA</bytes>
<reference key="NSCustomColorSpace" ref="719928199"/>
</object>
<reference key="IBUIFont" ref="453874746"/>
<bool key="IBUIClearsOnBeginEditing">YES</bool>
<bool key="IBUIAdjustsFontSizeToFit">YES</bool>
<float key="IBUIMinimumFontSize">2.900000e+01</float>
<object class="IBUITextInputTraits" key="IBUITextInputTraits"/>
</object>
<object class="IBUIImageView" id="670228971">
<reference key="NSNextResponder" ref="388289409"/>
<int key="NSvFlags">1316</int>
<string key="NSFrame">{{20, 253}, {32, 32}}</string>
<reference key="NSSuperview" ref="388289409"/>
<bool key="IBUIOpaque">NO</bool>
<bool key="IBUIClearsContextBeforeDrawing">NO</bool>
<int key="IBUIContentMode">4</int>
<bool key="IBUIUserInteractionEnabled">NO</bool>
</object>
<object class="IBUITextField" id="369550247">
<reference key="NSNextResponder" ref="388289409"/>
<int key="NSvFlags">1316</int>
<string key="NSFrame">{{60, 254}, {240, 31}}</string>
<reference key="NSSuperview" ref="388289409"/>
<bool key="IBUIOpaque">NO</bool>
<bool key="IBUIClearsContextBeforeDrawing">NO</bool>
<int key="IBUIContentVerticalAlignment">0</int>
<string key="IBUIText"/>
<int key="IBUIBorderStyle">3</int>
<object class="NSColor" key="IBUITextColor">
<int key="NSColorSpace">3</int>
<bytes key="NSWhite">MAA</bytes>
<reference key="NSCustomColorSpace" ref="719928199"/>
</object>
<bool key="IBUIClearsOnBeginEditing">YES</bool>
<bool key="IBUIAdjustsFontSizeToFit">YES</bool>
<float key="IBUIMinimumFontSize">1.200000e+01</float>
<object class="IBUITextInputTraits" key="IBUITextInputTraits"/>
</object>
<object class="IBUITextField" id="633401159">
<reference key="NSNextResponder" ref="388289409"/>
<int key="NSvFlags">1316</int>
<string key="NSFrame">{{20, 216}, {280, 29}}</string>
<reference key="NSSuperview" ref="388289409"/>
<bool key="IBUIOpaque">NO</bool>
<bool key="IBUIClearsContextBeforeDrawing">NO</bool>
<int key="IBUIContentVerticalAlignment">0</int>
<string key="IBUIText">Local WiFi</string>
<object class="NSColor" key="IBUITextColor">
<int key="NSColorSpace">3</int>
<bytes key="NSWhite">MAA</bytes>
<reference key="NSCustomColorSpace" ref="719928199"/>
</object>
<reference key="IBUIFont" ref="453874746"/>
<bool key="IBUIClearsOnBeginEditing">YES</bool>
<bool key="IBUIAdjustsFontSizeToFit">YES</bool>
<float key="IBUIMinimumFontSize">2.900000e+01</float>
<object class="IBUITextInputTraits" key="IBUITextInputTraits"/>
</object>
</object>
<string key="NSFrameSize">{320, 480}</string>
<reference key="NSSuperview" ref="380026005"/>
<object class="NSColor" key="IBUIBackgroundColor">
<int key="NSColorSpace">3</int>
<bytes key="NSWhite">MQA</bytes>
<reference key="NSCustomColorSpace" ref="719928199"/>
</object>
<bool key="IBUIClearsContextBeforeDrawing">NO</bool>
<bool key="IBUIUserInteractionEnabled">NO</bool>
</object>
</object>
<object class="NSPSMatrix" key="NSFrameMatrix"/>
<string key="NSFrameSize">{320, 480}</string>
<reference key="NSSuperview"/>
<object class="NSColor" key="IBUIBackgroundColor">
<int key="NSColorSpace">1</int>
<bytes key="NSRGB">MSAxIDEAA</bytes>
</object>
<bool key="IBUIOpaque">NO</bool>
<bool key="IBUIClearsContextBeforeDrawing">NO</bool>
</object>
</object>
<object class="IBObjectContainer" key="IBDocument.Objects">
<object class="NSMutableArray" key="connectionRecords">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="IBConnectionRecord">
<object class="IBCocoaTouchOutletConnection" key="connection">
<string key="label">delegate</string>
<reference key="source" ref="841351856"/>
<reference key="destination" ref="987256611"/>
</object>
<int key="connectionID">5</int>
</object>
<object class="IBConnectionRecord">
<object class="IBCocoaTouchOutletConnection" key="connection">
<string key="label">window</string>
<reference key="source" ref="987256611"/>
<reference key="destination" ref="380026005"/>
</object>
<int key="connectionID">6</int>
</object>
<object class="IBConnectionRecord">
<object class="IBCocoaTouchOutletConnection" key="connection">
<string key="label">contentView</string>
<reference key="source" ref="987256611"/>
<reference key="destination" ref="388289409"/>
</object>
<int key="connectionID">8</int>
</object>
<object class="IBConnectionRecord">
<object class="IBCocoaTouchOutletConnection" key="connection">
<string key="label">summaryLabel</string>
<reference key="source" ref="987256611"/>
<reference key="destination" ref="1041619883"/>
</object>
<int key="connectionID">10</int>
</object>
<object class="IBConnectionRecord">
<object class="IBCocoaTouchOutletConnection" key="connection">
<string key="label">internetConnectionStatusField</string>
<reference key="source" ref="987256611"/>
<reference key="destination" ref="898416413"/>
</object>
<int key="connectionID">21</int>
</object>
<object class="IBConnectionRecord">
<object class="IBCocoaTouchOutletConnection" key="connection">
<string key="label">remoteHostIcon</string>
<reference key="source" ref="987256611"/>
<reference key="destination" ref="355548833"/>
</object>
<int key="connectionID">24</int>
</object>
<object class="IBConnectionRecord">
<object class="IBCocoaTouchOutletConnection" key="connection">
<string key="label">remoteHostLabel</string>
<reference key="source" ref="987256611"/>
<reference key="destination" ref="503258438"/>
</object>
<int key="connectionID">27</int>
</object>
<object class="IBConnectionRecord">
<object class="IBCocoaTouchOutletConnection" key="connection">
<string key="label">remoteHostStatusField</string>
<reference key="source" ref="987256611"/>
<reference key="destination" ref="167828665"/>
</object>
<int key="connectionID">28</int>
</object>
<object class="IBConnectionRecord">
<object class="IBCocoaTouchOutletConnection" key="connection">
<string key="label">internetConnectionIcon</string>
<reference key="source" ref="987256611"/>
<reference key="destination" ref="917119444"/>
</object>
<int key="connectionID">30</int>
</object>
<object class="IBConnectionRecord">
<object class="IBCocoaTouchOutletConnection" key="connection">
<string key="label">localWiFiConnectionIcon</string>
<reference key="source" ref="987256611"/>
<reference key="destination" ref="670228971"/>
</object>
<int key="connectionID">35</int>
</object>
<object class="IBConnectionRecord">
<object class="IBCocoaTouchOutletConnection" key="connection">
<string key="label">localWiFiConnectionStatusField</string>
<reference key="source" ref="987256611"/>
<reference key="destination" ref="369550247"/>
</object>
<int key="connectionID">36</int>
</object>
</object>
<object class="IBMutableOrderedSet" key="objectRecords">
<object class="NSArray" key="orderedObjects">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="IBObjectRecord">
<int key="objectID">0</int>
<object class="NSArray" key="object" id="686063971">
<bool key="EncodedWithXMLCoder">YES</bool>
</object>
<reference key="children" ref="1000"/>
<nil key="parent"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">2</int>
<reference key="object" ref="380026005"/>
<object class="NSMutableArray" key="children">
<bool key="EncodedWithXMLCoder">YES</bool>
<reference ref="388289409"/>
</object>
<reference key="parent" ref="686063971"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">-1</int>
<reference key="object" ref="841351856"/>
<reference key="parent" ref="686063971"/>
<string type="base64-UTF8" key="objectName">RmlsZSdzIE93bmVyA</string>
</object>
<object class="IBObjectRecord">
<int key="objectID">4</int>
<reference key="object" ref="987256611"/>
<reference key="parent" ref="686063971"/>
<string key="objectName">ReachabilityAppDelegate</string>
</object>
<object class="IBObjectRecord">
<int key="objectID">7</int>
<reference key="object" ref="388289409"/>
<object class="NSMutableArray" key="children">
<bool key="EncodedWithXMLCoder">YES</bool>
<reference ref="670228971"/>
<reference ref="369550247"/>
<reference ref="633401159"/>
<reference ref="898416413"/>
<reference ref="355548833"/>
<reference ref="167828665"/>
<reference ref="503258438"/>
<reference ref="154536692"/>
<reference ref="917119444"/>
<reference ref="1041619883"/>
</object>
<reference key="parent" ref="380026005"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">9</int>
<reference key="object" ref="1041619883"/>
<reference key="parent" ref="388289409"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">-2</int>
<reference key="object" ref="841400030"/>
<reference key="parent" ref="686063971"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">11</int>
<reference key="object" ref="355548833"/>
<reference key="parent" ref="388289409"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">12</int>
<reference key="object" ref="167828665"/>
<reference key="parent" ref="388289409"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">13</int>
<reference key="object" ref="503258438"/>
<reference key="parent" ref="388289409"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">15</int>
<reference key="object" ref="898416413"/>
<reference key="parent" ref="388289409"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">16</int>
<reference key="object" ref="154536692"/>
<reference key="parent" ref="388289409"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">17</int>
<reference key="object" ref="670228971"/>
<reference key="parent" ref="388289409"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">18</int>
<reference key="object" ref="369550247"/>
<reference key="parent" ref="388289409"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">19</int>
<reference key="object" ref="633401159"/>
<reference key="parent" ref="388289409"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">29</int>
<reference key="object" ref="917119444"/>
<reference key="parent" ref="388289409"/>
</object>
</object>
</object>
<object class="NSMutableDictionary" key="flattenedProperties">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="NSMutableArray" key="dict.sortedKeys">
<bool key="EncodedWithXMLCoder">YES</bool>
<string>-1.CustomClassName</string>
<string>-2.CustomClassName</string>
<string>11.IBPluginDependency</string>
<string>12.IBPluginDependency</string>
<string>13.IBPluginDependency</string>
<string>15.IBPluginDependency</string>
<string>16.IBPluginDependency</string>
<string>17.IBPluginDependency</string>
<string>18.IBPluginDependency</string>
<string>19.IBPluginDependency</string>
<string>2.IBAttributePlaceholdersKey</string>
<string>2.IBEditorWindowLastContentRect</string>
<string>2.IBPluginDependency</string>
<string>2.UIWindow.visibleAtLaunch</string>
<string>29.IBPluginDependency</string>
<string>4.CustomClassName</string>
<string>4.IBPluginDependency</string>
<string>7.IBPluginDependency</string>
<string>9.IBPluginDependency</string>
</object>
<object class="NSMutableArray" key="dict.values">
<bool key="EncodedWithXMLCoder">YES</bool>
<string>UIApplication</string>
<string>UIResponder</string>
<string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
<string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
<string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
<string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
<string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
<string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
<string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
<string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
<object class="NSMutableDictionary">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="NSArray" key="dict.sortedKeys">
<bool key="EncodedWithXMLCoder">YES</bool>
</object>
<object class="NSMutableArray" key="dict.values">
<bool key="EncodedWithXMLCoder">YES</bool>
</object>
</object>
<string>{{287, 643}, {320, 480}}</string>
<string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
<integer value="1"/>
<string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
<string>ReachabilityAppDelegate</string>
<string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
<string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
<string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
</object>
</object>
<object class="NSMutableDictionary" key="unlocalizedProperties">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="NSArray" key="dict.sortedKeys">
<bool key="EncodedWithXMLCoder">YES</bool>
</object>
<object class="NSMutableArray" key="dict.values">
<bool key="EncodedWithXMLCoder">YES</bool>
</object>
</object>
<nil key="activeLocalization"/>
<object class="NSMutableDictionary" key="localizations">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="NSArray" key="dict.sortedKeys">
<bool key="EncodedWithXMLCoder">YES</bool>
</object>
<object class="NSMutableArray" key="dict.values">
<bool key="EncodedWithXMLCoder">YES</bool>
</object>
</object>
<nil key="sourceID"/>
<int key="maxID">36</int>
</object>
<object class="IBClassDescriber" key="IBDocument.Classes">
<object class="NSMutableArray" key="referencedPartialClassDescriptions">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="IBPartialClassDescription">
<string key="className">ReachabilityAppDelegate</string>
<string key="superclassName">NSObject</string>
<object class="NSMutableDictionary" key="outlets">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="NSMutableArray" key="dict.sortedKeys">
<bool key="EncodedWithXMLCoder">YES</bool>
<string>contentView</string>
<string>internetConnectionIcon</string>
<string>internetConnectionStatusField</string>
<string>localWiFiConnectionIcon</string>
<string>localWiFiConnectionStatusField</string>
<string>remoteHostIcon</string>
<string>remoteHostLabel</string>
<string>remoteHostStatusField</string>
<string>summaryLabel</string>
<string>window</string>
</object>
<object class="NSMutableArray" key="dict.values">
<bool key="EncodedWithXMLCoder">YES</bool>
<string>UIView</string>
<string>UIImageView</string>
<string>UITextField</string>
<string>UIImageView</string>
<string>UITextField</string>
<string>UIImageView</string>
<string>UITextField</string>
<string>UITextField</string>
<string>UILabel</string>
<string>UIWindow</string>
</object>
</object>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBProjectSource</string>
<string key="minorKey">Classes/ReachabilityAppDelegate.h</string>
</object>
</object>
</object>
</object>
<int key="IBDocument.localizationMode">0</int>
<string key="IBDocument.LastKnownRelativeProjectPath">Reachability.xcodeproj</string>
<int key="IBDocument.defaultPropertyAccessControl">3</int>
<string key="IBCocoaTouchPluginVersion">3.0</string>
</data>
</archive>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleDisplayName</key>
<string>${PRODUCT_NAME}</string>
<key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string>
<key>CFBundleIconFile</key>
<string></string>
<key>CFBundleIdentifier</key>
<string>com.yourcompany.${PRODUCT_NAME:rfc1034identifier}</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>${PRODUCT_NAME}</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSMainNibFile</key>
<string>MainWindow</string>
</dict>
</plist>

View File

@@ -0,0 +1,175 @@
// !$*UTF8*$!
{
1D6058900D05DD3D006BFB54 /* Reachability */ = {
activeExec = 0;
executables = (
CEAC717A10E9227C00087CCD /* Reachability */,
);
};
29B97313FDCFA39411CA2CEA /* Project object */ = {
activeBuildConfigurationName = Debug;
activeExecutable = CEAC717A10E9227C00087CCD /* Reachability */;
activeSDKPreference = iphonesimulator3.1.2;
activeTarget = 1D6058900D05DD3D006BFB54 /* Reachability */;
addToTargets = (
1D6058900D05DD3D006BFB54 /* Reachability */,
);
breakpoints = (
CEAC722610E92B0F00087CCD /* Reachability.m:426 */,
);
codeSenseManager = CEAC718410E9228600087CCD /* Code sense */;
executables = (
CEAC717A10E9227C00087CCD /* Reachability */,
);
perUserDictionary = {
PBXConfiguration.PBXFileTableDataSource3.PBXFileTableDataSource = {
PBXFileTableDataSourceColumnSortingDirectionKey = "-1";
PBXFileTableDataSourceColumnSortingKey = PBXFileDataSource_Filename_ColumnID;
PBXFileTableDataSourceColumnWidthsKey = (
20,
341,
20,
48,
43,
43,
20,
);
PBXFileTableDataSourceColumnsKey = (
PBXFileDataSource_FiletypeID,
PBXFileDataSource_Filename_ColumnID,
PBXFileDataSource_Built_ColumnID,
PBXFileDataSource_ObjectSize_ColumnID,
PBXFileDataSource_Errors_ColumnID,
PBXFileDataSource_Warnings_ColumnID,
PBXFileDataSource_Target_ColumnID,
);
};
PBXPerProjectTemplateStateSaveDate = 287943446;
PBXWorkspaceStateSaveDate = 287943446;
};
perUserProjectItems = {
CE244DDA10E963A000FF3178 /* PBXTextBookmark */ = CE244DDA10E963A000FF3178 /* PBXTextBookmark */;
CE417BDF10E960A7002374BB /* PBXBookmark */ = CE417BDF10E960A7002374BB /* PBXBookmark */;
CE6EB7691129AC3300A4FF2B /* PBXTextBookmark */ = CE6EB7691129AC3300A4FF2B /* PBXTextBookmark */;
CE6EB7851129AC9D00A4FF2B /* PBXTextBookmark */ = CE6EB7851129AC9D00A4FF2B /* PBXTextBookmark */;
};
sourceControlManager = CEAC718310E9228600087CCD /* Source Control */;
userBuildSettings = {
};
};
CE244DDA10E963A000FF3178 /* PBXTextBookmark */ = {
isa = PBXTextBookmark;
fRef = CE417BD310E96042002374BB /* Reachability.h */;
name = "Reachability.h: 6";
rLen = 0;
rLoc = 131;
rType = 0;
vrLen = 490;
vrLoc = 0;
};
CE417BD310E96042002374BB /* Reachability.h */ = {
uiCtxt = {
sepNavIntBoundsRect = "{{0, 0}, {614, 3000}}";
sepNavSelRange = "{131, 0}";
sepNavVisRange = "{27, 463}";
sepNavWindowFrame = "{{15, 0}, {898, 1177}}";
};
};
CE417BD410E96042002374BB /* Reachability.m */ = {
uiCtxt = {
sepNavIntBoundsRect = "{{0, 0}, {1013, 11085}}";
sepNavSelRange = "{16378, 0}";
sepNavVisRange = "{0, 3271}";
sepNavWindowFrame = "{{779, 1}, {898, 1177}}";
};
};
CE417BDF10E960A7002374BB /* PBXBookmark */ = {
isa = PBXBookmark;
fRef = CEAC71AF10E9255500087CCD /* Airport.png */;
};
CE6EB7691129AC3300A4FF2B /* PBXTextBookmark */ = {
isa = PBXTextBookmark;
fRef = CE417BD410E96042002374BB /* Reachability.m */;
name = "Reachability.m: 1";
rLen = 0;
rLoc = 0;
rType = 0;
vrLen = 356;
vrLoc = 0;
};
CE6EB7851129AC9D00A4FF2B /* PBXTextBookmark */ = {
isa = PBXTextBookmark;
fRef = CE417BD310E96042002374BB /* Reachability.h */;
name = "Reachability.h: 6";
rLen = 0;
rLoc = 131;
rType = 0;
vrLen = 463;
vrLoc = 27;
};
CEAC717A10E9227C00087CCD /* Reachability */ = {
isa = PBXExecutable;
activeArgIndices = (
);
argumentStrings = (
);
autoAttachOnCrash = 1;
breakpointsEnabled = 1;
configStateDict = {
};
customDataFormattersEnabled = 1;
dataTipCustomDataFormattersEnabled = 1;
dataTipShowTypeColumn = 1;
dataTipSortType = 0;
debuggerPlugin = GDBDebugging;
disassemblyDisplayState = 0;
dylibVariantSuffix = "";
enableDebugStr = 1;
environmentEntries = (
);
executableSystemSymbolLevel = 0;
executableUserSymbolLevel = 0;
libgmallocEnabled = 0;
name = Reachability;
savedGlobals = {
};
showTypeColumn = 0;
sourceDirectories = (
);
variableFormatDictionary = {
"flags-SCNetworkReachabilityFlags--[Reachability networkStatusForFlags:]" = 1;
};
};
CEAC718310E9228600087CCD /* Source Control */ = {
isa = PBXSourceControlManager;
fallbackIsa = XCSourceControlManager;
isSCMEnabled = 0;
scmConfiguration = {
repositoryNamesForRoots = {
"" = "";
};
};
};
CEAC718410E9228600087CCD /* Code sense */ = {
isa = PBXCodeSenseManager;
indexTemplatePath = "";
};
CEAC722610E92B0F00087CCD /* Reachability.m:426 */ = {
isa = PBXFileBreakpoint;
actions = (
);
breakpointStyle = 0;
continueAfterActions = 0;
countType = 0;
delayBeforeContinue = 0;
fileReference = CE417BD410E96042002374BB /* Reachability.m */;
functionName = "-networkStatusForFlags:";
hitCount = 0;
ignoreCount = 0;
lineNumber = 426;
location = Reachability;
modificationTime = 287943736.833593;
originalNumberOfMultipleMatches = 1;
state = 2;
};
}

View File

@@ -0,0 +1,292 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 45;
objects = {
/* Begin PBXBuildFile section */
1D3623260D0F684500981E51 /* ReachabilityAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 1D3623250D0F684500981E51 /* ReachabilityAppDelegate.m */; };
1D60589B0D05DD56006BFB54 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 29B97316FDCFA39411CA2CEA /* main.m */; };
1D60589F0D05DD5A006BFB54 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1D30AB110D05D00D00671497 /* Foundation.framework */; };
1DF5F4E00D08C38300B7A737 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1DF5F4DF0D08C38300B7A737 /* UIKit.framework */; };
288765FD0DF74451002DB57D /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 288765FC0DF74451002DB57D /* CoreGraphics.framework */; };
28AD733F0D9D9553002E5188 /* MainWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 28AD733E0D9D9553002E5188 /* MainWindow.xib */; };
CE417BD510E96042002374BB /* Reachability.m in Sources */ = {isa = PBXBuildFile; fileRef = CE417BD410E96042002374BB /* Reachability.m */; };
CEAC718B10E9231400087CCD /* Default.png in Resources */ = {isa = PBXBuildFile; fileRef = CEAC718510E9231400087CCD /* Default.png */; };
CEAC718C10E9231400087CCD /* icon.png in Resources */ = {isa = PBXBuildFile; fileRef = CEAC718610E9231400087CCD /* icon.png */; };
CEAC718D10E9231400087CCD /* Network.png in Resources */ = {isa = PBXBuildFile; fileRef = CEAC718710E9231400087CCD /* Network.png */; };
CEAC718E10E9231400087CCD /* red.png in Resources */ = {isa = PBXBuildFile; fileRef = CEAC718810E9231400087CCD /* red.png */; };
CEAC718F10E9231400087CCD /* stop-32.png in Resources */ = {isa = PBXBuildFile; fileRef = CEAC718910E9231400087CCD /* stop-32.png */; };
CEAC719010E9231400087CCD /* WWAN5.png in Resources */ = {isa = PBXBuildFile; fileRef = CEAC718A10E9231400087CCD /* WWAN5.png */; };
CEAC719A10E9237400087CCD /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CEAC719910E9237400087CCD /* SystemConfiguration.framework */; };
CEAC71B010E9255500087CCD /* Airport.png in Resources */ = {isa = PBXBuildFile; fileRef = CEAC71AF10E9255500087CCD /* Airport.png */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
1D30AB110D05D00D00671497 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
1D3623240D0F684500981E51 /* ReachabilityAppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ReachabilityAppDelegate.h; sourceTree = "<group>"; };
1D3623250D0F684500981E51 /* ReachabilityAppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ReachabilityAppDelegate.m; sourceTree = "<group>"; };
1D6058910D05DD3D006BFB54 /* Reachability.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Reachability.app; sourceTree = BUILT_PRODUCTS_DIR; };
1DF5F4DF0D08C38300B7A737 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; };
288765FC0DF74451002DB57D /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; };
28AD733E0D9D9553002E5188 /* MainWindow.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = MainWindow.xib; sourceTree = "<group>"; };
29B97316FDCFA39411CA2CEA /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
32CA4F630368D1EE00C91783 /* Reachability_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Reachability_Prefix.pch; sourceTree = "<group>"; };
8D1107310486CEB800E47090 /* Reachability-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "Reachability-Info.plist"; plistStructureDefinitionIdentifier = "com.apple.xcode.plist.structure-definition.iphone.info-plist"; sourceTree = "<group>"; };
CE417BD310E96042002374BB /* Reachability.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Reachability.h; sourceTree = "<group>"; };
CE417BD410E96042002374BB /* Reachability.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Reachability.m; sourceTree = "<group>"; };
CEAC718510E9231400087CCD /* Default.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Default.png; sourceTree = "<group>"; };
CEAC718610E9231400087CCD /* icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = icon.png; sourceTree = "<group>"; };
CEAC718710E9231400087CCD /* Network.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Network.png; sourceTree = "<group>"; };
CEAC718810E9231400087CCD /* red.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = red.png; sourceTree = "<group>"; };
CEAC718910E9231400087CCD /* stop-32.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "stop-32.png"; sourceTree = "<group>"; };
CEAC718A10E9231400087CCD /* WWAN5.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = WWAN5.png; sourceTree = "<group>"; };
CEAC719910E9237400087CCD /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; };
CEAC71AF10E9255500087CCD /* Airport.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Airport.png; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
1D60588F0D05DD3D006BFB54 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
1D60589F0D05DD5A006BFB54 /* Foundation.framework in Frameworks */,
1DF5F4E00D08C38300B7A737 /* UIKit.framework in Frameworks */,
288765FD0DF74451002DB57D /* CoreGraphics.framework in Frameworks */,
CEAC719A10E9237400087CCD /* SystemConfiguration.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
080E96DDFE201D6D7F000001 /* Classes */ = {
isa = PBXGroup;
children = (
CE417BD210E96042002374BB /* Reachability */,
1D3623240D0F684500981E51 /* ReachabilityAppDelegate.h */,
1D3623250D0F684500981E51 /* ReachabilityAppDelegate.m */,
);
path = Classes;
sourceTree = "<group>";
};
19C28FACFE9D520D11CA2CBB /* Products */ = {
isa = PBXGroup;
children = (
1D6058910D05DD3D006BFB54 /* Reachability.app */,
);
name = Products;
sourceTree = "<group>";
};
29B97314FDCFA39411CA2CEA /* CustomTemplate */ = {
isa = PBXGroup;
children = (
080E96DDFE201D6D7F000001 /* Classes */,
29B97315FDCFA39411CA2CEA /* Other Sources */,
29B97317FDCFA39411CA2CEA /* Resources */,
29B97323FDCFA39411CA2CEA /* Frameworks */,
19C28FACFE9D520D11CA2CBB /* Products */,
);
name = CustomTemplate;
sourceTree = "<group>";
};
29B97315FDCFA39411CA2CEA /* Other Sources */ = {
isa = PBXGroup;
children = (
32CA4F630368D1EE00C91783 /* Reachability_Prefix.pch */,
29B97316FDCFA39411CA2CEA /* main.m */,
);
name = "Other Sources";
sourceTree = "<group>";
};
29B97317FDCFA39411CA2CEA /* Resources */ = {
isa = PBXGroup;
children = (
CEAC71AF10E9255500087CCD /* Airport.png */,
CEAC718510E9231400087CCD /* Default.png */,
CEAC718610E9231400087CCD /* icon.png */,
CEAC718710E9231400087CCD /* Network.png */,
CEAC718810E9231400087CCD /* red.png */,
CEAC718910E9231400087CCD /* stop-32.png */,
CEAC718A10E9231400087CCD /* WWAN5.png */,
28AD733E0D9D9553002E5188 /* MainWindow.xib */,
8D1107310486CEB800E47090 /* Reachability-Info.plist */,
);
name = Resources;
sourceTree = "<group>";
};
29B97323FDCFA39411CA2CEA /* Frameworks */ = {
isa = PBXGroup;
children = (
1DF5F4DF0D08C38300B7A737 /* UIKit.framework */,
1D30AB110D05D00D00671497 /* Foundation.framework */,
288765FC0DF74451002DB57D /* CoreGraphics.framework */,
CEAC719910E9237400087CCD /* SystemConfiguration.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
CE417BD210E96042002374BB /* Reachability */ = {
isa = PBXGroup;
children = (
CE417BD310E96042002374BB /* Reachability.h */,
CE417BD410E96042002374BB /* Reachability.m */,
);
name = Reachability;
path = ../Reachability;
sourceTree = SOURCE_ROOT;
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
1D6058900D05DD3D006BFB54 /* Reachability */ = {
isa = PBXNativeTarget;
buildConfigurationList = 1D6058960D05DD3E006BFB54 /* Build configuration list for PBXNativeTarget "Reachability" */;
buildPhases = (
1D60588D0D05DD3D006BFB54 /* Resources */,
1D60588E0D05DD3D006BFB54 /* Sources */,
1D60588F0D05DD3D006BFB54 /* Frameworks */,
);
buildRules = (
);
dependencies = (
);
name = Reachability;
productName = Reachability;
productReference = 1D6058910D05DD3D006BFB54 /* Reachability.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
29B97313FDCFA39411CA2CEA /* Project object */ = {
isa = PBXProject;
buildConfigurationList = C01FCF4E08A954540054247B /* Build configuration list for PBXProject "Reachability" */;
compatibilityVersion = "Xcode 3.1";
hasScannedForEncodings = 1;
mainGroup = 29B97314FDCFA39411CA2CEA /* CustomTemplate */;
projectDirPath = "";
projectRoot = "";
targets = (
1D6058900D05DD3D006BFB54 /* Reachability */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
1D60588D0D05DD3D006BFB54 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
28AD733F0D9D9553002E5188 /* MainWindow.xib in Resources */,
CEAC718B10E9231400087CCD /* Default.png in Resources */,
CEAC718C10E9231400087CCD /* icon.png in Resources */,
CEAC718D10E9231400087CCD /* Network.png in Resources */,
CEAC718E10E9231400087CCD /* red.png in Resources */,
CEAC718F10E9231400087CCD /* stop-32.png in Resources */,
CEAC719010E9231400087CCD /* WWAN5.png in Resources */,
CEAC71B010E9255500087CCD /* Airport.png in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
1D60588E0D05DD3D006BFB54 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
1D60589B0D05DD56006BFB54 /* main.m in Sources */,
1D3623260D0F684500981E51 /* ReachabilityAppDelegate.m in Sources */,
CE417BD510E96042002374BB /* Reachability.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
1D6058940D05DD3E006BFB54 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
COPY_PHASE_STRIP = NO;
GCC_DYNAMIC_NO_PIC = NO;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = Reachability_Prefix.pch;
INFOPLIST_FILE = "Reachability-Info.plist";
PRODUCT_NAME = Reachability;
};
name = Debug;
};
1D6058950D05DD3E006BFB54 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
COPY_PHASE_STRIP = YES;
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = Reachability_Prefix.pch;
INFOPLIST_FILE = "Reachability-Info.plist";
PRODUCT_NAME = Reachability;
};
name = Release;
};
C01FCF4F08A954540054247B /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ARCHS = "$(ARCHS_STANDARD_32_BIT)";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
GCC_C_LANGUAGE_STANDARD = c99;
GCC_PREPROCESSOR_DEFINITIONS = "DEBUG=1";
GCC_WARN_ABOUT_RETURN_TYPE = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 3.0;
PREBINDING = NO;
"PROVISIONING_PROFILE[sdk=iphoneos*]" = "";
SDKROOT = iphoneos3.1.2;
};
name = Debug;
};
C01FCF5008A954540054247B /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ARCHS = "$(ARCHS_STANDARD_32_BIT)";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
GCC_C_LANGUAGE_STANDARD = c99;
GCC_PREPROCESSOR_DEFINITIONS = "NS_BLOCK_ASSERTIONS=1";
GCC_WARN_ABOUT_RETURN_TYPE = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 3.0;
PREBINDING = NO;
"PROVISIONING_PROFILE[sdk=iphoneos*]" = "";
SDKROOT = iphoneos3.1.2;
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
1D6058960D05DD3E006BFB54 /* Build configuration list for PBXNativeTarget "Reachability" */ = {
isa = XCConfigurationList;
buildConfigurations = (
1D6058940D05DD3E006BFB54 /* Debug */,
1D6058950D05DD3E006BFB54 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
C01FCF4E08A954540054247B /* Build configuration list for PBXProject "Reachability" */ = {
isa = XCConfigurationList;
buildConfigurations = (
C01FCF4F08A954540054247B /* Debug */,
C01FCF5008A954540054247B /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 29B97313FDCFA39411CA2CEA /* Project object */;
}

View File

@@ -0,0 +1,8 @@
//
// Prefix header for all source files of the 'Reachability' target in the 'Reachability' project
//
#ifdef __OBJC__
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#endif

View File

@@ -0,0 +1,89 @@
Reachability
========================================================================
DESCRIPTION:
The Reachability sample application demonstrates how to use the System
Configuration framework to monitor the network state of an iPhone or
iPod touch. In particular, it demonstrates how to know when IP can be
routed and when traffic will be routed through a Wireless Wide Area
Network (WWAN) interface such as EDGE or 3G.
Note: Reachability cannot tell your application if you can connect to a
particular host, only that an interface is available that might allow a
connection, and whether that interface is the WWAN.
========================================================================
USING THE SAMPLE
Build and run the sample using Xcode. When running the iPhone Simulator,
you can exercise the application by disconnecting the Ethernet cable,
turning off AirPort, or by joining an ad-hoc local Wi-Fi network.
By default, the application uses www.apple.com for its remote host. You
can change the host it uses in ReachabilityAppDelegate.m by modifying
the call to [Reachability reachabilityWithHostName] in
-applicationDidFinishLaunching.
IMPORTANT: Reachability must use DNS to resolve the host name before it
can determine the Reachability of that host, and this may take time on
certain network connections. Because of this, the API will return
NotReachable until name resolution has completed. This delay may be
visible in the interface on some networks.
The Reachability sample demonstrates the asynchronous use of the
SCNetworkReachability API. You can use the API synchronously, but do not
issue a synchronous check by hostName on the main thread. If the device
cannot reach a DNS server or is on a slow network, a synchronous call to
the SCNetworkReachabilityGetFlags function can block for up to 30
seconds trying to resolve the hostName. If this happens on the main
thread, the application watchdog will kill the application after 20
seconds of inactivity.
SCNetworkReachability API's do not currently provide a means to detect
support for GameKit Peer To Peer networking over BlueTooth.
========================================================================
BUILD REQUIREMENTS
iPhone OS 3.0
========================================================================
RUNTIME REQUIREMENTS
iPhone OS 3.0
========================================================================
PACKAGING LIST
Reachability.h Reachability.m -Basic demonstration of how to use the
SystemConfiguration Reachablity APIs.
ReachabilityAppDelegate.h ReachabilityAppDelegate.m -The application's
controller.
========================================================================
CHANGES FROM PREVIOUS VERSIONS
Version 2.0
-Greatly simplified UI code.
-Rewrote Reachability object to be fully asychronous and simplify
monitoring of multiple SCNetworkReachabilityRefs.
-Added code showing how to monitor wether a connection will be required.
Version 1.5
- Updated for and tested with iPhone OS 2.0. First public release.
Version 1.4
- Updated for Beta 7.
Version 1.3
- Updated for Beta 6. - Added LSRequiresIPhoneOS key to Info.plist.
Version 1.2
- Updated for Beta 4. Added code signing.
Version 1.1
- Updated for Beta 3 to use a nib file.
Copyright (C)2009 Apple Inc. All rights reserved.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

View File

@@ -0,0 +1,17 @@
//
// main.m
// Reachability
//
// Created by Andrew Donoho on 2009/12/28.
// Copyright Donoho Design Group, L.L.C. 2009. All rights reserved.
//
#import <UIKit/UIKit.h>
int main(int argc, char *argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
int retVal = UIApplicationMain(argc, argv, nil, nil);
[pool release];
return retVal;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 503 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1,193 @@
/*
File: Reachability.h
Abstract: Basic demonstration of how to use the SystemConfiguration Reachablity APIs.
Version: 2.0.4ddg
*/
/*
Significant additions made by Andrew W. Donoho, August 11, 2009.
This is a derived work of Apple's Reachability v2.0 class.
The below license is the new BSD license with the OSI recommended personalizations.
<http://www.opensource.org/licenses/bsd-license.php>
Extensions Copyright (C) 2009 Donoho Design Group, LLC. All Rights Reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of Andrew W. Donoho nor Donoho Design Group, L.L.C.
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY DONOHO DESIGN GROUP, L.L.C. "AS IS" AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*
Apple's Original License on Reachability v2.0
Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Inc.
("Apple") in consideration of your agreement to the following terms, and your
use, installation, modification or redistribution of this Apple software
constitutes acceptance of these terms. If you do not agree with these terms,
please do not use, install, modify or redistribute this Apple software.
In consideration of your agreement to abide by the following terms, and subject
to these terms, Apple grants you a personal, non-exclusive license, under
Apple's copyrights in this original Apple software (the "Apple Software"), to
use, reproduce, modify and redistribute the Apple Software, with or without
modifications, in source and/or binary forms; provided that if you redistribute
the Apple Software in its entirety and without modifications, you must retain
this notice and the following text and disclaimers in all such redistributions
of the Apple Software.
Neither the name, trademarks, service marks or logos of Apple Inc. may be used
to endorse or promote products derived from the Apple Software without specific
prior written permission from Apple. Except as expressly stated in this notice,
no other rights or licenses, express or implied, are granted by Apple herein,
including but not limited to any patent rights that may be infringed by your
derivative works or by other works in which the Apple Software may be
incorporated.
The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO
WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED
WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN
COMBINATION WITH YOUR PRODUCTS.
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR
DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF
CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF
APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Copyright (C) 2009 Apple Inc. All Rights Reserved.
*/
/*
DDG extensions include:
Each reachability object now has a copy of the key used to store it in a
dictionary. This allows each observer to quickly determine if the event is
important to them.
-currentReachabilityStatus also has a significantly different decision criteria than
Apple's code.
A multiple convenience test methods have been added.
*/
#import <Foundation/Foundation.h>
#import <SystemConfiguration/SystemConfiguration.h>
#define USE_DDG_EXTENSIONS 1 // Use DDG's Extensions to test network criteria.
// Since NSAssert and NSCAssert are used in this code,
// I recommend you set NS_BLOCK_ASSERTIONS=1 in the release versions of your projects.
enum {
// DDG NetworkStatus Constant Names.
kNotReachable = 0, // Apple's code depends upon 'NotReachable' being the same value as 'NO'.
kReachableViaWWAN, // Switched order from Apple's enum. WWAN is active before WiFi.
kReachableViaWiFi
};
typedef uint32_t NetworkStatus;
enum {
// Apple NetworkStatus Constant Names.
NotReachable = kNotReachable,
ReachableViaWiFi = kReachableViaWiFi,
ReachableViaWWAN = kReachableViaWWAN
};
extern NSString *const kInternetConnection;
extern NSString *const kLocalWiFiConnection;
extern NSString *const kReachabilityChangedNotification;
@interface Reachability: NSObject {
@private
NSString *key_;
SCNetworkReachabilityRef reachabilityRef;
}
@property (copy) NSString *key; // Atomic because network operations are asynchronous.
// Designated Initializer.
- (Reachability *) initWithReachabilityRef: (SCNetworkReachabilityRef) ref;
// Use to check the reachability of a particular host name.
+ (Reachability *) reachabilityWithHostName: (NSString*) hostName;
// Use to check the reachability of a particular IP address.
+ (Reachability *) reachabilityWithAddress: (const struct sockaddr_in*) hostAddress;
// Use to check whether the default route is available.
// Should be used to, at minimum, establish network connectivity.
+ (Reachability *) reachabilityForInternetConnection;
// Use to check whether a local wifi connection is available.
+ (Reachability *) reachabilityForLocalWiFi;
//Start listening for reachability notifications on the current run loop.
- (BOOL) startNotifier;
- (void) stopNotifier;
// Comparison routines to enable choosing actions in a notification.
- (BOOL) isEqual: (Reachability *) r;
// These are the status tests.
- (NetworkStatus) currentReachabilityStatus;
// The main direct test of reachability.
- (BOOL) isReachable;
// WWAN may be available, but not active until a connection has been established.
// WiFi may require a connection for VPN on Demand.
- (BOOL) isConnectionRequired; // Identical DDG variant.
- (BOOL) connectionRequired; // Apple's routine.
// Dynamic, on demand connection?
- (BOOL) isConnectionOnDemand;
// Is user intervention required?
- (BOOL) isInterventionRequired;
// Routines for specific connection testing by your app.
- (BOOL) isReachableViaWWAN;
- (BOOL) isReachableViaWiFi;
- (SCNetworkReachabilityFlags) reachabilityFlags;
@end

View File

@@ -0,0 +1,814 @@
/*
File: Reachability.m
Abstract: Basic demonstration of how to use the SystemConfiguration Reachablity APIs.
Version: 2.0.4ddg
*/
/*
Significant additions made by Andrew W. Donoho, August 11, 2009.
This is a derived work of Apple's Reachability v2.0 class.
The below license is the new BSD license with the OSI recommended personalizations.
<http://www.opensource.org/licenses/bsd-license.php>
Extensions Copyright (C) 2009 Donoho Design Group, LLC. All Rights Reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of Andrew W. Donoho nor Donoho Design Group, L.L.C.
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY DONOHO DESIGN GROUP, L.L.C. "AS IS" AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*
Apple's Original License on Reachability v2.0
Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Inc.
("Apple") in consideration of your agreement to the following terms, and your
use, installation, modification or redistribution of this Apple software
constitutes acceptance of these terms. If you do not agree with these terms,
please do not use, install, modify or redistribute this Apple software.
In consideration of your agreement to abide by the following terms, and subject
to these terms, Apple grants you a personal, non-exclusive license, under
Apple's copyrights in this original Apple software (the "Apple Software"), to
use, reproduce, modify and redistribute the Apple Software, with or without
modifications, in source and/or binary forms; provided that if you redistribute
the Apple Software in its entirety and without modifications, you must retain
this notice and the following text and disclaimers in all such redistributions
of the Apple Software.
Neither the name, trademarks, service marks or logos of Apple Inc. may be used
to endorse or promote products derived from the Apple Software without specific
prior written permission from Apple. Except as expressly stated in this notice,
no other rights or licenses, express or implied, are granted by Apple herein,
including but not limited to any patent rights that may be infringed by your
derivative works or by other works in which the Apple Software may be
incorporated.
The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO
WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED
WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN
COMBINATION WITH YOUR PRODUCTS.
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR
DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF
CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF
APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Copyright (C) 2009 Apple Inc. All Rights Reserved.
*/
/*
Each reachability object now has a copy of the key used to store it in a dictionary.
This allows each observer to quickly determine if the event is important to them.
*/
#import <sys/socket.h>
#import <netinet/in.h>
#import <netinet6/in6.h>
#import <arpa/inet.h>
#import <ifaddrs.h>
#import <netdb.h>
#import <CoreFoundation/CoreFoundation.h>
#import "Reachability.h"
NSString *const kInternetConnection = @"InternetConnection";
NSString *const kLocalWiFiConnection = @"LocalWiFiConnection";
NSString *const kReachabilityChangedNotification = @"NetworkReachabilityChangedNotification";
#define CLASS_DEBUG 1 // Turn on logReachabilityFlags. Must also have a project wide defined DEBUG.
#if (defined DEBUG && defined CLASS_DEBUG)
#define logReachabilityFlags(flags) (logReachabilityFlags_(__PRETTY_FUNCTION__, __LINE__, flags))
static NSString *reachabilityFlags_(SCNetworkReachabilityFlags flags) {
#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 30000) // Apple advises you to use the magic number instead of a symbol.
return [NSString stringWithFormat:@"Reachability Flags: %c%c %c%c%c%c%c%c%c",
(flags & kSCNetworkReachabilityFlagsIsWWAN) ? 'W' : '-',
(flags & kSCNetworkReachabilityFlagsReachable) ? 'R' : '-',
(flags & kSCNetworkReachabilityFlagsConnectionRequired) ? 'c' : '-',
(flags & kSCNetworkReachabilityFlagsTransientConnection) ? 't' : '-',
(flags & kSCNetworkReachabilityFlagsInterventionRequired) ? 'i' : '-',
(flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) ? 'C' : '-',
(flags & kSCNetworkReachabilityFlagsConnectionOnDemand) ? 'D' : '-',
(flags & kSCNetworkReachabilityFlagsIsLocalAddress) ? 'l' : '-',
(flags & kSCNetworkReachabilityFlagsIsDirect) ? 'd' : '-'];
#else
// Compile out the v3.0 features for v2.2.1 deployment.
return [NSString stringWithFormat:@"Reachability Flags: %c%c %c%c%c%c%c%c",
(flags & kSCNetworkReachabilityFlagsIsWWAN) ? 'W' : '-',
(flags & kSCNetworkReachabilityFlagsReachable) ? 'R' : '-',
(flags & kSCNetworkReachabilityFlagsConnectionRequired) ? 'c' : '-',
(flags & kSCNetworkReachabilityFlagsTransientConnection) ? 't' : '-',
(flags & kSCNetworkReachabilityFlagsInterventionRequired) ? 'i' : '-',
// v3 kSCNetworkReachabilityFlagsConnectionOnTraffic == v2 kSCNetworkReachabilityFlagsConnectionAutomatic
(flags & kSCNetworkReachabilityFlagsConnectionAutomatic) ? 'C' : '-',
// (flags & kSCNetworkReachabilityFlagsConnectionOnDemand) ? 'D' : '-', // No v2 equivalent.
(flags & kSCNetworkReachabilityFlagsIsLocalAddress) ? 'l' : '-',
(flags & kSCNetworkReachabilityFlagsIsDirect) ? 'd' : '-'];
#endif
} // reachabilityFlags_()
static void logReachabilityFlags_(const char *name, int line, SCNetworkReachabilityFlags flags) {
NSLog(@"%s (%d) \n\t%@", name, line, reachabilityFlags_(flags));
} // logReachabilityFlags_()
#define logNetworkStatus(status) (logNetworkStatus_(__PRETTY_FUNCTION__, __LINE__, status))
static void logNetworkStatus_(const char *name, int line, NetworkStatus status) {
NSString *statusString = nil;
switch (status) {
case kNotReachable:
statusString = [NSString stringWithString: @"Not Reachable"];
break;
case kReachableViaWWAN:
statusString = [NSString stringWithString: @"Reachable via WWAN"];
break;
case kReachableViaWiFi:
statusString = [NSString stringWithString: @"Reachable via WiFi"];
break;
}
NSLog(@"%s (%d) \n\tNetwork Status: %@", name, line, statusString);
} // logNetworkStatus_()
#else
#define logReachabilityFlags(flags)
#define logNetworkStatus(status)
#endif
@interface Reachability (private)
- (NetworkStatus) networkStatusForFlags: (SCNetworkReachabilityFlags) flags;
@end
@implementation Reachability
@synthesize key = key_;
// Preclude direct access to ivars.
+ (BOOL) accessInstanceVariablesDirectly {
return NO;
} // accessInstanceVariablesDirectly
- (void) dealloc {
[self stopNotifier];
if(reachabilityRef) {
CFRelease(reachabilityRef); reachabilityRef = NULL;
}
self.key = nil;
[super dealloc];
} // dealloc
- (Reachability *) initWithReachabilityRef: (SCNetworkReachabilityRef) ref {
if (self = [super init]) {
reachabilityRef = ref;
}
return self;
} // initWithReachabilityRef:
#if (defined DEBUG && defined CLASS_DEBUG)
- (NSString *) description {
NSAssert(reachabilityRef, @"-description called with NULL reachabilityRef");
SCNetworkReachabilityFlags flags = 0;
SCNetworkReachabilityGetFlags(reachabilityRef, &flags);
return [NSString stringWithFormat: @"%@\n\t%@", self.key, reachabilityFlags_(flags)];
} // description
#endif
#pragma mark -
#pragma mark Notification Management Methods
//Start listening for reachability notifications on the current run loop
static void ReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void* info) {
#pragma unused (target, flags)
NSCAssert(info, @"info was NULL in ReachabilityCallback");
NSCAssert([(NSObject*) info isKindOfClass: [Reachability class]], @"info was the wrong class in ReachabilityCallback");
//We're on the main RunLoop, so an NSAutoreleasePool is not necessary, but is added defensively
// in case someone uses the Reachablity object in a different thread.
NSAutoreleasePool* pool = [NSAutoreleasePool new];
// Post a notification to notify the client that the network reachability changed.
[[NSNotificationCenter defaultCenter] postNotificationName: kReachabilityChangedNotification
object: (Reachability *) info];
[pool release];
} // ReachabilityCallback()
- (BOOL) startNotifier {
SCNetworkReachabilityContext context = {0, self, NULL, NULL, NULL};
if(SCNetworkReachabilitySetCallback(reachabilityRef, ReachabilityCallback, &context)) {
if(SCNetworkReachabilityScheduleWithRunLoop(reachabilityRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode)) {
return YES;
}
}
return NO;
} // startNotifier
- (void) stopNotifier {
if(reachabilityRef) {
SCNetworkReachabilityUnscheduleFromRunLoop(reachabilityRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
}
} // stopNotifier
- (BOOL) isEqual: (Reachability *) r {
return [r.key isEqualToString: self.key];
} // isEqual:
#pragma mark -
#pragma mark Reachability Allocation Methods
+ (Reachability *) reachabilityWithHostName: (NSString *) hostName {
SCNetworkReachabilityRef ref = SCNetworkReachabilityCreateWithName(NULL, [hostName UTF8String]);
if (ref) {
Reachability *r = [[[self alloc] initWithReachabilityRef: ref] autorelease];
r.key = hostName;
return r;
}
return nil;
} // reachabilityWithHostName
+ (NSString *) makeAddressKey: (in_addr_t) addr {
// addr is assumed to be in network byte order.
static const int highShift = 24;
static const int highMidShift = 16;
static const int lowMidShift = 8;
static const in_addr_t mask = 0x000000ff;
addr = ntohl(addr);
return [NSString stringWithFormat: @"%d.%d.%d.%d",
(addr >> highShift) & mask,
(addr >> highMidShift) & mask,
(addr >> lowMidShift) & mask,
addr & mask];
} // makeAddressKey:
+ (Reachability *) reachabilityWithAddress: (const struct sockaddr_in *) hostAddress {
SCNetworkReachabilityRef ref = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr*)hostAddress);
if (ref) {
Reachability *r = [[[self alloc] initWithReachabilityRef: ref] autorelease];
r.key = [self makeAddressKey: hostAddress->sin_addr.s_addr];
return r;
}
return nil;
} // reachabilityWithAddress
+ (Reachability *) reachabilityForInternetConnection {
struct sockaddr_in zeroAddress;
bzero(&zeroAddress, sizeof(zeroAddress));
zeroAddress.sin_len = sizeof(zeroAddress);
zeroAddress.sin_family = AF_INET;
Reachability *r = [self reachabilityWithAddress: &zeroAddress];
r.key = kInternetConnection;
return r;
} // reachabilityForInternetConnection
+ (Reachability *) reachabilityForLocalWiFi {
struct sockaddr_in localWifiAddress;
bzero(&localWifiAddress, sizeof(localWifiAddress));
localWifiAddress.sin_len = sizeof(localWifiAddress);
localWifiAddress.sin_family = AF_INET;
// IN_LINKLOCALNETNUM is defined in <netinet/in.h> as 169.254.0.0
localWifiAddress.sin_addr.s_addr = htonl(IN_LINKLOCALNETNUM);
Reachability *r = [self reachabilityWithAddress: &localWifiAddress];
r.key = kLocalWiFiConnection;
return r;
} // reachabilityForLocalWiFi
#pragma mark -
#pragma mark Network Flag Handling Methods
#if USE_DDG_EXTENSIONS
//
// iPhone condition codes as reported by a 3GS running iPhone OS v3.0.
// Airplane Mode turned on: Reachability Flag Status: -- -------
// WWAN Active: Reachability Flag Status: WR -t-----
// WWAN Connection required: Reachability Flag Status: WR ct-----
// WiFi turned on: Reachability Flag Status: -R ------- Reachable.
// Local WiFi turned on: Reachability Flag Status: -R xxxxxxd Reachable.
// WiFi turned on: Reachability Flag Status: -R ct----- Connection down. (Non-intuitive, empirically determined answer.)
const SCNetworkReachabilityFlags kConnectionDown = kSCNetworkReachabilityFlagsConnectionRequired |
kSCNetworkReachabilityFlagsTransientConnection;
// WiFi turned on: Reachability Flag Status: -R ct-i--- Reachable but it will require user intervention (e.g. enter a WiFi password).
// WiFi turned on: Reachability Flag Status: -R -t----- Reachable via VPN.
//
// In the below method, an 'x' in the flag status means I don't care about its value.
//
// This method differs from Apple's by testing explicitly for empirically observed values.
// This gives me more confidence in it's correct behavior. Apple's code covers more cases
// than mine. My code covers the cases that occur.
//
- (NetworkStatus) networkStatusForFlags: (SCNetworkReachabilityFlags) flags {
if (flags & kSCNetworkReachabilityFlagsReachable) {
// Local WiFi -- Test derived from Apple's code: -localWiFiStatusForFlags:.
if (self.key == kLocalWiFiConnection) {
// Reachability Flag Status: xR xxxxxxd Reachable.
return (flags & kSCNetworkReachabilityFlagsIsDirect) ? kReachableViaWiFi : kNotReachable;
}
// Observed WWAN Values:
// WWAN Active: Reachability Flag Status: WR -t-----
// WWAN Connection required: Reachability Flag Status: WR ct-----
//
// Test Value: Reachability Flag Status: WR xxxxxxx
if (flags & kSCNetworkReachabilityFlagsIsWWAN) { return kReachableViaWWAN; }
// Clear moot bits.
flags &= ~kSCNetworkReachabilityFlagsReachable;
flags &= ~kSCNetworkReachabilityFlagsIsDirect;
flags &= ~kSCNetworkReachabilityFlagsIsLocalAddress; // kInternetConnection is local.
// Reachability Flag Status: -R ct---xx Connection down.
if (flags == kConnectionDown) { return kNotReachable; }
// Reachability Flag Status: -R -t---xx Reachable. WiFi + VPN(is up) (Thank you Ling Wang)
if (flags & kSCNetworkReachabilityFlagsTransientConnection) { return kReachableViaWiFi; }
// Reachability Flag Status: -R -----xx Reachable.
if (flags == 0) { return kReachableViaWiFi; }
// Apple's code tests for dynamic connection types here. I don't.
// If a connection is required, regardless of whether it is on demand or not, it is a WiFi connection.
// If you care whether a connection needs to be brought up, use -isConnectionRequired.
// If you care about whether user intervention is necessary, use -isInterventionRequired.
// If you care about dynamically establishing the connection, use -isConnectionIsOnDemand.
// Reachability Flag Status: -R cxxxxxx Reachable.
if (flags & kSCNetworkReachabilityFlagsConnectionRequired) { return kReachableViaWiFi; }
// Required by the compiler. Should never get here. Default to not connected.
#if (defined DEBUG && defined CLASS_DEBUG)
NSAssert1(NO, @"Uncaught reachability test. Flags: %@", reachabilityFlags_(flags));
#endif
return kNotReachable;
}
// Reachability Flag Status: x- xxxxxxx
return kNotReachable;
} // networkStatusForFlags:
- (NetworkStatus) currentReachabilityStatus {
NSAssert(reachabilityRef, @"currentReachabilityStatus called with NULL reachabilityRef");
SCNetworkReachabilityFlags flags = 0;
NetworkStatus status = kNotReachable;
if (SCNetworkReachabilityGetFlags(reachabilityRef, &flags)) {
// logReachabilityFlags(flags);
status = [self networkStatusForFlags: flags];
return status;
}
return kNotReachable;
} // currentReachabilityStatus
- (BOOL) isReachable {
NSAssert(reachabilityRef, @"isReachable called with NULL reachabilityRef");
SCNetworkReachabilityFlags flags = 0;
NetworkStatus status = kNotReachable;
if (SCNetworkReachabilityGetFlags(reachabilityRef, &flags)) {
// logReachabilityFlags(flags);
status = [self networkStatusForFlags: flags];
// logNetworkStatus(status);
return (kNotReachable != status);
}
return NO;
} // isReachable
- (BOOL) isConnectionRequired {
NSAssert(reachabilityRef, @"isConnectionRequired called with NULL reachabilityRef");
SCNetworkReachabilityFlags flags;
if (SCNetworkReachabilityGetFlags(reachabilityRef, &flags)) {
logReachabilityFlags(flags);
return (flags & kSCNetworkReachabilityFlagsConnectionRequired);
}
return NO;
} // isConnectionRequired
- (BOOL) connectionRequired {
return [self isConnectionRequired];
} // connectionRequired
#endif
#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 30000)
static const SCNetworkReachabilityFlags kOnDemandConnection = kSCNetworkReachabilityFlagsConnectionOnTraffic |
kSCNetworkReachabilityFlagsConnectionOnDemand;
#else
static const SCNetworkReachabilityFlags kOnDemandConnection = kSCNetworkReachabilityFlagsConnectionAutomatic;
#endif
- (BOOL) isConnectionOnDemand {
NSAssert(reachabilityRef, @"isConnectionIsOnDemand called with NULL reachabilityRef");
SCNetworkReachabilityFlags flags;
if (SCNetworkReachabilityGetFlags(reachabilityRef, &flags)) {
logReachabilityFlags(flags);
return ((flags & kSCNetworkReachabilityFlagsConnectionRequired) &&
(flags & kOnDemandConnection));
}
return NO;
} // isConnectionOnDemand
- (BOOL) isInterventionRequired {
NSAssert(reachabilityRef, @"isInterventionRequired called with NULL reachabilityRef");
SCNetworkReachabilityFlags flags;
if (SCNetworkReachabilityGetFlags(reachabilityRef, &flags)) {
logReachabilityFlags(flags);
return ((flags & kSCNetworkReachabilityFlagsConnectionRequired) &&
(flags & kSCNetworkReachabilityFlagsInterventionRequired));
}
return NO;
} // isInterventionRequired
- (BOOL) isReachableViaWWAN {
NSAssert(reachabilityRef, @"isReachableViaWWAN called with NULL reachabilityRef");
SCNetworkReachabilityFlags flags = 0;
NetworkStatus status = kNotReachable;
if (SCNetworkReachabilityGetFlags(reachabilityRef, &flags)) {
logReachabilityFlags(flags);
status = [self networkStatusForFlags: flags];
return (kReachableViaWWAN == status);
}
return NO;
} // isReachableViaWWAN
- (BOOL) isReachableViaWiFi {
NSAssert(reachabilityRef, @"isReachableViaWiFi called with NULL reachabilityRef");
SCNetworkReachabilityFlags flags = 0;
NetworkStatus status = kNotReachable;
if (SCNetworkReachabilityGetFlags(reachabilityRef, &flags)) {
logReachabilityFlags(flags);
status = [self networkStatusForFlags: flags];
return (kReachableViaWiFi == status);
}
return NO;
} // isReachableViaWiFi
- (SCNetworkReachabilityFlags) reachabilityFlags {
NSAssert(reachabilityRef, @"reachabilityFlags called with NULL reachabilityRef");
SCNetworkReachabilityFlags flags = 0;
if (SCNetworkReachabilityGetFlags(reachabilityRef, &flags)) {
logReachabilityFlags(flags);
return flags;
}
return 0;
} // reachabilityFlags
#pragma mark -
#pragma mark Apple's Network Flag Handling Methods
#if !USE_DDG_EXTENSIONS
/*
*
* Apple's Network Status testing code.
* The only changes that have been made are to use the new logReachabilityFlags macro and
* test for local WiFi via the key instead of Apple's boolean. Also, Apple's code was for v3.0 only
* iPhone OS. v2.2.1 and earlier conditional compiling is turned on. Hence, to mirror Apple's behavior,
* set your Base SDK to v3.0 or higher.
*
*/
- (NetworkStatus) localWiFiStatusForFlags: (SCNetworkReachabilityFlags) flags
{
logReachabilityFlags(flags);
BOOL retVal = NotReachable;
if((flags & kSCNetworkReachabilityFlagsReachable) && (flags & kSCNetworkReachabilityFlagsIsDirect))
{
retVal = ReachableViaWiFi;
}
return retVal;
}
- (NetworkStatus) networkStatusForFlags: (SCNetworkReachabilityFlags) flags
{
logReachabilityFlags(flags);
if (!(flags & kSCNetworkReachabilityFlagsReachable))
{
// if target host is not reachable
return NotReachable;
}
BOOL retVal = NotReachable;
if (!(flags & kSCNetworkReachabilityFlagsConnectionRequired))
{
// if target host is reachable and no connection is required
// then we'll assume (for now) that your on Wi-Fi
retVal = ReachableViaWiFi;
}
#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 30000) // Apple advises you to use the magic number instead of a symbol.
if ((flags & kSCNetworkReachabilityFlagsConnectionOnDemand) ||
(flags & kSCNetworkReachabilityFlagsConnectionOnTraffic))
#else
if (flags & kSCNetworkReachabilityFlagsConnectionAutomatic)
#endif
{
// ... and the connection is on-demand (or on-traffic) if the
// calling application is using the CFSocketStream or higher APIs
if (!(flags & kSCNetworkReachabilityFlagsInterventionRequired))
{
// ... and no [user] intervention is needed
retVal = ReachableViaWiFi;
}
}
if (flags & kSCNetworkReachabilityFlagsIsWWAN)
{
// ... but WWAN connections are OK if the calling application
// is using the CFNetwork (CFSocketStream?) APIs.
retVal = ReachableViaWWAN;
}
return retVal;
}
- (NetworkStatus) currentReachabilityStatus
{
NSAssert(reachabilityRef, @"currentReachabilityStatus called with NULL reachabilityRef");
NetworkStatus retVal = NotReachable;
SCNetworkReachabilityFlags flags;
if (SCNetworkReachabilityGetFlags(reachabilityRef, &flags))
{
if(self.key == kLocalWiFiConnection)
{
retVal = [self localWiFiStatusForFlags: flags];
}
else
{
retVal = [self networkStatusForFlags: flags];
}
}
return retVal;
}
- (BOOL) isReachable {
NSAssert(reachabilityRef, @"isReachable called with NULL reachabilityRef");
SCNetworkReachabilityFlags flags = 0;
NetworkStatus status = kNotReachable;
if (SCNetworkReachabilityGetFlags(reachabilityRef, &flags)) {
logReachabilityFlags(flags);
if(self.key == kLocalWiFiConnection) {
status = [self localWiFiStatusForFlags: flags];
} else {
status = [self networkStatusForFlags: flags];
}
return (kNotReachable != status);
}
return NO;
} // isReachable
- (BOOL) isConnectionRequired {
return [self connectionRequired];
} // isConnectionRequired
- (BOOL) connectionRequired {
NSAssert(reachabilityRef, @"connectionRequired called with NULL reachabilityRef");
SCNetworkReachabilityFlags flags;
if (SCNetworkReachabilityGetFlags(reachabilityRef, &flags)) {
logReachabilityFlags(flags);
return (flags & kSCNetworkReachabilityFlagsConnectionRequired);
}
return NO;
} // connectionRequired
#endif
@end

View File

@@ -0,0 +1,45 @@
<?xml version="1.0" ?>
<menu>
<main>
<item iconId="5" displayName="Play" action="playMenu" particleFile="particle1.psi" />
<item iconId="2" displayName="Deck Editor" action="deckEditor" particleFile="particle2.psi" />
<item iconId="1" displayName="Shop" action="shop" particleFile="particle3.psi" />
<item iconId="4" displayName="Options" action="options" particleFile="particle4.psi" />
<item iconId="3" displayName="Credits" action="credits" particleFile="particle5.psi" />
</main>
<other>
<item displayName="Trophy Room" action="trophies" />
</other>
</menu>
<cardgui>
<background>
<card id="6" color="artifact" img="artifact.jpg" thumb="artifact_thumb.jpg" menuicon="1"/>
<card id="0" color="green" img="green.jpg" thumb="green_thumb.jpg" menuicon="1"/>
<card id="1" color="blue" img="blue.jpg" thumb="blue_thumb.jpg" menuicon="1"/>
<card id="3" color="red" img="red.jpg" thumb="red_thumb.jpg" menuicon="1"/>
<card id="2" color="black" img="black.jpg" thumb="black_thumb.jpg" menuicon="1"/>
<card id="4" color="white" img="white.jpg" thumb="white_thumb.jpg" menuicon="1"/>
<card id="5" color="land" img="land.jpg" thumb="land_thumb.jpg" menuicon="1"/>
<card id="7" color="gold" img="gold.jpg" thumb="gold_thumb.jpg" menuicon="0"/>
</background>
<renderbig>
<item name="title" posx="22" posy="25" formattedtext="" type=""/>
<item name="description" posx="22" posy="80" formattedtext="" type=""/>
<item name="powerlife" posx="158" posy="106" formattedtext="power/life" type="creature"/>
<item name="mana" posx="75" posy="-112" formattedtext="" type=""/>
<item name="types" posx="22" posy="49" formattedtext="" type=""/>
<item name="expansionrarity" posx="22" posy="113" formattedtext="expansion rarity" type=""/>
</renderbig>
<rendertinycrop>
<item name="title" posx="22" posy="25" formattedtext="" type=""/>
<item name="description" posx="22" posy="80" formattedtext="" type=""/>
<item name="powerlife" posx="158" posy="106" formattedtext="power/life" type="creature"/>
<item name="mana" posx="75" posy="-112" formattedtext="" type=""/>
<item name="types" posx="22" posy="49" formattedtext="" type=""/>
<item name="expansionrarity" posx="22" posy="113" formattedtext="expansion rarity" type=""/>
</rendertinycrop>
</cardgui>

View File

@@ -0,0 +1,19 @@
//
// WagicDownloadProgressView.h
// wagic
//
// Created by Michael Nguyen on 12/10/11.
// Copyright (c) 2011 __MyCompanyName__. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface WagicDownloadProgressViewController : UIViewController
{
}
@property (nonatomic, retain) UITextView *downloadMessageStatus;
@property (nonatomic, retain) UIProgressView *downloadProgressView;
@end

View File

@@ -0,0 +1,213 @@
//
// WagicDownloadProgressView.m
// wagic
//
// Created by Michael Nguyen on 12/10/11.
// Copyright (c) 2011 __MyCompanyName__. All rights reserved.
//
#import "WagicDownloadProgressViewController.h"
#import "wagicAppDelegate.h"
#import "ZipArchive.h"
#import "ASIHTTPRequest.h"
#import "QuartzCore/QuartzCore.h"
@implementation WagicDownloadProgressViewController
@synthesize downloadProgressView;
@synthesize downloadMessageStatus;
//static NSString *kDownloadUrlPath = @"http://wololo.net/files/wagic/";
static NSString *kDownloadUrlPath = @"http://forevernow.net/wagic/";
static NSString *kDownloadFileName = @"core_017_iOS.zip";
- (void) unpackageResources
{
NSError *error = nil;
NSFileManager *fm = [NSFileManager defaultManager];
NSArray *paths = NSSearchPathForDirectoriesInDomains( NSDocumentDirectory,
NSUserDomainMask, YES);
NSString *userDocumentsDirectory = [paths objectAtIndex:0];
NSString *downloadFilePath = [[paths objectAtIndex: 0] stringByAppendingString: [NSString stringWithFormat: @"/%@", kDownloadFileName]];
ZipArchive *za = [[ZipArchive alloc] init];
if ([za UnzipOpenFile: downloadFilePath])
{
BOOL ret = [za UnzipFileTo: [NSString stringWithFormat: @"%@/Res/",userDocumentsDirectory] overWrite: YES];
if (ret == NO)
{
// some error occurred
}
[za UnzipCloseFile];
if (ret == YES)
{
// delete the archive
[fm removeItemAtPath: downloadFilePath error: &error];
if (error != nil)
{
NSLog(@"error occurred while trying to delete zip file! %@\n%@", downloadFilePath, [error localizedDescription] );
}
else
{
wagicAppDelegate *appDelegate = (wagicAppDelegate *)[[UIApplication sharedApplication] delegate];
NSNotificationCenter *dnc = [NSNotificationCenter defaultCenter];
[dnc postNotificationName:@"readyToStartGame" object: appDelegate];
}
}
}
[za release], za = nil;
}
- (id) init
{
self = [super init];
if (self) {
[self.view setFrame: CGRectMake(0, 0, 320, 480)];
[self.view setAutoresizingMask: UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight];
// Initialization code
downloadMessageStatus = [[UITextView alloc] initWithFrame: CGRectMake(0, 0, 320, 320)];
[self.downloadMessageStatus setBackgroundColor:[UIColor clearColor]];
[downloadMessageStatus setEditable: NO];
[self.view setBackgroundColor:[UIColor clearColor]];
[self.downloadMessageStatus setTextColor: [UIColor whiteColor]];
[self.downloadMessageStatus setTextAlignment: UITextAlignmentCenter];
self.downloadMessageStatus.clipsToBounds = YES;
self.downloadMessageStatus.layer.cornerRadius = 10.0f;
[self.downloadMessageStatus setAutoresizingMask: UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight ];
[self.downloadMessageStatus setFont: [UIFont systemFontOfSize: 20]];
[self.downloadMessageStatus setText: @"Please wait while the core files are being downloaded."];
downloadProgressView = [[UIProgressView alloc] initWithProgressViewStyle: UIProgressViewStyleDefault];
[self.downloadProgressView setFrame: CGRectMake(0, 0, 250, 50)];
[self.downloadProgressView setAutoresizingMask: UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight ];
NSArray *paths = NSSearchPathForDirectoriesInDomains( NSDocumentDirectory,
NSUserDomainMask, YES);
NSString *userResourceDirectory = [[paths objectAtIndex:0] stringByAppendingString: @"/Res"];
NSString *downloadFilePath = [[paths objectAtIndex: 0] stringByAppendingString: [NSString stringWithFormat: @"/%@", kDownloadFileName]];
// download the zip file but show a splash screen
NSURL *url = [NSURL URLWithString: [NSString stringWithFormat: @"%@/%@", kDownloadUrlPath, kDownloadFileName]];
__block ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setTemporaryFileDownloadPath: [NSString stringWithFormat: @"%@.tmp", userResourceDirectory]];
[request setDownloadDestinationPath: downloadFilePath];
[request setDownloadProgressDelegate: downloadProgressView];
[request setCompletionBlock:^{
[self unpackageResources];
}];
[request setFailedBlock:^{
NSError *error = [request error];
}];
[request startAsynchronous];
[self.view addSubview: downloadMessageStatus];
[self.view addSubview: downloadProgressView];
}
return self;
}
#pragma mark Application Lifecycle
- (void) didReceiveMemoryWarning
{
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Relinquish ownership any cached data, images, etc that aren't in use.
}
#pragma mark -
#pragma mark UIView Delegate
- (void) handleRotation: (UIInterfaceOrientation) interfaceOrientation
{
// position the activityIndicator
bool isPhone = (UI_USER_INTERFACE_IDIOM()) == UIUserInterfaceIdiomPhone;
bool isLandscapeOrientation = (UIInterfaceOrientationIsLandscape(interfaceOrientation));
if (isLandscapeOrientation)
{
CGFloat height = self.view.bounds.size.width;
[self.downloadProgressView setCenter: CGPointMake( height/2, 150)];
}
if (!isPhone)
{
CGFloat messageStatusHeight = [self.downloadMessageStatus.text sizeWithFont: [downloadMessageStatus font]].height;
CGFloat logoCenterPointX = isLandscapeOrientation ? 512 : 384;
[downloadProgressView setCenter: CGPointMake( logoCenterPointX, messageStatusHeight )];
}
}
- (void) viewDidAppear:(BOOL)animated
{
[self handleRotation: self.interfaceOrientation];
}
- (void) viewWillAppear:(BOOL)animated
{
[super viewWillAppear: animated];
[self handleRotation: self.interfaceOrientation];
}
- (void) viewDidLoad
{
[super viewDidLoad];
[self handleRotation: self.interfaceOrientation];
}
// Only allow auto rotation on iPads.
- (void) didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
{
[self handleRotation: self.interfaceOrientation];
}
- (void) willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval) duration
{
wagicAppDelegate *appDelegate = (wagicAppDelegate *)[[UIApplication sharedApplication] delegate];
[appDelegate rotateBackgroundImage: self.interfaceOrientation toInterfaceOrientation: toInterfaceOrientation];
}
- (BOOL) shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
bool isPhone = (UI_USER_INTERFACE_IDIOM()) == UIUserInterfaceIdiomPhone;
BOOL rotateDevice = !((interfaceOrientation == UIInterfaceOrientationPortraitUpsideDown)
|| (interfaceOrientation == UIInterfaceOrientationPortrait));
if (isPhone)
return rotateDevice;
return YES;
}
#pragma mark -
/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{
// Drawing code
}
*/
@end

View File

@@ -0,0 +1,50 @@
//
// ZipArchive.h
//
//
// Created by aish on 08-9-11.
// acsolu@gmail.com
// Copyright 2008 Inc. All rights reserved.
//
// History:
// 09-11-2008 version 1.0 release
// 10-18-2009 version 1.1 support password protected zip files
// 10-21-2009 version 1.2 fix date bug
#import <UIKit/UIKit.h>
#include "zip.h"
#include "unzip.h"
@protocol ZipArchiveDelegate <NSObject>
@optional
-(void) ErrorMessage:(NSString*) msg;
-(BOOL) OverWriteOperation:(NSString*) file;
-(void) UnzipProgress:(uLong)myCurrentFileIndex total:(uLong)myTotalFileCount;
@end
@interface ZipArchive : NSObject {
@private
zipFile _zipFile;
unzFile _unzFile;
NSString* _password;
uLong _totalFileCount;
id<ZipArchiveDelegate> _delegate;
}
@property (nonatomic, assign) id<ZipArchiveDelegate> delegate;
-(BOOL) CreateZipFile2:(NSString*) zipFile;
-(BOOL) CreateZipFile2:(NSString*) zipFile Password:(NSString*) password;
-(BOOL) addFileToZip:(NSString*) file newname:(NSString*) newname;
-(BOOL) CloseZipFile2;
-(BOOL) UnzipOpenFile:(NSString*) zipFile;
-(BOOL) UnzipOpenFile:(NSString*) zipFile Password:(NSString*) password;
-(BOOL) UnzipFileTo:(NSString*) path overWrite:(BOOL) overwrite;
-(NSMutableArray *) getZipFileContents;
-(BOOL) UnzipCloseFile;
@end

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View 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

View File

@@ -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

View 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

View File

@@ -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

View File

@@ -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

Some files were not shown because too many files have changed in this diff Show More