Ben Copsey

Added support for S3 storage class (eg Reduced Redundancy)

Added tests for canned ACLs
@@ -10,6 +10,9 @@ @@ -10,6 +10,9 @@
10 #import <Foundation/Foundation.h> 10 #import <Foundation/Foundation.h>
11 #import "ASIS3Request.h" 11 #import "ASIS3Request.h"
12 12
  13 +// Constants for storage class
  14 +extern NSString *const ASIS3StorageClassStandard;
  15 +extern NSString *const ASIS3StorageClassReducedRedundancy;
13 16
14 @interface ASIS3ObjectRequest : ASIS3Request { 17 @interface ASIS3ObjectRequest : ASIS3Request {
15 18
@@ -28,7 +31,14 @@ @@ -28,7 +31,14 @@
28 // Can be autodetected when PUTing a file from disk, will default to 'application/octet-stream' when PUTing data 31 // Can be autodetected when PUTing a file from disk, will default to 'application/octet-stream' when PUTing data
29 NSString *mimeType; 32 NSString *mimeType;
30 33
  34 + // Set this to specify you want to work with a particular subresource (eg an acl for that resource)
  35 + // See requestWithBucket:key:subResource:, below.
31 NSString* subResource; 36 NSString* subResource;
  37 +
  38 + // The storage class to be used for PUT requests
  39 + // Set this to ASIS3StorageClassReducedRedundancy to save money on storage, at (presumably) a slightly higher risk you will lose the data
  40 + // If this is not set, no x-amz-storage-class header will be sent to S3, and their default will be used
  41 + NSString *storageClass;
32 } 42 }
33 43
34 // Create a request, building an appropriate url 44 // Create a request, building an appropriate url
@@ -66,5 +76,5 @@ @@ -66,5 +76,5 @@
66 @property (retain, nonatomic) NSString *sourceKey; 76 @property (retain, nonatomic) NSString *sourceKey;
67 @property (retain, nonatomic) NSString *mimeType; 77 @property (retain, nonatomic) NSString *mimeType;
68 @property (retain, nonatomic) NSString *subResource; 78 @property (retain, nonatomic) NSString *subResource;
69 - 79 +@property (retain, nonatomic) NSString *storageClass;
70 @end 80 @end
@@ -8,6 +8,8 @@ @@ -8,6 +8,8 @@
8 8
9 #import "ASIS3ObjectRequest.h" 9 #import "ASIS3ObjectRequest.h"
10 10
  11 +NSString *const ASIS3StorageClassStandard = @"STANDARD";
  12 +NSString *const ASIS3StorageClassReducedRedundancy = @"REDUCED_REDUNDANCY";
11 13
12 @implementation ASIS3ObjectRequest 14 @implementation ASIS3ObjectRequest
13 15
@@ -96,13 +98,14 @@ @@ -96,13 +98,14 @@
96 [sourceKey release]; 98 [sourceKey release];
97 [sourceBucket release]; 99 [sourceBucket release];
98 [subResource release]; 100 [subResource release];
  101 + [storageClass release];
99 [super dealloc]; 102 [super dealloc];
100 } 103 }
101 104
102 - (void)buildURL 105 - (void)buildURL
103 { 106 {
104 if ([self subResource]) { 107 if ([self subResource]) {
105 - [self setURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@://%@.%@%@?%@",[self requestScheme],[[self class] S3Host],[ASIS3Request stringByURLEncodingForS3Path:[self key]],[self subResource]]]]; 108 + [self setURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@://%@.%@%@?%@",[self requestScheme],[self bucket],[[self class] S3Host],[ASIS3Request stringByURLEncodingForS3Path:[self key]],[self subResource]]]];
106 } else { 109 } else {
107 [self setURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@://%@.%@%@",[self requestScheme],[self bucket],[[self class] S3Host],[ASIS3Request stringByURLEncodingForS3Path:[self key]]]]]; 110 [self setURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@://%@.%@%@",[self requestScheme],[self bucket],[[self class] S3Host],[ASIS3Request stringByURLEncodingForS3Path:[self key]]]]];
108 } 111 }
@@ -134,6 +137,9 @@ @@ -134,6 +137,9 @@
134 NSString *path = [ASIS3Request stringByURLEncodingForS3Path:[self sourceKey]]; 137 NSString *path = [ASIS3Request stringByURLEncodingForS3Path:[self sourceKey]];
135 [headers setObject:[[self sourceBucket] stringByAppendingString:path] forKey:@"x-amz-copy-source"]; 138 [headers setObject:[[self sourceBucket] stringByAppendingString:path] forKey:@"x-amz-copy-source"];
136 } 139 }
  140 + if ([self storageClass]) {
  141 + [headers setObject:[self storageClass] forKey:@"x-amz-storage-class"];
  142 + }
137 return headers; 143 return headers;
138 } 144 }
139 145
@@ -146,12 +152,11 @@ @@ -146,12 +152,11 @@
146 return [super stringToSignForHeaders:canonicalizedAmzHeaders resource:canonicalizedResource]; 152 return [super stringToSignForHeaders:canonicalizedAmzHeaders resource:canonicalizedResource];
147 } 153 }
148 154
149 -  
150 @synthesize bucket; 155 @synthesize bucket;
151 @synthesize key; 156 @synthesize key;
152 @synthesize sourceBucket; 157 @synthesize sourceBucket;
153 @synthesize sourceKey; 158 @synthesize sourceKey;
154 @synthesize mimeType; 159 @synthesize mimeType;
155 @synthesize subResource; 160 @synthesize subResource;
156 - 161 +@synthesize storageClass;
157 @end 162 @end
@@ -23,13 +23,18 @@ extern NSString *const ASIS3AccessPolicyAuthenticatedRead; @@ -23,13 +23,18 @@ extern NSString *const ASIS3AccessPolicyAuthenticatedRead;
23 extern NSString *const ASIS3AccessPolicyBucketOwnerRead; 23 extern NSString *const ASIS3AccessPolicyBucketOwnerRead;
24 extern NSString *const ASIS3AccessPolicyBucketOwnerFullControl; 24 extern NSString *const ASIS3AccessPolicyBucketOwnerFullControl;
25 25
  26 +// Constants for requestScheme - defaults is ASIS3RequestSchemeHTTP
  27 +extern NSString *const ASIS3RequestSchemeHTTP;
  28 +extern NSString *const ASIS3RequestSchemeHTTPS;
  29 +
  30 +
  31 +
26 typedef enum _ASIS3ErrorType { 32 typedef enum _ASIS3ErrorType {
27 ASIS3ResponseParsingFailedType = 1, 33 ASIS3ResponseParsingFailedType = 1,
28 ASIS3ResponseErrorType = 2 34 ASIS3ResponseErrorType = 2
29 } ASIS3ErrorType; 35 } ASIS3ErrorType;
30 36
31 -extern NSString *const ASIS3RequestSchemeHTTP; 37 +
32 -extern NSString *const ASIS3RequestSchemeHTTPS;  
33 38
34 @interface ASIS3Request : ASIHTTPRequest <NSCopying, NSXMLParserDelegate> { 39 @interface ASIS3Request : ASIHTTPRequest <NSCopying, NSXMLParserDelegate> {
35 40
@@ -19,7 +19,6 @@ NSString *const ASIS3AccessPolicyBucketOwnerFullControl = @"bucket-owner-full-co @@ -19,7 +19,6 @@ NSString *const ASIS3AccessPolicyBucketOwnerFullControl = @"bucket-owner-full-co
19 NSString *const ASIS3RequestSchemeHTTP = @"http"; 19 NSString *const ASIS3RequestSchemeHTTP = @"http";
20 NSString *const ASIS3RequestSchemeHTTPS = @"https"; 20 NSString *const ASIS3RequestSchemeHTTPS = @"https";
21 21
22 -  
23 static NSString *sharedAccessKey = nil; 22 static NSString *sharedAccessKey = nil;
24 static NSString *sharedSecretAccessKey = nil; 23 static NSString *sharedSecretAccessKey = nil;
25 24
@@ -119,7 +118,7 @@ static NSString *sharedSecretAccessKey = nil; @@ -119,7 +118,7 @@ static NSString *sharedSecretAccessKey = nil;
119 // Add a header for the access policy if one was set, otherwise we won't add one (and S3 will default to private) 118 // Add a header for the access policy if one was set, otherwise we won't add one (and S3 will default to private)
120 NSMutableDictionary *amzHeaders = [self S3Headers]; 119 NSMutableDictionary *amzHeaders = [self S3Headers];
121 NSString *canonicalizedAmzHeaders = @""; 120 NSString *canonicalizedAmzHeaders = @"";
122 - for (NSString *header in [amzHeaders keyEnumerator]) { 121 + for (NSString *header in [amzHeaders keysSortedByValueUsingSelector:@selector(compare:)]) {
123 canonicalizedAmzHeaders = [NSString stringWithFormat:@"%@%@:%@\n",canonicalizedAmzHeaders,[header lowercaseString],[amzHeaders objectForKey:header]]; 122 canonicalizedAmzHeaders = [NSString stringWithFormat:@"%@%@:%@\n",canonicalizedAmzHeaders,[header lowercaseString],[amzHeaders objectForKey:header]];
124 [self addRequestHeader:header value:[amzHeaders objectForKey:header]]; 123 [self addRequestHeader:header value:[amzHeaders objectForKey:header]];
125 } 124 }
@@ -20,8 +20,6 @@ static NSString *accessKey = @""; @@ -20,8 +20,6 @@ static NSString *accessKey = @"";
20 // You should run these tests on a bucket that does not yet exist 20 // You should run these tests on a bucket that does not yet exist
21 static NSString *bucket = @""; 21 static NSString *bucket = @"";
22 22
23 -  
24 -  
25 // Used for subclass test 23 // Used for subclass test
26 @interface ASIS3ObjectRequestSubclass : ASIS3ObjectRequest {} 24 @interface ASIS3ObjectRequestSubclass : ASIS3ObjectRequest {}
27 @end 25 @end
@@ -220,6 +218,7 @@ static NSString *bucket = @""; @@ -220,6 +218,7 @@ static NSString *bucket = @"";
220 ASIS3ObjectRequest *request = [ASIS3ObjectRequest PUTRequestForFile:filePath withBucket:bucket key:key]; 218 ASIS3ObjectRequest *request = [ASIS3ObjectRequest PUTRequestForFile:filePath withBucket:bucket key:key];
221 [request setSecretAccessKey:secretAccessKey]; 219 [request setSecretAccessKey:secretAccessKey];
222 [request setAccessKey:accessKey]; 220 [request setAccessKey:accessKey];
  221 + [request setStorageClass:ASIS3StorageClassReducedRedundancy];
223 [request startSynchronous]; 222 [request startSynchronous];
224 success = [[request responseString] isEqualToString:@""]; 223 success = [[request responseString] isEqualToString:@""];
225 GHAssertTrue(success,@"Failed to PUT a file to S3"); 224 GHAssertTrue(success,@"Failed to PUT a file to S3");
@@ -785,9 +784,55 @@ static NSString *bucket = @""; @@ -785,9 +784,55 @@ static NSString *bucket = @"";
785 784
786 // DELETE it 785 // DELETE it
787 request = [ASIS3ObjectRequest DELETERequestWithBucket:bucket key:@"king"]; 786 request = [ASIS3ObjectRequest DELETERequestWithBucket:bucket key:@"king"];
  787 + [request setRequestScheme:ASIS3RequestSchemeHTTPS];
788 [request startSynchronous]; 788 [request startSynchronous];
789 success = [[request responseString] isEqualToString:@""]; 789 success = [[request responseString] isEqualToString:@""];
790 - GHAssertTrue(success,@"Failed to DELETE the copy from S3"); 790 + GHAssertTrue(success,@"Failed to DELETE the object from S3");
  791 +
  792 + // Delete the bucket
  793 + request = [ASIS3BucketRequest DELETERequestWithBucket:bucket];
  794 + [request setRequestScheme:ASIS3RequestSchemeHTTPS];
  795 + [request startSynchronous];
  796 + GHAssertNil([request error],@"Failed to delete a bucket");
  797 +
  798 + [ASIS3Request setSharedAccessKey:nil];
  799 + [ASIS3Request setSharedSecretAccessKey:nil];
  800 +}
  801 +
  802 +// Ideally this test would actually parse the ACL XML and check it, but for now it just makes sure S3 doesn't return an error
  803 +- (void)testCannedACLs
  804 +{
  805 + [ASIS3Request setSharedAccessKey:accessKey];
  806 + [ASIS3Request setSharedSecretAccessKey:secretAccessKey];
  807 +
  808 + // Create a bucket
  809 + ASIS3Request *request = [ASIS3BucketRequest PUTRequestWithBucket:bucket];
  810 + [request setRequestScheme:ASIS3RequestSchemeHTTPS];
  811 + [request startSynchronous];
  812 + GHAssertNil([request error],@"Failed to create a bucket");
  813 +
  814 + NSArray *ACLs = [NSArray arrayWithObjects:ASIS3AccessPolicyPrivate,ASIS3AccessPolicyPublicRead,ASIS3AccessPolicyPublicReadWrite,ASIS3AccessPolicyAuthenticatedRead,ASIS3AccessPolicyBucketOwnerRead,ASIS3AccessPolicyBucketOwnerFullControl,nil];
  815 +
  816 + for (NSString *cannedACL in ACLs) {
  817 + // PUT object
  818 + NSString *key = @"king";
  819 + request = [ASIS3ObjectRequest PUTRequestForData:[@"fink" dataUsingEncoding:NSUTF8StringEncoding] withBucket:bucket key:key];
  820 + [request setAccessPolicy:cannedACL];
  821 + [request startSynchronous];
  822 + GHAssertNil([request error],@"Failed to PUT some data into S3");
  823 +
  824 + // GET object ACL
  825 + request = [ASIS3ObjectRequest requestWithBucket:bucket key:key subResource:@"acl"];
  826 + [request startSynchronous];
  827 + GHAssertNil([request error],@"Failed to fetch the object");
  828 + }
  829 +
  830 + // DELETE it
  831 + request = [ASIS3ObjectRequest DELETERequestWithBucket:bucket key:@"king"];
  832 + [request setRequestScheme:ASIS3RequestSchemeHTTPS];
  833 + [request startSynchronous];
  834 + BOOL success = [[request responseString] isEqualToString:@""];
  835 + GHAssertTrue(success,@"Failed to DELETE the object from S3");
791 836
792 // Delete the bucket 837 // Delete the bucket
793 request = [ASIS3BucketRequest DELETERequestWithBucket:bucket]; 838 request = [ASIS3BucketRequest DELETERequestWithBucket:bucket];