Permalink
Browse files

S3: Added COPY requests

S3: Added more convenience constructors (HEAD/DELETE)
S3: More tests, tweaks
  • Loading branch information...
1 parent d8b4a1e commit 83231507cf099a2aa258e99d20e5a170c50081fc @pokeb committed Jul 15, 2009
Showing with 127 additions and 36 deletions.
  1. +6 −1 Classes/ASIS3ListRequest.h
  2. +5 −5 Classes/ASIS3ListRequest.m
  3. +16 −7 Classes/ASIS3Request.h
  4. +57 −23 Classes/ASIS3Request.m
  5. +43 −0 Classes/Tests/ASIS3RequestTests.m
@@ -23,7 +23,12 @@
ASIS3BucketObject *currentObject;
NSMutableArray *objects;
-
+ // 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;
}
// Create a list request
+ (id)listRequestWithBucket:(NSString *)bucket;
View
@@ -38,9 +38,13 @@ + (id)listRequestWithBucket:(NSString *)bucket
- (void)dealloc
{
- [currentElement release];
[currentObject release];
+ [currentElement release];
+ [currentContent release];
[objects release];
+ [prefix release];
+ [marker release];
+ [delimiter release];
[super dealloc];
}
@@ -120,10 +124,6 @@ - (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
[self setCurrentContent:[[self currentContent] stringByAppendingString:string]];
}
-- (void)parserDidEndDocument:(NSXMLParser *)parser
-{
-}
-
@synthesize currentContent;
@synthesize currentElement;
View
@@ -46,12 +46,9 @@ typedef enum _ASIS3ErrorType {
// 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;
+ // The bucket + path of the object to be copied (used with COPYRequestFromBucket:path:toBucket:path:)
+ NSString *sourceBucket;
+ NSString *sourcePath;
// Internally used while parsing errors
NSString *currentErrorString;
@@ -66,6 +63,17 @@ typedef enum _ASIS3ErrorType {
// Create a PUT request using the file at filePath as the body
+ (id)PUTRequestForFile:(NSString *)filePath withBucket:(NSString *)bucket path:(NSString *)path;
+// Create a DELETE request for the object at path
++ (id)DELETERequestWithBucket:(NSString *)bucket path:(NSString *)path;
+
+// 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;
+
+// Creates a HEAD request for the object at path
++ (id)HEADRequestWithBucket:(NSString *)bucket path:(NSString *)path;
+
+
// Generates the request headers S3 needs
// Automatically called before the request begins in startRequest
- (void)generateS3Headers;
@@ -94,5 +102,6 @@ typedef enum _ASIS3ErrorType {
@property (retain) NSString *accessKey;
@property (retain) NSString *secretAccessKey;
@property (retain) NSString *accessPolicy;
-
+@property (retain) NSString *sourceBucket;
+@property (retain) NSString *sourcePath;
@end
View
@@ -26,20 +26,11 @@ + (NSString *)base64forData:(NSData *)theData;
@implementation ASIS3Request
-- (void)dealloc
-{
- [bucket release];
- [path release];
- [dateString release];
- [mimeType release];
- [accessKey release];
- [secretAccessKey release];
- [super dealloc];
-}
+#pragma mark Constructors
+ (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;
@@ -55,6 +46,43 @@ + (id)PUTRequestForFile:(NSString *)filePath withBucket:(NSString *)bucket path:
return request;
}
++ (id)DELETERequestWithBucket:(NSString *)bucket path:(NSString *)path
+{
+ ASIS3Request *request = [ASIS3Request requestWithBucket:bucket path:path];
+ [request setRequestMethod:@"DELETE"];
+ return request;
+}
+
++ (id)COPYRequestFromBucket:(NSString *)sourceBucket path:(NSString *)sourcePath toBucket:(NSString *)bucket path:(NSString *)path
+{
+ ASIS3Request *request = [ASIS3Request requestWithBucket:bucket path:path];
+ [request setRequestMethod:@"PUT"];
+ [request setSourceBucket:sourceBucket];
+ [request setSourcePath:sourcePath];
+ return request;
+}
+
++ (id)HEADRequestWithBucket:(NSString *)bucket path:(NSString *)path
+{
+ ASIS3Request *request = [ASIS3Request requestWithBucket:bucket path:path];
+ [request setRequestMethod:@"HEAD"];
+ return request;
+}
+
+- (void)dealloc
+{
+ [bucket release];
+ [path release];
+ [dateString release];
+ [mimeType release];
+ [accessKey release];
+ [secretAccessKey release];
+ [sourcePath release];
+ [sourceBucket release];
+ [super dealloc];
+}
+
+
- (void)setDate:(NSDate *)date
{
NSDateFormatter *dateFormatter = [[[NSDateFormatter alloc] init] autorelease];
@@ -88,15 +116,23 @@ - (void)generateS3Headers
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)
+ NSMutableDictionary *amzHeaders = [[[NSMutableDictionary alloc] init] autorelease];
NSString *canonicalizedAmzHeaders = @"";
if ([self accessPolicy]) {
- [self addRequestHeader:@"x-amz-acl" value:[self accessPolicy]];
- canonicalizedAmzHeaders = [NSString stringWithFormat:@"x-amz-acl:%@\n",[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"];
}
+ for (NSString *key in [amzHeaders keyEnumerator]) {
+ canonicalizedAmzHeaders = [NSString stringWithFormat:@"%@%@:%@\n",canonicalizedAmzHeaders,[key lowercaseString],[amzHeaders objectForKey:key]];
+ [self addRequestHeader:key value:[amzHeaders objectForKey:key]];
+ }
+
// Jump through hoops while eating hot food
NSString *stringToSign;
- if ([[self requestMethod] isEqualToString:@"PUT"]) {
+ if ([[self requestMethod] isEqualToString:@"PUT"] && ![self sourcePath]) {
[self addRequestHeader:@"Content-Type" value:[self mimeType]];
stringToSign = [NSString stringWithFormat:@"PUT\n\n%@\n%@\n%@%@",[self mimeType],dateString,canonicalizedAmzHeaders,canonicalizedResource];
} else {
@@ -115,6 +151,11 @@ - (void)main
- (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]) {
+ [self parseError];
+ return;
+ }
if ([self responseStatusCode] < 207) {
[super requestFinished];
return;
@@ -157,14 +198,6 @@ - (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
@@ -284,5 +317,6 @@ + (NSString*)base64forData:(NSData*)theData {
@synthesize secretAccessKey;
@synthesize accessPolicy;
@synthesize currentErrorString;
-
+@synthesize sourceBucket;
+@synthesize sourcePath;
@end
@@ -142,6 +142,30 @@ - (void)testREST
success = [[request responseString] isEqualToString:@"This is my content"];
GHAssertTrue(success,@"Failed to GET the correct data from S3");
+ // COPY the file
+ request = [ASIS3Request COPYRequestFromBucket:bucket path:path toBucket:bucket path:@"/test-copy"];
+ [request setSecretAccessKey:secretAccessKey];
+ [request setAccessKey:accessKey];
+ [request start];
+ GHAssertNil([request error],@"Failed to COPY a file");
+
+ // GET the copy
+ request = [ASIS3Request requestWithBucket:bucket path:@"/test-copy"];
+ [request setSecretAccessKey:secretAccessKey];
+ [request setAccessKey:accessKey];
+ [request start];
+ success = [[request responseString] isEqualToString:@"This is my content"];
+ GHAssertTrue(success,@"Failed to GET the correct data from S3");
+
+
+ // HEAD the copy
+ request = [ASIS3Request HEADRequestWithBucket:bucket path:@"/test-copy"];
+ [request setSecretAccessKey:secretAccessKey];
+ [request setAccessKey:accessKey];
+ [request start];
+ success = [[request responseString] isEqualToString:@""];
+ GHAssertTrue(success,@"Got a response body for a HEAD request");
+
// Get a list of files
ASIS3ListRequest *listRequest = [ASIS3ListRequest listRequestWithBucket:bucket];
[listRequest setPrefix:@"test"];
@@ -160,6 +184,25 @@ - (void)testREST
[request start];
success = [[request responseString] isEqualToString:@""];
GHAssertTrue(success,@"Failed to DELETE the file from S3");
+
+ // (Also DELETE the copy we made)
+ request = [ASIS3Request requestWithBucket:bucket path:@"/test-copy"];
+ [request setSecretAccessKey:secretAccessKey];
+ [request setRequestMethod:@"DELETE"];
+ [request setAccessKey:accessKey];
+ [request start];
+ success = [[request responseString] isEqualToString:@""];
+ GHAssertTrue(success,@"Failed to DELETE the copy from S3");
+
+ // Attempt to COPY the file, even though it is no longer there
+ request = [ASIS3Request COPYRequestFromBucket:bucket path:path toBucket:bucket path:@"/test-copy"];
+ [request setSecretAccessKey:secretAccessKey];
+ [request setAccessKey:accessKey];
+ [request start];
+ GHAssertNotNil([request error],@"Failed generate an error for what should have been a failed COPY");
+
+ success = [[[request error] localizedDescription] isEqualToString:@"The specified key does not exist."];
+ GHAssertTrue(success, @"Got the wrong error message");
}
- (void)testListRequest

0 comments on commit 8323150

Please sign in to comment.