Ben Copsey

Change S3 API to take a real S3 key as a parameter when creating a request, rath…

…er than forcing the user to encode the key themselves or add a '/' on the front
My thanks to Tom Andersen for his report!
... ... @@ -21,7 +21,7 @@
#import "ASIInputStream.h"
// Automatically set on build
NSString *ASIHTTPRequestVersion = @"v1.6-3 2010-03-11";
NSString *ASIHTTPRequestVersion = @"v1.6-4 2010-03-16";
NSString* const NetworkRequestErrorDomain = @"ASIHTTPRequestErrorDomain";
... ...
... ... @@ -30,17 +30,17 @@
- (ASIS3Request *)GETRequest
{
return [ASIS3Request requestWithBucket:[self bucket] path:[NSString stringWithFormat:@"/%@",[self key]]];
return [ASIS3Request requestWithBucket:[self bucket] key:[self key]];
}
- (ASIS3Request *)PUTRequestWithFile:(NSString *)filePath
{
return [ASIS3Request PUTRequestForFile:filePath withBucket:[self bucket] path:[NSString stringWithFormat:@"/%@",[self key]]];
return [ASIS3Request PUTRequestForFile:filePath withBucket:[self bucket] key:[self key]];
}
- (ASIS3Request *)DELETERequest
{
ASIS3Request *request = [ASIS3Request requestWithBucket:[self bucket] path:[NSString stringWithFormat:@"/%@",[self key]]];
ASIS3Request *request = [ASIS3Request requestWithBucket:[self bucket] key:[self key]];
[request setRequestMethod:@"DELETE"];
return request;
}
... ...
... ... @@ -132,7 +132,7 @@ static NSDateFormatter *dateFormatter = nil;
[newRequest setPrefix:[self prefix]];
[newRequest setMarker:[self marker]];
[newRequest setMaxResultCount:[self maxResultCount]];
[newRequest setDelimiter:[self path]];
[newRequest setDelimiter:[self delimiter]];
return newRequest;
}
... ...
... ... @@ -37,8 +37,8 @@ typedef enum _ASIS3ErrorType {
// Name of the bucket to talk to
NSString *bucket;
// Path to the resource you want to access on S3. Leave empty for the bucket root
NSString *path;
// Key of the resource you want to access on S3. Leave empty for the bucket root
NSString *key;
// 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;
... ... @@ -53,7 +53,7 @@ typedef enum _ASIS3ErrorType {
// The bucket + path of the object to be copied (used with COPYRequestFromBucket:path:toBucket:path:)
NSString *sourceBucket;
NSString *sourcePath;
NSString *sourceKey;
// Internally used while parsing errors
NSString *currentErrorString;
... ... @@ -63,28 +63,30 @@ typedef enum _ASIS3ErrorType {
#pragma mark Constructors
// Create a request, building an appropriate url
+ (id)requestWithBucket:(NSString *)bucket path:(NSString *)path;
+ (id)requestWithBucket:(NSString *)bucket key:(NSString *)key;
// Create a PUT request using the file at filePath as the body
+ (id)PUTRequestForFile:(NSString *)filePath withBucket:(NSString *)bucket path:(NSString *)path;
+ (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 path:(NSString *)path;
+ (id)PUTRequestForData:(NSData *)data withBucket:(NSString *)bucket key:(NSString *)key;
// Create a DELETE request for the object at path
+ (id)DELETERequestWithBucket:(NSString *)bucket path:(NSString *)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 path:(NSString *)sourcePath toBucket:(NSString *)bucket path:(NSString *)path;
+ (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 path:(NSString *)path;
+ (id)HEADRequestWithBucket:(NSString *)bucket key:(NSString *)key;
// Uses the supplied date to create a Date header string
- (void)setDate:(NSDate *)date;
+ (NSString *)stringByURLEncodingForS3Path:(NSString *)key;
#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
... ... @@ -95,12 +97,12 @@ typedef enum _ASIS3ErrorType {
@property (retain) NSString *bucket;
@property (retain) NSString *path;
@property (retain) NSString *key;
@property (retain) NSString *dateString;
@property (retain) NSString *mimeType;
@property (retain) NSString *accessKey;
@property (retain) NSString *secretAccessKey;
@property (retain) NSString *accessPolicy;
@property (retain) NSString *sourceBucket;
@property (retain) NSString *sourcePath;
@property (retain) NSString *sourceKey;
@end
... ...
... ... @@ -29,25 +29,38 @@ static NSString *sharedSecretAccessKey = nil;
#pragma mark Constructors
+ (id)requestWithBucket:(NSString *)bucket path:(NSString *)path
+ (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;
}
+ (id)requestWithBucket:(NSString *)bucket key:(NSString *)key
{
NSString *path = [ASIS3Request stringByURLEncodingForS3Path:key];
ASIS3Request *request = [[[self alloc] initWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"http://%@.s3.amazonaws.com%@",bucket,path]]] autorelease];
[request setBucket:bucket];
[request setPath:path];
[request setKey:key];
return request;
}
+ (id)PUTRequestForData:(NSData *)data withBucket:(NSString *)bucket path:(NSString *)path
+ (id)PUTRequestForData:(NSData *)data withBucket:(NSString *)bucket key:(NSString *)key
{
ASIS3Request *request = [self requestWithBucket:bucket path:path];
ASIS3Request *request = [self requestWithBucket:bucket key:key];
[request appendPostData:data];
[request setRequestMethod:@"PUT"];
return request;
}
+ (id)PUTRequestForFile:(NSString *)filePath withBucket:(NSString *)bucket path:(NSString *)path
+ (id)PUTRequestForFile:(NSString *)filePath withBucket:(NSString *)bucket key:(NSString *)key
{
ASIS3Request *request = [self requestWithBucket:bucket path:path];
ASIS3Request *request = [self requestWithBucket:bucket key:key];
[request setPostBodyFilePath:filePath];
[request setShouldStreamPostDataFromDisk:YES];
[request setRequestMethod:@"PUT"];
... ... @@ -55,25 +68,25 @@ static NSString *sharedSecretAccessKey = nil;
return request;
}
+ (id)DELETERequestWithBucket:(NSString *)bucket path:(NSString *)path
+ (id)DELETERequestWithBucket:(NSString *)bucket key:(NSString *)key
{
ASIS3Request *request = [self requestWithBucket:bucket path:path];
ASIS3Request *request = [self requestWithBucket:bucket key:key];
[request setRequestMethod:@"DELETE"];
return request;
}
+ (id)COPYRequestFromBucket:(NSString *)sourceBucket path:(NSString *)sourcePath toBucket:(NSString *)bucket path:(NSString *)path
+ (id)COPYRequestFromBucket:(NSString *)sourceBucket key:(NSString *)sourceKey toBucket:(NSString *)bucket key:(NSString *)key
{
ASIS3Request *request = [self requestWithBucket:bucket path:path];
ASIS3Request *request = [self requestWithBucket:bucket key:key];
[request setRequestMethod:@"PUT"];
[request setSourceBucket:sourceBucket];
[request setSourcePath:sourcePath];
[request setSourceKey:sourceKey];
return request;
}
+ (id)HEADRequestWithBucket:(NSString *)bucket path:(NSString *)path
+ (id)HEADRequestWithBucket:(NSString *)bucket key:(NSString *)key
{
ASIS3Request *request = [self requestWithBucket:bucket path:path];
ASIS3Request *request = [self requestWithBucket:bucket key:key];
[request setRequestMethod:@"HEAD"];
return request;
}
... ... @@ -81,12 +94,12 @@ static NSString *sharedSecretAccessKey = nil;
- (void)dealloc
{
[bucket release];
[path release];
[key release];
[dateString release];
[mimeType release];
[accessKey release];
[secretAccessKey release];
[sourcePath release];
[sourceKey release];
[sourceBucket release];
[super dealloc];
}
... ... @@ -106,7 +119,7 @@ static NSString *sharedSecretAccessKey = nil;
ASIS3Request *headRequest = (ASIS3Request *)[super HEADRequest];
[headRequest setAccessKey:[self accessKey]];
[headRequest setSecretAccessKey:[self secretAccessKey]];
[headRequest setPath:[self path]];
[headRequest setKey:[self key]];
[headRequest setBucket:[self bucket]];
return headRequest;
}
... ... @@ -130,11 +143,7 @@ static NSString *sharedSecretAccessKey = nil;
[self addRequestHeader:@"Date" value:[self dateString]];
// Ensure our formatted string doesn't use '(null)' for the empty path
if (![self path]) {
[self setPath:@"/"];
}
NSString *canonicalizedResource = [NSString stringWithFormat:@"/%@%@",[self bucket],[self path]];
NSString *canonicalizedResource = [NSString stringWithFormat:@"/%@%@",[self bucket],[ASIS3Request stringByURLEncodingForS3Path:[self key]]];
// 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 = [[[NSMutableDictionary alloc] init] autorelease];
... ... @@ -142,18 +151,19 @@ static NSString *sharedSecretAccessKey = nil;
if ([self accessPolicy]) {
[amzHeaders setObject:[self accessPolicy] forKey:@"x-amz-acl"];
}
if ([self sourcePath]) {
[amzHeaders setObject:[[self sourceBucket] stringByAppendingString:[self sourcePath]] forKey:@"x-amz-copy-source"];
if ([self sourceKey]) {
NSString *path = [ASIS3Request stringByURLEncodingForS3Path:[self sourceKey]];
[amzHeaders setObject:[[self sourceBucket] stringByAppendingString:path] forKey:@"x-amz-copy-source"];
}
for (NSString *key in [amzHeaders keyEnumerator]) {
canonicalizedAmzHeaders = [NSString stringWithFormat:@"%@%@:%@\n",canonicalizedAmzHeaders,[key lowercaseString],[amzHeaders objectForKey:key]];
for (NSString *header in [amzHeaders keyEnumerator]) {
canonicalizedAmzHeaders = [NSString stringWithFormat:@"%@%@:%@\n",canonicalizedAmzHeaders,[header lowercaseString],[amzHeaders objectForKey:header]];
[self addRequestHeader:key value:[amzHeaders objectForKey:key]];
}
// Jump through hoops while eating hot food
NSString *stringToSign;
if ([[self requestMethod] isEqualToString:@"PUT"] && ![self sourcePath]) {
if ([[self requestMethod] isEqualToString:@"PUT"] && ![self sourceKey]) {
[self addRequestHeader:@"Content-Type" value:[self mimeType]];
stringToSign = [NSString stringWithFormat:@"PUT\n\n%@\n%@\n%@%@",[self mimeType],dateString,canonicalizedAmzHeaders,canonicalizedResource];
} else {
... ... @@ -170,7 +180,7 @@ static NSString *sharedSecretAccessKey = nil;
- (void)requestFinished
{
// COPY requests return a 200 whether they succeed or fail, so we need to look at the XML to see if we were successful.
if ([self responseStatusCode] == 200 && [self sourcePath] && [self sourceBucket]) {
if ([self responseStatusCode] == 200 && [self sourceKey] && [self sourceBucket]) {
[self parseError];
return;
}
... ... @@ -222,11 +232,11 @@ static NSString *sharedSecretAccessKey = nil;
[newRequest setAccessKey:[self accessKey]];
[newRequest setSecretAccessKey:[self secretAccessKey]];
[newRequest setBucket:[self bucket]];
[newRequest setPath:[self path]];
[newRequest setKey:[self key]];
[newRequest setMimeType:[self mimeType]];
[newRequest setAccessPolicy:[self accessPolicy]];
[newRequest setSourceBucket:[self sourceBucket]];
[newRequest setSourcePath:[self sourcePath]];
[newRequest setSourceKey:[self sourceKey]];
return newRequest;
}
... ... @@ -277,7 +287,7 @@ static NSString *sharedSecretAccessKey = nil;
}
@synthesize bucket;
@synthesize path;
@synthesize key;
@synthesize dateString;
@synthesize mimeType;
@synthesize accessKey;
... ... @@ -285,5 +295,5 @@ static NSString *sharedSecretAccessKey = nil;
@synthesize accessPolicy;
@synthesize currentErrorString;
@synthesize sourceBucket;
@synthesize sourcePath;
@synthesize sourceKey;
@end
... ...
This diff is collapsed. Click to expand it.