Ben Copsey

Added S3 starting point

  1 +//
  2 +// ASIS3Request.h
  3 +// Mac
  4 +//
  5 +// Created by Ben Copsey on 30/06/2009.
  6 +// Copyright 2009 All-Seeing Interactive. All rights reserved.
  7 +//
  8 +
  9 +#import <Foundation/Foundation.h>
  10 +#import "ASIHTTPRequest.h"
  11 +
  12 +@interface ASIS3Request : ASIHTTPRequest {
  13 + NSString *bucket;
  14 + NSString *path;
  15 + NSString *dateString;
  16 + NSString *mimeType;
  17 + NSString *accessKey;
  18 + NSString *secretAccessKey;
  19 +}
  20 +
  21 +#pragma mark Constructors
  22 +
  23 ++ (id)requestWithBucket:(NSString *)bucket path:(NSString *)path;
  24 ++ (id)PUTRequestForFile:(NSString *)filePath withBucket:(NSString *)bucket path:(NSString *)path;
  25 ++ (id)GETRequestWithBucket:(NSString *)bucket path:(NSString *)path;
  26 ++ (id)listRequestWithBucket:(NSString *)bucket prefix:(NSString *)prefix maxResults:(int)maxResults marker:(NSString *)marker;
  27 ++ (id)ACLRequestWithBucket:(NSString *)bucket path:(NSString *)path;
  28 +
  29 +
  30 +- (void)generateS3Headers;
  31 +- (void)setDate:(NSDate *)date;
  32 +
  33 +
  34 ++ (NSString *)mimeTypeForFileAtPath:(NSString *)path;
  35 +
  36 +#pragma mark Shared access keys
  37 ++ (NSString *)sharedAccessKey;
  38 ++ (void)setSharedAccessKey:(NSString *)newAccessKey;
  39 ++ (NSString *)sharedSecretAccessKey;
  40 ++ (void)setSharedSecretAccessKey:(NSString *)newAccessKey;
  41 +
  42 +#pragma mark S3 Authentication helpers
  43 ++ (NSData *)HMACSHA1withKey:(NSString *)key forString:(NSString *)string;
  44 ++ (NSString *)base64forData:(NSData *)theData;
  45 +
  46 +@property (retain) NSString *bucket;
  47 +@property (retain) NSString *path;
  48 +@property (retain) NSString *dateString;
  49 +@property (retain) NSString *mimeType;
  50 +@property (retain) NSString *accessKey;
  51 +@property (retain) NSString *secretAccessKey;
  52 +@end
  1 +//
  2 +// ASIS3Request.m
  3 +// Mac
  4 +//
  5 +// Created by Ben Copsey on 30/06/2009.
  6 +// Copyright 2009 All-Seeing Interactive. All rights reserved.
  7 +//
  8 +
  9 +#import "ASIS3Request.h"
  10 +#import <CommonCrypto/CommonHMAC.h>
  11 +
  12 +static NSString *sharedAccessKey = nil;
  13 +static NSString *sharedSecretAccessKey = nil;
  14 +
  15 +@implementation ASIS3Request
  16 +
  17 +
  18 ++ (id)requestWithBucket:(NSString *)bucket path:(NSString *)path
  19 +{
  20 + ASIS3Request *request = [[[ASIS3Request alloc] initWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"http://%@.s3.amazonaws.com/%@",bucket,path]]] autorelease];
  21 + [request setBucket:bucket];
  22 + [request setPath:path];
  23 + return request;
  24 +}
  25 +
  26 ++ (id)PUTRequestForFile:(NSString *)filePath withBucket:(NSString *)bucket path:(NSString *)path
  27 +{
  28 + ASIS3Request *request = [ASIS3Request requestWithBucket:bucket path:path];
  29 + [request setPostBodyFilePath:filePath];
  30 + [request setShouldStreamPostDataFromDisk:YES];
  31 + [request setRequestMethod:@"PUT"];
  32 + [request setMimeType:[ASIS3Request mimeTypeForFileAtPath:path]];
  33 + return request;
  34 +}
  35 +
  36 ++ (id)GETRequestWithBucket:(NSString *)bucket path:(NSString *)path
  37 +{
  38 + ASIS3Request *request = [ASIS3Request requestWithBucket:bucket path:path];
  39 + [request setRequestMethod:@"GET"];
  40 + return request;
  41 +}
  42 +
  43 ++ (id)ACLRequestWithBucket:(NSString *)bucket path:(NSString *)path
  44 +{
  45 + ASIS3Request *request = [ASIS3Request requestWithBucket:bucket path:[NSString stringWithFormat:@"%@?acl",path]];
  46 + [request setRequestMethod:@"GET"];
  47 + return request;
  48 +}
  49 +
  50 ++ (id)listRequestWithBucket:(NSString *)bucket prefix:(NSString *)prefix maxResults:(int)maxResults marker:(NSString *)marker
  51 +{
  52 + ASIS3Request *request = [[[ASIS3Request alloc] initWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"http://%@.s3.amazonaws.com/?prefix=/%@&max-keys=%hi&marker=%@",bucket,prefix,maxResults,marker]]] autorelease];
  53 + [request setBucket:bucket];
  54 + [request setRequestMethod:@"GET"];
  55 + return request;
  56 +}
  57 +
  58 ++ (NSString *)mimeTypeForFileAtPath:(NSString *)path
  59 +{
  60 +// NSTask does seem to exist in the 2.2.1 SDK, though it's not in the 3.0 SDK. It's probably best if we just use a generic mime type on iPhone all the time.
  61 +#if TARGET_OS_IPHONE
  62 + return @"application/octet-stream";
  63 +#else
  64 + NSTask *task = [[NSTask alloc] init];
  65 + [task setLaunchPath: @"/usr/bin/file"];
  66 + [task setArguments:[NSMutableArray arrayWithObjects:@"-Ib",path,nil]];
  67 +
  68 + NSPipe *pipe = [NSPipe pipe];
  69 + [task setStandardOutput:pipe];
  70 +
  71 + NSFileHandle *file = [pipe fileHandleForReading];
  72 +
  73 + [task launch];
  74 +
  75 + NSString *mimeTypeString = [[[NSString alloc] initWithData:[file readDataToEndOfFile] encoding: NSUTF8StringEncoding] autorelease];
  76 + return [[mimeTypeString componentsSeparatedByString:@";"] objectAtIndex:0];
  77 +#endif
  78 +}
  79 +
  80 +- (void)setDate:(NSDate *)date
  81 +{
  82 + NSDateFormatter *dateFormatter = [[[NSDateFormatter alloc] init] autorelease];
  83 + [dateFormatter setDateFormat:@"EEE, d MMM yyyy HH:mm:ss zzzz"];
  84 + [self setDateString:[dateFormatter stringFromDate:date]];
  85 +}
  86 +
  87 +- (void)generateS3Headers
  88 +{
  89 + if (![self accessKey]) {
  90 + [self setAccessKey:[ASIS3Request sharedAccessKey]];
  91 + }
  92 + if (![self secretAccessKey]) {
  93 + [self setAccessKey:[ASIS3Request sharedSecretAccessKey]];
  94 + }
  95 + if (![self dateString]) {
  96 + [self setDate:[NSDate date]];
  97 + }
  98 + if (![self path]) {
  99 + [self setPath:@""];
  100 + }
  101 +
  102 + [self addRequestHeader:@"Date" value:[self dateString]];
  103 +
  104 + [self addRequestHeader:@"x-amz-acl" value:@"private"];
  105 +
  106 + NSString *stringToSign;
  107 + NSString *canonicalizedResource = [NSString stringWithFormat:@"/%@/%@",[self bucket],[self path]];
  108 + NSString *canonicalizedAmzHeaders = @"x-amz-acl:private";
  109 + if ([[self requestMethod] isEqualToString:@"PUT"]) {
  110 + [self addRequestHeader:@"Content-Type" value:[self mimeType]];
  111 + stringToSign = [NSString stringWithFormat:@"PUT\n\n%@\n%@\n%@",[self mimeType],dateString,canonicalizedResource];
  112 + } else {
  113 + stringToSign = [NSString stringWithFormat:@"%@\n\n\n%@\n%@",[self requestMethod],dateString,canonicalizedResource];
  114 + }
  115 + NSLog(@"%@",stringToSign);
  116 + NSString *signature = [ASIS3Request base64forData:[ASIS3Request HMACSHA1withKey:[self secretAccessKey] forString:stringToSign]];
  117 + NSString *authorizationString = [NSString stringWithFormat:@"AWS %@:%@",[self accessKey],signature];
  118 + [self addRequestHeader:@"Authorization" value:authorizationString];
  119 +
  120 +}
  121 +
  122 +#pragma mark Shared access keys
  123 +
  124 ++ (NSString *)sharedAccessKey
  125 +{
  126 + return sharedAccessKey;
  127 +}
  128 +
  129 ++ (void)setSharedAccessKey:(NSString *)newAccessKey
  130 +{
  131 + [sharedAccessKey release];
  132 + sharedAccessKey = [newAccessKey retain];
  133 +}
  134 +
  135 ++ (NSString *)sharedSecretAccessKey
  136 +{
  137 + return sharedSecretAccessKey;
  138 +}
  139 +
  140 ++ (void)setSharedSecretAccessKey:(NSString *)newAccessKey
  141 +{
  142 + [sharedSecretAccessKey release];
  143 + sharedSecretAccessKey = [newAccessKey retain];
  144 +}
  145 +
  146 +
  147 +#pragma mark S3 Authentication helpers
  148 +
  149 +// From: http://stackoverflow.com/questions/476455/is-there-a-library-for-iphone-to-work-with-hmac-sha-1-encoding
  150 +
  151 ++ (NSData *)HMACSHA1withKey:(NSString *)key forString:(NSString *)string
  152 +{
  153 + NSData *clearTextData = [string dataUsingEncoding:NSUTF8StringEncoding];
  154 + NSData *keyData = [key dataUsingEncoding:NSUTF8StringEncoding];
  155 +
  156 + uint8_t digest[CC_SHA1_DIGEST_LENGTH] = {0};
  157 +
  158 + CCHmacContext hmacContext;
  159 + CCHmacInit(&hmacContext, kCCHmacAlgSHA1, keyData.bytes, keyData.length);
  160 + CCHmacUpdate(&hmacContext, clearTextData.bytes, clearTextData.length);
  161 + CCHmacFinal(&hmacContext, digest);
  162 +
  163 + return [NSData dataWithBytes:digest length:CC_SHA1_DIGEST_LENGTH];
  164 +}
  165 +
  166 +
  167 +// From: http://www.cocoadev.com/index.pl?BaseSixtyFour
  168 +
  169 ++ (NSString*)base64forData:(NSData*)theData {
  170 +
  171 + const uint8_t* input = (const uint8_t*)[theData bytes];
  172 + NSInteger length = [theData length];
  173 +
  174 + static char table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
  175 +
  176 + NSMutableData* data = [NSMutableData dataWithLength:((length + 2) / 3) * 4];
  177 + uint8_t* output = (uint8_t*)data.mutableBytes;
  178 +
  179 + for (NSInteger i = 0; i < length; i += 3) {
  180 + NSInteger value = 0;
  181 + for (NSInteger j = i; j < (i + 3); j++) {
  182 + value <<= 8;
  183 +
  184 + if (j < length) {
  185 + value |= (0xFF & input[j]);
  186 + }
  187 + }
  188 +
  189 + NSInteger index = (i / 3) * 4;
  190 + output[index + 0] = table[(value >> 18) & 0x3F];
  191 + output[index + 1] = table[(value >> 12) & 0x3F];
  192 + output[index + 2] = (i + 1) < length ? table[(value >> 6) & 0x3F] : '=';
  193 + output[index + 3] = (i + 2) < length ? table[(value >> 0) & 0x3F] : '=';
  194 + }
  195 +
  196 + return [[[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding] autorelease];
  197 +}
  198 +
  199 +
  200 +@synthesize bucket;
  201 +@synthesize path;
  202 +@synthesize dateString;
  203 +@synthesize mimeType;
  204 +@synthesize accessKey;
  205 +@synthesize secretAccessKey;
  206 +@end
  1 +//
  2 +// ASIS3RequestTests.h
  3 +// Mac
  4 +//
  5 +// Created by Ben Copsey on 12/07/2009.
  6 +// Copyright 2009 All-Seeing Interactive. All rights reserved.
  7 +//
  8 +
  9 +#if TARGET_OS_IPHONE
  10 +#import "GHUnit.h"
  11 +#else
  12 +#import <GHUnit/GHUnit.h>
  13 +#endif
  14 +
  15 +@interface ASIS3RequestTests : GHTestCase {
  16 +
  17 +}
  18 +
  19 +- (void)testAuthenticationHeaderGeneration;
  20 +
  21 +@end
  1 +//
  2 +// ASIS3RequestTests.m
  3 +// Mac
  4 +//
  5 +// Created by Ben Copsey on 12/07/2009.
  6 +// Copyright 2009 All-Seeing Interactive. All rights reserved.
  7 +//
  8 +
  9 +#import "ASIS3RequestTests.h"
  10 +#import "ASIS3Request.h"
  11 +
  12 +@implementation ASIS3RequestTests
  13 +
  14 +
  15 +- (void)testAuthenticationHeaderGeneration
  16 +{
  17 + // All these tests are based on Amazon's examples at: http://docs.amazonwebservices.com/AmazonS3/2006-03-01/
  18 +
  19 + NSString *secretAccessKey = @"uV3F3YluFJax1cknvbcGwgjvx4QpvB+leU8dUj2o";
  20 + NSString *accessKey = @"0PN5J17HBGZHT7JJ3X82";
  21 + NSString *bucket = @"johnsmith";
  22 +
  23 +
  24 + // Test GET
  25 + NSString *path = @"photos/puppy.jpg";
  26 + NSString *dateString = @"Tue, 27 Mar 2007 19:36:42 +0000";
  27 + ASIS3Request *request = [ASIS3Request requestWithBucket:bucket path:path];
  28 + [request setDateString:dateString];
  29 + [request setSecretAccessKey:secretAccessKey];
  30 + [request setAccessKey:accessKey];
  31 + [request generateS3Headers];
  32 + BOOL success = [[[request requestHeaders] valueForKey:@"Authorization"] isEqualToString:@"AWS 0PN5J17HBGZHT7JJ3X82:xXjDGYUmKxnwqr5KXNPGldn5LbA="];
  33 + GHAssertTrue(success,@"Failed to generate the correct authorisation header for a GET request");
  34 +
  35 + // Test PUT
  36 + path = @"photos/puppy.jpg";
  37 + dateString = @"Tue, 27 Mar 2007 21:15:45 +0000";
  38 + request = [ASIS3Request requestWithBucket:bucket path:path];
  39 + [request setRequestMethod:@"PUT"];
  40 + [request setMimeType:@"image/jpeg"];
  41 + [request setDateString:dateString];
  42 + [request setSecretAccessKey:secretAccessKey];
  43 + [request setAccessKey:accessKey];
  44 + [request generateS3Headers];
  45 + success = [[[request requestHeaders] valueForKey:@"Authorization"] isEqualToString:@"AWS 0PN5J17HBGZHT7JJ3X82:hcicpDDvL9SsO6AkvxqmIWkmOuQ="];
  46 + GHAssertTrue(success,@"Failed to generate the correct authorisation header for a PUT request");
  47 +
  48 + // Test List
  49 + path = @"";
  50 + dateString = @"Tue, 27 Mar 2007 19:42:41 +0000";
  51 + request = [ASIS3Request listRequestWithBucket:bucket prefix:@"photos" maxResults:50 marker:@"puppy"];
  52 + [request setDateString:dateString];
  53 + [request setSecretAccessKey:secretAccessKey];
  54 + [request setAccessKey:accessKey];
  55 + [request generateS3Headers];
  56 + success = [[[request requestHeaders] valueForKey:@"Authorization"] isEqualToString:@"AWS 0PN5J17HBGZHT7JJ3X82:jsRt/rhG+Vtp88HrYL706QhE4w4="];
  57 + GHAssertTrue(success,@"Failed to generate the correct authorisation header for a list request");
  58 +
  59 + // Test fetch ACL
  60 + path = @"";
  61 + dateString = @"Tue, 27 Mar 2007 19:44:46 +0000";
  62 + request = [ASIS3Request ACLRequestWithBucket:bucket path:path];
  63 + [request setDateString:dateString];
  64 + [request setSecretAccessKey:secretAccessKey];
  65 + [request setAccessKey:accessKey];
  66 + [request generateS3Headers];
  67 + success = [[[request requestHeaders] valueForKey:@"Authorization"] isEqualToString:@"AWS 0PN5J17HBGZHT7JJ3X82:thdUi9VAkzhkniLj96JIrOPGi0g="];
  68 + GHAssertTrue(success,@"Failed to generate the correct authorisation header for a list request");
  69 +
  70 + // Test Unicode keys
  71 + // (I think Amazon's name for this example is misleading since this test actually only uses URL encoded strings)
  72 + bucket = @"dictionary";
  73 + path = @"fran%C3%A7ais/pr%c3%a9f%c3%a8re";
  74 + dateString = @"Wed, 28 Mar 2007 01:49:49 +0000";
  75 + request = [ASIS3Request requestWithBucket:bucket path:path];
  76 + [request setDateString:dateString];
  77 + [request setSecretAccessKey:secretAccessKey];
  78 + [request setAccessKey:accessKey];
  79 + [request generateS3Headers];
  80 + success = [[[request requestHeaders] valueForKey:@"Authorization"] isEqualToString:@"AWS 0PN5J17HBGZHT7JJ3X82:dxhSBHoI6eVSPcXJqEghlUzZMnY="];
  81 + GHAssertTrue(success,@"Failed to generate the correct authorisation header for a list request");
  82 +
  83 +}
  84 +
  85 +
  86 +
  87 +
  88 +@end
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.