├── AFAmazonS3Client.podspec ├── LICENSE ├── README.md └── AFAmazonS3Client ├── AFAmazonS3Client.h └── AFAmazonS3Client.m /AFAmazonS3Client.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "AFAmazonS3Client" 3 | s.version = "0.3.0" 4 | s.summary = "AFNetworking Client for the Amazon S3 API." 5 | s.homepage = "https://github.com/AFNetworking/AFAmazonS3Client" 6 | s.license = 'MIT' 7 | s.author = { "Mattt Thompson" => "m@mattt.me" } 8 | s.source = { :git => "https://github.com/AFNetworking/AFAmazonS3Client.git", 9 | :tag => "0.3.0" } 10 | 11 | s.source_files = 'AFAmazonS3Client' 12 | s.requires_arc = true 13 | 14 | s.dependency 'AFNetworking', '~> 1.3' 15 | end 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 Mattt Thompson (http://mattt.me/) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AFAmazonS3Client 2 | 3 | `AFAmazonS3Client` is an `AFHTTPClient` subclass for interacting with the [Amazon S3 API](http://aws.amazon.com/s3/). 4 | 5 | As the S3 API returns XML responses, you may find it useful to include [AFKissXMLRequestOperation](https://github.com/AFNetworking/AFKissXMLRequestOperation) (just remember to do `-registerHTTPOperationClass:`) 6 | 7 | **Caution:** This code is still in its early stages of development, so exercise caution when incorporating this into production code. 8 | 9 | ## Example Usage 10 | 11 | ```objective-c 12 | AFAmazonS3Client *s3Client = [[AFAmazonS3Client alloc] initWithAccessKeyID:@"..." secret:@"..."]; 13 | s3Client.bucket = @"my-bucket-name"; 14 | [s3Client postObjectWithFile:@"/path/to/file" destinationPath:@"https://s3.amazonaws.com/example" parameters:nil progress:^(NSUInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite) { 15 | NSLog(@"%f%% Uploaded", (totalBytesWritten / (totalBytesExpectedToWrite * 1.0f) * 100)); 16 | } success:^(id responseObject) { 17 | NSLog(@"Upload Complete"); 18 | } failure:^(NSError *error) { 19 | NSLog(@"Error: %@", error); 20 | }]; 21 | ``` 22 | 23 | ## Contact 24 | 25 | Mattt Thompson 26 | 27 | - http://github.com/mattt 28 | - http://twitter.com/mattt 29 | - m@mattt.me 30 | 31 | ## License 32 | 33 | AFAmazonS3Client is available under the MIT license. See the LICENSE file for more info. 34 | -------------------------------------------------------------------------------- /AFAmazonS3Client/AFAmazonS3Client.h: -------------------------------------------------------------------------------- 1 | // 2 | // AFAmazonS3Client.h 3 | // 4 | // Copyright (c) 2012 Mattt Thompson (http://mattt.me/) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | 24 | #import "AFHTTPClient.h" 25 | 26 | /** 27 | AFAmazonS3Client` is an `AFHTTPClient` subclass for interacting with the Amazon S3 webservice API (http://aws.amazon.com/s3/). 28 | */ 29 | @interface AFAmazonS3Client : AFHTTPClient 30 | 31 | /** 32 | The base URL for the HTTP client. 33 | 34 | @discussion By default, the `baseURL` of `AFAmazonS3Client` is derived from the `bucket` and `region` values. If `baseURL` is set directly, it will override the default `baseURL` and disregard any `bucket` or `region` property. 35 | */ 36 | @property (nonatomic, strong) NSURL *baseURL; 37 | 38 | /** 39 | The S3 bucket for the client. `nil` by default. 40 | 41 | @see `AFAmazonS3Client -baseURL` 42 | */ 43 | @property (nonatomic, copy) NSString *bucket; 44 | 45 | /** 46 | The AWS region for the client. `AFAmazonS3USStandardRegion` by default. See "AWS Regions" for defined constant values. 47 | 48 | @see `AFAmazonS3Client -baseURL` 49 | */ 50 | @property (nonatomic, copy) NSString *region; 51 | 52 | /** 53 | Initializes and returns a newly allocated Amazon S3 client with specified credentials. 54 | 55 | This is the designated initializer. 56 | 57 | @param accessKey The AWS access key. 58 | @param secret The AWS secret. 59 | */ 60 | - (id)initWithAccessKeyID:(NSString *)accessKey 61 | secret:(NSString *)secret; 62 | 63 | /** 64 | Returns the AWS authorization HTTP header fields for the specified request. 65 | 66 | @param request The request. 67 | 68 | @return A dictionary of HTTP header fields values for `Authorization` and `Date`. 69 | */ 70 | - (NSDictionary *)authorizationHeadersForRequest:(NSMutableURLRequest *)request; 71 | 72 | /** 73 | Creates and enqueues a request operation to the client's operation queue. 74 | 75 | @param method The HTTP method for the request. 76 | @param path The path to be appended to the HTTP client's base URL and used as the request URL. 77 | @param success A block object to be executed when the request operation finishes successfully. This block has no return value and takes a single argument: the response object from the server. 78 | @param failure A block object to be executed when the request operation finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes a single argument: the `NSError` object describing error that occurred. 79 | */ 80 | - (void)enqueueS3RequestOperationWithMethod:(NSString *)method 81 | path:(NSString *)path 82 | parameters:(NSDictionary *)parameters 83 | success:(void (^)(id responseObject))success 84 | failure:(void (^)(NSError *error))failure; 85 | 86 | ///------------------------- 87 | /// @name Service Operations 88 | ///------------------------- 89 | 90 | /** 91 | Returns a list of all buckets owned by the authenticated request sender. 92 | 93 | @param success A block object to be executed when the request operation finishes successfully. This block has no return value and takes a single argument: the response object from the server. 94 | @param failure A block object to be executed when the request operation finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes a single argument: the `NSError` object describing error that occurred. 95 | */ 96 | - (void)getServiceWithSuccess:(void (^)(id responseObject))success 97 | failure:(void (^)(NSError *error))failure; 98 | 99 | 100 | ///------------------------ 101 | /// @name Bucket Operations 102 | ///------------------------ 103 | 104 | /** 105 | Lists information about the objects in a bucket for a user that has read access to the bucket. 106 | 107 | @param bucket The S3 bucket to get. 108 | @param success A block object to be executed when the request operation finishes successfully. This block has no return value and takes a single argument: the response object from the server. 109 | @param failure A block object to be executed when the request operation finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes a single argument: the `NSError` object describing error that occurred. 110 | */ 111 | - (void)getBucket:(NSString *)bucket 112 | success:(void (^)(id responseObject))success 113 | failure:(void (^)(NSError *error))failure; 114 | 115 | /** 116 | Creates a new bucket belonging to the account of the authenticated request sender. Optionally, you can specify a EU (Ireland) or US-West (N. California) location constraint. 117 | 118 | @param bucket The S3 bucket to create. 119 | @param parameters The parameters to be encoded and set in the request HTTP body. 120 | @param success A block object to be executed when the request operation finishes successfully. This block has no return value and takes a single argument: the response object from the server. 121 | @param failure A block object to be executed when the request operation finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes a single argument: the `NSError` object describing error that occurred. 122 | */ 123 | - (void)putBucket:(NSString *)bucket 124 | parameters:(NSDictionary *)parameters 125 | success:(void (^)(id responseObject))success 126 | failure:(void (^)(NSError *error))failure; 127 | 128 | /** 129 | Deletes the specified bucket. All objects in the bucket must be deleted before the bucket itself can be deleted. 130 | 131 | @param bucket The S3 bucket to be delete. 132 | @param parameters The parameters to be encoded and set in the request HTTP body. 133 | @param success A block object to be executed when the request operation finishes successfully. This block has no return value and takes a single argument: the response object from the server. 134 | @param failure A block object to be executed when the request operation finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes a single argument: the `NSError` object describing error that occurred. 135 | */ 136 | - (void)deleteBucket:(NSString *)bucket 137 | success:(void (^)(id responseObject))success 138 | failure:(void (^)(NSError *error))failure; 139 | 140 | ///---------------------------------------------- 141 | /// @name Object Operations 142 | ///---------------------------------------------- 143 | 144 | /** 145 | Retrieves information about an object for a user with read access without fetching the object. 146 | 147 | @param path The object path. 148 | @param success A block object to be executed when the request operation finishes successfully. This block has no return value and takes a single argument: the response object from the server. 149 | @param failure A block object to be executed when the request operation finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes a single argument: the `NSError` object describing error that occurred. 150 | */ 151 | - (void)headObjectWithPath:(NSString *)path 152 | success:(void (^)(id responseObject))success 153 | failure:(void (^)(NSError *error))failure; 154 | 155 | /** 156 | Gets an object for a user that has read access to the object. 157 | 158 | @param path The object path. 159 | @param progress A block object to be called when an undetermined number of bytes have been downloaded from the server. This block has no return value and takes three arguments: the number of bytes read since the last time the download progress block was called, the total bytes read, and the total bytes expected to be read during the request, as initially determined by the expected content size of the `NSHTTPURLResponse` object. This block may be called multiple times, and will execute on the main thread. 160 | @param success A block object to be executed when the request operation finishes successfully. This block has no return value and takes a single argument: the response object from the server. 161 | @param failure A block object to be executed when the request operation finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes a single argument: the `NSError` object describing error that occurred. 162 | */ 163 | - (void)getObjectWithPath:(NSString *)path 164 | progress:(void (^)(NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead))progress 165 | success:(void (^)(id responseObject, NSData *responseData))success 166 | failure:(void (^)(NSError *error))failure; 167 | 168 | /** 169 | Gets an object for a user that has read access to the object. 170 | 171 | @param path The object path. 172 | @param outputStream The `NSOutputStream` object receiving data from the request. 173 | @param progress A block object to be called when an undetermined number of bytes have been downloaded from the server. This block has no return value and takes three arguments: the number of bytes read since the last time the download progress block was called, the total bytes read, and the total bytes expected to be read during the request, as initially determined by the expected content size of the `NSHTTPURLResponse` object. This block may be called multiple times, and will execute on the main thread. 174 | @param success A block object to be executed when the request operation finishes successfully. This block has no return value and takes a single argument: the response object from the server. 175 | @param failure A block object to be executed when the request operation finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes a single argument: the `NSError` object describing error that occurred. 176 | */ 177 | - (void)getObjectWithPath:(NSString *)path 178 | outputStream:(NSOutputStream *)outputStream 179 | progress:(void (^)(NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead))progress 180 | success:(void (^)(id responseObject))success 181 | failure:(void (^)(NSError *error))failure; 182 | 183 | /** 184 | Adds an object to a bucket using forms. 185 | 186 | @param path The path to the local file. 187 | @param destinationPath The destination path for the remote file. 188 | @param parameters The parameters to be encoded and set in the request HTTP body. 189 | @param progress A block object to be called when an undetermined number of bytes have been uploaded to the server. This block has no return value and takes three arguments: the number of bytes written since the last time the upload progress block was called, the total bytes written, and the total bytes expected to be written during the request, as initially determined by the length of the HTTP body. This block may be called multiple times, and will execute on the main thread. 190 | @param success A block object to be executed when the request operation finishes successfully. This block has no return value and takes a single argument: the response object from the server. 191 | @param failure A block object to be executed when the request operation finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes a single argument: the `NSError` object describing error that occurred. 192 | */ 193 | - (void)postObjectWithFile:(NSString *)path 194 | destinationPath:(NSString *)destinationPath 195 | parameters:(NSDictionary *)parameters 196 | progress:(void (^)(NSUInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite))progress 197 | success:(void (^)(id responseObject))success 198 | failure:(void (^)(NSError *error))failure; 199 | 200 | /** 201 | Adds an object to a bucket for a user that has write access to the bucket. A success response indicates the object was successfully stored; if the object already exists, it will be overwritten. 202 | 203 | @param path The path to the local file. 204 | @param destinationPath The destination path for the remote file. 205 | @param parameters The parameters to be encoded and set in the request HTTP body. 206 | @param progress A block object to be called when an undetermined number of bytes have been uploaded to the server. This block has no return value and takes three arguments: the number of bytes written since the last time the upload progress block was called, the total bytes written, and the total bytes expected to be written during the request, as initially determined by the length of the HTTP body. This block may be called multiple times, and will execute on the main thread. 207 | @param success A block object to be executed when the request operation finishes successfully. This block has no return value and takes a single argument: the response object from the server. 208 | @param failure A block object to be executed when the request operation finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes a single argument: the `NSError` object describing error that occurred. 209 | */ 210 | - (void)putObjectWithFile:(NSString *)path 211 | destinationPath:(NSString *)destinationPath 212 | parameters:(NSDictionary *)parameters 213 | progress:(void (^)(NSUInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite))progress 214 | success:(void (^)(id responseObject))success 215 | failure:(void (^)(NSError *error))failure; 216 | 217 | /** 218 | Deletes the specified object. Once deleted, there is no method to restore or undelete an object. 219 | 220 | @param path The path for the remote file to be deleted. 221 | @param success A block object to be executed when the request operation finishes successfully. This block has no return value and takes a single argument: the response object from the server. 222 | @param failure A block object to be executed when the request operation finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes a single argument: the `NSError` object describing error that occurred. 223 | */ 224 | - (void)deleteObjectWithPath:(NSString *)path 225 | success:(void (^)(id responseObject))success 226 | failure:(void (^)(NSError *error))failure; 227 | 228 | @end 229 | 230 | ///---------------- 231 | /// @name Constants 232 | ///---------------- 233 | 234 | /** 235 | ## AWS Regions 236 | 237 | The following AWS regions are defined: 238 | 239 | `AFAmazonS3USStandardRegion`: US Standard (s3.amazonaws.com); 240 | `AFAmazonS3USWest1Region`: US West (Oregon) Region (s3-us-west-1.amazonaws.com) 241 | `AFAmazonS3USWest2Region`: US West (Northern California) Region (s3-us-west-2.amazonaws.com) 242 | `AFAmazonS3EUWest1Region`: EU (Ireland) Region (s3-eu-west-1.amazonaws.com) 243 | `AFAmazonS3APSoutheast1Region`: Asia Pacific (Singapore) Region (s3-ap-southeast-1.amazonaws.com) 244 | `AFAmazonS3APSoutheast2Region`: Asia Pacific (Sydney) Region (s3-ap-southeast-2.amazonaws.com) 245 | `AFAmazonS3APNortheast2Region`: Asia Pacific (Tokyo) Region (s3-ap-northeast-1.amazonaws.com) 246 | `AFAmazonS3SAEast1Region`: South America (Sao Paulo) Region (s3-sa-east-1.amazonaws.com) 247 | 248 | For a full list of available regions, see http://docs.aws.amazon.com/general/latest/gr/rande.html#s3_region 249 | */ 250 | extern NSString * const AFAmazonS3USStandardRegion; 251 | extern NSString * const AFAmazonS3USWest1Region; 252 | extern NSString * const AFAmazonS3USWest2Region; 253 | extern NSString * const AFAmazonS3EUWest1Region; 254 | extern NSString * const AFAmazonS3APSoutheast1Region; 255 | extern NSString * const AFAmazonS3APSoutheast2Region; 256 | extern NSString * const AFAmazonS3APNortheast2Region; 257 | extern NSString * const AFAmazonS3SAEast1Region; 258 | -------------------------------------------------------------------------------- /AFAmazonS3Client/AFAmazonS3Client.m: -------------------------------------------------------------------------------- 1 | // 2 | // AFAmazonS3Client.m 3 | // 4 | // Copyright (c) 2012 Mattt Thompson (http://mattt.me/) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | 24 | #import 25 | 26 | #import "AFAmazonS3Client.h" 27 | #import "AFXMLRequestOperation.h" 28 | 29 | static NSString * const AFAmazonS3ClientDefaultBaseURLString = @"http://s3.amazonaws.com"; 30 | 31 | NSString * const AFAmazonS3USStandardRegion = @"s3.amazonaws.com"; 32 | NSString * const AFAmazonS3USWest1Region = @"s3-us-west-1.amazonaws.com"; 33 | NSString * const AFAmazonS3USWest2Region = @"s3-us-west-2.amazonaws.com"; 34 | NSString * const AFAmazonS3EUWest1Region = @"s3-eu-west-1.amazonaws.com"; 35 | NSString * const AFAmazonS3APSoutheast1Region = @"s3-ap-southeast-1.amazonaws.com"; 36 | NSString * const AFAmazonS3APSoutheast2Region = @"s3-ap-southeast-2.amazonaws.com"; 37 | NSString * const AFAmazonS3APNortheast2Region = @"s3-ap-northeast-1.amazonaws.com"; 38 | NSString * const AFAmazonS3SAEast1Region = @"s3-sa-east-1.amazonaws.com"; 39 | 40 | static NSString * AFAmazonS3BaseURLStringWithBucketInRegion(NSString *bucket, NSString *region) { 41 | if (!region) { 42 | region = AFAmazonS3USStandardRegion; 43 | } 44 | 45 | if (!bucket) { 46 | return [NSString stringWithFormat:@"http://%@", region]; 47 | } else { 48 | return [NSString stringWithFormat:@"http://%@.%@", bucket, region]; 49 | } 50 | } 51 | 52 | static NSData * AFHMACSHA1EncodedDataFromStringWithKey(NSString *string, NSString *key) { 53 | NSData *data = [string dataUsingEncoding:NSASCIIStringEncoding]; 54 | CCHmacContext context; 55 | const char *keyCString = [key cStringUsingEncoding:NSASCIIStringEncoding]; 56 | 57 | CCHmacInit(&context, kCCHmacAlgSHA1, keyCString, strlen(keyCString)); 58 | CCHmacUpdate(&context, [data bytes], [data length]); 59 | 60 | unsigned char digestRaw[CC_SHA1_DIGEST_LENGTH]; 61 | NSInteger digestLength = CC_SHA1_DIGEST_LENGTH; 62 | 63 | CCHmacFinal(&context, digestRaw); 64 | 65 | return [NSData dataWithBytes:digestRaw length:digestLength]; 66 | } 67 | 68 | static NSString * AFRFC822FormatStringFromDate(NSDate *date) { 69 | NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; 70 | [dateFormatter setTimeZone:[NSTimeZone timeZoneWithName:@"GMT"]]; 71 | [dateFormatter setDateFormat:@"EEE, dd MMM yyyy HH:mm:ss z"]; 72 | [dateFormatter setLocale:[[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]]; 73 | 74 | return [dateFormatter stringFromDate:date]; 75 | } 76 | 77 | NSString * AFBase64EncodedStringFromData(NSData *data) { 78 | NSUInteger length = [data length]; 79 | NSMutableData *mutableData = [NSMutableData dataWithLength:((length + 2) / 3) * 4]; 80 | 81 | uint8_t *input = (uint8_t *)[data bytes]; 82 | uint8_t *output = (uint8_t *)[mutableData mutableBytes]; 83 | 84 | for (NSUInteger i = 0; i < length; i += 3) { 85 | NSUInteger value = 0; 86 | for (NSUInteger j = i; j < (i + 3); j++) { 87 | value <<= 8; 88 | if (j < length) { 89 | value |= (0xFF & input[j]); 90 | } 91 | } 92 | 93 | static uint8_t const kAFBase64EncodingTable[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 94 | 95 | NSUInteger idx = (i / 3) * 4; 96 | output[idx + 0] = kAFBase64EncodingTable[(value >> 18) & 0x3F]; 97 | output[idx + 1] = kAFBase64EncodingTable[(value >> 12) & 0x3F]; 98 | output[idx + 2] = (i + 1) < length ? kAFBase64EncodingTable[(value >> 6) & 0x3F] : '='; 99 | output[idx + 3] = (i + 2) < length ? kAFBase64EncodingTable[(value >> 0) & 0x3F] : '='; 100 | } 101 | 102 | return [[NSString alloc] initWithData:mutableData encoding:NSASCIIStringEncoding]; 103 | } 104 | 105 | #pragma mark - 106 | 107 | @interface AFAmazonS3Client () 108 | @property (readwrite, nonatomic, copy) NSString *accessKey; 109 | @property (readwrite, nonatomic, copy) NSString *secret; 110 | 111 | - (void)setObjectWithMethod:(NSString *)method 112 | file:(NSString *)filePath 113 | destinationPath:(NSString *)destinationPath 114 | parameters:(NSDictionary *)parameters 115 | progress:(void (^)(NSUInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite))progressBlock 116 | success:(void (^)(id responseObject))success 117 | failure:(void (^)(NSError *error))failure; 118 | @end 119 | 120 | @implementation AFAmazonS3Client 121 | @synthesize baseURL = _s3_baseURL; 122 | @synthesize bucket = _bucket; 123 | @synthesize region = _region; 124 | @synthesize accessKey = _accessKey; 125 | @synthesize secret = _secret; 126 | 127 | - (id)initWithAccessKeyID:(NSString *)accessKey 128 | secret:(NSString *)secret 129 | { 130 | self = [self initWithBaseURL:[NSURL URLWithString:AFAmazonS3ClientDefaultBaseURLString]]; 131 | if (!self) { 132 | return nil; 133 | } 134 | 135 | // Workaround for designated initializer of subclass 136 | self.baseURL = nil; 137 | 138 | self.accessKey = accessKey; 139 | self.secret = secret; 140 | 141 | return self; 142 | } 143 | 144 | - (id)initWithBaseURL:(NSURL *)url { 145 | self = [super initWithBaseURL:url]; 146 | if (!self) { 147 | return nil; 148 | } 149 | 150 | [self registerHTTPOperationClass:[AFXMLRequestOperation class]]; 151 | 152 | return self; 153 | } 154 | 155 | - (NSURL *)baseURL { 156 | if (!_s3_baseURL) { 157 | return [NSURL URLWithString:AFAmazonS3BaseURLStringWithBucketInRegion(self.bucket, self.region)]; 158 | } 159 | 160 | return _s3_baseURL; 161 | } 162 | 163 | - (void)setBucket:(NSString *)bucket { 164 | [self willChangeValueForKey:@"baseURL"]; 165 | [self willChangeValueForKey:@"bucket"]; 166 | _bucket = bucket; 167 | [self didChangeValueForKey:@"bucket"]; 168 | [self didChangeValueForKey:@"baseURL"]; 169 | } 170 | 171 | - (void)setRegion:(NSString *)region { 172 | [self willChangeValueForKey:@"baseURL"]; 173 | [self willChangeValueForKey:@"region"]; 174 | _region = region; 175 | [self didChangeValueForKey:@"region"]; 176 | [self didChangeValueForKey:@"baseURL"]; 177 | } 178 | 179 | - (NSDictionary *)authorizationHeadersForRequest:(NSMutableURLRequest *)request { 180 | if (self.accessKey && self.secret) { 181 | // Long header values that are subject to "folding" should split into new lines according to AWS's documentation. 182 | NSMutableDictionary *mutableAMZHeaderFields = [NSMutableDictionary dictionary]; 183 | [[request allHTTPHeaderFields] enumerateKeysAndObjectsUsingBlock:^(NSString *key, id value, BOOL *stop) { 184 | key = [key lowercaseString]; 185 | if ([key hasPrefix:@"x-amz"]) { 186 | if ([mutableAMZHeaderFields objectForKey:key]) { 187 | value = [[mutableAMZHeaderFields objectForKey:key] stringByAppendingFormat:@",%@", value]; 188 | } 189 | [mutableAMZHeaderFields setObject:value forKey:key]; 190 | } 191 | }]; 192 | 193 | NSMutableString *mutableCanonicalizedAMZHeaderString = [NSMutableString string]; 194 | for (NSString *key in [[mutableAMZHeaderFields allKeys] sortedArrayUsingSelector:@selector(compare:)]) { 195 | id value = [mutableAMZHeaderFields objectForKey:key]; 196 | [mutableCanonicalizedAMZHeaderString appendFormat:@"%@:%@\n", key, value]; 197 | } 198 | 199 | NSString *canonicalizedResource = [NSString stringWithFormat:@"/%@%@", self.bucket, request.URL.path]; 200 | NSString *method = [request HTTPMethod]; 201 | NSString *contentMD5 = [request valueForHTTPHeaderField:@"Content-MD5"]; 202 | NSString *contentType = [request valueForHTTPHeaderField:@"Content-Type"]; 203 | NSString *date = AFRFC822FormatStringFromDate([NSDate date]); 204 | 205 | NSMutableString *mutableString = [NSMutableString string]; 206 | [mutableString appendFormat:@"%@\n", (method) ? method : @""]; 207 | [mutableString appendFormat:@"%@\n", (contentMD5) ? contentMD5 : @""]; 208 | [mutableString appendFormat:@"%@\n", (contentType) ? contentType : @""]; 209 | [mutableString appendFormat:@"%@\n", (date) ? date : @""]; 210 | [mutableString appendFormat:@"%@", mutableCanonicalizedAMZHeaderString]; 211 | [mutableString appendFormat:@"%@", canonicalizedResource]; 212 | 213 | NSData *hmac = AFHMACSHA1EncodedDataFromStringWithKey(mutableString, self.secret); 214 | NSString *signature = AFBase64EncodedStringFromData(hmac); 215 | 216 | return @{@"Authorization": [NSString stringWithFormat:@"AWS %@:%@", self.accessKey, signature], 217 | @"Date": (date) ? date : @"" 218 | }; 219 | } 220 | 221 | return nil; 222 | } 223 | 224 | #pragma mark - 225 | 226 | - (void)enqueueS3RequestOperationWithMethod:(NSString *)method 227 | path:(NSString *)path 228 | parameters:(NSDictionary *)parameters 229 | success:(void (^)(id responseObject))success 230 | failure:(void (^)(NSError *error))failure 231 | { 232 | NSURLRequest *request = [self requestWithMethod:method path:path parameters:parameters]; 233 | 234 | AFHTTPRequestOperation *requestOperation = [self HTTPRequestOperationWithRequest:request success:^(AFHTTPRequestOperation *operation, id responseObject) { 235 | if (success) { 236 | success(responseObject); 237 | } 238 | } failure:^(AFHTTPRequestOperation *operation, NSError *error) { 239 | if (failure) { 240 | failure(error); 241 | } 242 | }]; 243 | 244 | [self enqueueHTTPRequestOperation:requestOperation]; 245 | } 246 | 247 | 248 | #pragma mark Service Operations 249 | 250 | - (void)getServiceWithSuccess:(void (^)(id responseObject))success 251 | failure:(void (^)(NSError *error))failure 252 | { 253 | [self enqueueS3RequestOperationWithMethod:@"GET" path:@"/" parameters:nil success:success failure:failure]; 254 | } 255 | 256 | #pragma mark Bucket Operations 257 | 258 | - (void)getBucket:(NSString *)bucket 259 | success:(void (^)(id responseObject))success 260 | failure:(void (^)(NSError *error))failure 261 | { 262 | [self enqueueS3RequestOperationWithMethod:@"GET" path:bucket parameters:nil success:success failure:failure]; 263 | } 264 | 265 | - (void)putBucket:(NSString *)bucket 266 | parameters:(NSDictionary *)parameters 267 | success:(void (^)(id responseObject))success 268 | failure:(void (^)(NSError *error))failure 269 | { 270 | [self enqueueS3RequestOperationWithMethod:@"PUT" path:bucket parameters:parameters success:success failure:failure]; 271 | 272 | } 273 | 274 | - (void)deleteBucket:(NSString *)bucket 275 | success:(void (^)(id responseObject))success 276 | failure:(void (^)(NSError *error))failure 277 | { 278 | [self enqueueS3RequestOperationWithMethod:@"DELETE" path:bucket parameters:nil success:success failure:failure]; 279 | } 280 | 281 | #pragma mark Object Operations 282 | 283 | - (void)headObjectWithPath:(NSString *)path 284 | success:(void (^)(id responseObject))success 285 | failure:(void (^)(NSError *error))failure 286 | { 287 | [self enqueueS3RequestOperationWithMethod:@"HEAD" path:path parameters:nil success:success failure:failure]; 288 | } 289 | 290 | - (void)getObjectWithPath:(NSString *)path 291 | progress:(void (^)(NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead))progress 292 | success:(void (^)(id responseObject, NSData *responseData))success 293 | failure:(void (^)(NSError *error))failure 294 | { 295 | NSURLRequest *request = [self requestWithMethod:@"GET" path:path parameters:nil]; 296 | 297 | AFHTTPRequestOperation *requestOperation = [self HTTPRequestOperationWithRequest:request success:^(AFHTTPRequestOperation *operation, id responseObject) { 298 | if (success) { 299 | success(responseObject, operation.responseData); 300 | } 301 | } failure:^(AFHTTPRequestOperation *operation, NSError *error) { 302 | if (failure) { 303 | failure(error); 304 | } 305 | }]; 306 | 307 | [requestOperation setDownloadProgressBlock:progress]; 308 | 309 | [self enqueueHTTPRequestOperation:requestOperation]; 310 | } 311 | 312 | - (void)getObjectWithPath:(NSString *)path 313 | outputStream:(NSOutputStream *)outputStream 314 | progress:(void (^)(NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead))progress 315 | success:(void (^)(id responseObject))success 316 | failure:(void (^)(NSError *error))failure 317 | { 318 | NSURLRequest *request = [self requestWithMethod:@"GET" path:path parameters:nil]; 319 | 320 | AFHTTPRequestOperation *requestOperation = [self HTTPRequestOperationWithRequest:request success:^(AFHTTPRequestOperation *operation, id responseObject) { 321 | if (success) { 322 | success(responseObject); 323 | } 324 | } failure:^(AFHTTPRequestOperation *operation, NSError *error) { 325 | if (failure) { 326 | failure(error); 327 | } 328 | }]; 329 | 330 | [requestOperation setDownloadProgressBlock:progress]; 331 | [requestOperation setOutputStream:outputStream]; 332 | 333 | [self enqueueHTTPRequestOperation:requestOperation]; 334 | } 335 | 336 | - (void)postObjectWithFile:(NSString *)path 337 | destinationPath:(NSString *)destinationPath 338 | parameters:(NSDictionary *)parameters 339 | progress:(void (^)(NSUInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite))progress 340 | success:(void (^)(id responseObject))success 341 | failure:(void (^)(NSError *error))failure 342 | { 343 | [self setObjectWithMethod:@"POST" file:path destinationPath:destinationPath parameters:parameters progress:progress success:success failure:failure]; 344 | } 345 | 346 | - (void)putObjectWithFile:(NSString *)path 347 | destinationPath:(NSString *)destinationPath 348 | parameters:(NSDictionary *)parameters 349 | progress:(void (^)(NSUInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite))progress 350 | success:(void (^)(id responseObject))success 351 | failure:(void (^)(NSError *error))failure 352 | { 353 | [self setObjectWithMethod:@"PUT" file:path destinationPath:destinationPath parameters:parameters progress:progress success:success failure:failure]; 354 | } 355 | 356 | - (void)deleteObjectWithPath:(NSString *)path 357 | success:(void (^)(id responseObject))success 358 | failure:(void (^)(NSError *error))failure 359 | { 360 | [self enqueueS3RequestOperationWithMethod:@"DELETE" path:path parameters:nil success:success failure:failure]; 361 | } 362 | 363 | - (void)setObjectWithMethod:(NSString *)method 364 | file:(NSString *)filePath 365 | destinationPath:(NSString *)destinationPath 366 | parameters:(NSDictionary *)parameters 367 | progress:(void (^)(NSUInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite))progress 368 | success:(void (^)(id responseObject))success 369 | failure:(void (^)(NSError *error))failure 370 | { 371 | NSMutableURLRequest *fileRequest = [NSMutableURLRequest requestWithURL:[NSURL fileURLWithPath:filePath]]; 372 | [fileRequest setCachePolicy:NSURLCacheStorageNotAllowed]; 373 | 374 | NSURLResponse *response = nil; 375 | NSError *fileError = nil; 376 | NSData *data = [NSURLConnection sendSynchronousRequest:fileRequest returningResponse:&response error:&fileError]; 377 | 378 | if (data && response) { 379 | NSMutableURLRequest *request = [self multipartFormRequestWithMethod:method path:destinationPath parameters:parameters constructingBodyWithBlock:^(id formData) { 380 | if (![parameters valueForKey:@"key"]) { 381 | [formData appendPartWithFormData:[[filePath lastPathComponent] dataUsingEncoding:NSUTF8StringEncoding] name:@"key"]; 382 | } 383 | [formData appendPartWithFileData:data name:@"file" fileName:[filePath lastPathComponent] mimeType:[response MIMEType]]; 384 | }]; 385 | 386 | AFHTTPRequestOperation *requestOperation = [self HTTPRequestOperationWithRequest:request success:^(AFHTTPRequestOperation *operation, id responseObject) { 387 | if (success) { 388 | success(responseObject); 389 | } 390 | } failure:^(AFHTTPRequestOperation *operation, NSError *error) { 391 | if (failure) { 392 | failure(error); 393 | } 394 | }]; 395 | 396 | [requestOperation setUploadProgressBlock:progress]; 397 | 398 | [self enqueueHTTPRequestOperation:requestOperation]; 399 | } 400 | } 401 | 402 | #pragma mark - AFHTTPClient 403 | 404 | - (NSMutableURLRequest *)requestWithMethod:(NSString *)method 405 | path:(NSString *)path 406 | parameters:(NSDictionary *)parameters 407 | { 408 | NSMutableURLRequest *request = [super requestWithMethod:method path:path parameters:parameters]; 409 | 410 | [[self authorizationHeadersForRequest:request] enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL *stop) { 411 | [request setValue:value forHTTPHeaderField:field]; 412 | }]; 413 | 414 | return request; 415 | } 416 | 417 | @end 418 | --------------------------------------------------------------------------------