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