Ben Copsey

Added support for S3 list requests

Added more tests, bug fixes
//
// ASIS3BucketObject.h
// Mac
//
// Created by Ben Copsey on 13/07/2009.
// Copyright 2009 All-Seeing Interactive. All rights reserved.
//
#import <Foundation/Foundation.h>
@class ASIS3Request;
@interface ASIS3BucketObject : NSObject {
// 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
- (ASIS3Request *)GETRequest;
// Returns a request that will replace this object with the contents of the file at filePath when run
- (ASIS3Request *)PUTRequestWithFile:(NSString *)filePath;
// Returns a request that will delete this object when run
- (ASIS3Request *)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
... ...
//
// ASIS3BucketObject.m
// Mac
//
// Created by Ben Copsey on 13/07/2009.
// Copyright 2009 All-Seeing Interactive. All rights reserved.
//
#import "ASIS3BucketObject.h"
#import "ASIS3Request.h"
@implementation ASIS3BucketObject
+ (id)objectWithBucket:(NSString *)bucket
{
ASIS3BucketObject *object = [[[ASIS3BucketObject alloc] init] autorelease];
[object setBucket:bucket];
return object;
}
- (void)dealloc
{
[key release];
[lastModified release];
[ETag release];
[ownerID release];
[ownerName release];
[super dealloc];
}
- (ASIS3Request *)GETRequest
{
return [ASIS3Request requestWithBucket:[self bucket] path:[NSString stringWithFormat:@"/%@",[self key]]];
}
- (ASIS3Request *)PUTRequestWithFile:(NSString *)filePath
{
return [ASIS3Request PUTRequestForFile:filePath withBucket:[self bucket] path:[NSString stringWithFormat:@"/%@",[self key]]];
}
- (ASIS3Request *)DELETERequest
{
ASIS3Request *request = [ASIS3Request requestWithBucket:[self bucket] path:[NSString stringWithFormat:@"/%@",[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]];
}
@synthesize bucket;
@synthesize key;
@synthesize lastModified;
@synthesize ETag;
@synthesize size;
@synthesize ownerID;
@synthesize ownerName;
@end
... ...
//
// ASIS3ListRequest.h
// Mac
//
// Created by Ben Copsey on 13/07/2009.
// Copyright 2009 All-Seeing Interactive. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "ASIS3Request.h"
@class ASIS3BucketObject;
@interface ASIS3ListRequest : ASIS3Request {
NSString *prefix;
NSString *marker;
int maxResultCount;
NSString *delimiter;
// Internally used while parsing the response
NSString *currentContent;
NSString *currentElement;
ASIS3BucketObject *currentObject;
NSMutableArray *objects;
}
// Create a list request
+ (id)listRequestWithBucket:(NSString *)bucket;
// Returns an array of ASIS3BucketObjects created from the XML response
- (NSArray *)bucketObjects;
//Builds a query string out of the list parameters we supplied
- (void)createQueryString;
@property (retain) NSString *prefix;
@property (retain) NSString *marker;
@property (assign) int maxResultCount;
@property (retain) NSString *delimiter;
@end
... ...
//
// ASIS3ListRequest.m
// Mac
//
// Created by Ben Copsey on 13/07/2009.
// Copyright 2009 All-Seeing Interactive. All rights reserved.
//
#import "ASIS3ListRequest.h"
#import "ASIS3BucketObject.h"
static NSDateFormatter *dateFormatter = nil;
// Private stuff
@interface ASIS3ListRequest ()
@property (retain,setter=setURL:) NSURL *url;
@property (retain, nonatomic) NSString *currentContent;
@property (retain, nonatomic) NSString *currentElement;
@property (retain, nonatomic) ASIS3BucketObject *currentObject;
@property (retain, nonatomic) NSMutableArray *objects;
@end
@implementation ASIS3ListRequest
+ (void)initialize
{
dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setTimeZone:[NSTimeZone timeZoneWithAbbreviation:@"UTC"]];
[dateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss'.000Z'"];
}
+ (id)listRequestWithBucket:(NSString *)bucket
{
ASIS3ListRequest *request = [[[ASIS3ListRequest alloc] initWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"http://%@.s3.amazonaws.com",bucket]]] autorelease];
[request setBucket:bucket];
return request;
}
- (void)dealloc
{
[currentElement release];
[currentObject release];
[objects release];
[super dealloc];
}
- (void)createQueryString
{
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:@"delimiter=%hi",[self maxResultCount]]];
}
if ([queryParts count]) {
[self setURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@?%@",[[self url] absoluteString],[queryParts componentsJoinedByString:@"&"]]]];
}
}
- (void)main
{
[self createQueryString];
[super main];
}
- (NSArray *)bucketObjects
{
if ([self objects]) {
return [self objects];
}
[self setObjects:[[[NSMutableArray alloc] init] autorelease]];
NSXMLParser *parser = [[[NSXMLParser alloc] initWithData:[self responseData]] autorelease];
[parser setDelegate:self];
[parser setShouldProcessNamespaces:NO];
[parser setShouldReportNamespacePrefixes:NO];
[parser setShouldResolveExternalEntities:NO];
[parser parse];
return [self objects];
}
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict
{
[self setCurrentElement:elementName];
if ([elementName isEqualToString:@"Contents"]) {
[self setCurrentObject:[ASIS3BucketObject objectWithBucket:[self bucket]]];
}
[self setCurrentContent:@""];
}
- (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 currentContent]];
} else if ([elementName isEqualToString:@"LastModified"]) {
[[self currentObject] setLastModified:[dateFormatter dateFromString:[self currentContent]]];
} else if ([elementName isEqualToString:@"ETag"]) {
[[self currentObject] setETag:[self currentContent]];
} else if ([elementName isEqualToString:@"Size"]) {
[[self currentObject] setSize:(unsigned long long)[[self currentContent] longLongValue]];
} else if ([elementName isEqualToString:@"ID"]) {
[[self currentObject] setOwnerID:[self currentContent]];
} else if ([elementName isEqualToString:@"DisplayName"]) {
[[self currentObject] setOwnerName:[self currentContent]];
}
}
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
[self setCurrentContent:[[self currentContent] stringByAppendingString:string]];
}
- (void)parserDidEndDocument:(NSXMLParser *)parser
{
}
@synthesize currentContent;
@synthesize currentElement;
@synthesize currentObject;
@synthesize objects;
@synthesize prefix;
@synthesize marker;
@synthesize maxResultCount;
@synthesize delimiter;
@synthesize url;
@end
... ...
... ... @@ -4,8 +4,7 @@
// Created by Ben Copsey on 30/06/2009.
// Copyright 2009 All-Seeing Interactive. All rights reserved.
//
// A (basic) class for accessing data stored on Amazon's Simple Storage Service (http://aws.amazon.com/s3/)
// 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)
// A (basic) class for accessing data stored on Amazon's Simple Storage Service (http://aws.amazon.com/s3/) using the REST API
#import <Foundation/Foundation.h>
#import "ASIHTTPRequest.h"
... ... @@ -16,6 +15,12 @@ extern NSString *const ASIS3AccessPolicyPublicRead;
extern NSString *const ASIS3AccessPolicyPublicReadWrote;
extern NSString *const ASIS3AccessPolicyAuthenticatedRead;
typedef enum _ASIS3ErrorType {
ASIS3ResponseParsingFailedType = 1,
ASIS3ResponseErrorType = 2
} ASIS3ErrorType;
@interface ASIS3Request : ASIHTTPRequest {
// Your S3 access key. Set it on the request, or set it globally using [ASIS3Request setSharedAccessKey:]
... ... @@ -38,7 +43,19 @@ extern NSString *const ASIS3AccessPolicyAuthenticatedRead;
// Will be set to 'application/octet-stream' otherwise in iPhone apps, or autodetected on Mac OS X
NSString *mimeType;
// The access policy to use when PUTting a file (see the string constants at the top of this header)
NSString *accessPolicy;
// Options for filtering list requests
// See http://docs.amazonwebservices.com/AmazonS3/2006-03-01/index.html?RESTBucketGET.html
NSString *listPrefix;
NSString *listMarker;
int listMaxResults;
NSString *listDelimiter;
// Internally used while parsing errors
NSString *currentErrorString;
}
#pragma mark Constructors
... ... @@ -49,9 +66,6 @@ extern NSString *const ASIS3AccessPolicyAuthenticatedRead;
// Create a PUT request using the file at filePath as the body
+ (id)PUTRequestForFile:(NSString *)filePath withBucket:(NSString *)bucket path:(NSString *)path;
// Create a list request
+ (id)listRequestWithBucket:(NSString *)bucket prefix:(NSString *)prefix maxResults:(int)maxResults marker:(NSString *)marker;
// Generates the request headers S3 needs
// Automatically called before the request begins in startRequest
- (void)generateS3Headers;
... ... @@ -59,6 +73,8 @@ extern NSString *const ASIS3AccessPolicyAuthenticatedRead;
// Uses the supplied date to create a Date header string
- (void)setDate:(NSDate *)date;
#pragma mark Helper functions
// Only works on Mac OS, will always return 'application/octet-stream' on iPhone
+ (NSString *)mimeTypeForFileAtPath:(NSString *)path;
... ... @@ -77,5 +93,6 @@ extern NSString *const ASIS3AccessPolicyAuthenticatedRead;
@property (retain) NSString *mimeType;
@property (retain) NSString *accessKey;
@property (retain) NSString *secretAccessKey;
@property (assign) NSString *accessPolicy;
@property (retain) NSString *accessPolicy;
@end
... ...
... ... @@ -17,9 +17,11 @@ static NSString *sharedAccessKey = nil;
static NSString *sharedSecretAccessKey = nil;
// Private stuff
@interface ASIHTTPRequest ()
@interface ASIS3Request ()
- (void)parseError;
+ (NSData *)HMACSHA1withKey:(NSString *)key forString:(NSString *)string;
+ (NSString *)base64forData:(NSData *)theData;
@property (retain, nonatomic) NSString *currentErrorString;
@end
@implementation ASIS3Request
... ... @@ -37,7 +39,7 @@ static NSString *sharedSecretAccessKey = nil;
+ (id)requestWithBucket:(NSString *)bucket path:(NSString *)path
{
ASIS3Request *request = [[[ASIS3Request alloc] initWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"http://s3.amazonaws.com/%@/%@",bucket,path]]] autorelease];
ASIS3Request *request = [[[ASIS3Request alloc] initWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"http://%@.s3.amazonaws.com%@",bucket,path]]] autorelease];
[request setBucket:bucket];
[request setPath:path];
return request;
... ... @@ -53,51 +55,16 @@ static NSString *sharedSecretAccessKey = nil;
return request;
}
+ (id)listRequestWithBucket:(NSString *)bucket prefix:(NSString *)prefix maxResults:(int)maxResults marker:(NSString *)marker
{
ASIS3Request *request = [[[ASIS3Request alloc] initWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"http://s3.amazonaws.com/%@?prefix=/%@&max-keys=%hi&marker=%@",bucket,prefix,maxResults,marker]]] autorelease];
[request setBucket:bucket];
return request;
}
+ (NSString *)mimeTypeForFileAtPath:(NSString *)path
{
// 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.
#if TARGET_OS_IPHONE
return @"application/octet-stream";
// Grab the mime type using an NSTask to run the 'file' program, with the Mac OS-specific parameters to grab the mime type
// Perhaps there is a better way to do this?
#else
NSTask *task = [[NSTask alloc] init];
[task setLaunchPath: @"/usr/bin/file"];
[task setArguments:[NSMutableArray arrayWithObjects:@"-Ib",path,nil]];
NSPipe *outputPipe = [NSPipe pipe];
[task setStandardOutput:outputPipe];
NSFileHandle *file = [outputPipe fileHandleForReading];
[task launch];
[task waitUntilExit];
if ([task terminationStatus] != 0) {
return @"application/octet-stream";
}
NSString *mimeTypeString = [[[NSString alloc] initWithData:[file readDataToEndOfFile] encoding: NSUTF8StringEncoding] autorelease];
return [[mimeTypeString componentsSeparatedByString:@";"] objectAtIndex:0];
#endif
}
- (void)setDate:(NSDate *)date
{
NSDateFormatter *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"] autorelease]];
[dateFormatter setDateFormat:@"EEE, d MMM yyyy HH:mm:ss Z"];
[self setDateString:[dateFormatter stringFromDate:date]];
}
- (void)generateS3Headers
{
// 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;
[self setAccessKey:[ASIS3Request sharedAccessKey]];
}
if (![self secretAccessKey]) {
[self setAccessKey:[ASIS3Request sharedSecretAccessKey]];
[self setSecretAccessKey:[ASIS3Request sharedSecretAccessKey]];
}
// If a date string hasn't been set, we'll create one from the current time
if (![self dateString]) {
... ... @@ -115,10 +82,10 @@ static NSString *sharedSecretAccessKey = nil;
// Ensure our formatted string doesn't use '(null)' for the empty path
if (![self path]) {
[self setPath:@""];
[self setPath:@"/"];
}
NSString *canonicalizedResource = [NSString stringWithFormat:@"/%@/%@",[self bucket],[self path]];
NSString *canonicalizedResource = [NSString stringWithFormat:@"/%@%@",[self bucket],[self path]];
// Add a header for the access policy if one was set, otherwise we won't add one (and S3 will default to private)
NSString *canonicalizedAmzHeaders = @"";
... ... @@ -146,6 +113,59 @@ static NSString *sharedSecretAccessKey = nil;
[super main];
}
- (void)requestFinished
{
if ([self responseStatusCode] < 207) {
[super requestFinished];
return;
}
[self parseError];
}
#pragma mark Error XML parsing
- (void)parseError
{
NSXMLParser *parser = [[[NSXMLParser alloc] initWithData:[self responseData]] autorelease];
[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 setCurrentErrorString:@""];
}
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
{
if ([elementName isEqualToString:@"Message"]) {
[self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIS3ResponseErrorType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[self currentErrorString],NSLocalizedDescriptionKey,nil]]];
}
}
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
[self setCurrentErrorString:[[self currentErrorString] stringByAppendingString:string]];
}
- (void)parserDidEndDocument:(NSXMLParser *)parser
{
// We've got to the end of the XML error, without encountering a <Message></Message>, I don't think this should happen, but anyway
if (![self error]) {
[self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIS3ResponseErrorType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"An unspecified S3 error ocurred",NSLocalizedDescriptionKey,nil]]];
}
}
#pragma mark Shared access keys
+ (NSString *)sharedAccessKey
... ... @@ -170,6 +190,38 @@ static NSString *sharedSecretAccessKey = nil;
sharedSecretAccessKey = [newAccessKey retain];
}
#pragma mark Helper functions
+ (NSString *)mimeTypeForFileAtPath:(NSString *)path
{
// 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.
#if TARGET_OS_IPHONE
return @"application/octet-stream";
// Grab the mime type using an NSTask to run the 'file' program, with the Mac OS-specific parameters to grab the mime type
// Perhaps there is a better way to do this?
#else
NSTask *task = [[[NSTask alloc] init] autorelease];
[task setLaunchPath: @"/usr/bin/file"];
[task setArguments:[NSMutableArray arrayWithObjects:@"-Ib",path,nil]];
NSPipe *outputPipe = [NSPipe pipe];
[task setStandardOutput:outputPipe];
NSFileHandle *file = [outputPipe fileHandleForReading];
[task launch];
[task waitUntilExit];
if ([task terminationStatus] != 0) {
return @"application/octet-stream";
}
NSString *mimeTypeString = [[[[NSString alloc] initWithData:[file readDataToEndOfFile] encoding: NSUTF8StringEncoding] autorelease] stringByReplacingOccurrencesOfString:@"\n" withString:@""];
return [[mimeTypeString componentsSeparatedByString:@";"] objectAtIndex:0];
#endif
}
#pragma mark S3 Authentication helpers
... ... @@ -231,4 +283,6 @@ static NSString *sharedSecretAccessKey = nil;
@synthesize accessKey;
@synthesize secretAccessKey;
@synthesize accessPolicy;
@synthesize currentErrorString;
@end
... ...
... ... @@ -663,7 +663,7 @@
{
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:@"http://allseeing-i.com/ASIHTTPRequest/tests/redirect_to_new_domain"]];
[request start];
BOOL success = [[[request url] absoluteString] isEqualTo:@"http://www.apple.com/"];
BOOL success = [[[request url] absoluteString] isEqualToString:@"http://www.apple.com/"];
GHAssertTrue(success,@"Failed to redirect to a different domain");
}
... ...
... ... @@ -13,10 +13,11 @@
#endif
@interface ASIS3RequestTests : GHTestCase {
}
- (void)testAuthenticationHeaderGeneration;
//- (void)testREST;
- (void)testREST;
- (void)testFailure;
- (void)testListRequest;
@end
... ...
... ... @@ -7,62 +7,68 @@
//
#import "ASIS3RequestTests.h"
#import "ASIS3Request.h"
#import "ASIS3ListRequest.h"
#import "ASINetworkQueue.h"
#import "ASIS3BucketObject.h"
@implementation ASIS3RequestTests
// Fill in these to run the tests that actually connect and manipulate objects on S3
static NSString *secretAccessKey = @"";
static NSString *accessKey = @"";
static NSString *bucket = @"";
// All these tests are based on Amazon's examples at: http://docs.amazonwebservices.com/AmazonS3/2006-03-01/
- (void)testAuthenticationHeaderGeneration
{
// All these tests are based on Amazon's examples at: http://docs.amazonwebservices.com/AmazonS3/2006-03-01/
NSString *secretAccessKey = @"uV3F3YluFJax1cknvbcGwgjvx4QpvB+leU8dUj2o";
NSString *accessKey = @"0PN5J17HBGZHT7JJ3X82";
NSString *exampleSecretAccessKey = @"uV3F3YluFJax1cknvbcGwgjvx4QpvB+leU8dUj2o";
NSString *exampleAccessKey = @"0PN5J17HBGZHT7JJ3X82";
NSString *bucket = @"johnsmith";
// Test GET
NSString *path = @"photos/puppy.jpg";
NSString *path = @"/photos/puppy.jpg";
NSString *dateString = @"Tue, 27 Mar 2007 19:36:42 +0000";
ASIS3Request *request = [ASIS3Request requestWithBucket:bucket path:path];
[request setDateString:dateString];
[request setSecretAccessKey:secretAccessKey];
[request setAccessKey:accessKey];
[request setSecretAccessKey:exampleSecretAccessKey];
[request setAccessKey:exampleAccessKey];
[request generateS3Headers];
BOOL success = [[[request requestHeaders] valueForKey:@"Authorization"] isEqualToString:@"AWS 0PN5J17HBGZHT7JJ3X82:xXjDGYUmKxnwqr5KXNPGldn5LbA="];
GHAssertTrue(success,@"Failed to generate the correct authorisation header for a GET request");
// Test PUT
path = @"photos/puppy.jpg";
path = @"/photos/puppy.jpg";
dateString = @"Tue, 27 Mar 2007 21:15:45 +0000";
request = [ASIS3Request requestWithBucket:bucket path:path];
[request setRequestMethod:@"PUT"];
[request setMimeType:@"image/jpeg"];
[request setDateString:dateString];
[request setSecretAccessKey:secretAccessKey];
[request setAccessKey:accessKey];
[request setSecretAccessKey:exampleSecretAccessKey];
[request setAccessKey:exampleAccessKey];
[request generateS3Headers];
success = [[[request requestHeaders] valueForKey:@"Authorization"] isEqualToString:@"AWS 0PN5J17HBGZHT7JJ3X82:hcicpDDvL9SsO6AkvxqmIWkmOuQ="];
GHAssertTrue(success,@"Failed to generate the correct authorisation header for a PUT request");
// Test List
path = @"";
dateString = @"Tue, 27 Mar 2007 19:42:41 +0000";
request = [ASIS3Request listRequestWithBucket:bucket prefix:@"photos" maxResults:50 marker:@"puppy"];
[request setDateString:dateString];
[request setSecretAccessKey:secretAccessKey];
[request setAccessKey:accessKey];
[request generateS3Headers];
success = [[[request requestHeaders] valueForKey:@"Authorization"] isEqualToString:@"AWS 0PN5J17HBGZHT7JJ3X82:jsRt/rhG+Vtp88HrYL706QhE4w4="];
ASIS3ListRequest *listRequest = [ASIS3ListRequest listRequestWithBucket:bucket];
[listRequest setPrefix:@"photos"];
[listRequest setMaxResultCount:50];
[listRequest setMarker:@"puppy"];
[listRequest setDateString:dateString];
[listRequest setSecretAccessKey:exampleSecretAccessKey];
[listRequest setAccessKey:exampleAccessKey];
[listRequest generateS3Headers];
success = [[[listRequest requestHeaders] valueForKey:@"Authorization"] isEqualToString:@"AWS 0PN5J17HBGZHT7JJ3X82:jsRt/rhG+Vtp88HrYL706QhE4w4="];
GHAssertTrue(success,@"Failed to generate the correct authorisation header for a list request");
// Test fetch ACL
path = @"?acl";
path = @"/?acl";
dateString = @"Tue, 27 Mar 2007 19:44:46 +0000";
request = [ASIS3Request requestWithBucket:bucket path:path];
[request setDateString:dateString];
[request setSecretAccessKey:secretAccessKey];
[request setAccessKey:accessKey];
[request setSecretAccessKey:exampleSecretAccessKey];
[request setAccessKey:exampleAccessKey];
[request generateS3Headers];
success = [[[request requestHeaders] valueForKey:@"Authorization"] isEqualToString:@"AWS 0PN5J17HBGZHT7JJ3X82:thdUi9VAkzhkniLj96JIrOPGi0g="];
GHAssertTrue(success,@"Failed to generate the correct authorisation header for a list request");
... ... @@ -70,25 +76,50 @@
// Test Unicode keys
// (I think Amazon's name for this example is misleading since this test actually only uses URL encoded strings)
bucket = @"dictionary";
path = @"fran%C3%A7ais/pr%c3%a9f%c3%a8re";
path = @"/fran%C3%A7ais/pr%c3%a9f%c3%a8re";
dateString = @"Wed, 28 Mar 2007 01:49:49 +0000";
request = [ASIS3Request requestWithBucket:bucket path:path];
[request setDateString:dateString];
[request setSecretAccessKey:secretAccessKey];
[request setAccessKey:accessKey];
[request setSecretAccessKey:exampleSecretAccessKey];
[request setAccessKey:exampleAccessKey];
[request generateS3Headers];
success = [[[request requestHeaders] valueForKey:@"Authorization"] isEqualToString:@"AWS 0PN5J17HBGZHT7JJ3X82:dxhSBHoI6eVSPcXJqEghlUzZMnY="];
GHAssertTrue(success,@"Failed to generate the correct authorisation header for a list request");
}
- (void)testFailure
{
// Needs expanding to cover more failure states - this is just a test to ensure Amazon's error description is being added to the error
// We're actually going to try with the Amazon example details, but the request will fail because the date is old
NSString *exampleSecretAccessKey = @"uV3F3YluFJax1cknvbcGwgjvx4QpvB+leU8dUj2o";
NSString *exampleAccessKey = @"0PN5J17HBGZHT7JJ3X82";
NSString *bucket = @"johnsmith";
NSString *path = @"/photos/puppy.jpg";
NSString *dateString = @"Tue, 27 Mar 2007 19:36:42 +0000";
ASIS3Request *request = [ASIS3Request requestWithBucket:bucket path:path];
[request setDateString:dateString];
[request setSecretAccessKey:exampleSecretAccessKey];
[request setAccessKey:exampleAccessKey];
[request start];
GHAssertNotNil([request error],@"Failed to generate an error when the request was not correctly signed");
BOOL success = ([[request error] code] == ASIS3ResponseErrorType);
GHAssertTrue(success,@"Generated error had the wrong error code");
success = ([[[request error] localizedDescription] isEqualToString:@"The difference between the request time and the current time is too large."]);
GHAssertTrue(success,@"Generated error had the wrong description");
}
// To run this test, uncomment and fill in your S3 access details
/*
- (void)testREST
{
NSString *secretAccessKey = @"my-secret-key";
NSString *accessKey = @"my-access-key";
NSString *bucket = @"bucket-name";
NSString *path = @"path/to/file";
BOOL success = (![secretAccessKey isEqualToString:@""] && ![accessKey isEqualToString:@""] && ![bucket isEqualToString:@""]);
GHAssertTrue(success,@"You need to supply your S3 access details to run the REST test (see the top of ASIS3RequestTests.m)");
NSString *path = @"/test";
// Create the fle
NSString *text = @"This is my content";
... ... @@ -97,11 +128,10 @@
// PUT the file
ASIS3Request *request = [ASIS3Request PUTRequestForFile:filePath withBucket:bucket path:path];
[request setRequestMethod:@"PUT"];
[request setSecretAccessKey:secretAccessKey];
[request setAccessKey:accessKey];
[request start];
BOOL success = [[request responseString] isEqualToString:@""];
success = [[request responseString] isEqualToString:@""];
GHAssertTrue(success,@"Failed to PUT a file to S3");
// GET the file
... ... @@ -110,7 +140,17 @@
[request setAccessKey:accessKey];
[request start];
success = [[request responseString] isEqualToString:@"This is my content"];
GHAssertTrue(success,@"Failed to GET the correct data from S3");
GHAssertTrue(success,@"Failed to GET the correct data from S3");
// Get a list of files
ASIS3ListRequest *listRequest = [ASIS3ListRequest listRequestWithBucket:bucket];
[listRequest setPrefix:@"test"];
[listRequest setSecretAccessKey:secretAccessKey];
[listRequest setAccessKey:accessKey];
[listRequest start];
GHAssertNil([listRequest error],@"Failed to download a list from S3");
success = [[listRequest bucketObjects] count];
GHAssertTrue(success,@"The file didn't show up in the list");
// DELETE the file
request = [ASIS3Request requestWithBucket:bucket path:path];
... ... @@ -121,7 +161,131 @@
success = [[request responseString] isEqualToString:@""];
GHAssertTrue(success,@"Failed to DELETE the file from S3");
}
*/
- (void)testListRequest
{
BOOL success = (![secretAccessKey isEqualToString:@""] && ![accessKey isEqualToString:@""] && ![bucket isEqualToString:@""]);
GHAssertTrue(success,@"You need to supply your S3 access details to run the list test (see the top of ASIS3RequestTests.m)");
// Firstly, create and upload 5 files
int i;
for (i=0; i<5; i++) {
NSString *text = [NSString stringWithFormat:@"This is the content of file #%hi",i];
NSString *filePath = [[[[NSBundle mainBundle] bundlePath] stringByDeletingLastPathComponent] stringByAppendingPathComponent:[NSString stringWithFormat:@"%hi.txt",i]];
[[text dataUsingEncoding:NSUTF8StringEncoding] writeToFile:filePath atomically:NO];
NSString *path = [NSString stringWithFormat:@"/test-file/%hi",i];
ASIS3Request *request = [ASIS3Request PUTRequestForFile:filePath withBucket:bucket path:path];
[request setSecretAccessKey:secretAccessKey];
[request setAccessKey:accessKey];
[request start];
GHAssertNil([request error],@"Give up on list request test - failed to upload a file");
}
// Now get a list of the files
ASIS3ListRequest *listRequest = [ASIS3ListRequest listRequestWithBucket:bucket];
[listRequest setPrefix:@"test-file"];
[listRequest setSecretAccessKey:secretAccessKey];
[listRequest setAccessKey:accessKey];
[listRequest start];
GHAssertNil([listRequest error],@"Failed to download a list from S3");
success = ([[listRequest bucketObjects] count] == 5);
GHAssertTrue(success,@"List did not contain all files");
// Please don't use an autoreleased operation queue with waitUntilAllOperationsAreFinished in your own code unless you're writing a test like this one
// (The end result is no better than using synchronous requests) thx - Ben :)
ASINetworkQueue *queue = [[[ASINetworkQueue alloc] init] autorelease];
// Test fetching all the items
[queue setRequestDidFinishSelector:@selector(GETRequestDone:)];
[queue setRequestDidFailSelector:@selector(GETRequestFailed:)];
[queue setDelegate:self];
for (ASIS3BucketObject *object in [listRequest bucketObjects]) {
ASIS3Request *request = [object GETRequest];
[request setAccessKey:accessKey];
[request setSecretAccessKey:secretAccessKey];
[queue addOperation:request];
}
[queue go];
[queue waitUntilAllOperationsAreFinished];
// Test uploading new files for all the items
[queue setRequestDidFinishSelector:@selector(PUTRequestDone:)];
[queue setRequestDidFailSelector:@selector(PUTRequestFailed:)];
[queue setDelegate:self];
i=0;
// For each one, we'll just upload the same content again
for (ASIS3BucketObject *object in [listRequest bucketObjects]) {
NSString *oldFilePath = [[[[NSBundle mainBundle] bundlePath] stringByDeletingLastPathComponent] stringByAppendingPathComponent:[NSString stringWithFormat:@"%hi.txt",i]];;
ASIS3Request *request = [object PUTRequestWithFile:oldFilePath];
[request setAccessKey:accessKey];
[request setSecretAccessKey:secretAccessKey];
[queue addOperation:request];
i++;
}
[queue go];
[queue waitUntilAllOperationsAreFinished];
// Test deleting all the items
[queue setRequestDidFinishSelector:@selector(DELETERequestDone:)];
[queue setRequestDidFailSelector:@selector(DELETERequestFailed:)];
[queue setDelegate:self];
i=0;
for (ASIS3BucketObject *object in [listRequest bucketObjects]) {
ASIS3Request *request = [object DELETERequest];
[request setAccessKey:accessKey];
[request setSecretAccessKey:secretAccessKey];
[queue addOperation:request];
i++;
}
[queue go];
[queue waitUntilAllOperationsAreFinished];
// Grab the list again, it should be empty now
listRequest = [ASIS3ListRequest listRequestWithBucket:bucket];
[listRequest setPrefix:@"test-file"];
[listRequest setSecretAccessKey:secretAccessKey];
[listRequest setAccessKey:accessKey];
[listRequest start];
GHAssertNil([listRequest error],@"Failed to download a list from S3");
success = ([[listRequest bucketObjects] count] == 0);
GHAssertTrue(success,@"List contained files that should have been deleted");
}
- (void)GETRequestDone:(ASIS3Request *)request
{
NSString *expectedContent = [NSString stringWithFormat:@"This is the content of file #%@",[[[request url] absoluteString] lastPathComponent]];
BOOL success = ([[request responseString] isEqualToString:expectedContent]);
GHAssertTrue(success,@"Got the wrong content when downloading one of the files");
}
- (void)GETRequestFailed:(ASIS3Request *)request
{
GHAssertTrue(NO,@"GET request failed for one of the items in the list");
}
- (void)PUTRequestDone:(ASIS3Request *)request
{
}
- (void)PUTRequestFailed:(ASIS3Request *)request
{
GHAssertTrue(NO,@"PUT request failed for one of the items in the list");
}
- (void)DELETERequestDone:(ASIS3Request *)request
{
}
- (void)DELETERequestFailed:(ASIS3Request *)request
{
GHAssertTrue(NO,@"DELETE request failed for one of the items in the list");
}
@end
... ...
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.