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 42const int kMinidumpFileLengthLimit = 2 * 1024 * 1024; // 2MB 43 44#define kApplePrefsSyncExcludeAllKey \ 45 @"com.apple.PreferenceSync.ExcludeAllSyncKeys" 46 47NSString *const kGoogleServerType = @"google"; 48NSString *const kSocorroServerType = @"socorro"; 49NSString *const kDefaultServerType = @"google"; 50 51#pragma mark - 52 53namespace { 54// Read one line from the configuration file. 55NSString *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. 76NSData *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. 88NSDictionary *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 688