/ src / client / mac / sender / uploader.mm
uploader.mm
  1  // Copyright 2011 Google LLC
  2  //
  3  // Redistribution and use in source and binary forms, with or without
  4  // modification, are permitted provided that the following conditions are
  5  // met:
  6  //
  7  //     * Redistributions of source code must retain the above copyright
  8  // notice, this list of conditions and the following disclaimer.
  9  //     * Redistributions in binary form must reproduce the above
 10  // copyright notice, this list of conditions and the following disclaimer
 11  // in the documentation and/or other materials provided with the
 12  // distribution.
 13  //     * Neither the name of Google LLC nor the names of its
 14  // contributors may be used to endorse or promote products derived from
 15  // this software without specific prior written permission.
 16  //
 17  // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 18  // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 19  // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 20  // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 21  // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 22  // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 23  // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 24  // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 25  // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 26  // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 27  // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 28  
 29  #import <fcntl.h>
 30  #include <stdio.h>
 31  #import <sys/stat.h>
 32  #include <TargetConditionals.h>
 33  #import <unistd.h>
 34  
 35  #import <SystemConfiguration/SystemConfiguration.h>
 36  
 37  #import "common/mac/HTTPMultipartUpload.h"
 38  
 39  #import "client/apple/Framework/BreakpadDefines.h"
 40  #import "client/mac/sender/uploader.h"
 41  
 42  const int kMinidumpFileLengthLimit = 2 * 1024 * 1024;  // 2MB
 43  
 44  #define kApplePrefsSyncExcludeAllKey \
 45    @"com.apple.PreferenceSync.ExcludeAllSyncKeys"
 46  
 47  NSString *const kGoogleServerType = @"google";
 48  NSString *const kSocorroServerType = @"socorro";
 49  NSString *const kDefaultServerType = @"google";
 50  
 51  #pragma mark -
 52  
 53  namespace {
 54  // Read one line from the configuration file.
 55  NSString *readString(int fileId) {
 56    NSMutableString *str = [NSMutableString stringWithCapacity:32];
 57    char ch[2] = { 0 };
 58  
 59    while (read(fileId, &ch[0], 1) == 1) {
 60      if (ch[0] == '\n') {
 61        // Break if this is the first newline after reading some other string
 62        // data.
 63        if ([str length])
 64          break;
 65      } else {
 66        [str appendString:[NSString stringWithUTF8String:ch]];
 67      }
 68    }
 69  
 70    return str;
 71  }
 72  
 73  //=============================================================================
 74  // Read |length| of binary data from the configuration file. This method will
 75  // returns |nil| in case of error.
 76  NSData *readData(int fileId, ssize_t length) {
 77    NSMutableData *data = [NSMutableData dataWithLength:length];
 78    char *bytes = (char *)[data bytes];
 79  
 80    if (read(fileId, bytes, length) != length)
 81      return nil;
 82  
 83    return data;
 84  }
 85  
 86  //=============================================================================
 87  // Read the configuration from the config file.
 88  NSDictionary *readConfigurationData(const char *configFile) {
 89    int fileId = open(configFile, O_RDONLY, 0600);
 90    if (fileId == -1) {
 91      fprintf(stderr, "Breakpad Uploader: Couldn't open config file %s - %s",
 92              configFile, strerror(errno));
 93    }
 94  
 95    // we want to avoid a build-up of old config files even if they
 96    // have been incorrectly written by the framework
 97    if (unlink(configFile)) {
 98      fprintf(stderr, "Breakpad Uploader: Couldn't unlink config file %s - %s",
 99              configFile, strerror(errno));
100    }
101  
102    if (fileId == -1) {
103      return nil;
104    }
105  
106    NSMutableDictionary *config = [NSMutableDictionary dictionary];
107  
108    while (1) {
109      NSString *key = readString(fileId);
110  
111      if (![key length])
112        break;
113  
114      // Read the data.  Try to convert to a UTF-8 string, or just save
115      // the data
116      NSString *lenStr = readString(fileId);
117      ssize_t len = [lenStr intValue];
118      NSData *data = readData(fileId, len);
119      id value = [[NSString alloc] initWithData:data
120                                       encoding:NSUTF8StringEncoding];
121  
122      [config setObject:(value ? value : data) forKey:key];
123      [value release];
124    }
125  
126    close(fileId);
127    return config;
128  }
129  }  // namespace
130  
131  #pragma mark -
132  
133  @interface Uploader(PrivateMethods)
134  
135  // Update |parameters_| as well as the server parameters using |config|.
136  - (void)translateConfigurationData:(NSDictionary *)config;
137  
138  // Read the minidump referenced in |parameters_| and update |minidumpContents_|
139  // with its content.
140  - (BOOL)readMinidumpData;
141  
142  // Read the log files referenced in |parameters_| and update |logFileData_|
143  // with their content.
144  - (BOOL)readLogFileData;
145  
146  // Returns a unique client id (user-specific), creating a persistent
147  // one in the user defaults, if necessary.
148  - (NSString*)clientID;
149  
150  // Returns a dictionary that can be used to map Breakpad parameter names to
151  // URL parameter names.
152  - (NSMutableDictionary *)dictionaryForServerType:(NSString *)serverType;
153  
154  // Helper method to set HTTP parameters based on server type.  This is
155  // called right before the upload - crashParameters will contain, on exit,
156  // URL parameters that should be sent with the minidump.
157  - (BOOL)populateServerDictionary:(NSMutableDictionary *)crashParameters;
158  
159  // Initialization helper to create dictionaries mapping Breakpad
160  // parameters to URL parameters
161  - (void)createServerParameterDictionaries;
162  
163  // Accessor method for the URL parameter dictionary
164  - (NSMutableDictionary *)urlParameterDictionary;
165  
166  // Records the uploaded crash ID to the log file.
167  - (void)logUploadWithID:(const char *)uploadID;
168  
169  // Builds an URL parameter for a given dictionary key. Uses Uploader's
170  // parameters to provide its value. Returns nil if no item is stored for the
171  // given key.
172  - (NSURLQueryItem *)queryItemWithName:(NSString *)queryItemName
173                            forParamKey:(NSString *)key;
174  @end
175  
176  @implementation Uploader
177  
178  //=============================================================================
179  - (id)initWithConfigFile:(const char *)configFile {
180    NSDictionary *config = readConfigurationData(configFile);
181    if (!config)
182      return nil;
183  
184    return [self initWithConfig:config];
185  }
186  
187  //=============================================================================
188  - (id)initWithConfig:(NSDictionary *)config {
189    if ((self = [super init])) {
190      // Because the reporter is embedded in the framework (and many copies
191      // of the framework may exist) its not completely certain that the OS
192      // will obey the com.apple.PreferenceSync.ExcludeAllSyncKeys in our
193      // Info.plist. To make sure, also set the key directly if needed.
194      NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
195      if (![ud boolForKey:kApplePrefsSyncExcludeAllKey]) {
196        [ud setBool:YES forKey:kApplePrefsSyncExcludeAllKey];
197      }
198  
199      [self createServerParameterDictionaries];
200  
201      [self translateConfigurationData:config];
202  
203      // Read the minidump into memory.
204      [self readMinidumpData];
205      [self readLogFileData];
206    }
207    return self;
208  }
209  
210  //=============================================================================
211  + (NSDictionary *)readConfigurationDataFromFile:(NSString *)configFile {
212    return readConfigurationData([configFile fileSystemRepresentation]);
213  }
214  
215  //=============================================================================
216  - (void)translateConfigurationData:(NSDictionary *)config {
217    parameters_ = [[NSMutableDictionary alloc] init];
218  
219    NSEnumerator *it = [config keyEnumerator];
220    while (NSString *key = [it nextObject]) {
221      // If the keyname is prefixed by BREAKPAD_SERVER_PARAMETER_PREFIX
222      // that indicates that it should be uploaded to the server along
223      // with the minidump, so we treat it specially.
224      if ([key hasPrefix:@BREAKPAD_SERVER_PARAMETER_PREFIX]) {
225        NSString *urlParameterKey =
226          [key substringFromIndex:[@BREAKPAD_SERVER_PARAMETER_PREFIX length]];
227        if ([urlParameterKey length]) {
228          id value = [config objectForKey:key];
229          if ([value isKindOfClass:[NSString class]]) {
230            [self addServerParameter:(NSString *)value
231                              forKey:urlParameterKey];
232          } else {
233            [self addServerParameter:(NSData *)value
234                              forKey:urlParameterKey];
235          }
236        }
237      } else {
238        [parameters_ setObject:[config objectForKey:key] forKey:key];
239      }
240    }
241  
242    // generate a unique client ID based on this host's MAC address
243    // then add a key/value pair for it
244    NSString *clientID = [self clientID];
245    [parameters_ setObject:clientID forKey:@"guid"];
246  }
247  
248  // Per user per machine
249  - (NSString *)clientID {
250    NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
251    NSString *crashClientID = [ud stringForKey:kClientIdPreferenceKey];
252    if (crashClientID) {
253      return crashClientID;
254    }
255  
256    // Otherwise, if we have no client id, generate one!
257    srandom((int)[[NSDate date] timeIntervalSince1970]);
258    long clientId1 = random();
259    long clientId2 = random();
260    long clientId3 = random();
261    crashClientID = [NSString stringWithFormat:@"%lx%lx%lx",
262                              clientId1, clientId2, clientId3];
263  
264    [ud setObject:crashClientID forKey:kClientIdPreferenceKey];
265    [ud synchronize];
266    return crashClientID;
267  }
268  
269  //=============================================================================
270  - (BOOL)readLogFileData {
271  #if TARGET_OS_IPHONE
272    return NO;
273  #else
274    unsigned int logFileCounter = 0;
275  
276    NSString *logPath;
277    size_t logFileTailSize =
278        [[parameters_ objectForKey:@BREAKPAD_LOGFILE_UPLOAD_SIZE] intValue];
279  
280    NSMutableArray *logFilenames; // An array of NSString, one per log file
281    logFilenames = [[NSMutableArray alloc] init];
282  
283    char tmpDirTemplate[80] = "/tmp/CrashUpload-XXXXX";
284    char *tmpDir = mkdtemp(tmpDirTemplate);
285  
286    // Construct key names for the keys we expect to contain log file paths
287    for(logFileCounter = 0;; logFileCounter++) {
288      NSString *logFileKey = [NSString stringWithFormat:@"%@%d",
289                                       @BREAKPAD_LOGFILE_KEY_PREFIX,
290                                       logFileCounter];
291  
292      logPath = [parameters_ objectForKey:logFileKey];
293  
294      // They should all be consecutive, so if we don't find one, assume
295      // we're done
296  
297      if (!logPath) {
298        break;
299      }
300  
301      NSData *entireLogFile = [[NSData alloc] initWithContentsOfFile:logPath];
302  
303      if (entireLogFile == nil) {
304        continue;
305      }
306  
307      NSRange fileRange;
308  
309      // Truncate the log file, only if necessary
310  
311      if ([entireLogFile length] <= logFileTailSize) {
312        fileRange = NSMakeRange(0, [entireLogFile length]);
313      } else {
314        fileRange = NSMakeRange([entireLogFile length] - logFileTailSize,
315                                logFileTailSize);
316      }
317  
318      char tmpFilenameTemplate[100];
319  
320      // Generate a template based on the log filename
321      sprintf(tmpFilenameTemplate,"%s/%s-XXXX", tmpDir,
322              [[logPath lastPathComponent] fileSystemRepresentation]);
323  
324      char *tmpFile = mktemp(tmpFilenameTemplate);
325  
326      NSData *logSubdata = [entireLogFile subdataWithRange:fileRange];
327      NSString *tmpFileString = [NSString stringWithUTF8String:tmpFile];
328      [logSubdata writeToFile:tmpFileString atomically:NO];
329  
330      [logFilenames addObject:[tmpFileString lastPathComponent]];
331      [entireLogFile release];
332    }
333  
334    if ([logFilenames count] == 0) {
335      [logFilenames release];
336      logFileData_ =  nil;
337      return NO;
338    }
339  
340    // now, bzip all files into one
341    NSTask *tarTask = [[NSTask alloc] init];
342  
343    [tarTask setCurrentDirectoryPath:[NSString stringWithUTF8String:tmpDir]];
344    [tarTask setLaunchPath:@"/usr/bin/tar"];
345  
346    NSMutableArray *bzipArgs = [NSMutableArray arrayWithObjects:@"-cjvf",
347                                               @"log.tar.bz2",nil];
348    [bzipArgs addObjectsFromArray:logFilenames];
349  
350    [logFilenames release];
351  
352    [tarTask setArguments:bzipArgs];
353    [tarTask launch];
354    [tarTask waitUntilExit];
355    [tarTask release];
356  
357    NSString *logTarFile = [NSString stringWithFormat:@"%s/log.tar.bz2",tmpDir];
358    logFileData_ = [[NSData alloc] initWithContentsOfFile:logTarFile];
359    if (logFileData_ == nil) {
360      fprintf(stderr, "Breakpad Uploader: Cannot find temp tar log file: %s",
361              [logTarFile UTF8String]);
362      return NO;
363    }
364    return YES;
365  #endif  // TARGET_OS_IPHONE
366  }
367  
368  //=============================================================================
369  - (BOOL)readMinidumpData {
370    NSString *minidumpDir =
371        [parameters_ objectForKey:@kReporterMinidumpDirectoryKey];
372    NSString *minidumpID = [parameters_ objectForKey:@kReporterMinidumpIDKey];
373  
374    if (![minidumpID length])
375      return NO;
376  
377    NSString *path = [minidumpDir stringByAppendingPathComponent:minidumpID];
378    path = [path stringByAppendingPathExtension:@"dmp"];
379  
380    // check the size of the minidump and limit it to a reasonable size
381    // before attempting to load into memory and upload
382    const char *fileName = [path fileSystemRepresentation];
383    struct stat fileStatus;
384  
385    BOOL success = YES;
386  
387    if (!stat(fileName, &fileStatus)) {
388      if (fileStatus.st_size > kMinidumpFileLengthLimit) {
389        fprintf(stderr, "Breakpad Uploader: minidump file too large " \
390                "to upload : %d\n", (int)fileStatus.st_size);
391        success = NO;
392      }
393    } else {
394        fprintf(stderr, "Breakpad Uploader: unable to determine minidump " \
395                "file length\n");
396        success = NO;
397    }
398  
399    if (success) {
400      minidumpContents_ = [[NSData alloc] initWithContentsOfFile:path];
401      success = ([minidumpContents_ length] ? YES : NO);
402    }
403  
404    if (!success) {
405      // something wrong with the minidump file -- delete it
406      unlink(fileName);
407    }
408  
409    return success;
410  }
411  
412  #pragma mark -
413  //=============================================================================
414  
415  - (void)createServerParameterDictionaries {
416    serverDictionary_ = [[NSMutableDictionary alloc] init];
417    socorroDictionary_ = [[NSMutableDictionary alloc] init];
418    googleDictionary_ = [[NSMutableDictionary alloc] init];
419    extraServerVars_ = [[NSMutableDictionary alloc] init];
420  
421    [serverDictionary_ setObject:socorroDictionary_ forKey:kSocorroServerType];
422    [serverDictionary_ setObject:googleDictionary_ forKey:kGoogleServerType];
423  
424    [googleDictionary_ setObject:@"ptime" forKey:@BREAKPAD_PROCESS_UP_TIME];
425    [googleDictionary_ setObject:@"email" forKey:@BREAKPAD_EMAIL];
426    [googleDictionary_ setObject:@"comments" forKey:@BREAKPAD_COMMENTS];
427    [googleDictionary_ setObject:@"prod" forKey:@BREAKPAD_PRODUCT];
428    [googleDictionary_ setObject:@"ver" forKey:@BREAKPAD_VERSION];
429    [googleDictionary_ setObject:@"guid" forKey:@"guid"];
430  
431    [socorroDictionary_ setObject:@"Comments" forKey:@BREAKPAD_COMMENTS];
432    [socorroDictionary_ setObject:@"CrashTime"
433                           forKey:@BREAKPAD_PROCESS_CRASH_TIME];
434    [socorroDictionary_ setObject:@"StartupTime"
435                           forKey:@BREAKPAD_PROCESS_START_TIME];
436    [socorroDictionary_ setObject:@"Version"
437                           forKey:@BREAKPAD_VERSION];
438    [socorroDictionary_ setObject:@"ProductName"
439                           forKey:@BREAKPAD_PRODUCT];
440    [socorroDictionary_ setObject:@"Email"
441                           forKey:@BREAKPAD_EMAIL];
442  }
443  
444  - (NSMutableDictionary *)dictionaryForServerType:(NSString *)serverType {
445    if (serverType == nil || [serverType length] == 0) {
446      return [serverDictionary_ objectForKey:kDefaultServerType];
447    }
448    return [serverDictionary_ objectForKey:serverType];
449  }
450  
451  - (NSMutableDictionary *)urlParameterDictionary {
452    NSString *serverType = [parameters_ objectForKey:@BREAKPAD_SERVER_TYPE];
453    return [self dictionaryForServerType:serverType];
454  
455  }
456  
457  - (BOOL)populateServerDictionary:(NSMutableDictionary *)crashParameters {
458    NSDictionary *urlParameterNames = [self urlParameterDictionary];
459  
460    id key;
461    NSEnumerator *enumerator = [parameters_ keyEnumerator];
462  
463    while ((key = [enumerator nextObject])) {
464      // The key from parameters_ corresponds to a key in
465      // urlParameterNames.  The value in parameters_ gets stored in
466      // crashParameters with a key that is the value in
467      // urlParameterNames.
468  
469      // For instance, if parameters_ has [PRODUCT_NAME => "FOOBAR"] and
470      // urlParameterNames has [PRODUCT_NAME => "pname"] the final HTTP
471      // URL parameter becomes [pname => "FOOBAR"].
472      NSString *breakpadParameterName = (NSString *)key;
473      NSString *urlParameter = [urlParameterNames
474                                     objectForKey:breakpadParameterName];
475      if (urlParameter) {
476        [crashParameters setObject:[parameters_ objectForKey:key]
477                            forKey:urlParameter];
478      }
479    }
480  
481    // Now, add the parameters that were added by the application.
482    enumerator = [extraServerVars_ keyEnumerator];
483  
484    while ((key = [enumerator nextObject])) {
485      NSString *urlParameterName = (NSString *)key;
486      NSString *urlParameterValue =
487        [extraServerVars_ objectForKey:urlParameterName];
488      [crashParameters setObject:urlParameterValue
489                          forKey:urlParameterName];
490    }
491    return YES;
492  }
493  
494  - (void)addServerParameter:(id)value forKey:(NSString *)key {
495    [extraServerVars_ setObject:value forKey:key];
496  }
497  
498  //=============================================================================
499  - (void)handleNetworkResponse:(NSData *)data withError:(NSError *)error {
500    NSString *result = [[NSString alloc] initWithData:data
501                                             encoding:NSUTF8StringEncoding];
502    const char *reportID = "ERR";
503    if (error) {
504      fprintf(stderr, "Breakpad Uploader: Send Error: %s\n",
505              [[error description] UTF8String]);
506    } else {
507      NSCharacterSet *trimSet =
508          [NSCharacterSet whitespaceAndNewlineCharacterSet];
509      reportID = [[result stringByTrimmingCharactersInSet:trimSet] UTF8String];
510      [self logUploadWithID:reportID];
511    }
512    if (uploadCompletion_) {
513      uploadCompletion_([NSString stringWithUTF8String:reportID], error);
514    }
515  
516    // rename the minidump file according to the id returned from the server
517    NSString *minidumpDir =
518        [parameters_ objectForKey:@kReporterMinidumpDirectoryKey];
519    NSString *minidumpID = [parameters_ objectForKey:@kReporterMinidumpIDKey];
520  
521    NSString *srcString = [NSString stringWithFormat:@"%@/%@.dmp",
522                                    minidumpDir, minidumpID];
523    NSString *destString = [NSString stringWithFormat:@"%@/%s.dmp",
524                                     minidumpDir, reportID];
525  
526    const char *src = [srcString fileSystemRepresentation];
527    const char *dest = [destString fileSystemRepresentation];
528  
529    if (rename(src, dest) == 0) {
530      fprintf(stderr,
531              "Breakpad Uploader: Renamed %s to %s after successful upload", src,
532              dest);
533    }
534    else {
535      // can't rename - don't worry - it's not important for users
536      fprintf(stderr, "Breakpad Uploader: successful upload report ID = %s\n",
537              reportID);
538    }
539    [result release];
540  }
541  
542  //=============================================================================
543  - (NSURLQueryItem *)queryItemWithName:(NSString *)queryItemName
544                            forParamKey:(NSString *)key {
545    NSString *value = [parameters_ objectForKey:key];
546    NSString *escapedValue =
547      [value stringByAddingPercentEncodingWithAllowedCharacters:
548        [NSCharacterSet URLQueryAllowedCharacterSet]];
549    return [NSURLQueryItem queryItemWithName:queryItemName value:escapedValue];
550  }
551  
552  //=============================================================================
553  - (void)setUploadCompletionBlock:(UploadCompletionBlock)uploadCompletion {
554    uploadCompletion_ = uploadCompletion;
555  }
556  
557  //=============================================================================
558  - (void)report {
559    NSURL *url = [NSURL URLWithString:[parameters_ objectForKey:@BREAKPAD_URL]];
560  
561    NSString *serverType = [parameters_ objectForKey:@BREAKPAD_SERVER_TYPE];
562    if ([serverType length] == 0 ||
563        [serverType isEqualToString:kGoogleServerType]) {
564      // when communicating to Google's crash collecting service, add URL params
565      // which identify the product
566      NSURLComponents *urlComponents = [NSURLComponents componentsWithURL:url
567                                                  resolvingAgainstBaseURL:false];
568      NSMutableArray *queryItemsToAdd = [urlComponents.queryItems mutableCopy];
569      if (queryItemsToAdd == nil) {
570        queryItemsToAdd = [[NSMutableArray alloc] init];
571      }
572  
573      NSURLQueryItem *queryItemProduct =
574        [self queryItemWithName:@"product" forParamKey:@BREAKPAD_PRODUCT];
575      NSURLQueryItem *queryItemVersion =
576        [self queryItemWithName:@"version" forParamKey:@BREAKPAD_VERSION];
577      NSURLQueryItem *queryItemGuid =
578        [self queryItemWithName:@"guid" forParamKey:@"guid"];
579  
580      if (queryItemProduct != nil) [queryItemsToAdd addObject:queryItemProduct];
581      if (queryItemVersion != nil) [queryItemsToAdd addObject:queryItemVersion];
582      if (queryItemGuid != nil) [queryItemsToAdd addObject:queryItemGuid];
583  
584      urlComponents.queryItems = queryItemsToAdd;
585      url = [urlComponents URL];
586    }
587  
588    HTTPMultipartUpload *upload = [[HTTPMultipartUpload alloc] initWithURL:url];
589    NSMutableDictionary *uploadParameters = [NSMutableDictionary dictionary];
590  
591    if (![self populateServerDictionary:uploadParameters]) {
592      [upload release];
593      return;
594    }
595  
596    [upload setParameters:uploadParameters];
597  
598    // Add minidump file
599    if (minidumpContents_) {
600      [upload addFileContents:minidumpContents_ name:@"upload_file_minidump"];
601  
602      // If there is a log file, upload it together with the minidump.
603      if (logFileData_) {
604        [upload addFileContents:logFileData_ name:@"log"];
605      }
606  
607      // Send it
608      NSError *error = nil;
609      NSData *data = [upload send:&error];
610  
611      if (![url isFileURL]) {
612        [self handleNetworkResponse:data withError:error];
613      } else {
614        if (error) {
615          fprintf(stderr, "Breakpad Uploader: Error writing request file: %s\n",
616                  [[error description] UTF8String]);
617        }
618      }
619  
620    } else {
621      // Minidump is missing -- upload just the log file.
622      if (logFileData_) {
623        [self uploadData:logFileData_ name:@"log"];
624      }
625    }
626    [upload release];
627  }
628  
629  - (void)uploadData:(NSData *)data name:(NSString *)name {
630    NSURL *url = [NSURL URLWithString:[parameters_ objectForKey:@BREAKPAD_URL]];
631    NSMutableDictionary *uploadParameters = [NSMutableDictionary dictionary];
632  
633    if (![self populateServerDictionary:uploadParameters])
634      return;
635  
636    HTTPMultipartUpload *upload =
637        [[HTTPMultipartUpload alloc] initWithURL:url];
638  
639    [uploadParameters setObject:name forKey:@"type"];
640    [upload setParameters:uploadParameters];
641    [upload addFileContents:data name:name];
642  
643    [upload send:nil];
644    [upload release];
645  }
646  
647  - (void)logUploadWithID:(const char *)uploadID {
648    NSString *minidumpDir =
649        [parameters_ objectForKey:@kReporterMinidumpDirectoryKey];
650    NSString *logFilePath = [NSString stringWithFormat:@"%@/%s",
651        minidumpDir, kReporterLogFilename];
652    NSString *logLine = [NSString stringWithFormat:@"%0.f,%s\n",
653        [[NSDate date] timeIntervalSince1970], uploadID];
654    NSData *logData = [logLine dataUsingEncoding:NSUTF8StringEncoding];
655  
656    NSFileManager *fileManager = [NSFileManager defaultManager];
657    if ([fileManager fileExistsAtPath:logFilePath]) {
658      NSFileHandle *logFileHandle =
659         [NSFileHandle fileHandleForWritingAtPath:logFilePath];
660      [logFileHandle seekToEndOfFile];
661      [logFileHandle writeData:logData];
662      [logFileHandle closeFile];
663    } else {
664      [fileManager createFileAtPath:logFilePath
665                           contents:logData
666                         attributes:nil];
667    }
668  }
669  
670  //=============================================================================
671  - (NSMutableDictionary *)parameters {
672    return parameters_;
673  }
674  
675  //=============================================================================
676  - (void)dealloc {
677    [parameters_ release];
678    [minidumpContents_ release];
679    [logFileData_ release];
680    [googleDictionary_ release];
681    [socorroDictionary_ release];
682    [serverDictionary_ release];
683    [extraServerVars_ release];
684    [super dealloc];
685  }
686  
687  @end