1 // Copyright (c) 2006, Google Inc. 2 // All rights reserved. 3 // 4 // Redistribution and use in source and binary forms, with or without 5 // modification, are permitted provided that the following conditions are 6 // met: 7 // 8 // * Redistributions of source code must retain the above copyright 9 // notice, this list of conditions and the following disclaimer. 10 // * Redistributions in binary form must reproduce the above 11 // copyright notice, this list of conditions and the following disclaimer 12 // in the documentation and/or other materials provided with the 13 // distribution. 14 // * Neither the name of Google Inc. nor the names of its 15 // contributors may be used to endorse or promote products derived from 16 // this software without specific prior written permission. 17 // 18 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 30 #import "HTTPMultipartUpload.h" 31 #import "GTMDefines.h" 32 33 @interface HTTPMultipartUpload(PrivateMethods) 34 - (NSString *)multipartBoundary; 35 // Each of the following methods will append the starting multipart boundary, 36 // but not the ending one. 37 - (NSData *)formDataForKey:(NSString *)key value:(NSString *)value; 38 - (NSData *)formDataForFileContents:(NSData *)contents name:(NSString *)name; 39 - (NSData *)formDataForFile:(NSString *)file name:(NSString *)name; 40 @end 41 42 @implementation HTTPMultipartUpload 43 //============================================================================= 44 #pragma mark - 45 #pragma mark || Private || 46 //============================================================================= 47 - (NSString *)multipartBoundary { 48 // The boundary has 27 '-' characters followed by 16 hex digits 49 return [NSString stringWithFormat:@"---------------------------%08X%08X", 50 rand(), rand()]; 51 } 52 53 //============================================================================= 54 - (NSData *)formDataForKey:(NSString *)key value:(NSString *)value { 55 NSString *escaped = 56 [key stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; 57 NSString *fmt = 58 @"--%@\r\nContent-Disposition: form-data; name=\"%@\"\r\n\r\n%@\r\n"; 59 NSString *form = [NSString stringWithFormat:fmt, boundary_, escaped, value]; 60 61 return [form dataUsingEncoding:NSUTF8StringEncoding]; 62 } 63 64 //============================================================================= 65 - (NSData *)formDataForFileContents:(NSData *)contents name:(NSString *)name { 66 NSMutableData *data = [NSMutableData data]; 67 NSString *escaped = 68 [name stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; 69 NSString *fmt = @"--%@\r\nContent-Disposition: form-data; name=\"%@\"; " 70 "filename=\"minidump.dmp\"\r\nContent-Type: application/octet-stream\r\n\r\n"; 71 NSString *pre = [NSString stringWithFormat:fmt, boundary_, escaped]; 72 73 [data appendData:[pre dataUsingEncoding:NSUTF8StringEncoding]]; 74 [data appendData:contents]; 75 76 return data; 77 } 78 79 //============================================================================= 80 - (NSData *)formDataForFile:(NSString *)file name:(NSString *)name { 81 NSData *contents = [NSData dataWithContentsOfFile:file]; 82 83 return [self formDataForFileContents:contents name:name]; 84 } 85 86 //============================================================================= 87 #pragma mark - 88 #pragma mark || Public || 89 //============================================================================= 90 - (id)initWithURL:(NSURL *)url { 91 if ((self = [super init])) { 92 url_ = [url copy]; 93 boundary_ = [[self multipartBoundary] retain]; 94 files_ = [[NSMutableDictionary alloc] init]; 95 } 96 97 return self; 98 } 99 100 //============================================================================= 101 - (void)dealloc { 102 [url_ release]; 103 [parameters_ release]; 104 [files_ release]; 105 [boundary_ release]; 106 [response_ release]; 107 108 [super dealloc]; 109 } 110 111 //============================================================================= 112 - (NSURL *)URL { 113 return url_; 114 } 115 116 //============================================================================= 117 - (void)setParameters:(NSDictionary *)parameters { 118 if (parameters != parameters_) { 119 [parameters_ release]; 120 parameters_ = [parameters copy]; 121 } 122 } 123 124 //============================================================================= 125 - (NSDictionary *)parameters { 126 return parameters_; 127 } 128 129 //============================================================================= 130 - (void)addFileAtPath:(NSString *)path name:(NSString *)name { 131 [files_ setObject:path forKey:name]; 132 } 133 134 //============================================================================= 135 - (void)addFileContents:(NSData *)data name:(NSString *)name { 136 [files_ setObject:data forKey:name]; 137 } 138 139 //============================================================================= 140 - (NSDictionary *)files { 141 return files_; 142 } 143 144 //============================================================================= 145 - (NSData *)send:(NSError **)error { 146 NSMutableURLRequest *req = 147 [[NSMutableURLRequest alloc] 148 initWithURL:url_ cachePolicy:NSURLRequestUseProtocolCachePolicy 149 timeoutInterval:10.0 ]; 150 151 NSMutableData *postBody = [NSMutableData data]; 152 153 [req setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@", 154 boundary_] forHTTPHeaderField:@"Content-type"]; 155 156 // Add any parameters to the message 157 NSArray *parameterKeys = [parameters_ allKeys]; 158 NSString *key; 159 160 NSInteger count = [parameterKeys count]; 161 for (NSInteger i = 0; i < count; ++i) { 162 key = [parameterKeys objectAtIndex:i]; 163 [postBody appendData:[self formDataForKey:key 164 value:[parameters_ objectForKey:key]]]; 165 } 166 167 // Add any files to the message 168 NSArray *fileNames = [files_ allKeys]; 169 count = [fileNames count]; 170 for (NSInteger i = 0; i < count; ++i) { 171 NSString *name = [fileNames objectAtIndex:i]; 172 id fileOrData = [files_ objectForKey:name]; 173 NSData *fileData; 174 175 // The object can be either the path to a file (NSString) or the contents 176 // of the file (NSData). 177 if ([fileOrData isKindOfClass:[NSData class]]) 178 fileData = [self formDataForFileContents:fileOrData name:name]; 179 else 180 fileData = [self formDataForFile:fileOrData name:name]; 181 182 [postBody appendData:fileData]; 183 } 184 185 NSString *epilogue = [NSString stringWithFormat:@"\r\n--%@--\r\n", boundary_]; 186 [postBody appendData:[epilogue dataUsingEncoding:NSUTF8StringEncoding]]; 187 188 [req setHTTPBody:postBody]; 189 [req setHTTPMethod:@"POST"]; 190 191 [response_ release]; 192 response_ = nil; 193 194 NSData *data = nil; 195 if ([[req URL] isFileURL]) { 196 [[req HTTPBody] writeToURL:[req URL] options:0 error:error]; 197 } else { 198 data = [NSURLConnection sendSynchronousRequest:req 199 returningResponse:&response_ 200 error:error]; 201 [response_ retain]; 202 } 203 [req release]; 204 205 return data; 206 } 207 208 //============================================================================= 209 - (NSHTTPURLResponse *)response { 210 return response_; 211 } 212 213 @end 214