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