Added support for S3 list requests
Added more tests, bug fixes
Showing
11 changed files
with
614 additions
and
86 deletions
Classes/ASIS3BucketObject.h
0 → 100644
| 1 | +// | ||
| 2 | +// ASIS3BucketObject.h | ||
| 3 | +// Mac | ||
| 4 | +// | ||
| 5 | +// Created by Ben Copsey on 13/07/2009. | ||
| 6 | +// Copyright 2009 All-Seeing Interactive. All rights reserved. | ||
| 7 | +// | ||
| 8 | + | ||
| 9 | +#import <Foundation/Foundation.h> | ||
| 10 | +@class ASIS3Request; | ||
| 11 | + | ||
| 12 | +@interface ASIS3BucketObject : NSObject { | ||
| 13 | + | ||
| 14 | + // The bucket this object belongs to | ||
| 15 | + NSString *bucket; | ||
| 16 | + | ||
| 17 | + // The key (path) of this object in the bucket | ||
| 18 | + NSString *key; | ||
| 19 | + | ||
| 20 | + // When this object was last modified | ||
| 21 | + NSDate *lastModified; | ||
| 22 | + | ||
| 23 | + // The ETag for this object's content | ||
| 24 | + NSString *ETag; | ||
| 25 | + | ||
| 26 | + // The size in bytes of this object | ||
| 27 | + unsigned long long size; | ||
| 28 | + | ||
| 29 | + // Info about the owner | ||
| 30 | + NSString *ownerID; | ||
| 31 | + NSString *ownerName; | ||
| 32 | +} | ||
| 33 | + | ||
| 34 | ++ (id)objectWithBucket:(NSString *)bucket; | ||
| 35 | + | ||
| 36 | +// Returns a request that will fetch this object when run | ||
| 37 | +- (ASIS3Request *)GETRequest; | ||
| 38 | + | ||
| 39 | +// Returns a request that will replace this object with the contents of the file at filePath when run | ||
| 40 | +- (ASIS3Request *)PUTRequestWithFile:(NSString *)filePath; | ||
| 41 | + | ||
| 42 | +// Returns a request that will delete this object when run | ||
| 43 | +- (ASIS3Request *)DELETERequest; | ||
| 44 | + | ||
| 45 | +@property (retain) NSString *bucket; | ||
| 46 | +@property (retain) NSString *key; | ||
| 47 | +@property (retain) NSDate *lastModified; | ||
| 48 | +@property (retain) NSString *ETag; | ||
| 49 | +@property (assign) unsigned long long size; | ||
| 50 | +@property (retain) NSString *ownerID; | ||
| 51 | +@property (retain) NSString *ownerName; | ||
| 52 | +@end |
Classes/ASIS3BucketObject.m
0 → 100644
| 1 | +// | ||
| 2 | +// ASIS3BucketObject.m | ||
| 3 | +// Mac | ||
| 4 | +// | ||
| 5 | +// Created by Ben Copsey on 13/07/2009. | ||
| 6 | +// Copyright 2009 All-Seeing Interactive. All rights reserved. | ||
| 7 | +// | ||
| 8 | + | ||
| 9 | +#import "ASIS3BucketObject.h" | ||
| 10 | +#import "ASIS3Request.h" | ||
| 11 | + | ||
| 12 | +@implementation ASIS3BucketObject | ||
| 13 | + | ||
| 14 | ++ (id)objectWithBucket:(NSString *)bucket | ||
| 15 | +{ | ||
| 16 | + ASIS3BucketObject *object = [[[ASIS3BucketObject alloc] init] autorelease]; | ||
| 17 | + [object setBucket:bucket]; | ||
| 18 | + return object; | ||
| 19 | +} | ||
| 20 | + | ||
| 21 | +- (void)dealloc | ||
| 22 | +{ | ||
| 23 | + [key release]; | ||
| 24 | + [lastModified release]; | ||
| 25 | + [ETag release]; | ||
| 26 | + [ownerID release]; | ||
| 27 | + [ownerName release]; | ||
| 28 | + [super dealloc]; | ||
| 29 | +} | ||
| 30 | + | ||
| 31 | +- (ASIS3Request *)GETRequest | ||
| 32 | +{ | ||
| 33 | + return [ASIS3Request requestWithBucket:[self bucket] path:[NSString stringWithFormat:@"/%@",[self key]]]; | ||
| 34 | +} | ||
| 35 | + | ||
| 36 | +- (ASIS3Request *)PUTRequestWithFile:(NSString *)filePath | ||
| 37 | +{ | ||
| 38 | + return [ASIS3Request PUTRequestForFile:filePath withBucket:[self bucket] path:[NSString stringWithFormat:@"/%@",[self key]]]; | ||
| 39 | +} | ||
| 40 | + | ||
| 41 | +- (ASIS3Request *)DELETERequest | ||
| 42 | +{ | ||
| 43 | + ASIS3Request *request = [ASIS3Request requestWithBucket:[self bucket] path:[NSString stringWithFormat:@"/%@",[self key]]]; | ||
| 44 | + [request setRequestMethod:@"DELETE"]; | ||
| 45 | + return request; | ||
| 46 | +} | ||
| 47 | + | ||
| 48 | + | ||
| 49 | +- (NSString *)description | ||
| 50 | +{ | ||
| 51 | + return [NSString stringWithFormat:@"Key: %@ lastModified: %@ ETag: %@ size: %llu ownerID: %@ ownerName: %@",[self key],[self lastModified],[self ETag],[self size],[self ownerID],[self ownerName]]; | ||
| 52 | +} | ||
| 53 | + | ||
| 54 | +@synthesize bucket; | ||
| 55 | +@synthesize key; | ||
| 56 | +@synthesize lastModified; | ||
| 57 | +@synthesize ETag; | ||
| 58 | +@synthesize size; | ||
| 59 | +@synthesize ownerID; | ||
| 60 | +@synthesize ownerName; | ||
| 61 | +@end |
Classes/ASIS3ListRequest.h
0 → 100644
| 1 | +// | ||
| 2 | +// ASIS3ListRequest.h | ||
| 3 | +// Mac | ||
| 4 | +// | ||
| 5 | +// Created by Ben Copsey on 13/07/2009. | ||
| 6 | +// Copyright 2009 All-Seeing Interactive. All rights reserved. | ||
| 7 | +// | ||
| 8 | + | ||
| 9 | +#import <Foundation/Foundation.h> | ||
| 10 | +#import "ASIS3Request.h" | ||
| 11 | +@class ASIS3BucketObject; | ||
| 12 | + | ||
| 13 | +@interface ASIS3ListRequest : ASIS3Request { | ||
| 14 | + | ||
| 15 | + NSString *prefix; | ||
| 16 | + NSString *marker; | ||
| 17 | + int maxResultCount; | ||
| 18 | + NSString *delimiter; | ||
| 19 | + | ||
| 20 | + // Internally used while parsing the response | ||
| 21 | + NSString *currentContent; | ||
| 22 | + NSString *currentElement; | ||
| 23 | + ASIS3BucketObject *currentObject; | ||
| 24 | + NSMutableArray *objects; | ||
| 25 | + | ||
| 26 | + | ||
| 27 | +} | ||
| 28 | +// Create a list request | ||
| 29 | ++ (id)listRequestWithBucket:(NSString *)bucket; | ||
| 30 | + | ||
| 31 | + | ||
| 32 | +// Returns an array of ASIS3BucketObjects created from the XML response | ||
| 33 | +- (NSArray *)bucketObjects; | ||
| 34 | + | ||
| 35 | +//Builds a query string out of the list parameters we supplied | ||
| 36 | +- (void)createQueryString; | ||
| 37 | + | ||
| 38 | +@property (retain) NSString *prefix; | ||
| 39 | +@property (retain) NSString *marker; | ||
| 40 | +@property (assign) int maxResultCount; | ||
| 41 | +@property (retain) NSString *delimiter; | ||
| 42 | +@end |
Classes/ASIS3ListRequest.m
0 → 100644
| 1 | +// | ||
| 2 | +// ASIS3ListRequest.m | ||
| 3 | +// Mac | ||
| 4 | +// | ||
| 5 | +// Created by Ben Copsey on 13/07/2009. | ||
| 6 | +// Copyright 2009 All-Seeing Interactive. All rights reserved. | ||
| 7 | +// | ||
| 8 | +#import "ASIS3ListRequest.h" | ||
| 9 | +#import "ASIS3BucketObject.h" | ||
| 10 | + | ||
| 11 | + | ||
| 12 | +static NSDateFormatter *dateFormatter = nil; | ||
| 13 | + | ||
| 14 | +// Private stuff | ||
| 15 | +@interface ASIS3ListRequest () | ||
| 16 | + @property (retain,setter=setURL:) NSURL *url; | ||
| 17 | + @property (retain, nonatomic) NSString *currentContent; | ||
| 18 | + @property (retain, nonatomic) NSString *currentElement; | ||
| 19 | + @property (retain, nonatomic) ASIS3BucketObject *currentObject; | ||
| 20 | + @property (retain, nonatomic) NSMutableArray *objects; | ||
| 21 | +@end | ||
| 22 | + | ||
| 23 | +@implementation ASIS3ListRequest | ||
| 24 | + | ||
| 25 | ++ (void)initialize | ||
| 26 | +{ | ||
| 27 | + dateFormatter = [[NSDateFormatter alloc] init]; | ||
| 28 | + [dateFormatter setTimeZone:[NSTimeZone timeZoneWithAbbreviation:@"UTC"]]; | ||
| 29 | + [dateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss'.000Z'"]; | ||
| 30 | +} | ||
| 31 | + | ||
| 32 | ++ (id)listRequestWithBucket:(NSString *)bucket | ||
| 33 | +{ | ||
| 34 | + ASIS3ListRequest *request = [[[ASIS3ListRequest alloc] initWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"http://%@.s3.amazonaws.com",bucket]]] autorelease]; | ||
| 35 | + [request setBucket:bucket]; | ||
| 36 | + return request; | ||
| 37 | +} | ||
| 38 | + | ||
| 39 | +- (void)dealloc | ||
| 40 | +{ | ||
| 41 | + [currentElement release]; | ||
| 42 | + [currentObject release]; | ||
| 43 | + [objects release]; | ||
| 44 | + [super dealloc]; | ||
| 45 | +} | ||
| 46 | + | ||
| 47 | +- (void)createQueryString | ||
| 48 | +{ | ||
| 49 | + NSMutableArray *queryParts = [[[NSMutableArray alloc] init] autorelease]; | ||
| 50 | + if ([self prefix]) { | ||
| 51 | + [queryParts addObject:[NSString stringWithFormat:@"prefix=%@",[[self prefix] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]]; | ||
| 52 | + } | ||
| 53 | + if ([self marker]) { | ||
| 54 | + [queryParts addObject:[NSString stringWithFormat:@"marker=%@",[[self marker] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]]; | ||
| 55 | + } | ||
| 56 | + if ([self delimiter]) { | ||
| 57 | + [queryParts addObject:[NSString stringWithFormat:@"delimiter=%@",[[self delimiter] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]]; | ||
| 58 | + } | ||
| 59 | + if ([self maxResultCount] > 0) { | ||
| 60 | + [queryParts addObject:[NSString stringWithFormat:@"delimiter=%hi",[self maxResultCount]]]; | ||
| 61 | + } | ||
| 62 | + if ([queryParts count]) { | ||
| 63 | + [self setURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@?%@",[[self url] absoluteString],[queryParts componentsJoinedByString:@"&"]]]]; | ||
| 64 | + } | ||
| 65 | +} | ||
| 66 | + | ||
| 67 | +- (void)main | ||
| 68 | +{ | ||
| 69 | + [self createQueryString]; | ||
| 70 | + [super main]; | ||
| 71 | +} | ||
| 72 | + | ||
| 73 | +- (NSArray *)bucketObjects | ||
| 74 | +{ | ||
| 75 | + if ([self objects]) { | ||
| 76 | + return [self objects]; | ||
| 77 | + } | ||
| 78 | + [self setObjects:[[[NSMutableArray alloc] init] autorelease]]; | ||
| 79 | + NSXMLParser *parser = [[[NSXMLParser alloc] initWithData:[self responseData]] autorelease]; | ||
| 80 | + [parser setDelegate:self]; | ||
| 81 | + [parser setShouldProcessNamespaces:NO]; | ||
| 82 | + [parser setShouldReportNamespacePrefixes:NO]; | ||
| 83 | + [parser setShouldResolveExternalEntities:NO]; | ||
| 84 | + [parser parse]; | ||
| 85 | + return [self objects]; | ||
| 86 | +} | ||
| 87 | + | ||
| 88 | +- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict | ||
| 89 | +{ | ||
| 90 | + [self setCurrentElement:elementName]; | ||
| 91 | + | ||
| 92 | + if ([elementName isEqualToString:@"Contents"]) { | ||
| 93 | + [self setCurrentObject:[ASIS3BucketObject objectWithBucket:[self bucket]]]; | ||
| 94 | + } | ||
| 95 | + [self setCurrentContent:@""]; | ||
| 96 | +} | ||
| 97 | + | ||
| 98 | +- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName | ||
| 99 | +{ | ||
| 100 | + if ([elementName isEqualToString:@"Contents"]) { | ||
| 101 | + [objects addObject:currentObject]; | ||
| 102 | + [self setCurrentObject:nil]; | ||
| 103 | + } else if ([elementName isEqualToString:@"Key"]) { | ||
| 104 | + [[self currentObject] setKey:[self currentContent]]; | ||
| 105 | + } else if ([elementName isEqualToString:@"LastModified"]) { | ||
| 106 | + [[self currentObject] setLastModified:[dateFormatter dateFromString:[self currentContent]]]; | ||
| 107 | + } else if ([elementName isEqualToString:@"ETag"]) { | ||
| 108 | + [[self currentObject] setETag:[self currentContent]]; | ||
| 109 | + } else if ([elementName isEqualToString:@"Size"]) { | ||
| 110 | + [[self currentObject] setSize:(unsigned long long)[[self currentContent] longLongValue]]; | ||
| 111 | + } else if ([elementName isEqualToString:@"ID"]) { | ||
| 112 | + [[self currentObject] setOwnerID:[self currentContent]]; | ||
| 113 | + } else if ([elementName isEqualToString:@"DisplayName"]) { | ||
| 114 | + [[self currentObject] setOwnerName:[self currentContent]]; | ||
| 115 | + } | ||
| 116 | +} | ||
| 117 | + | ||
| 118 | +- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string | ||
| 119 | +{ | ||
| 120 | + [self setCurrentContent:[[self currentContent] stringByAppendingString:string]]; | ||
| 121 | +} | ||
| 122 | + | ||
| 123 | +- (void)parserDidEndDocument:(NSXMLParser *)parser | ||
| 124 | +{ | ||
| 125 | +} | ||
| 126 | + | ||
| 127 | + | ||
| 128 | +@synthesize currentContent; | ||
| 129 | +@synthesize currentElement; | ||
| 130 | +@synthesize currentObject; | ||
| 131 | +@synthesize objects; | ||
| 132 | +@synthesize prefix; | ||
| 133 | +@synthesize marker; | ||
| 134 | +@synthesize maxResultCount; | ||
| 135 | +@synthesize delimiter; | ||
| 136 | +@synthesize url; | ||
| 137 | +@end |
| @@ -4,8 +4,7 @@ | @@ -4,8 +4,7 @@ | ||
| 4 | // Created by Ben Copsey on 30/06/2009. | 4 | // Created by Ben Copsey on 30/06/2009. |
| 5 | // Copyright 2009 All-Seeing Interactive. All rights reserved. | 5 | // Copyright 2009 All-Seeing Interactive. All rights reserved. |
| 6 | // | 6 | // |
| 7 | -// A (basic) class for accessing data stored on Amazon's Simple Storage Service (http://aws.amazon.com/s3/) | 7 | +// A (basic) class for accessing data stored on Amazon's Simple Storage Service (http://aws.amazon.com/s3/) using the REST API |
| 8 | -// It uses the REST API, with canned access policies rather than full support for ACLs (though if you build/parse them yourself you can still use ACLs) | ||
| 9 | 8 | ||
| 10 | #import <Foundation/Foundation.h> | 9 | #import <Foundation/Foundation.h> |
| 11 | #import "ASIHTTPRequest.h" | 10 | #import "ASIHTTPRequest.h" |
| @@ -16,6 +15,12 @@ extern NSString *const ASIS3AccessPolicyPublicRead; | @@ -16,6 +15,12 @@ extern NSString *const ASIS3AccessPolicyPublicRead; | ||
| 16 | extern NSString *const ASIS3AccessPolicyPublicReadWrote; | 15 | extern NSString *const ASIS3AccessPolicyPublicReadWrote; |
| 17 | extern NSString *const ASIS3AccessPolicyAuthenticatedRead; | 16 | extern NSString *const ASIS3AccessPolicyAuthenticatedRead; |
| 18 | 17 | ||
| 18 | +typedef enum _ASIS3ErrorType { | ||
| 19 | + ASIS3ResponseParsingFailedType = 1, | ||
| 20 | + ASIS3ResponseErrorType = 2 | ||
| 21 | + | ||
| 22 | +} ASIS3ErrorType; | ||
| 23 | + | ||
| 19 | @interface ASIS3Request : ASIHTTPRequest { | 24 | @interface ASIS3Request : ASIHTTPRequest { |
| 20 | 25 | ||
| 21 | // Your S3 access key. Set it on the request, or set it globally using [ASIS3Request setSharedAccessKey:] | 26 | // Your S3 access key. Set it on the request, or set it globally using [ASIS3Request setSharedAccessKey:] |
| @@ -38,7 +43,19 @@ extern NSString *const ASIS3AccessPolicyAuthenticatedRead; | @@ -38,7 +43,19 @@ extern NSString *const ASIS3AccessPolicyAuthenticatedRead; | ||
| 38 | // Will be set to 'application/octet-stream' otherwise in iPhone apps, or autodetected on Mac OS X | 43 | // Will be set to 'application/octet-stream' otherwise in iPhone apps, or autodetected on Mac OS X |
| 39 | NSString *mimeType; | 44 | NSString *mimeType; |
| 40 | 45 | ||
| 46 | + // The access policy to use when PUTting a file (see the string constants at the top of this header) | ||
| 41 | NSString *accessPolicy; | 47 | NSString *accessPolicy; |
| 48 | + | ||
| 49 | + // Options for filtering list requests | ||
| 50 | + // See http://docs.amazonwebservices.com/AmazonS3/2006-03-01/index.html?RESTBucketGET.html | ||
| 51 | + NSString *listPrefix; | ||
| 52 | + NSString *listMarker; | ||
| 53 | + int listMaxResults; | ||
| 54 | + NSString *listDelimiter; | ||
| 55 | + | ||
| 56 | + // Internally used while parsing errors | ||
| 57 | + NSString *currentErrorString; | ||
| 58 | + | ||
| 42 | } | 59 | } |
| 43 | 60 | ||
| 44 | #pragma mark Constructors | 61 | #pragma mark Constructors |
| @@ -49,9 +66,6 @@ extern NSString *const ASIS3AccessPolicyAuthenticatedRead; | @@ -49,9 +66,6 @@ extern NSString *const ASIS3AccessPolicyAuthenticatedRead; | ||
| 49 | // Create a PUT request using the file at filePath as the body | 66 | // Create a PUT request using the file at filePath as the body |
| 50 | + (id)PUTRequestForFile:(NSString *)filePath withBucket:(NSString *)bucket path:(NSString *)path; | 67 | + (id)PUTRequestForFile:(NSString *)filePath withBucket:(NSString *)bucket path:(NSString *)path; |
| 51 | 68 | ||
| 52 | -// Create a list request | ||
| 53 | -+ (id)listRequestWithBucket:(NSString *)bucket prefix:(NSString *)prefix maxResults:(int)maxResults marker:(NSString *)marker; | ||
| 54 | - | ||
| 55 | // Generates the request headers S3 needs | 69 | // Generates the request headers S3 needs |
| 56 | // Automatically called before the request begins in startRequest | 70 | // Automatically called before the request begins in startRequest |
| 57 | - (void)generateS3Headers; | 71 | - (void)generateS3Headers; |
| @@ -59,6 +73,8 @@ extern NSString *const ASIS3AccessPolicyAuthenticatedRead; | @@ -59,6 +73,8 @@ extern NSString *const ASIS3AccessPolicyAuthenticatedRead; | ||
| 59 | // Uses the supplied date to create a Date header string | 73 | // Uses the supplied date to create a Date header string |
| 60 | - (void)setDate:(NSDate *)date; | 74 | - (void)setDate:(NSDate *)date; |
| 61 | 75 | ||
| 76 | +#pragma mark Helper functions | ||
| 77 | + | ||
| 62 | // Only works on Mac OS, will always return 'application/octet-stream' on iPhone | 78 | // Only works on Mac OS, will always return 'application/octet-stream' on iPhone |
| 63 | + (NSString *)mimeTypeForFileAtPath:(NSString *)path; | 79 | + (NSString *)mimeTypeForFileAtPath:(NSString *)path; |
| 64 | 80 | ||
| @@ -77,5 +93,6 @@ extern NSString *const ASIS3AccessPolicyAuthenticatedRead; | @@ -77,5 +93,6 @@ extern NSString *const ASIS3AccessPolicyAuthenticatedRead; | ||
| 77 | @property (retain) NSString *mimeType; | 93 | @property (retain) NSString *mimeType; |
| 78 | @property (retain) NSString *accessKey; | 94 | @property (retain) NSString *accessKey; |
| 79 | @property (retain) NSString *secretAccessKey; | 95 | @property (retain) NSString *secretAccessKey; |
| 80 | -@property (assign) NSString *accessPolicy; | 96 | +@property (retain) NSString *accessPolicy; |
| 97 | + | ||
| 81 | @end | 98 | @end |
| @@ -17,9 +17,11 @@ static NSString *sharedAccessKey = nil; | @@ -17,9 +17,11 @@ static NSString *sharedAccessKey = nil; | ||
| 17 | static NSString *sharedSecretAccessKey = nil; | 17 | static NSString *sharedSecretAccessKey = nil; |
| 18 | 18 | ||
| 19 | // Private stuff | 19 | // Private stuff |
| 20 | -@interface ASIHTTPRequest () | 20 | +@interface ASIS3Request () |
| 21 | + - (void)parseError; | ||
| 21 | + (NSData *)HMACSHA1withKey:(NSString *)key forString:(NSString *)string; | 22 | + (NSData *)HMACSHA1withKey:(NSString *)key forString:(NSString *)string; |
| 22 | + (NSString *)base64forData:(NSData *)theData; | 23 | + (NSString *)base64forData:(NSData *)theData; |
| 24 | + @property (retain, nonatomic) NSString *currentErrorString; | ||
| 23 | @end | 25 | @end |
| 24 | 26 | ||
| 25 | @implementation ASIS3Request | 27 | @implementation ASIS3Request |
| @@ -37,7 +39,7 @@ static NSString *sharedSecretAccessKey = nil; | @@ -37,7 +39,7 @@ static NSString *sharedSecretAccessKey = nil; | ||
| 37 | 39 | ||
| 38 | + (id)requestWithBucket:(NSString *)bucket path:(NSString *)path | 40 | + (id)requestWithBucket:(NSString *)bucket path:(NSString *)path |
| 39 | { | 41 | { |
| 40 | - ASIS3Request *request = [[[ASIS3Request alloc] initWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"http://s3.amazonaws.com/%@/%@",bucket,path]]] autorelease]; | 42 | + ASIS3Request *request = [[[ASIS3Request alloc] initWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"http://%@.s3.amazonaws.com%@",bucket,path]]] autorelease]; |
| 41 | [request setBucket:bucket]; | 43 | [request setBucket:bucket]; |
| 42 | [request setPath:path]; | 44 | [request setPath:path]; |
| 43 | return request; | 45 | return request; |
| @@ -53,51 +55,16 @@ static NSString *sharedSecretAccessKey = nil; | @@ -53,51 +55,16 @@ static NSString *sharedSecretAccessKey = nil; | ||
| 53 | return request; | 55 | return request; |
| 54 | } | 56 | } |
| 55 | 57 | ||
| 56 | - | ||
| 57 | -+ (id)listRequestWithBucket:(NSString *)bucket prefix:(NSString *)prefix maxResults:(int)maxResults marker:(NSString *)marker | ||
| 58 | -{ | ||
| 59 | - ASIS3Request *request = [[[ASIS3Request alloc] initWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"http://s3.amazonaws.com/%@?prefix=/%@&max-keys=%hi&marker=%@",bucket,prefix,maxResults,marker]]] autorelease]; | ||
| 60 | - [request setBucket:bucket]; | ||
| 61 | - return request; | ||
| 62 | -} | ||
| 63 | - | ||
| 64 | -+ (NSString *)mimeTypeForFileAtPath:(NSString *)path | ||
| 65 | -{ | ||
| 66 | -// NSTask does seem to exist in the 2.2.1 SDK, though it's not in the 3.0 SDK. It's probably best if we just use a generic mime type on iPhone all the time. | ||
| 67 | -#if TARGET_OS_IPHONE | ||
| 68 | - return @"application/octet-stream"; | ||
| 69 | - | ||
| 70 | -// Grab the mime type using an NSTask to run the 'file' program, with the Mac OS-specific parameters to grab the mime type | ||
| 71 | -// Perhaps there is a better way to do this? | ||
| 72 | -#else | ||
| 73 | - NSTask *task = [[NSTask alloc] init]; | ||
| 74 | - [task setLaunchPath: @"/usr/bin/file"]; | ||
| 75 | - [task setArguments:[NSMutableArray arrayWithObjects:@"-Ib",path,nil]]; | ||
| 76 | - | ||
| 77 | - NSPipe *outputPipe = [NSPipe pipe]; | ||
| 78 | - [task setStandardOutput:outputPipe]; | ||
| 79 | - | ||
| 80 | - NSFileHandle *file = [outputPipe fileHandleForReading]; | ||
| 81 | - | ||
| 82 | - [task launch]; | ||
| 83 | - [task waitUntilExit]; | ||
| 84 | - | ||
| 85 | - if ([task terminationStatus] != 0) { | ||
| 86 | - return @"application/octet-stream"; | ||
| 87 | - } | ||
| 88 | - | ||
| 89 | - NSString *mimeTypeString = [[[NSString alloc] initWithData:[file readDataToEndOfFile] encoding: NSUTF8StringEncoding] autorelease]; | ||
| 90 | - return [[mimeTypeString componentsSeparatedByString:@";"] objectAtIndex:0]; | ||
| 91 | -#endif | ||
| 92 | -} | ||
| 93 | - | ||
| 94 | - (void)setDate:(NSDate *)date | 58 | - (void)setDate:(NSDate *)date |
| 95 | { | 59 | { |
| 96 | NSDateFormatter *dateFormatter = [[[NSDateFormatter alloc] init] autorelease]; | 60 | NSDateFormatter *dateFormatter = [[[NSDateFormatter alloc] init] autorelease]; |
| 61 | + // Prevent problems with dates generated by other locales (tip from: http://rel.me/t/date/) | ||
| 62 | + [dateFormatter setLocale:[[[NSLocale alloc] initWithLocaleIdentifier:@"en_US"] autorelease]]; | ||
| 97 | [dateFormatter setDateFormat:@"EEE, d MMM yyyy HH:mm:ss Z"]; | 63 | [dateFormatter setDateFormat:@"EEE, d MMM yyyy HH:mm:ss Z"]; |
| 98 | [self setDateString:[dateFormatter stringFromDate:date]]; | 64 | [self setDateString:[dateFormatter stringFromDate:date]]; |
| 99 | } | 65 | } |
| 100 | 66 | ||
| 67 | + | ||
| 101 | - (void)generateS3Headers | 68 | - (void)generateS3Headers |
| 102 | { | 69 | { |
| 103 | // If an access key / secret access keyu haven't been set for this request, let's use the shared keys | 70 | // If an access key / secret access keyu haven't been set for this request, let's use the shared keys |
| @@ -105,7 +72,7 @@ static NSString *sharedSecretAccessKey = nil; | @@ -105,7 +72,7 @@ static NSString *sharedSecretAccessKey = nil; | ||
| 105 | [self setAccessKey:[ASIS3Request sharedAccessKey]]; | 72 | [self setAccessKey:[ASIS3Request sharedAccessKey]]; |
| 106 | } | 73 | } |
| 107 | if (![self secretAccessKey]) { | 74 | if (![self secretAccessKey]) { |
| 108 | - [self setAccessKey:[ASIS3Request sharedSecretAccessKey]]; | 75 | + [self setSecretAccessKey:[ASIS3Request sharedSecretAccessKey]]; |
| 109 | } | 76 | } |
| 110 | // If a date string hasn't been set, we'll create one from the current time | 77 | // If a date string hasn't been set, we'll create one from the current time |
| 111 | if (![self dateString]) { | 78 | if (![self dateString]) { |
| @@ -115,10 +82,10 @@ static NSString *sharedSecretAccessKey = nil; | @@ -115,10 +82,10 @@ static NSString *sharedSecretAccessKey = nil; | ||
| 115 | 82 | ||
| 116 | // Ensure our formatted string doesn't use '(null)' for the empty path | 83 | // Ensure our formatted string doesn't use '(null)' for the empty path |
| 117 | if (![self path]) { | 84 | if (![self path]) { |
| 118 | - [self setPath:@""]; | 85 | + [self setPath:@"/"]; |
| 119 | } | 86 | } |
| 120 | 87 | ||
| 121 | - NSString *canonicalizedResource = [NSString stringWithFormat:@"/%@/%@",[self bucket],[self path]]; | 88 | + NSString *canonicalizedResource = [NSString stringWithFormat:@"/%@%@",[self bucket],[self path]]; |
| 122 | 89 | ||
| 123 | // Add a header for the access policy if one was set, otherwise we won't add one (and S3 will default to private) | 90 | // Add a header for the access policy if one was set, otherwise we won't add one (and S3 will default to private) |
| 124 | NSString *canonicalizedAmzHeaders = @""; | 91 | NSString *canonicalizedAmzHeaders = @""; |
| @@ -146,6 +113,59 @@ static NSString *sharedSecretAccessKey = nil; | @@ -146,6 +113,59 @@ static NSString *sharedSecretAccessKey = nil; | ||
| 146 | [super main]; | 113 | [super main]; |
| 147 | } | 114 | } |
| 148 | 115 | ||
| 116 | +- (void)requestFinished | ||
| 117 | +{ | ||
| 118 | + if ([self responseStatusCode] < 207) { | ||
| 119 | + [super requestFinished]; | ||
| 120 | + return; | ||
| 121 | + } | ||
| 122 | + [self parseError]; | ||
| 123 | +} | ||
| 124 | + | ||
| 125 | +#pragma mark Error XML parsing | ||
| 126 | + | ||
| 127 | +- (void)parseError | ||
| 128 | +{ | ||
| 129 | + NSXMLParser *parser = [[[NSXMLParser alloc] initWithData:[self responseData]] autorelease]; | ||
| 130 | + [parser setDelegate:self]; | ||
| 131 | + [parser setShouldProcessNamespaces:NO]; | ||
| 132 | + [parser setShouldReportNamespacePrefixes:NO]; | ||
| 133 | + [parser setShouldResolveExternalEntities:NO]; | ||
| 134 | + [parser parse]; | ||
| 135 | + | ||
| 136 | +} | ||
| 137 | + | ||
| 138 | +- (void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError | ||
| 139 | +{ | ||
| 140 | + [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIS3ResponseParsingFailedType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Parsing the resposnse failed",NSLocalizedDescriptionKey,parseError,NSUnderlyingErrorKey,nil]]]; | ||
| 141 | +} | ||
| 142 | + | ||
| 143 | +- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict | ||
| 144 | +{ | ||
| 145 | + [self setCurrentErrorString:@""]; | ||
| 146 | +} | ||
| 147 | + | ||
| 148 | +- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName | ||
| 149 | +{ | ||
| 150 | + if ([elementName isEqualToString:@"Message"]) { | ||
| 151 | + [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIS3ResponseErrorType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[self currentErrorString],NSLocalizedDescriptionKey,nil]]]; | ||
| 152 | + } | ||
| 153 | +} | ||
| 154 | + | ||
| 155 | +- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string | ||
| 156 | +{ | ||
| 157 | + [self setCurrentErrorString:[[self currentErrorString] stringByAppendingString:string]]; | ||
| 158 | +} | ||
| 159 | + | ||
| 160 | +- (void)parserDidEndDocument:(NSXMLParser *)parser | ||
| 161 | +{ | ||
| 162 | + // We've got to the end of the XML error, without encountering a <Message></Message>, I don't think this should happen, but anyway | ||
| 163 | + if (![self error]) { | ||
| 164 | + [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIS3ResponseErrorType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"An unspecified S3 error ocurred",NSLocalizedDescriptionKey,nil]]]; | ||
| 165 | + } | ||
| 166 | +} | ||
| 167 | + | ||
| 168 | + | ||
| 149 | #pragma mark Shared access keys | 169 | #pragma mark Shared access keys |
| 150 | 170 | ||
| 151 | + (NSString *)sharedAccessKey | 171 | + (NSString *)sharedAccessKey |
| @@ -170,6 +190,38 @@ static NSString *sharedSecretAccessKey = nil; | @@ -170,6 +190,38 @@ static NSString *sharedSecretAccessKey = nil; | ||
| 170 | sharedSecretAccessKey = [newAccessKey retain]; | 190 | sharedSecretAccessKey = [newAccessKey retain]; |
| 171 | } | 191 | } |
| 172 | 192 | ||
| 193 | +#pragma mark Helper functions | ||
| 194 | + | ||
| 195 | ++ (NSString *)mimeTypeForFileAtPath:(NSString *)path | ||
| 196 | +{ | ||
| 197 | + // NSTask does seem to exist in the 2.2.1 SDK, though it's not in the 3.0 SDK. It's probably best if we just use a generic mime type on iPhone all the time. | ||
| 198 | +#if TARGET_OS_IPHONE | ||
| 199 | + return @"application/octet-stream"; | ||
| 200 | + | ||
| 201 | + // Grab the mime type using an NSTask to run the 'file' program, with the Mac OS-specific parameters to grab the mime type | ||
| 202 | + // Perhaps there is a better way to do this? | ||
| 203 | +#else | ||
| 204 | + NSTask *task = [[[NSTask alloc] init] autorelease]; | ||
| 205 | + [task setLaunchPath: @"/usr/bin/file"]; | ||
| 206 | + [task setArguments:[NSMutableArray arrayWithObjects:@"-Ib",path,nil]]; | ||
| 207 | + | ||
| 208 | + NSPipe *outputPipe = [NSPipe pipe]; | ||
| 209 | + [task setStandardOutput:outputPipe]; | ||
| 210 | + | ||
| 211 | + NSFileHandle *file = [outputPipe fileHandleForReading]; | ||
| 212 | + | ||
| 213 | + [task launch]; | ||
| 214 | + [task waitUntilExit]; | ||
| 215 | + | ||
| 216 | + if ([task terminationStatus] != 0) { | ||
| 217 | + return @"application/octet-stream"; | ||
| 218 | + } | ||
| 219 | + | ||
| 220 | + NSString *mimeTypeString = [[[[NSString alloc] initWithData:[file readDataToEndOfFile] encoding: NSUTF8StringEncoding] autorelease] stringByReplacingOccurrencesOfString:@"\n" withString:@""]; | ||
| 221 | + return [[mimeTypeString componentsSeparatedByString:@";"] objectAtIndex:0]; | ||
| 222 | +#endif | ||
| 223 | +} | ||
| 224 | + | ||
| 173 | 225 | ||
| 174 | #pragma mark S3 Authentication helpers | 226 | #pragma mark S3 Authentication helpers |
| 175 | 227 | ||
| @@ -231,4 +283,6 @@ static NSString *sharedSecretAccessKey = nil; | @@ -231,4 +283,6 @@ static NSString *sharedSecretAccessKey = nil; | ||
| 231 | @synthesize accessKey; | 283 | @synthesize accessKey; |
| 232 | @synthesize secretAccessKey; | 284 | @synthesize secretAccessKey; |
| 233 | @synthesize accessPolicy; | 285 | @synthesize accessPolicy; |
| 286 | +@synthesize currentErrorString; | ||
| 287 | + | ||
| 234 | @end | 288 | @end |
| @@ -663,7 +663,7 @@ | @@ -663,7 +663,7 @@ | ||
| 663 | { | 663 | { |
| 664 | ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:@"http://allseeing-i.com/ASIHTTPRequest/tests/redirect_to_new_domain"]]; | 664 | ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:@"http://allseeing-i.com/ASIHTTPRequest/tests/redirect_to_new_domain"]]; |
| 665 | [request start]; | 665 | [request start]; |
| 666 | - BOOL success = [[[request url] absoluteString] isEqualTo:@"http://www.apple.com/"]; | 666 | + BOOL success = [[[request url] absoluteString] isEqualToString:@"http://www.apple.com/"]; |
| 667 | GHAssertTrue(success,@"Failed to redirect to a different domain"); | 667 | GHAssertTrue(success,@"Failed to redirect to a different domain"); |
| 668 | } | 668 | } |
| 669 | 669 |
| @@ -13,10 +13,11 @@ | @@ -13,10 +13,11 @@ | ||
| 13 | #endif | 13 | #endif |
| 14 | 14 | ||
| 15 | @interface ASIS3RequestTests : GHTestCase { | 15 | @interface ASIS3RequestTests : GHTestCase { |
| 16 | - | ||
| 17 | } | 16 | } |
| 18 | 17 | ||
| 19 | - (void)testAuthenticationHeaderGeneration; | 18 | - (void)testAuthenticationHeaderGeneration; |
| 20 | -//- (void)testREST; | 19 | +- (void)testREST; |
| 20 | +- (void)testFailure; | ||
| 21 | +- (void)testListRequest; | ||
| 21 | 22 | ||
| 22 | @end | 23 | @end |
| @@ -7,62 +7,68 @@ | @@ -7,62 +7,68 @@ | ||
| 7 | // | 7 | // |
| 8 | 8 | ||
| 9 | #import "ASIS3RequestTests.h" | 9 | #import "ASIS3RequestTests.h" |
| 10 | -#import "ASIS3Request.h" | 10 | +#import "ASIS3ListRequest.h" |
| 11 | +#import "ASINetworkQueue.h" | ||
| 12 | +#import "ASIS3BucketObject.h" | ||
| 11 | 13 | ||
| 12 | @implementation ASIS3RequestTests | 14 | @implementation ASIS3RequestTests |
| 13 | 15 | ||
| 16 | +// Fill in these to run the tests that actually connect and manipulate objects on S3 | ||
| 17 | +static NSString *secretAccessKey = @""; | ||
| 18 | +static NSString *accessKey = @""; | ||
| 19 | +static NSString *bucket = @""; | ||
| 14 | 20 | ||
| 21 | +// All these tests are based on Amazon's examples at: http://docs.amazonwebservices.com/AmazonS3/2006-03-01/ | ||
| 15 | - (void)testAuthenticationHeaderGeneration | 22 | - (void)testAuthenticationHeaderGeneration |
| 16 | { | 23 | { |
| 17 | - // All these tests are based on Amazon's examples at: http://docs.amazonwebservices.com/AmazonS3/2006-03-01/ | 24 | + NSString *exampleSecretAccessKey = @"uV3F3YluFJax1cknvbcGwgjvx4QpvB+leU8dUj2o"; |
| 18 | - | 25 | + NSString *exampleAccessKey = @"0PN5J17HBGZHT7JJ3X82"; |
| 19 | - NSString *secretAccessKey = @"uV3F3YluFJax1cknvbcGwgjvx4QpvB+leU8dUj2o"; | ||
| 20 | - NSString *accessKey = @"0PN5J17HBGZHT7JJ3X82"; | ||
| 21 | NSString *bucket = @"johnsmith"; | 26 | NSString *bucket = @"johnsmith"; |
| 22 | 27 | ||
| 23 | - | ||
| 24 | // Test GET | 28 | // Test GET |
| 25 | - NSString *path = @"photos/puppy.jpg"; | 29 | + NSString *path = @"/photos/puppy.jpg"; |
| 26 | NSString *dateString = @"Tue, 27 Mar 2007 19:36:42 +0000"; | 30 | NSString *dateString = @"Tue, 27 Mar 2007 19:36:42 +0000"; |
| 27 | ASIS3Request *request = [ASIS3Request requestWithBucket:bucket path:path]; | 31 | ASIS3Request *request = [ASIS3Request requestWithBucket:bucket path:path]; |
| 28 | [request setDateString:dateString]; | 32 | [request setDateString:dateString]; |
| 29 | - [request setSecretAccessKey:secretAccessKey]; | 33 | + [request setSecretAccessKey:exampleSecretAccessKey]; |
| 30 | - [request setAccessKey:accessKey]; | 34 | + [request setAccessKey:exampleAccessKey]; |
| 31 | [request generateS3Headers]; | 35 | [request generateS3Headers]; |
| 32 | BOOL success = [[[request requestHeaders] valueForKey:@"Authorization"] isEqualToString:@"AWS 0PN5J17HBGZHT7JJ3X82:xXjDGYUmKxnwqr5KXNPGldn5LbA="]; | 36 | BOOL success = [[[request requestHeaders] valueForKey:@"Authorization"] isEqualToString:@"AWS 0PN5J17HBGZHT7JJ3X82:xXjDGYUmKxnwqr5KXNPGldn5LbA="]; |
| 33 | GHAssertTrue(success,@"Failed to generate the correct authorisation header for a GET request"); | 37 | GHAssertTrue(success,@"Failed to generate the correct authorisation header for a GET request"); |
| 34 | 38 | ||
| 35 | // Test PUT | 39 | // Test PUT |
| 36 | - path = @"photos/puppy.jpg"; | 40 | + path = @"/photos/puppy.jpg"; |
| 37 | dateString = @"Tue, 27 Mar 2007 21:15:45 +0000"; | 41 | dateString = @"Tue, 27 Mar 2007 21:15:45 +0000"; |
| 38 | request = [ASIS3Request requestWithBucket:bucket path:path]; | 42 | request = [ASIS3Request requestWithBucket:bucket path:path]; |
| 39 | [request setRequestMethod:@"PUT"]; | 43 | [request setRequestMethod:@"PUT"]; |
| 40 | [request setMimeType:@"image/jpeg"]; | 44 | [request setMimeType:@"image/jpeg"]; |
| 41 | [request setDateString:dateString]; | 45 | [request setDateString:dateString]; |
| 42 | - [request setSecretAccessKey:secretAccessKey]; | 46 | + [request setSecretAccessKey:exampleSecretAccessKey]; |
| 43 | - [request setAccessKey:accessKey]; | 47 | + [request setAccessKey:exampleAccessKey]; |
| 44 | [request generateS3Headers]; | 48 | [request generateS3Headers]; |
| 45 | success = [[[request requestHeaders] valueForKey:@"Authorization"] isEqualToString:@"AWS 0PN5J17HBGZHT7JJ3X82:hcicpDDvL9SsO6AkvxqmIWkmOuQ="]; | 49 | success = [[[request requestHeaders] valueForKey:@"Authorization"] isEqualToString:@"AWS 0PN5J17HBGZHT7JJ3X82:hcicpDDvL9SsO6AkvxqmIWkmOuQ="]; |
| 46 | GHAssertTrue(success,@"Failed to generate the correct authorisation header for a PUT request"); | 50 | GHAssertTrue(success,@"Failed to generate the correct authorisation header for a PUT request"); |
| 47 | 51 | ||
| 48 | // Test List | 52 | // Test List |
| 49 | - path = @""; | ||
| 50 | dateString = @"Tue, 27 Mar 2007 19:42:41 +0000"; | 53 | dateString = @"Tue, 27 Mar 2007 19:42:41 +0000"; |
| 51 | - request = [ASIS3Request listRequestWithBucket:bucket prefix:@"photos" maxResults:50 marker:@"puppy"]; | 54 | + ASIS3ListRequest *listRequest = [ASIS3ListRequest listRequestWithBucket:bucket]; |
| 52 | - [request setDateString:dateString]; | 55 | + [listRequest setPrefix:@"photos"]; |
| 53 | - [request setSecretAccessKey:secretAccessKey]; | 56 | + [listRequest setMaxResultCount:50]; |
| 54 | - [request setAccessKey:accessKey]; | 57 | + [listRequest setMarker:@"puppy"]; |
| 55 | - [request generateS3Headers]; | 58 | + [listRequest setDateString:dateString]; |
| 56 | - success = [[[request requestHeaders] valueForKey:@"Authorization"] isEqualToString:@"AWS 0PN5J17HBGZHT7JJ3X82:jsRt/rhG+Vtp88HrYL706QhE4w4="]; | 59 | + [listRequest setSecretAccessKey:exampleSecretAccessKey]; |
| 60 | + [listRequest setAccessKey:exampleAccessKey]; | ||
| 61 | + [listRequest generateS3Headers]; | ||
| 62 | + success = [[[listRequest requestHeaders] valueForKey:@"Authorization"] isEqualToString:@"AWS 0PN5J17HBGZHT7JJ3X82:jsRt/rhG+Vtp88HrYL706QhE4w4="]; | ||
| 57 | GHAssertTrue(success,@"Failed to generate the correct authorisation header for a list request"); | 63 | GHAssertTrue(success,@"Failed to generate the correct authorisation header for a list request"); |
| 58 | 64 | ||
| 59 | // Test fetch ACL | 65 | // Test fetch ACL |
| 60 | - path = @"?acl"; | 66 | + path = @"/?acl"; |
| 61 | dateString = @"Tue, 27 Mar 2007 19:44:46 +0000"; | 67 | dateString = @"Tue, 27 Mar 2007 19:44:46 +0000"; |
| 62 | request = [ASIS3Request requestWithBucket:bucket path:path]; | 68 | request = [ASIS3Request requestWithBucket:bucket path:path]; |
| 63 | [request setDateString:dateString]; | 69 | [request setDateString:dateString]; |
| 64 | - [request setSecretAccessKey:secretAccessKey]; | 70 | + [request setSecretAccessKey:exampleSecretAccessKey]; |
| 65 | - [request setAccessKey:accessKey]; | 71 | + [request setAccessKey:exampleAccessKey]; |
| 66 | [request generateS3Headers]; | 72 | [request generateS3Headers]; |
| 67 | success = [[[request requestHeaders] valueForKey:@"Authorization"] isEqualToString:@"AWS 0PN5J17HBGZHT7JJ3X82:thdUi9VAkzhkniLj96JIrOPGi0g="]; | 73 | success = [[[request requestHeaders] valueForKey:@"Authorization"] isEqualToString:@"AWS 0PN5J17HBGZHT7JJ3X82:thdUi9VAkzhkniLj96JIrOPGi0g="]; |
| 68 | GHAssertTrue(success,@"Failed to generate the correct authorisation header for a list request"); | 74 | GHAssertTrue(success,@"Failed to generate the correct authorisation header for a list request"); |
| @@ -70,25 +76,50 @@ | @@ -70,25 +76,50 @@ | ||
| 70 | // Test Unicode keys | 76 | // Test Unicode keys |
| 71 | // (I think Amazon's name for this example is misleading since this test actually only uses URL encoded strings) | 77 | // (I think Amazon's name for this example is misleading since this test actually only uses URL encoded strings) |
| 72 | bucket = @"dictionary"; | 78 | bucket = @"dictionary"; |
| 73 | - path = @"fran%C3%A7ais/pr%c3%a9f%c3%a8re"; | 79 | + path = @"/fran%C3%A7ais/pr%c3%a9f%c3%a8re"; |
| 74 | dateString = @"Wed, 28 Mar 2007 01:49:49 +0000"; | 80 | dateString = @"Wed, 28 Mar 2007 01:49:49 +0000"; |
| 75 | request = [ASIS3Request requestWithBucket:bucket path:path]; | 81 | request = [ASIS3Request requestWithBucket:bucket path:path]; |
| 76 | [request setDateString:dateString]; | 82 | [request setDateString:dateString]; |
| 77 | - [request setSecretAccessKey:secretAccessKey]; | 83 | + [request setSecretAccessKey:exampleSecretAccessKey]; |
| 78 | - [request setAccessKey:accessKey]; | 84 | + [request setAccessKey:exampleAccessKey]; |
| 79 | [request generateS3Headers]; | 85 | [request generateS3Headers]; |
| 80 | success = [[[request requestHeaders] valueForKey:@"Authorization"] isEqualToString:@"AWS 0PN5J17HBGZHT7JJ3X82:dxhSBHoI6eVSPcXJqEghlUzZMnY="]; | 86 | success = [[[request requestHeaders] valueForKey:@"Authorization"] isEqualToString:@"AWS 0PN5J17HBGZHT7JJ3X82:dxhSBHoI6eVSPcXJqEghlUzZMnY="]; |
| 81 | GHAssertTrue(success,@"Failed to generate the correct authorisation header for a list request"); | 87 | GHAssertTrue(success,@"Failed to generate the correct authorisation header for a list request"); |
| 82 | } | 88 | } |
| 83 | 89 | ||
| 90 | +- (void)testFailure | ||
| 91 | +{ | ||
| 92 | + // Needs expanding to cover more failure states - this is just a test to ensure Amazon's error description is being added to the error | ||
| 93 | + | ||
| 94 | + // We're actually going to try with the Amazon example details, but the request will fail because the date is old | ||
| 95 | + NSString *exampleSecretAccessKey = @"uV3F3YluFJax1cknvbcGwgjvx4QpvB+leU8dUj2o"; | ||
| 96 | + NSString *exampleAccessKey = @"0PN5J17HBGZHT7JJ3X82"; | ||
| 97 | + NSString *bucket = @"johnsmith"; | ||
| 98 | + NSString *path = @"/photos/puppy.jpg"; | ||
| 99 | + NSString *dateString = @"Tue, 27 Mar 2007 19:36:42 +0000"; | ||
| 100 | + ASIS3Request *request = [ASIS3Request requestWithBucket:bucket path:path]; | ||
| 101 | + [request setDateString:dateString]; | ||
| 102 | + [request setSecretAccessKey:exampleSecretAccessKey]; | ||
| 103 | + [request setAccessKey:exampleAccessKey]; | ||
| 104 | + [request start]; | ||
| 105 | + GHAssertNotNil([request error],@"Failed to generate an error when the request was not correctly signed"); | ||
| 106 | + | ||
| 107 | + BOOL success = ([[request error] code] == ASIS3ResponseErrorType); | ||
| 108 | + GHAssertTrue(success,@"Generated error had the wrong error code"); | ||
| 109 | + | ||
| 110 | + success = ([[[request error] localizedDescription] isEqualToString:@"The difference between the request time and the current time is too large."]); | ||
| 111 | + GHAssertTrue(success,@"Generated error had the wrong description"); | ||
| 112 | + | ||
| 113 | +} | ||
| 114 | + | ||
| 84 | // To run this test, uncomment and fill in your S3 access details | 115 | // To run this test, uncomment and fill in your S3 access details |
| 85 | -/* | ||
| 86 | - (void)testREST | 116 | - (void)testREST |
| 87 | { | 117 | { |
| 88 | - NSString *secretAccessKey = @"my-secret-key"; | 118 | + |
| 89 | - NSString *accessKey = @"my-access-key"; | 119 | + BOOL success = (![secretAccessKey isEqualToString:@""] && ![accessKey isEqualToString:@""] && ![bucket isEqualToString:@""]); |
| 90 | - NSString *bucket = @"bucket-name"; | 120 | + GHAssertTrue(success,@"You need to supply your S3 access details to run the REST test (see the top of ASIS3RequestTests.m)"); |
| 91 | - NSString *path = @"path/to/file"; | 121 | + |
| 122 | + NSString *path = @"/test"; | ||
| 92 | 123 | ||
| 93 | // Create the fle | 124 | // Create the fle |
| 94 | NSString *text = @"This is my content"; | 125 | NSString *text = @"This is my content"; |
| @@ -97,11 +128,10 @@ | @@ -97,11 +128,10 @@ | ||
| 97 | 128 | ||
| 98 | // PUT the file | 129 | // PUT the file |
| 99 | ASIS3Request *request = [ASIS3Request PUTRequestForFile:filePath withBucket:bucket path:path]; | 130 | ASIS3Request *request = [ASIS3Request PUTRequestForFile:filePath withBucket:bucket path:path]; |
| 100 | - [request setRequestMethod:@"PUT"]; | ||
| 101 | [request setSecretAccessKey:secretAccessKey]; | 131 | [request setSecretAccessKey:secretAccessKey]; |
| 102 | [request setAccessKey:accessKey]; | 132 | [request setAccessKey:accessKey]; |
| 103 | [request start]; | 133 | [request start]; |
| 104 | - BOOL success = [[request responseString] isEqualToString:@""]; | 134 | + success = [[request responseString] isEqualToString:@""]; |
| 105 | GHAssertTrue(success,@"Failed to PUT a file to S3"); | 135 | GHAssertTrue(success,@"Failed to PUT a file to S3"); |
| 106 | 136 | ||
| 107 | // GET the file | 137 | // GET the file |
| @@ -110,7 +140,17 @@ | @@ -110,7 +140,17 @@ | ||
| 110 | [request setAccessKey:accessKey]; | 140 | [request setAccessKey:accessKey]; |
| 111 | [request start]; | 141 | [request start]; |
| 112 | success = [[request responseString] isEqualToString:@"This is my content"]; | 142 | success = [[request responseString] isEqualToString:@"This is my content"]; |
| 113 | - GHAssertTrue(success,@"Failed to GET the correct data from S3"); | 143 | + GHAssertTrue(success,@"Failed to GET the correct data from S3"); |
| 144 | + | ||
| 145 | + // Get a list of files | ||
| 146 | + ASIS3ListRequest *listRequest = [ASIS3ListRequest listRequestWithBucket:bucket]; | ||
| 147 | + [listRequest setPrefix:@"test"]; | ||
| 148 | + [listRequest setSecretAccessKey:secretAccessKey]; | ||
| 149 | + [listRequest setAccessKey:accessKey]; | ||
| 150 | + [listRequest start]; | ||
| 151 | + GHAssertNil([listRequest error],@"Failed to download a list from S3"); | ||
| 152 | + success = [[listRequest bucketObjects] count]; | ||
| 153 | + GHAssertTrue(success,@"The file didn't show up in the list"); | ||
| 114 | 154 | ||
| 115 | // DELETE the file | 155 | // DELETE the file |
| 116 | request = [ASIS3Request requestWithBucket:bucket path:path]; | 156 | request = [ASIS3Request requestWithBucket:bucket path:path]; |
| @@ -121,7 +161,131 @@ | @@ -121,7 +161,131 @@ | ||
| 121 | success = [[request responseString] isEqualToString:@""]; | 161 | success = [[request responseString] isEqualToString:@""]; |
| 122 | GHAssertTrue(success,@"Failed to DELETE the file from S3"); | 162 | GHAssertTrue(success,@"Failed to DELETE the file from S3"); |
| 123 | } | 163 | } |
| 124 | -*/ | 164 | + |
| 165 | +- (void)testListRequest | ||
| 166 | +{ | ||
| 167 | + | ||
| 168 | + BOOL success = (![secretAccessKey isEqualToString:@""] && ![accessKey isEqualToString:@""] && ![bucket isEqualToString:@""]); | ||
| 169 | + GHAssertTrue(success,@"You need to supply your S3 access details to run the list test (see the top of ASIS3RequestTests.m)"); | ||
| 170 | + | ||
| 171 | + // Firstly, create and upload 5 files | ||
| 172 | + int i; | ||
| 173 | + for (i=0; i<5; i++) { | ||
| 174 | + NSString *text = [NSString stringWithFormat:@"This is the content of file #%hi",i]; | ||
| 175 | + NSString *filePath = [[[[NSBundle mainBundle] bundlePath] stringByDeletingLastPathComponent] stringByAppendingPathComponent:[NSString stringWithFormat:@"%hi.txt",i]]; | ||
| 176 | + [[text dataUsingEncoding:NSUTF8StringEncoding] writeToFile:filePath atomically:NO]; | ||
| 177 | + NSString *path = [NSString stringWithFormat:@"/test-file/%hi",i]; | ||
| 178 | + ASIS3Request *request = [ASIS3Request PUTRequestForFile:filePath withBucket:bucket path:path]; | ||
| 179 | + [request setSecretAccessKey:secretAccessKey]; | ||
| 180 | + [request setAccessKey:accessKey]; | ||
| 181 | + [request start]; | ||
| 182 | + GHAssertNil([request error],@"Give up on list request test - failed to upload a file"); | ||
| 183 | + } | ||
| 184 | + | ||
| 185 | + // Now get a list of the files | ||
| 186 | + ASIS3ListRequest *listRequest = [ASIS3ListRequest listRequestWithBucket:bucket]; | ||
| 187 | + [listRequest setPrefix:@"test-file"]; | ||
| 188 | + [listRequest setSecretAccessKey:secretAccessKey]; | ||
| 189 | + [listRequest setAccessKey:accessKey]; | ||
| 190 | + [listRequest start]; | ||
| 191 | + GHAssertNil([listRequest error],@"Failed to download a list from S3"); | ||
| 192 | + success = ([[listRequest bucketObjects] count] == 5); | ||
| 193 | + GHAssertTrue(success,@"List did not contain all files"); | ||
| 194 | + | ||
| 195 | + // Please don't use an autoreleased operation queue with waitUntilAllOperationsAreFinished in your own code unless you're writing a test like this one | ||
| 196 | + // (The end result is no better than using synchronous requests) thx - Ben :) | ||
| 197 | + ASINetworkQueue *queue = [[[ASINetworkQueue alloc] init] autorelease]; | ||
| 198 | + | ||
| 199 | + // Test fetching all the items | ||
| 200 | + [queue setRequestDidFinishSelector:@selector(GETRequestDone:)]; | ||
| 201 | + [queue setRequestDidFailSelector:@selector(GETRequestFailed:)]; | ||
| 202 | + [queue setDelegate:self]; | ||
| 203 | + for (ASIS3BucketObject *object in [listRequest bucketObjects]) { | ||
| 204 | + ASIS3Request *request = [object GETRequest]; | ||
| 205 | + [request setAccessKey:accessKey]; | ||
| 206 | + [request setSecretAccessKey:secretAccessKey]; | ||
| 207 | + [queue addOperation:request]; | ||
| 208 | + } | ||
| 209 | + [queue go]; | ||
| 210 | + [queue waitUntilAllOperationsAreFinished]; | ||
| 211 | + | ||
| 212 | + | ||
| 213 | + // Test uploading new files for all the items | ||
| 214 | + [queue setRequestDidFinishSelector:@selector(PUTRequestDone:)]; | ||
| 215 | + [queue setRequestDidFailSelector:@selector(PUTRequestFailed:)]; | ||
| 216 | + [queue setDelegate:self]; | ||
| 217 | + i=0; | ||
| 218 | + // For each one, we'll just upload the same content again | ||
| 219 | + for (ASIS3BucketObject *object in [listRequest bucketObjects]) { | ||
| 220 | + NSString *oldFilePath = [[[[NSBundle mainBundle] bundlePath] stringByDeletingLastPathComponent] stringByAppendingPathComponent:[NSString stringWithFormat:@"%hi.txt",i]];; | ||
| 221 | + ASIS3Request *request = [object PUTRequestWithFile:oldFilePath]; | ||
| 222 | + [request setAccessKey:accessKey]; | ||
| 223 | + [request setSecretAccessKey:secretAccessKey]; | ||
| 224 | + [queue addOperation:request]; | ||
| 225 | + i++; | ||
| 226 | + } | ||
| 227 | + [queue go]; | ||
| 228 | + [queue waitUntilAllOperationsAreFinished]; | ||
| 229 | + | ||
| 230 | + | ||
| 231 | + // Test deleting all the items | ||
| 232 | + [queue setRequestDidFinishSelector:@selector(DELETERequestDone:)]; | ||
| 233 | + [queue setRequestDidFailSelector:@selector(DELETERequestFailed:)]; | ||
| 234 | + [queue setDelegate:self]; | ||
| 235 | + i=0; | ||
| 236 | + | ||
| 237 | + for (ASIS3BucketObject *object in [listRequest bucketObjects]) { | ||
| 238 | + ASIS3Request *request = [object DELETERequest]; | ||
| 239 | + [request setAccessKey:accessKey]; | ||
| 240 | + [request setSecretAccessKey:secretAccessKey]; | ||
| 241 | + [queue addOperation:request]; | ||
| 242 | + i++; | ||
| 243 | + } | ||
| 244 | + [queue go]; | ||
| 245 | + [queue waitUntilAllOperationsAreFinished]; | ||
| 246 | + | ||
| 247 | + // Grab the list again, it should be empty now | ||
| 248 | + listRequest = [ASIS3ListRequest listRequestWithBucket:bucket]; | ||
| 249 | + [listRequest setPrefix:@"test-file"]; | ||
| 250 | + [listRequest setSecretAccessKey:secretAccessKey]; | ||
| 251 | + [listRequest setAccessKey:accessKey]; | ||
| 252 | + [listRequest start]; | ||
| 253 | + GHAssertNil([listRequest error],@"Failed to download a list from S3"); | ||
| 254 | + success = ([[listRequest bucketObjects] count] == 0); | ||
| 255 | + GHAssertTrue(success,@"List contained files that should have been deleted"); | ||
| 256 | + | ||
| 257 | +} | ||
| 258 | + | ||
| 259 | +- (void)GETRequestDone:(ASIS3Request *)request | ||
| 260 | +{ | ||
| 261 | + NSString *expectedContent = [NSString stringWithFormat:@"This is the content of file #%@",[[[request url] absoluteString] lastPathComponent]]; | ||
| 262 | + BOOL success = ([[request responseString] isEqualToString:expectedContent]); | ||
| 263 | + GHAssertTrue(success,@"Got the wrong content when downloading one of the files"); | ||
| 264 | + | ||
| 265 | +} | ||
| 266 | + | ||
| 267 | +- (void)GETRequestFailed:(ASIS3Request *)request | ||
| 268 | +{ | ||
| 269 | + GHAssertTrue(NO,@"GET request failed for one of the items in the list"); | ||
| 270 | +} | ||
| 271 | + | ||
| 272 | +- (void)PUTRequestDone:(ASIS3Request *)request | ||
| 273 | +{ | ||
| 274 | +} | ||
| 275 | + | ||
| 276 | +- (void)PUTRequestFailed:(ASIS3Request *)request | ||
| 277 | +{ | ||
| 278 | + GHAssertTrue(NO,@"PUT request failed for one of the items in the list"); | ||
| 279 | +} | ||
| 280 | + | ||
| 281 | +- (void)DELETERequestDone:(ASIS3Request *)request | ||
| 282 | +{ | ||
| 283 | +} | ||
| 284 | + | ||
| 285 | +- (void)DELETERequestFailed:(ASIS3Request *)request | ||
| 286 | +{ | ||
| 287 | + GHAssertTrue(NO,@"DELETE request failed for one of the items in the list"); | ||
| 288 | +} | ||
| 125 | 289 | ||
| 126 | 290 | ||
| 127 | @end | 291 | @end |
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
-
Please register or login to post a comment