Home | History | Annotate | Download | only in mac
      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