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 @@ @@ -21,7 +21,7 @@
21 #import "ASIInputStream.h" 21 #import "ASIInputStream.h"
22 22
23 // Automatically set on build 23 // Automatically set on build
24 -NSString *ASIHTTPRequestVersion = @"v1.6-3 2010-03-11"; 24 +NSString *ASIHTTPRequestVersion = @"v1.6-4 2010-03-16";
25 25
26 NSString* const NetworkRequestErrorDomain = @"ASIHTTPRequestErrorDomain"; 26 NSString* const NetworkRequestErrorDomain = @"ASIHTTPRequestErrorDomain";
27 27
@@ -30,17 +30,17 @@ @@ -30,17 +30,17 @@
30 30
31 - (ASIS3Request *)GETRequest 31 - (ASIS3Request *)GETRequest
32 { 32 {
33 - return [ASIS3Request requestWithBucket:[self bucket] path:[NSString stringWithFormat:@"/%@",[self key]]]; 33 + return [ASIS3Request requestWithBucket:[self bucket] key:[self key]];
34 } 34 }
35 35
36 - (ASIS3Request *)PUTRequestWithFile:(NSString *)filePath 36 - (ASIS3Request *)PUTRequestWithFile:(NSString *)filePath
37 { 37 {
38 - return [ASIS3Request PUTRequestForFile:filePath withBucket:[self bucket] path:[NSString stringWithFormat:@"/%@",[self key]]]; 38 + return [ASIS3Request PUTRequestForFile:filePath withBucket:[self bucket] key:[self key]];
39 } 39 }
40 40
41 - (ASIS3Request *)DELETERequest 41 - (ASIS3Request *)DELETERequest
42 { 42 {
43 - ASIS3Request *request = [ASIS3Request requestWithBucket:[self bucket] path:[NSString stringWithFormat:@"/%@",[self key]]]; 43 + ASIS3Request *request = [ASIS3Request requestWithBucket:[self bucket] key:[self key]];
44 [request setRequestMethod:@"DELETE"]; 44 [request setRequestMethod:@"DELETE"];
45 return request; 45 return request;
46 } 46 }
@@ -132,7 +132,7 @@ static NSDateFormatter *dateFormatter = nil; @@ -132,7 +132,7 @@ static NSDateFormatter *dateFormatter = nil;
132 [newRequest setPrefix:[self prefix]]; 132 [newRequest setPrefix:[self prefix]];
133 [newRequest setMarker:[self marker]]; 133 [newRequest setMarker:[self marker]];
134 [newRequest setMaxResultCount:[self maxResultCount]]; 134 [newRequest setMaxResultCount:[self maxResultCount]];
135 - [newRequest setDelimiter:[self path]]; 135 + [newRequest setDelimiter:[self delimiter]];
136 return newRequest; 136 return newRequest;
137 } 137 }
138 138
@@ -37,8 +37,8 @@ typedef enum _ASIS3ErrorType { @@ -37,8 +37,8 @@ typedef enum _ASIS3ErrorType {
37 // Name of the bucket to talk to 37 // Name of the bucket to talk to
38 NSString *bucket; 38 NSString *bucket;
39 39
40 - // Path to the resource you want to access on S3. Leave empty for the bucket root 40 + // Key of the resource you want to access on S3. Leave empty for the bucket root
41 - NSString *path; 41 + NSString *key;
42 42
43 // 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 43 // 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
44 NSString *dateString; 44 NSString *dateString;
@@ -53,7 +53,7 @@ typedef enum _ASIS3ErrorType { @@ -53,7 +53,7 @@ typedef enum _ASIS3ErrorType {
53 53
54 // The bucket + path of the object to be copied (used with COPYRequestFromBucket:path:toBucket:path:) 54 // The bucket + path of the object to be copied (used with COPYRequestFromBucket:path:toBucket:path:)
55 NSString *sourceBucket; 55 NSString *sourceBucket;
56 - NSString *sourcePath; 56 + NSString *sourceKey;
57 57
58 // Internally used while parsing errors 58 // Internally used while parsing errors
59 NSString *currentErrorString; 59 NSString *currentErrorString;
@@ -63,28 +63,30 @@ typedef enum _ASIS3ErrorType { @@ -63,28 +63,30 @@ typedef enum _ASIS3ErrorType {
63 #pragma mark Constructors 63 #pragma mark Constructors
64 64
65 // Create a request, building an appropriate url 65 // Create a request, building an appropriate url
66 -+ (id)requestWithBucket:(NSString *)bucket path:(NSString *)path; 66 ++ (id)requestWithBucket:(NSString *)bucket key:(NSString *)key;
67 67
68 // Create a PUT request using the file at filePath as the body 68 // Create a PUT request using the file at filePath as the body
69 -+ (id)PUTRequestForFile:(NSString *)filePath withBucket:(NSString *)bucket path:(NSString *)path; 69 ++ (id)PUTRequestForFile:(NSString *)filePath withBucket:(NSString *)bucket key:(NSString *)key;
70 70
71 // Create a PUT request using the supplied NSData as the body (set the mime-type manually with setMimeType: if necessary) 71 // Create a PUT request using the supplied NSData as the body (set the mime-type manually with setMimeType: if necessary)
72 -+ (id)PUTRequestForData:(NSData *)data withBucket:(NSString *)bucket path:(NSString *)path; 72 ++ (id)PUTRequestForData:(NSData *)data withBucket:(NSString *)bucket key:(NSString *)key;
73 73
74 // Create a DELETE request for the object at path 74 // Create a DELETE request for the object at path
75 -+ (id)DELETERequestWithBucket:(NSString *)bucket path:(NSString *)path; 75 ++ (id)DELETERequestWithBucket:(NSString *)bucket key:(NSString *)key;
76 76
77 // Create a PUT request to copy an object from one location to another 77 // Create a PUT request to copy an object from one location to another
78 // Clang will complain because it thinks this method should return an object with +1 retain :( 78 // Clang will complain because it thinks this method should return an object with +1 retain :(
79 -+ (id)COPYRequestFromBucket:(NSString *)sourceBucket path:(NSString *)sourcePath toBucket:(NSString *)bucket path:(NSString *)path; 79 ++ (id)COPYRequestFromBucket:(NSString *)sourceBucket key:(NSString *)sourceKey toBucket:(NSString *)bucket key:(NSString *)key;
80 80
81 // Creates a HEAD request for the object at path 81 // Creates a HEAD request for the object at path
82 -+ (id)HEADRequestWithBucket:(NSString *)bucket path:(NSString *)path; 82 ++ (id)HEADRequestWithBucket:(NSString *)bucket key:(NSString *)key;
83 83
84 84
85 // Uses the supplied date to create a Date header string 85 // Uses the supplied date to create a Date header string
86 - (void)setDate:(NSDate *)date; 86 - (void)setDate:(NSDate *)date;
87 87
  88 ++ (NSString *)stringByURLEncodingForS3Path:(NSString *)key;
  89 +
88 #pragma mark Shared access keys 90 #pragma mark Shared access keys
89 91
90 // Get and set the global access key, this will be used for all requests the access key hasn't been set for 92 // 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 { @@ -95,12 +97,12 @@ typedef enum _ASIS3ErrorType {
95 97
96 98
97 @property (retain) NSString *bucket; 99 @property (retain) NSString *bucket;
98 -@property (retain) NSString *path; 100 +@property (retain) NSString *key;
99 @property (retain) NSString *dateString; 101 @property (retain) NSString *dateString;
100 @property (retain) NSString *mimeType; 102 @property (retain) NSString *mimeType;
101 @property (retain) NSString *accessKey; 103 @property (retain) NSString *accessKey;
102 @property (retain) NSString *secretAccessKey; 104 @property (retain) NSString *secretAccessKey;
103 @property (retain) NSString *accessPolicy; 105 @property (retain) NSString *accessPolicy;
104 @property (retain) NSString *sourceBucket; 106 @property (retain) NSString *sourceBucket;
105 -@property (retain) NSString *sourcePath; 107 +@property (retain) NSString *sourceKey;
106 @end 108 @end
@@ -29,25 +29,38 @@ static NSString *sharedSecretAccessKey = nil; @@ -29,25 +29,38 @@ static NSString *sharedSecretAccessKey = nil;
29 29
30 #pragma mark Constructors 30 #pragma mark Constructors
31 31
32 -+ (id)requestWithBucket:(NSString *)bucket path:(NSString *)path 32 ++ (NSString *)stringByURLEncodingForS3Path:(NSString *)key
33 { 33 {
  34 + if (!key) {
  35 + return @"/";
  36 + }
  37 + NSString *path = [(NSString *)CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)key, NULL, CFSTR(":?#[]@!$ &'()*+,;=\"<>%{}|\\^~`"), CFStringConvertNSStringEncodingToEncoding(NSUTF8StringEncoding)) autorelease];
  38 + if (![[path substringWithRange:NSMakeRange(0, 1)] isEqualToString:@"/"]) {
  39 + path = [@"/" stringByAppendingString:path];
  40 + }
  41 + return path;
  42 +}
  43 +
  44 ++ (id)requestWithBucket:(NSString *)bucket key:(NSString *)key
  45 +{
  46 + NSString *path = [ASIS3Request stringByURLEncodingForS3Path:key];
34 ASIS3Request *request = [[[self alloc] initWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"http://%@.s3.amazonaws.com%@",bucket,path]]] autorelease]; 47 ASIS3Request *request = [[[self alloc] initWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"http://%@.s3.amazonaws.com%@",bucket,path]]] autorelease];
35 [request setBucket:bucket]; 48 [request setBucket:bucket];
36 - [request setPath:path]; 49 + [request setKey:key];
37 return request; 50 return request;
38 } 51 }
39 52
40 -+ (id)PUTRequestForData:(NSData *)data withBucket:(NSString *)bucket path:(NSString *)path 53 ++ (id)PUTRequestForData:(NSData *)data withBucket:(NSString *)bucket key:(NSString *)key
41 { 54 {
42 - ASIS3Request *request = [self requestWithBucket:bucket path:path]; 55 + ASIS3Request *request = [self requestWithBucket:bucket key:key];
43 [request appendPostData:data]; 56 [request appendPostData:data];
44 [request setRequestMethod:@"PUT"]; 57 [request setRequestMethod:@"PUT"];
45 return request; 58 return request;
46 } 59 }
47 60
48 -+ (id)PUTRequestForFile:(NSString *)filePath withBucket:(NSString *)bucket path:(NSString *)path 61 ++ (id)PUTRequestForFile:(NSString *)filePath withBucket:(NSString *)bucket key:(NSString *)key
49 { 62 {
50 - ASIS3Request *request = [self requestWithBucket:bucket path:path]; 63 + ASIS3Request *request = [self requestWithBucket:bucket key:key];
51 [request setPostBodyFilePath:filePath]; 64 [request setPostBodyFilePath:filePath];
52 [request setShouldStreamPostDataFromDisk:YES]; 65 [request setShouldStreamPostDataFromDisk:YES];
53 [request setRequestMethod:@"PUT"]; 66 [request setRequestMethod:@"PUT"];
@@ -55,25 +68,25 @@ static NSString *sharedSecretAccessKey = nil; @@ -55,25 +68,25 @@ static NSString *sharedSecretAccessKey = nil;
55 return request; 68 return request;
56 } 69 }
57 70
58 -+ (id)DELETERequestWithBucket:(NSString *)bucket path:(NSString *)path 71 ++ (id)DELETERequestWithBucket:(NSString *)bucket key:(NSString *)key
59 { 72 {
60 - ASIS3Request *request = [self requestWithBucket:bucket path:path]; 73 + ASIS3Request *request = [self requestWithBucket:bucket key:key];
61 [request setRequestMethod:@"DELETE"]; 74 [request setRequestMethod:@"DELETE"];
62 return request; 75 return request;
63 } 76 }
64 77
65 -+ (id)COPYRequestFromBucket:(NSString *)sourceBucket path:(NSString *)sourcePath toBucket:(NSString *)bucket path:(NSString *)path 78 ++ (id)COPYRequestFromBucket:(NSString *)sourceBucket key:(NSString *)sourceKey toBucket:(NSString *)bucket key:(NSString *)key
66 { 79 {
67 - ASIS3Request *request = [self requestWithBucket:bucket path:path]; 80 + ASIS3Request *request = [self requestWithBucket:bucket key:key];
68 [request setRequestMethod:@"PUT"]; 81 [request setRequestMethod:@"PUT"];
69 [request setSourceBucket:sourceBucket]; 82 [request setSourceBucket:sourceBucket];
70 - [request setSourcePath:sourcePath]; 83 + [request setSourceKey:sourceKey];
71 return request; 84 return request;
72 } 85 }
73 86
74 -+ (id)HEADRequestWithBucket:(NSString *)bucket path:(NSString *)path 87 ++ (id)HEADRequestWithBucket:(NSString *)bucket key:(NSString *)key
75 { 88 {
76 - ASIS3Request *request = [self requestWithBucket:bucket path:path]; 89 + ASIS3Request *request = [self requestWithBucket:bucket key:key];
77 [request setRequestMethod:@"HEAD"]; 90 [request setRequestMethod:@"HEAD"];
78 return request; 91 return request;
79 } 92 }
@@ -81,12 +94,12 @@ static NSString *sharedSecretAccessKey = nil; @@ -81,12 +94,12 @@ static NSString *sharedSecretAccessKey = nil;
81 - (void)dealloc 94 - (void)dealloc
82 { 95 {
83 [bucket release]; 96 [bucket release];
84 - [path release]; 97 + [key release];
85 [dateString release]; 98 [dateString release];
86 [mimeType release]; 99 [mimeType release];
87 [accessKey release]; 100 [accessKey release];
88 [secretAccessKey release]; 101 [secretAccessKey release];
89 - [sourcePath release]; 102 + [sourceKey release];
90 [sourceBucket release]; 103 [sourceBucket release];
91 [super dealloc]; 104 [super dealloc];
92 } 105 }
@@ -106,7 +119,7 @@ static NSString *sharedSecretAccessKey = nil; @@ -106,7 +119,7 @@ static NSString *sharedSecretAccessKey = nil;
106 ASIS3Request *headRequest = (ASIS3Request *)[super HEADRequest]; 119 ASIS3Request *headRequest = (ASIS3Request *)[super HEADRequest];
107 [headRequest setAccessKey:[self accessKey]]; 120 [headRequest setAccessKey:[self accessKey]];
108 [headRequest setSecretAccessKey:[self secretAccessKey]]; 121 [headRequest setSecretAccessKey:[self secretAccessKey]];
109 - [headRequest setPath:[self path]]; 122 + [headRequest setKey:[self key]];
110 [headRequest setBucket:[self bucket]]; 123 [headRequest setBucket:[self bucket]];
111 return headRequest; 124 return headRequest;
112 } 125 }
@@ -130,11 +143,7 @@ static NSString *sharedSecretAccessKey = nil; @@ -130,11 +143,7 @@ static NSString *sharedSecretAccessKey = nil;
130 [self addRequestHeader:@"Date" value:[self dateString]]; 143 [self addRequestHeader:@"Date" value:[self dateString]];
131 144
132 // Ensure our formatted string doesn't use '(null)' for the empty path 145 // Ensure our formatted string doesn't use '(null)' for the empty path
133 - if (![self path]) { 146 + NSString *canonicalizedResource = [NSString stringWithFormat:@"/%@%@",[self bucket],[ASIS3Request stringByURLEncodingForS3Path:[self key]]];
134 - [self setPath:@"/"];  
135 - }  
136 -  
137 - NSString *canonicalizedResource = [NSString stringWithFormat:@"/%@%@",[self bucket],[self path]];  
138 147
139 // Add a header for the access policy if one was set, otherwise we won't add one (and S3 will default to private) 148 // Add a header for the access policy if one was set, otherwise we won't add one (and S3 will default to private)
140 NSMutableDictionary *amzHeaders = [[[NSMutableDictionary alloc] init] autorelease]; 149 NSMutableDictionary *amzHeaders = [[[NSMutableDictionary alloc] init] autorelease];
@@ -142,18 +151,19 @@ static NSString *sharedSecretAccessKey = nil; @@ -142,18 +151,19 @@ static NSString *sharedSecretAccessKey = nil;
142 if ([self accessPolicy]) { 151 if ([self accessPolicy]) {
143 [amzHeaders setObject:[self accessPolicy] forKey:@"x-amz-acl"]; 152 [amzHeaders setObject:[self accessPolicy] forKey:@"x-amz-acl"];
144 } 153 }
145 - if ([self sourcePath]) { 154 + if ([self sourceKey]) {
146 - [amzHeaders setObject:[[self sourceBucket] stringByAppendingString:[self sourcePath]] forKey:@"x-amz-copy-source"]; 155 + NSString *path = [ASIS3Request stringByURLEncodingForS3Path:[self sourceKey]];
  156 + [amzHeaders setObject:[[self sourceBucket] stringByAppendingString:path] forKey:@"x-amz-copy-source"];
147 } 157 }
148 - for (NSString *key in [amzHeaders keyEnumerator]) { 158 + for (NSString *header in [amzHeaders keyEnumerator]) {
149 - canonicalizedAmzHeaders = [NSString stringWithFormat:@"%@%@:%@\n",canonicalizedAmzHeaders,[key lowercaseString],[amzHeaders objectForKey:key]]; 159 + canonicalizedAmzHeaders = [NSString stringWithFormat:@"%@%@:%@\n",canonicalizedAmzHeaders,[header lowercaseString],[amzHeaders objectForKey:header]];
150 [self addRequestHeader:key value:[amzHeaders objectForKey:key]]; 160 [self addRequestHeader:key value:[amzHeaders objectForKey:key]];
151 } 161 }
152 162
153 163
154 // Jump through hoops while eating hot food 164 // Jump through hoops while eating hot food
155 NSString *stringToSign; 165 NSString *stringToSign;
156 - if ([[self requestMethod] isEqualToString:@"PUT"] && ![self sourcePath]) { 166 + if ([[self requestMethod] isEqualToString:@"PUT"] && ![self sourceKey]) {
157 [self addRequestHeader:@"Content-Type" value:[self mimeType]]; 167 [self addRequestHeader:@"Content-Type" value:[self mimeType]];
158 stringToSign = [NSString stringWithFormat:@"PUT\n\n%@\n%@\n%@%@",[self mimeType],dateString,canonicalizedAmzHeaders,canonicalizedResource]; 168 stringToSign = [NSString stringWithFormat:@"PUT\n\n%@\n%@\n%@%@",[self mimeType],dateString,canonicalizedAmzHeaders,canonicalizedResource];
159 } else { 169 } else {
@@ -170,7 +180,7 @@ static NSString *sharedSecretAccessKey = nil; @@ -170,7 +180,7 @@ static NSString *sharedSecretAccessKey = nil;
170 - (void)requestFinished 180 - (void)requestFinished
171 { 181 {
172 // COPY requests return a 200 whether they succeed or fail, so we need to look at the XML to see if we were successful. 182 // COPY requests return a 200 whether they succeed or fail, so we need to look at the XML to see if we were successful.
173 - if ([self responseStatusCode] == 200 && [self sourcePath] && [self sourceBucket]) { 183 + if ([self responseStatusCode] == 200 && [self sourceKey] && [self sourceBucket]) {
174 [self parseError]; 184 [self parseError];
175 return; 185 return;
176 } 186 }
@@ -222,11 +232,11 @@ static NSString *sharedSecretAccessKey = nil; @@ -222,11 +232,11 @@ static NSString *sharedSecretAccessKey = nil;
222 [newRequest setAccessKey:[self accessKey]]; 232 [newRequest setAccessKey:[self accessKey]];
223 [newRequest setSecretAccessKey:[self secretAccessKey]]; 233 [newRequest setSecretAccessKey:[self secretAccessKey]];
224 [newRequest setBucket:[self bucket]]; 234 [newRequest setBucket:[self bucket]];
225 - [newRequest setPath:[self path]]; 235 + [newRequest setKey:[self key]];
226 [newRequest setMimeType:[self mimeType]]; 236 [newRequest setMimeType:[self mimeType]];
227 [newRequest setAccessPolicy:[self accessPolicy]]; 237 [newRequest setAccessPolicy:[self accessPolicy]];
228 [newRequest setSourceBucket:[self sourceBucket]]; 238 [newRequest setSourceBucket:[self sourceBucket]];
229 - [newRequest setSourcePath:[self sourcePath]]; 239 + [newRequest setSourceKey:[self sourceKey]];
230 return newRequest; 240 return newRequest;
231 } 241 }
232 242
@@ -277,7 +287,7 @@ static NSString *sharedSecretAccessKey = nil; @@ -277,7 +287,7 @@ static NSString *sharedSecretAccessKey = nil;
277 } 287 }
278 288
279 @synthesize bucket; 289 @synthesize bucket;
280 -@synthesize path; 290 +@synthesize key;
281 @synthesize dateString; 291 @synthesize dateString;
282 @synthesize mimeType; 292 @synthesize mimeType;
283 @synthesize accessKey; 293 @synthesize accessKey;
@@ -285,5 +295,5 @@ static NSString *sharedSecretAccessKey = nil; @@ -285,5 +295,5 @@ static NSString *sharedSecretAccessKey = nil;
285 @synthesize accessPolicy; 295 @synthesize accessPolicy;
286 @synthesize currentErrorString; 296 @synthesize currentErrorString;
287 @synthesize sourceBucket; 297 @synthesize sourceBucket;
288 -@synthesize sourcePath; 298 +@synthesize sourceKey;
289 @end 299 @end
This diff is collapsed. Click to expand it.