Ben Copsey

Added support for S3 storage class (eg Reduced Redundancy)

Added tests for canned ACLs
... ... @@ -10,6 +10,9 @@
#import <Foundation/Foundation.h>
#import "ASIS3Request.h"
// Constants for storage class
extern NSString *const ASIS3StorageClassStandard;
extern NSString *const ASIS3StorageClassReducedRedundancy;
@interface ASIS3ObjectRequest : ASIS3Request {
... ... @@ -28,7 +31,14 @@
// Can be autodetected when PUTing a file from disk, will default to 'application/octet-stream' when PUTing data
NSString *mimeType;
// Set this to specify you want to work with a particular subresource (eg an acl for that resource)
// See requestWithBucket:key:subResource:, below.
NSString* subResource;
// The storage class to be used for PUT requests
// Set this to ASIS3StorageClassReducedRedundancy to save money on storage, at (presumably) a slightly higher risk you will lose the data
// If this is not set, no x-amz-storage-class header will be sent to S3, and their default will be used
NSString *storageClass;
}
// Create a request, building an appropriate url
... ... @@ -66,5 +76,5 @@
@property (retain, nonatomic) NSString *sourceKey;
@property (retain, nonatomic) NSString *mimeType;
@property (retain, nonatomic) NSString *subResource;
@property (retain, nonatomic) NSString *storageClass;
@end
... ...
... ... @@ -8,6 +8,8 @@
#import "ASIS3ObjectRequest.h"
NSString *const ASIS3StorageClassStandard = @"STANDARD";
NSString *const ASIS3StorageClassReducedRedundancy = @"REDUCED_REDUNDANCY";
@implementation ASIS3ObjectRequest
... ... @@ -96,13 +98,14 @@
[sourceKey release];
[sourceBucket release];
[subResource release];
[storageClass release];
[super dealloc];
}
- (void)buildURL
{
if ([self subResource]) {
[self setURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@://%@.%@%@?%@",[self requestScheme],[[self class] S3Host],[ASIS3Request stringByURLEncodingForS3Path:[self key]],[self subResource]]]];
[self setURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@://%@.%@%@?%@",[self requestScheme],[self bucket],[[self class] S3Host],[ASIS3Request stringByURLEncodingForS3Path:[self key]],[self subResource]]]];
} else {
[self setURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@://%@.%@%@",[self requestScheme],[self bucket],[[self class] S3Host],[ASIS3Request stringByURLEncodingForS3Path:[self key]]]]];
}
... ... @@ -134,6 +137,9 @@
NSString *path = [ASIS3Request stringByURLEncodingForS3Path:[self sourceKey]];
[headers setObject:[[self sourceBucket] stringByAppendingString:path] forKey:@"x-amz-copy-source"];
}
if ([self storageClass]) {
[headers setObject:[self storageClass] forKey:@"x-amz-storage-class"];
}
return headers;
}
... ... @@ -146,12 +152,11 @@
return [super stringToSignForHeaders:canonicalizedAmzHeaders resource:canonicalizedResource];
}
@synthesize bucket;
@synthesize key;
@synthesize sourceBucket;
@synthesize sourceKey;
@synthesize mimeType;
@synthesize subResource;
@synthesize storageClass;
@end
... ...
... ... @@ -23,13 +23,18 @@ extern NSString *const ASIS3AccessPolicyAuthenticatedRead;
extern NSString *const ASIS3AccessPolicyBucketOwnerRead;
extern NSString *const ASIS3AccessPolicyBucketOwnerFullControl;
// Constants for requestScheme - defaults is ASIS3RequestSchemeHTTP
extern NSString *const ASIS3RequestSchemeHTTP;
extern NSString *const ASIS3RequestSchemeHTTPS;
typedef enum _ASIS3ErrorType {
ASIS3ResponseParsingFailedType = 1,
ASIS3ResponseErrorType = 2
} ASIS3ErrorType;
extern NSString *const ASIS3RequestSchemeHTTP;
extern NSString *const ASIS3RequestSchemeHTTPS;
@interface ASIS3Request : ASIHTTPRequest <NSCopying, NSXMLParserDelegate> {
... ...
... ... @@ -19,7 +19,6 @@ NSString *const ASIS3AccessPolicyBucketOwnerFullControl = @"bucket-owner-full-co
NSString *const ASIS3RequestSchemeHTTP = @"http";
NSString *const ASIS3RequestSchemeHTTPS = @"https";
static NSString *sharedAccessKey = nil;
static NSString *sharedSecretAccessKey = nil;
... ... @@ -119,7 +118,7 @@ static NSString *sharedSecretAccessKey = nil;
// 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 = [self S3Headers];
NSString *canonicalizedAmzHeaders = @"";
for (NSString *header in [amzHeaders keyEnumerator]) {
for (NSString *header in [amzHeaders keysSortedByValueUsingSelector:@selector(compare:)]) {
canonicalizedAmzHeaders = [NSString stringWithFormat:@"%@%@:%@\n",canonicalizedAmzHeaders,[header lowercaseString],[amzHeaders objectForKey:header]];
[self addRequestHeader:header value:[amzHeaders objectForKey:header]];
}
... ...
... ... @@ -20,8 +20,6 @@ static NSString *accessKey = @"";
// You should run these tests on a bucket that does not yet exist
static NSString *bucket = @"";
// Used for subclass test
@interface ASIS3ObjectRequestSubclass : ASIS3ObjectRequest {}
@end
... ... @@ -220,6 +218,7 @@ static NSString *bucket = @"";
ASIS3ObjectRequest *request = [ASIS3ObjectRequest PUTRequestForFile:filePath withBucket:bucket key:key];
[request setSecretAccessKey:secretAccessKey];
[request setAccessKey:accessKey];
[request setStorageClass:ASIS3StorageClassReducedRedundancy];
[request startSynchronous];
success = [[request responseString] isEqualToString:@""];
GHAssertTrue(success,@"Failed to PUT a file to S3");
... ... @@ -785,9 +784,55 @@ static NSString *bucket = @"";
// DELETE it
request = [ASIS3ObjectRequest DELETERequestWithBucket:bucket key:@"king"];
[request setRequestScheme:ASIS3RequestSchemeHTTPS];
[request startSynchronous];
success = [[request responseString] isEqualToString:@""];
GHAssertTrue(success,@"Failed to DELETE the copy from S3");
GHAssertTrue(success,@"Failed to DELETE the object from S3");
// Delete the bucket
request = [ASIS3BucketRequest DELETERequestWithBucket:bucket];
[request setRequestScheme:ASIS3RequestSchemeHTTPS];
[request startSynchronous];
GHAssertNil([request error],@"Failed to delete a bucket");
[ASIS3Request setSharedAccessKey:nil];
[ASIS3Request setSharedSecretAccessKey:nil];
}
// 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
- (void)testCannedACLs
{
[ASIS3Request setSharedAccessKey:accessKey];
[ASIS3Request setSharedSecretAccessKey:secretAccessKey];
// Create a bucket
ASIS3Request *request = [ASIS3BucketRequest PUTRequestWithBucket:bucket];
[request setRequestScheme:ASIS3RequestSchemeHTTPS];
[request startSynchronous];
GHAssertNil([request error],@"Failed to create a bucket");
NSArray *ACLs = [NSArray arrayWithObjects:ASIS3AccessPolicyPrivate,ASIS3AccessPolicyPublicRead,ASIS3AccessPolicyPublicReadWrite,ASIS3AccessPolicyAuthenticatedRead,ASIS3AccessPolicyBucketOwnerRead,ASIS3AccessPolicyBucketOwnerFullControl,nil];
for (NSString *cannedACL in ACLs) {
// PUT object
NSString *key = @"king";
request = [ASIS3ObjectRequest PUTRequestForData:[@"fink" dataUsingEncoding:NSUTF8StringEncoding] withBucket:bucket key:key];
[request setAccessPolicy:cannedACL];
[request startSynchronous];
GHAssertNil([request error],@"Failed to PUT some data into S3");
// GET object ACL
request = [ASIS3ObjectRequest requestWithBucket:bucket key:key subResource:@"acl"];
[request startSynchronous];
GHAssertNil([request error],@"Failed to fetch the object");
}
// DELETE it
request = [ASIS3ObjectRequest DELETERequestWithBucket:bucket key:@"king"];
[request setRequestScheme:ASIS3RequestSchemeHTTPS];
[request startSynchronous];
BOOL success = [[request responseString] isEqualToString:@""];
GHAssertTrue(success,@"Failed to DELETE the object from S3");
// Delete the bucket
request = [ASIS3BucketRequest DELETERequestWithBucket:bucket];
... ...