Showing
6 changed files
with
367 additions
and
0 deletions
Classes/ASIS3Request.h
0 → 100644
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 |
Classes/ASIS3Request.m
0 → 100644
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 |
Classes/Tests/ASIS3RequestTests.h
0 → 100644
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 |
Classes/Tests/ASIS3RequestTests.m
0 → 100644
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.
-
Please register or login to post a comment