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