aboutsummaryrefslogtreecommitdiff
path: root/src/client
diff options
context:
space:
mode:
authorqsr@chromium.org <qsr@chromium.org@4c0a9323-5329-0410-9bdc-e9ce6186880e>2011-10-10 14:40:26 +0000
committerqsr@chromium.org <qsr@chromium.org@4c0a9323-5329-0410-9bdc-e9ce6186880e>2011-10-10 14:40:26 +0000
commitbf747d2dbb69a9ac069d9aa2438457baf370c333 (patch)
tree84e8c68dbaab4ddd6b849e331e4d4e09933efb5e /src/client
parent Extracting the config file class from the Inspector to be able to reuse it. (diff)
downloadbreakpad-bf747d2dbb69a9ac069d9aa2438457baf370c333.tar.xz
Extract the uploader process from crash_report_sender
The aim is to separate the process itself from the view, to be able to reuse the process on the iOS platform. Review URL: http://breakpad.appspot.com/309002 git-svn-id: http://google-breakpad.googlecode.com/svn/trunk@853 4c0a9323-5329-0410-9bdc-e9ce6186880e
Diffstat (limited to 'src/client')
-rw-r--r--src/client/mac/Breakpad.xcodeproj/project.pbxproj6
-rw-r--r--src/client/mac/sender/crash_report_sender.h27
-rw-r--r--src/client/mac/sender/crash_report_sender.m579
-rw-r--r--src/client/mac/sender/uploader.h74
-rw-r--r--src/client/mac/sender/uploader.m579
5 files changed, 695 insertions, 570 deletions
diff --git a/src/client/mac/Breakpad.xcodeproj/project.pbxproj b/src/client/mac/Breakpad.xcodeproj/project.pbxproj
index 2d396542..fc3e462f 100644
--- a/src/client/mac/Breakpad.xcodeproj/project.pbxproj
+++ b/src/client/mac/Breakpad.xcodeproj/project.pbxproj
@@ -38,6 +38,7 @@
163201D61443019E00C4DBF5 /* ConfigFile.h in Headers */ = {isa = PBXBuildFile; fileRef = 163201D41443019E00C4DBF5 /* ConfigFile.h */; };
163201D71443019E00C4DBF5 /* ConfigFile.mm in Sources */ = {isa = PBXBuildFile; fileRef = 163201D51443019E00C4DBF5 /* ConfigFile.mm */; };
163201E31443029300C4DBF5 /* ConfigFile.mm in Sources */ = {isa = PBXBuildFile; fileRef = 163201D51443019E00C4DBF5 /* ConfigFile.mm */; };
+ 163202451443201300C4DBF5 /* uploader.m in Sources */ = {isa = PBXBuildFile; fileRef = 163202441443201300C4DBF5 /* uploader.m */; };
3329D4ED0FA16D820007BBC5 /* Breakpad.xib in Resources */ = {isa = PBXBuildFile; fileRef = 3329D4EC0FA16D820007BBC5 /* Breakpad.xib */; };
33880C800F9E097100817F82 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 33880C7E0F9E097100817F82 /* InfoPlist.strings */; };
4084699D0F5D9CF900FDCA37 /* crash_report_sender.icns in Resources */ = {isa = PBXBuildFile; fileRef = 4084699C0F5D9CF900FDCA37 /* crash_report_sender.icns */; };
@@ -549,6 +550,8 @@
1058C7B1FEA5585E11CA2CBB /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; };
163201D41443019E00C4DBF5 /* ConfigFile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ConfigFile.h; path = crash_generation/ConfigFile.h; sourceTree = "<group>"; };
163201D51443019E00C4DBF5 /* ConfigFile.mm */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.objcpp; fileEncoding = 4; name = ConfigFile.mm; path = crash_generation/ConfigFile.mm; sourceTree = "<group>"; };
+ 163202431443201300C4DBF5 /* uploader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = uploader.h; path = sender/uploader.h; sourceTree = "<group>"; };
+ 163202441443201300C4DBF5 /* uploader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = uploader.m; path = sender/uploader.m; sourceTree = "<group>"; };
32DBCF5E0370ADEE00C91783 /* Breakpad_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Breakpad_Prefix.pch; path = Framework/Breakpad_Prefix.pch; sourceTree = "<group>"; };
3329D4EC0FA16D820007BBC5 /* Breakpad.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Breakpad.xib; path = sender/Breakpad.xib; sourceTree = "<group>"; };
33880C7F0F9E097100817F82 /* English */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = English; path = sender/English.lproj/InfoPlist.strings; sourceTree = "<group>"; };
@@ -992,6 +995,8 @@
F92C56A60ECE04B6009BE4BA /* sender */ = {
isa = PBXGroup;
children = (
+ 163202431443201300C4DBF5 /* uploader.h */,
+ 163202441443201300C4DBF5 /* uploader.m */,
F9B6309F100FF96B00D0F4AC /* goArrow.png */,
F92C56A70ECE04C5009BE4BA /* crash_report_sender.h */,
F92C56A80ECE04C5009BE4BA /* crash_report_sender.m */,
@@ -1732,6 +1737,7 @@
F9C44EA20EF09F93003AEBAA /* HTTPMultipartUpload.m in Sources */,
F92C56A90ECE04C5009BE4BA /* crash_report_sender.m in Sources */,
F9C44EE90EF0A3C1003AEBAA /* GTMLogger.m in Sources */,
+ 163202451443201300C4DBF5 /* uploader.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
diff --git a/src/client/mac/sender/crash_report_sender.h b/src/client/mac/sender/crash_report_sender.h
index c0728ca8..cff83fb4 100644
--- a/src/client/mac/sender/crash_report_sender.h
+++ b/src/client/mac/sender/crash_report_sender.h
@@ -34,15 +34,9 @@
#include <Foundation/Foundation.h>
-#include "client/mac/Framework/Breakpad.h"
+#include "client/mac/sender/uploader.h"
#import "GTMDefines.h"
-#define kClientIdPreferenceKey @"clientid"
-
-extern NSString *const kGoogleServerType;
-extern NSString *const kSocorroServerType;
-extern NSString *const kDefaultServerType;
-
// We're sublcassing NSTextField in order to override a particular
// method (see the implementation) that lets us reject changes if they
// are longer than a particular length. Bindings would normally solve
@@ -87,29 +81,12 @@ extern NSString *const kDefaultServerType;
NSString *countdownMessage_; // Message indicating time
// left for input.
@private
- int configFile_; // File descriptor for config file
- NSMutableDictionary *parameters_; // Key value pairs of data (STRONG)
- NSData *minidumpContents_; // The data in the minidump (STRONG)
- NSData *logFileData_; // An NSdata for the tar,
- // bz2'd log file.
NSTimeInterval remainingDialogTime_; // Keeps track of how long
// we have until we cancel
// the dialog
NSTimer *messageTimer_; // Timer we use to update
// the dialog
- NSMutableDictionary *serverDictionary_; // The dictionary mapping a
- // server type name to a
- // dictionary of server
- // parameter names.
- NSMutableDictionary *socorroDictionary_; // The dictionary for
- // Socorro.
- NSMutableDictionary *googleDictionary_; // The dictionary for
- // Google.
- NSMutableDictionary *extraServerVars_; // A dictionary containing
- // extra key/value pairs
- // that are uploaded to the
- // crash server with the
- // minidump.
+ Uploader* uploader_; // Uploader we use to send the data.
}
// Stops the modal panel with an NSAlertDefaultReturn value. This is the action
diff --git a/src/client/mac/sender/crash_report_sender.m b/src/client/mac/sender/crash_report_sender.m
index e3889761..155a9bf4 100644
--- a/src/client/mac/sender/crash_report_sender.m
+++ b/src/client/mac/sender/crash_report_sender.m
@@ -41,17 +41,12 @@
#define kLastSubmission @"LastSubmission"
-const int kMinidumpFileLengthLimit = 800000;
const int kUserCommentsMaxLength = 1500;
const int kEmailMaxLength = 64;
#define kApplePrefsSyncExcludeAllKey \
@"com.apple.PreferenceSync.ExcludeAllSyncKeys"
-NSString *const kGoogleServerType = @"google";
-NSString *const kSocorroServerType = @"socorro";
-NSString *const kDefaultServerType = @"google";
-
#pragma mark -
@interface NSView (ResizabilityExtentions)
@@ -160,18 +155,8 @@ NSString *const kDefaultServerType = @"google";
#pragma mark -
-
@interface Reporter(PrivateMethods)
-+ (uid_t)consoleUID;
-
-- (id)initWithConfigurationFD:(int)fd;
-
-- (NSString *)readString;
-- (NSData *)readData:(ssize_t)length;
-
-- (BOOL)readConfigurationData;
-- (BOOL)readMinidumpData;
-- (BOOL)readLogFileData;
+- (id)initWithConfigFile:(const char *)configFile;
// Returns YES if it has been long enough since the last report that we should
// submit a report for this crash.
@@ -221,30 +206,6 @@ NSString *const kDefaultServerType = @"google";
- (NSInteger)runModalWindow:(NSWindow*)window
withTimeout:(NSTimeInterval)timeout;
-// Returns a unique client id (user-specific), creating a persistent
-// one in the user defaults, if necessary.
-- (NSString*)clientID;
-
-// Returns a dictionary that can be used to map Breakpad parameter names to
-// URL parameter names.
-- (NSMutableDictionary *)dictionaryForServerType:(NSString *)serverType;
-
-// Helper method to set HTTP parameters based on server type. This is
-// called right before the upload - crashParameters will contain, on exit,
-// URL parameters that should be sent with the minidump.
-- (BOOL)populateServerDictionary:(NSMutableDictionary *)crashParameters;
-
-// Initialization helper to create dictionaries mapping Breakpad
-// parameters to URL parameters
-- (void)createServerParameterDictionaries;
-
-// Accessor method for the URL parameter dictionary
-- (NSMutableDictionary *)urlParameterDictionary;
-
-// This method adds a key/value pair to the dictionary that
-// will be uploaded to the crash server.
-- (void)addServerParameter:(id)value forKey:(NSString *)key;
-
// This method is used to periodically update the UI with how many
// seconds are left in the dialog display.
- (void)updateSecondsLeftInDialogDisplay:(NSTimer*)theTimer;
@@ -255,289 +216,22 @@ NSString *const kDefaultServerType = @"google";
// in their comments/email.
- (void)controlTextDidBeginEditing:(NSNotification *)aNotification;
-// Records the uploaded crash ID to the log file.
-- (void)logUploadWithID:(const char *)uploadID;
+- (void)report;
@end
@implementation Reporter
//=============================================================================
-+ (uid_t)consoleUID {
- SCDynamicStoreRef store =
- SCDynamicStoreCreate(kCFAllocatorDefault, CFSTR("Reporter"), NULL, NULL);
- uid_t uid = -2; // Default to "nobody"
- if (store) {
- CFStringRef user = SCDynamicStoreCopyConsoleUser(store, &uid, NULL);
-
- if (user)
- CFRelease(user);
- else
- uid = -2;
-
- CFRelease(store);
- }
-
- return uid;
-}
-
-//=============================================================================
-- (id)initWithConfigurationFD:(int)fd {
+- (id)initWithConfigFile:(const char *)configFile {
if ((self = [super init])) {
- configFile_ = fd;
remainingDialogTime_ = 0;
- }
-
- // Because the reporter is embedded in the framework (and many copies
- // of the framework may exist) its not completely certain that the OS
- // will obey the com.apple.PreferenceSync.ExcludeAllSyncKeys in our
- // Info.plist. To make sure, also set the key directly if needed.
- NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
- if (![ud boolForKey:kApplePrefsSyncExcludeAllKey]) {
- [ud setBool:YES forKey:kApplePrefsSyncExcludeAllKey];
- }
-
- [self createServerParameterDictionaries];
-
- return self;
-}
-
-//=============================================================================
-- (NSString *)readString {
- NSMutableString *str = [NSMutableString stringWithCapacity:32];
- char ch[2] = { 0 };
-
- while (read(configFile_, &ch[0], 1) == 1) {
- if (ch[0] == '\n') {
- // Break if this is the first newline after reading some other string
- // data.
- if ([str length])
- break;
- } else {
- [str appendString:[NSString stringWithUTF8String:ch]];
- }
- }
-
- return str;
-}
-
-//=============================================================================
-- (NSData *)readData:(ssize_t)length {
- NSMutableData *data = [NSMutableData dataWithLength:length];
- char *bytes = (char *)[data bytes];
-
- if (read(configFile_, bytes, length) != length)
- return nil;
-
- return data;
-}
-
-//=============================================================================
-- (BOOL)readConfigurationData {
- parameters_ = [[NSMutableDictionary alloc] init];
-
- while (1) {
- NSString *key = [self readString];
-
- if (![key length])
- break;
-
- // Read the data. Try to convert to a UTF-8 string, or just save
- // the data
- NSString *lenStr = [self readString];
- ssize_t len = [lenStr intValue];
- NSData *data = [self readData:len];
- id value = [[NSString alloc] initWithData:data
- encoding:NSUTF8StringEncoding];
-
- // If the keyname is prefixed by BREAKPAD_SERVER_PARAMETER_PREFIX
- // that indicates that it should be uploaded to the server along
- // with the minidump, so we treat it specially.
- if ([key hasPrefix:@BREAKPAD_SERVER_PARAMETER_PREFIX]) {
- NSString *urlParameterKey =
- [key substringFromIndex:[@BREAKPAD_SERVER_PARAMETER_PREFIX length]];
- if ([urlParameterKey length]) {
- if (value) {
- [self addServerParameter:value
- forKey:urlParameterKey];
- } else {
- [self addServerParameter:data
- forKey:urlParameterKey];
- }
- }
- } else {
- [parameters_ setObject:(value ? value : data) forKey:key];
- }
- [value release];
- }
-
- // generate a unique client ID based on this host's MAC address
- // then add a key/value pair for it
- NSString *clientID = [self clientID];
- [parameters_ setObject:clientID forKey:@"guid"];
-
- close(configFile_);
- configFile_ = -1;
-
- return YES;
-}
-
-// Per user per machine
-- (NSString *)clientID {
- NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
- NSString *crashClientID = [ud stringForKey:kClientIdPreferenceKey];
- if (crashClientID) {
- return crashClientID;
- }
-
- // Otherwise, if we have no client id, generate one!
- srandom((int)[[NSDate date] timeIntervalSince1970]);
- long clientId1 = random();
- long clientId2 = random();
- long clientId3 = random();
- crashClientID = [NSString stringWithFormat:@"%x%x%x",
- clientId1, clientId2, clientId3];
-
- [ud setObject:crashClientID forKey:kClientIdPreferenceKey];
- [ud synchronize];
- return crashClientID;
-}
-
-//=============================================================================
-- (BOOL)readLogFileData {
- unsigned int logFileCounter = 0;
-
- NSString *logPath;
- size_t logFileTailSize =
- [[parameters_ objectForKey:@BREAKPAD_LOGFILE_UPLOAD_SIZE] intValue];
-
- NSMutableArray *logFilenames; // An array of NSString, one per log file
- logFilenames = [[NSMutableArray alloc] init];
-
- char tmpDirTemplate[80] = "/tmp/CrashUpload-XXXXX";
- char *tmpDir = mkdtemp(tmpDirTemplate);
-
- // Construct key names for the keys we expect to contain log file paths
- for(logFileCounter = 0;; logFileCounter++) {
- NSString *logFileKey = [NSString stringWithFormat:@"%@%d",
- @BREAKPAD_LOGFILE_KEY_PREFIX,
- logFileCounter];
-
- logPath = [parameters_ objectForKey:logFileKey];
-
- // They should all be consecutive, so if we don't find one, assume
- // we're done
-
- if (!logPath) {
- break;
- }
-
- NSData *entireLogFile = [[NSData alloc] initWithContentsOfFile:logPath];
-
- if (entireLogFile == nil) {
- continue;
- }
-
- NSRange fileRange;
-
- // Truncate the log file, only if necessary
-
- if ([entireLogFile length] <= logFileTailSize) {
- fileRange = NSMakeRange(0, [entireLogFile length]);
- } else {
- fileRange = NSMakeRange([entireLogFile length] - logFileTailSize,
- logFileTailSize);
- }
-
- char tmpFilenameTemplate[100];
-
- // Generate a template based on the log filename
- sprintf(tmpFilenameTemplate,"%s/%s-XXXX", tmpDir,
- [[logPath lastPathComponent] fileSystemRepresentation]);
-
- char *tmpFile = mktemp(tmpFilenameTemplate);
-
- NSData *logSubdata = [entireLogFile subdataWithRange:fileRange];
- NSString *tmpFileString = [NSString stringWithUTF8String:tmpFile];
- [logSubdata writeToFile:tmpFileString atomically:NO];
-
- [logFilenames addObject:[tmpFileString lastPathComponent]];
- [entireLogFile release];
- }
-
- if ([logFilenames count] == 0) {
- [logFilenames release];
- logFileData_ = nil;
- return NO;
- }
-
- // now, bzip all files into one
- NSTask *tarTask = [[NSTask alloc] init];
-
- [tarTask setCurrentDirectoryPath:[NSString stringWithUTF8String:tmpDir]];
- [tarTask setLaunchPath:@"/usr/bin/tar"];
-
- NSMutableArray *bzipArgs = [NSMutableArray arrayWithObjects:@"-cjvf",
- @"log.tar.bz2",nil];
- [bzipArgs addObjectsFromArray:logFilenames];
-
- [logFilenames release];
-
- [tarTask setArguments:bzipArgs];
- [tarTask launch];
- [tarTask waitUntilExit];
- [tarTask release];
-
- NSString *logTarFile = [NSString stringWithFormat:@"%s/log.tar.bz2",tmpDir];
- logFileData_ = [[NSData alloc] initWithContentsOfFile:logTarFile];
- if (logFileData_ == nil) {
- GTMLoggerDebug(@"Cannot find temp tar log file: %@", logTarFile);
- return NO;
- }
- return YES;
-
-}
-
-//=============================================================================
-- (BOOL)readMinidumpData {
- NSString *minidumpDir = [parameters_ objectForKey:@kReporterMinidumpDirectoryKey];
- NSString *minidumpID = [parameters_ objectForKey:@kReporterMinidumpIDKey];
-
- if (![minidumpID length])
- return NO;
-
- NSString *path = [minidumpDir stringByAppendingPathComponent:minidumpID];
- path = [path stringByAppendingPathExtension:@"dmp"];
-
- // check the size of the minidump and limit it to a reasonable size
- // before attempting to load into memory and upload
- const char *fileName = [path fileSystemRepresentation];
- struct stat fileStatus;
-
- BOOL success = YES;
-
- if (!stat(fileName, &fileStatus)) {
- if (fileStatus.st_size > kMinidumpFileLengthLimit) {
- fprintf(stderr, "Breakpad Reporter: minidump file too large " \
- "to upload : %d\n", (int)fileStatus.st_size);
- success = NO;
+ uploader_ = [[Uploader alloc] initWithConfigFile:configFile];
+ if (!uploader_) {
+ [self release];
+ return nil;
}
- } else {
- fprintf(stderr, "Breakpad Reporter: unable to determine minidump " \
- "file length\n");
- success = NO;
- }
-
- if (success) {
- minidumpContents_ = [[NSData alloc] initWithContentsOfFile:path];
- success = ([minidumpContents_ length] ? YES : NO);
- }
-
- if (!success) {
- // something wrong with the minidump file -- delete it
- unlink(fileName);
}
-
- return success;
+ return self;
}
//=============================================================================
@@ -560,12 +254,14 @@ NSString *const kDefaultServerType = @"google";
buttonPressed = [self runModalWindow:alertWindow_ withTimeout:timeout];
- // Extract info from the user into the parameters_ dictionary
+ // Extract info from the user into the uploader_.
if ([self commentsValue]) {
- [parameters_ setObject:[self commentsValue] forKey:@BREAKPAD_COMMENTS];
+ [[uploader_ parameters] setObject:[self commentsValue]
+ forKey:@BREAKPAD_COMMENTS];
}
if ([self emailValue]) {
- [parameters_ setObject:[self emailValue] forKey:@BREAKPAD_EMAIL];
+ [[uploader_ parameters] setObject:[self emailValue]
+ forKey:@BREAKPAD_EMAIL];
}
} else {
// Create an alert panel to tell the user something happened
@@ -804,9 +500,9 @@ doCommandBySelector:(SEL)commandSelector {
#pragma mark -
//=============================================================================
- (BOOL)reportIntervalElapsed {
- float interval = [[parameters_ objectForKey:@BREAKPAD_REPORT_INTERVAL]
- floatValue];
- NSString *program = [parameters_ objectForKey:@BREAKPAD_PRODUCT];
+ float interval = [[[uploader_ parameters]
+ objectForKey:@BREAKPAD_REPORT_INTERVAL] floatValue];
+ NSString *program = [[uploader_ parameters] objectForKey:@BREAKPAD_PRODUCT];
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
NSMutableDictionary *programDict =
[NSMutableDictionary dictionaryWithDictionary:[ud dictionaryForKey:program]];
@@ -831,29 +527,30 @@ doCommandBySelector:(SEL)commandSelector {
}
- (BOOL)isOnDemand {
- return [[parameters_ objectForKey:@BREAKPAD_ON_DEMAND]
+ return [[[uploader_ parameters] objectForKey:@BREAKPAD_ON_DEMAND]
isEqualToString:@"YES"];
}
- (BOOL)shouldSubmitSilently {
- return [[parameters_ objectForKey:@BREAKPAD_SKIP_CONFIRM]
+ return [[[uploader_ parameters] objectForKey:@BREAKPAD_SKIP_CONFIRM]
isEqualToString:@"YES"];
}
- (BOOL)shouldRequestComments {
- return [[parameters_ objectForKey:@BREAKPAD_REQUEST_COMMENTS]
+ return [[[uploader_ parameters] objectForKey:@BREAKPAD_REQUEST_COMMENTS]
isEqualToString:@"YES"];
}
- (BOOL)shouldRequestEmail {
- return [[parameters_ objectForKey:@BREAKPAD_REQUEST_EMAIL]
+ return [[[uploader_ parameters] objectForKey:@BREAKPAD_REQUEST_EMAIL]
isEqualToString:@"YES"];
}
- (NSString*)shortDialogMessage {
- NSString *displayName = [parameters_ objectForKey:@BREAKPAD_PRODUCT_DISPLAY];
+ NSString *displayName =
+ [[uploader_ parameters] objectForKey:@BREAKPAD_PRODUCT_DISPLAY];
if (![displayName length])
- displayName = [parameters_ objectForKey:@BREAKPAD_PRODUCT];
+ displayName = [[uploader_ parameters] objectForKey:@BREAKPAD_PRODUCT];
if ([self isOnDemand]) {
return [NSString
@@ -867,11 +564,12 @@ doCommandBySelector:(SEL)commandSelector {
}
- (NSString*)explanatoryDialogText {
- NSString *displayName = [parameters_ objectForKey:@BREAKPAD_PRODUCT_DISPLAY];
+ NSString *displayName =
+ [[uploader_ parameters] objectForKey:@BREAKPAD_PRODUCT_DISPLAY];
if (![displayName length])
- displayName = [parameters_ objectForKey:@BREAKPAD_PRODUCT];
+ displayName = [[uploader_ parameters] objectForKey:@BREAKPAD_PRODUCT];
- NSString *vendor = [parameters_ objectForKey:@BREAKPAD_VENDOR];
+ NSString *vendor = [[uploader_ parameters] objectForKey:@BREAKPAD_VENDOR];
if (![vendor length])
vendor = @"unknown vendor";
@@ -888,8 +586,8 @@ doCommandBySelector:(SEL)commandSelector {
- (NSTimeInterval)messageTimeout {
// Get the timeout value for the notification.
- NSTimeInterval timeout = [[parameters_ objectForKey:@BREAKPAD_CONFIRM_TIMEOUT]
- floatValue];
+ NSTimeInterval timeout = [[[uploader_ parameters]
+ objectForKey:@BREAKPAD_CONFIRM_TIMEOUT] floatValue];
// Require a timeout of at least a minute (except 0, which means no timeout).
if (timeout > 0.001 && timeout < 60.0) {
timeout = 60.0;
@@ -897,194 +595,13 @@ doCommandBySelector:(SEL)commandSelector {
return timeout;
}
-- (void)createServerParameterDictionaries {
- serverDictionary_ = [[NSMutableDictionary alloc] init];
- socorroDictionary_ = [[NSMutableDictionary alloc] init];
- googleDictionary_ = [[NSMutableDictionary alloc] init];
- extraServerVars_ = [[NSMutableDictionary alloc] init];
-
- [serverDictionary_ setObject:socorroDictionary_ forKey:kSocorroServerType];
- [serverDictionary_ setObject:googleDictionary_ forKey:kGoogleServerType];
-
- [googleDictionary_ setObject:@"ptime" forKey:@BREAKPAD_PROCESS_UP_TIME];
- [googleDictionary_ setObject:@"email" forKey:@BREAKPAD_EMAIL];
- [googleDictionary_ setObject:@"comments" forKey:@BREAKPAD_COMMENTS];
- [googleDictionary_ setObject:@"prod" forKey:@BREAKPAD_PRODUCT];
- [googleDictionary_ setObject:@"ver" forKey:@BREAKPAD_VERSION];
-
- [socorroDictionary_ setObject:@"Comments" forKey:@BREAKPAD_COMMENTS];
- [socorroDictionary_ setObject:@"CrashTime"
- forKey:@BREAKPAD_PROCESS_CRASH_TIME];
- [socorroDictionary_ setObject:@"StartupTime"
- forKey:@BREAKPAD_PROCESS_START_TIME];
- [socorroDictionary_ setObject:@"Version"
- forKey:@BREAKPAD_VERSION];
- [socorroDictionary_ setObject:@"ProductName"
- forKey:@BREAKPAD_PRODUCT];
- [socorroDictionary_ setObject:@"Email"
- forKey:@BREAKPAD_EMAIL];
-}
-
-- (NSMutableDictionary *)dictionaryForServerType:(NSString *)serverType {
- if (serverType == nil || [serverType length] == 0) {
- return [serverDictionary_ objectForKey:kDefaultServerType];
- }
- return [serverDictionary_ objectForKey:serverType];
-}
-
-- (NSMutableDictionary *)urlParameterDictionary {
- NSString *serverType = [parameters_ objectForKey:@BREAKPAD_SERVER_TYPE];
- return [self dictionaryForServerType:serverType];
-
-}
-
-- (BOOL)populateServerDictionary:(NSMutableDictionary *)crashParameters {
- NSDictionary *urlParameterNames = [self urlParameterDictionary];
-
- id key;
- NSEnumerator *enumerator = [parameters_ keyEnumerator];
-
- while ((key = [enumerator nextObject])) {
- // The key from parameters_ corresponds to a key in
- // urlParameterNames. The value in parameters_ gets stored in
- // crashParameters with a key that is the value in
- // urlParameterNames.
-
- // For instance, if parameters_ has [PRODUCT_NAME => "FOOBAR"] and
- // urlParameterNames has [PRODUCT_NAME => "pname"] the final HTTP
- // URL parameter becomes [pname => "FOOBAR"].
- NSString *breakpadParameterName = (NSString *)key;
- NSString *urlParameter = [urlParameterNames
- objectForKey:breakpadParameterName];
- if (urlParameter) {
- [crashParameters setObject:[parameters_ objectForKey:key]
- forKey:urlParameter];
- }
- }
-
- // Now, add the parameters that were added by the application.
- enumerator = [extraServerVars_ keyEnumerator];
-
- while ((key = [enumerator nextObject])) {
- NSString *urlParameterName = (NSString *)key;
- NSString *urlParameterValue =
- [extraServerVars_ objectForKey:urlParameterName];
- [crashParameters setObject:urlParameterValue
- forKey:urlParameterName];
- }
- return YES;
-}
-
-- (void)addServerParameter:(id)value forKey:(NSString *)key {
- [extraServerVars_ setObject:value forKey:key];
-}
-
-//=============================================================================
- (void)report {
- NSURL *url = [NSURL URLWithString:[parameters_ objectForKey:@BREAKPAD_URL]];
- HTTPMultipartUpload *upload = [[HTTPMultipartUpload alloc] initWithURL:url];
- NSMutableDictionary *uploadParameters = [NSMutableDictionary dictionary];
-
- if (![self populateServerDictionary:uploadParameters]) {
- return;
- }
-
- [upload setParameters:uploadParameters];
-
- // Add minidump file
- if (minidumpContents_) {
- [upload addFileContents:minidumpContents_ name:@"upload_file_minidump"];
-
- // Send it
- NSError *error = nil;
- NSData *data = [upload send:&error];
- NSString *result = [[NSString alloc] initWithData:data
- encoding:NSUTF8StringEncoding];
- const char *reportID = "ERR";
-
- if (error) {
- fprintf(stderr, "Breakpad Reporter: Send Error: %s\n",
- [[error description] UTF8String]);
- } else {
- NSCharacterSet *trimSet = [NSCharacterSet whitespaceAndNewlineCharacterSet];
- reportID = [[result stringByTrimmingCharactersInSet:trimSet] UTF8String];
- [self logUploadWithID:reportID];
- }
-
- // rename the minidump file according to the id returned from the server
- NSString *minidumpDir = [parameters_ objectForKey:@kReporterMinidumpDirectoryKey];
- NSString *minidumpID = [parameters_ objectForKey:@kReporterMinidumpIDKey];
-
- NSString *srcString = [NSString stringWithFormat:@"%@/%@.dmp",
- minidumpDir, minidumpID];
- NSString *destString = [NSString stringWithFormat:@"%@/%s.dmp",
- minidumpDir, reportID];
-
- const char *src = [srcString fileSystemRepresentation];
- const char *dest = [destString fileSystemRepresentation];
-
- if (rename(src, dest) == 0) {
- GTMLoggerInfo(@"Breakpad Reporter: Renamed %s to %s after successful " \
- "upload",src, dest);
- }
- else {
- // can't rename - don't worry - it's not important for users
- GTMLoggerDebug(@"Breakpad Reporter: successful upload report ID = %s\n",
- reportID );
- }
- [result release];
- }
-
- if (logFileData_) {
- HTTPMultipartUpload *logUpload = [[HTTPMultipartUpload alloc] initWithURL:url];
-
- [uploadParameters setObject:@"log" forKey:@"type"];
- [logUpload setParameters:uploadParameters];
- [logUpload addFileContents:logFileData_ name:@"log"];
-
- NSError *error = nil;
- NSData *data = [logUpload send:&error];
- NSString *result = [[NSString alloc] initWithData:data
- encoding:NSUTF8StringEncoding];
- [result release];
- [logUpload release];
- }
-
- [upload release];
-}
-
-- (void)logUploadWithID:(const char *)uploadID {
- NSString *minidumpDir =
- [parameters_ objectForKey:@kReporterMinidumpDirectoryKey];
- NSString *logFilePath = [NSString stringWithFormat:@"%@/%s",
- minidumpDir, kReporterLogFilename];
- NSString *logLine = [NSString stringWithFormat:@"%0.f,%s\n",
- [[NSDate date] timeIntervalSince1970], uploadID];
- NSData *logData = [logLine dataUsingEncoding:kCFStringEncodingUTF8];
-
- NSFileManager *fileManager = [NSFileManager defaultManager];
- if ([fileManager fileExistsAtPath:logFilePath]) {
- NSFileHandle *logFileHandle =
- [NSFileHandle fileHandleForWritingAtPath:logFilePath];
- [logFileHandle seekToEndOfFile];
- [logFileHandle writeData:logData];
- [logFileHandle closeFile];
- } else {
- [fileManager createFileAtPath:logFilePath
- contents:logData
- attributes:nil];
- }
+ [uploader_ report];
}
//=============================================================================
- (void)dealloc {
- [parameters_ release];
- [minidumpContents_ release];
- [logFileData_ release];
- [googleDictionary_ release];
- [socorroDictionary_ release];
- [serverDictionary_ release];
- [extraServerVars_ release];
+ [uploader_ release];
[super dealloc];
}
@@ -1175,40 +692,12 @@ int main(int argc, const char *argv[]) {
exit(1);
}
- // Open the file before (potentially) switching to console user
- int configFile = open(argv[1], O_RDONLY, 0600);
-
- if (configFile == -1) {
- GTMLoggerDebug(@"Couldn't open config file %s - %s",
- argv[1],
- strerror(errno));
- }
-
- // we want to avoid a build-up of old config files even if they
- // have been incorrectly written by the framework
- unlink(argv[1]);
-
- if (configFile == -1) {
- GTMLoggerDebug(@"Couldn't unlink config file %s - %s",
- argv[1],
- strerror(errno));
- exit(1);
- }
-
- Reporter *reporter = [[Reporter alloc] initWithConfigurationFD:configFile];
-
- // Gather the configuration data
- if (![reporter readConfigurationData]) {
- GTMLoggerDebug(@"reporter readConfigurationData failed");
+ Reporter *reporter = [[Reporter alloc] initWithConfigFile:argv[1]];
+ if (!reporter) {
+ GTMLoggerDebug(@"reporter initialization failed");
exit(1);
}
- // Read the minidump into memory before we (potentially) switch from the
- // root user
- [reporter readMinidumpData];
-
- [reporter readLogFileData];
-
// only submit a report if we have not recently crashed in the past
BOOL shouldSubmitReport = [reporter reportIntervalElapsed];
BOOL okayToSend = NO;
diff --git a/src/client/mac/sender/uploader.h b/src/client/mac/sender/uploader.h
new file mode 100644
index 00000000..653bfacd
--- /dev/null
+++ b/src/client/mac/sender/uploader.h
@@ -0,0 +1,74 @@
+// Copyright (c) 2011, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// This component uses the HTTPMultipartUpload of the breakpad project to send
+// the minidump and associated data to the crash reporting servers.
+// It will perform throttling based on the parameters passed to it and will
+// prompt the user to send the minidump.
+
+#include <Foundation/Foundation.h>
+
+#include "client/mac/Framework/Breakpad.h"
+#import "common/mac/GTMDefines.h"
+
+#define kClientIdPreferenceKey @"clientid"
+
+extern NSString *const kGoogleServerType;
+extern NSString *const kSocorroServerType;
+extern NSString *const kDefaultServerType;
+
+@interface Uploader : NSObject {
+ @private
+ int configFile_; // File descriptor for config file
+ NSMutableDictionary *parameters_; // Key value pairs of data (STRONG)
+ NSData *minidumpContents_; // The data in the minidump (STRONG)
+ NSData *logFileData_; // An NSdata for the tar,
+ // bz2'd log file.
+ NSMutableDictionary *serverDictionary_; // The dictionary mapping a
+ // server type name to a
+ // dictionary of server
+ // parameter names.
+ NSMutableDictionary *socorroDictionary_; // The dictionary for
+ // Socorro.
+ NSMutableDictionary *googleDictionary_; // The dictionary for
+ // Google.
+ NSMutableDictionary *extraServerVars_; // A dictionary containing
+ // extra key/value pairs
+ // that are uploaded to the
+ // crash server with the
+ // minidump.
+}
+
+- (id)initWithConfigFile:(const char *)configFile;
+
+- (NSMutableDictionary *)parameters;
+
+- (void)report;
+
+@end
diff --git a/src/client/mac/sender/uploader.m b/src/client/mac/sender/uploader.m
new file mode 100644
index 00000000..45d98a54
--- /dev/null
+++ b/src/client/mac/sender/uploader.m
@@ -0,0 +1,579 @@
+// Copyright (c) 2011, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#import <pwd.h>
+#import <sys/stat.h>
+#import <unistd.h>
+
+#import <SystemConfiguration/SystemConfiguration.h>
+
+#import "common/mac/HTTPMultipartUpload.h"
+
+#import "client/mac/sender/uploader.h"
+#import "common/mac/GTMLogger.h"
+
+
+const int kMinidumpFileLengthLimit = 800000;
+
+#define kApplePrefsSyncExcludeAllKey \
+ @"com.apple.PreferenceSync.ExcludeAllSyncKeys"
+
+NSString *const kGoogleServerType = @"google";
+NSString *const kSocorroServerType = @"socorro";
+NSString *const kDefaultServerType = @"google";
+
+#define GTMLoggerDebug NSLog
+
+#pragma mark -
+
+@interface Uploader(PrivateMethods)
+- (NSString *)readString;
+- (NSData *)readData:(ssize_t)length;
+
+- (BOOL)readConfigurationData;
+- (BOOL)readMinidumpData;
+- (BOOL)readLogFileData;
+
+// Returns a unique client id (user-specific), creating a persistent
+// one in the user defaults, if necessary.
+- (NSString*)clientID;
+
+// Returns a dictionary that can be used to map Breakpad parameter names to
+// URL parameter names.
+- (NSMutableDictionary *)dictionaryForServerType:(NSString *)serverType;
+
+// Helper method to set HTTP parameters based on server type. This is
+// called right before the upload - crashParameters will contain, on exit,
+// URL parameters that should be sent with the minidump.
+- (BOOL)populateServerDictionary:(NSMutableDictionary *)crashParameters;
+
+// Initialization helper to create dictionaries mapping Breakpad
+// parameters to URL parameters
+- (void)createServerParameterDictionaries;
+
+// Accessor method for the URL parameter dictionary
+- (NSMutableDictionary *)urlParameterDictionary;
+
+// This method adds a key/value pair to the dictionary that
+// will be uploaded to the crash server.
+- (void)addServerParameter:(id)value forKey:(NSString *)key;
+
+// Records the uploaded crash ID to the log file.
+- (void)logUploadWithID:(const char *)uploadID;
+
+@end
+
+@implementation Uploader
+
+//=============================================================================
+- (id)initWithConfigFile:(const char *)configFile {
+ if ((self = [super init])) {
+
+ configFile_ = open(configFile, O_RDONLY, 0600);
+ if (configFile_ == -1) {
+ GTMLoggerDebug(@"Couldn't open config file %s - %s",
+ configFile,
+ strerror(errno));
+ }
+
+ // we want to avoid a build-up of old config files even if they
+ // have been incorrectly written by the framework
+ if (unlink(configFile)) {
+ GTMLoggerDebug(@"Couldn't unlink config file %s - %s",
+ configFile,
+ strerror(errno));
+ }
+
+ if (configFile_ == -1) {
+ [self release];
+ return nil;
+ }
+
+ // Because the reporter is embedded in the framework (and many copies
+ // of the framework may exist) its not completely certain that the OS
+ // will obey the com.apple.PreferenceSync.ExcludeAllSyncKeys in our
+ // Info.plist. To make sure, also set the key directly if needed.
+ NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
+ if (![ud boolForKey:kApplePrefsSyncExcludeAllKey]) {
+ [ud setBool:YES forKey:kApplePrefsSyncExcludeAllKey];
+ }
+
+ [self createServerParameterDictionaries];
+
+ if (![self readConfigurationData]) {
+ GTMLoggerDebug(@"uploader readConfigurationData failed");
+ [self release];
+ return nil;
+ }
+
+ // Read the minidump into memory.
+ [self readMinidumpData];
+ [self readLogFileData];
+
+ }
+ return self;
+}
+
+//=============================================================================
+- (NSString *)readString {
+ NSMutableString *str = [NSMutableString stringWithCapacity:32];
+ char ch[2] = { 0 };
+
+ while (read(configFile_, &ch[0], 1) == 1) {
+ if (ch[0] == '\n') {
+ // Break if this is the first newline after reading some other string
+ // data.
+ if ([str length])
+ break;
+ } else {
+ [str appendString:[NSString stringWithUTF8String:ch]];
+ }
+ }
+
+ return str;
+}
+
+//=============================================================================
+- (NSData *)readData:(ssize_t)length {
+ NSMutableData *data = [NSMutableData dataWithLength:length];
+ char *bytes = (char *)[data bytes];
+
+ if (read(configFile_, bytes, length) != length)
+ return nil;
+
+ return data;
+}
+
+//=============================================================================
+- (BOOL)readConfigurationData {
+ parameters_ = [[NSMutableDictionary alloc] init];
+
+ while (1) {
+ NSString *key = [self readString];
+
+ if (![key length])
+ break;
+
+ // Read the data. Try to convert to a UTF-8 string, or just save
+ // the data
+ NSString *lenStr = [self readString];
+ ssize_t len = [lenStr intValue];
+ NSData *data = [self readData:len];
+ id value = [[NSString alloc] initWithData:data
+ encoding:NSUTF8StringEncoding];
+
+ // If the keyname is prefixed by BREAKPAD_SERVER_PARAMETER_PREFIX
+ // that indicates that it should be uploaded to the server along
+ // with the minidump, so we treat it specially.
+ if ([key hasPrefix:@BREAKPAD_SERVER_PARAMETER_PREFIX]) {
+ NSString *urlParameterKey =
+ [key substringFromIndex:[@BREAKPAD_SERVER_PARAMETER_PREFIX length]];
+ if ([urlParameterKey length]) {
+ if (value) {
+ [self addServerParameter:value
+ forKey:urlParameterKey];
+ } else {
+ [self addServerParameter:data
+ forKey:urlParameterKey];
+ }
+ }
+ } else {
+ [parameters_ setObject:(value ? value : data) forKey:key];
+ }
+ [value release];
+ }
+
+ // generate a unique client ID based on this host's MAC address
+ // then add a key/value pair for it
+ NSString *clientID = [self clientID];
+ [parameters_ setObject:clientID forKey:@"guid"];
+
+ close(configFile_);
+ configFile_ = -1;
+
+ return YES;
+}
+
+// Per user per machine
+- (NSString *)clientID {
+ NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
+ NSString *crashClientID = [ud stringForKey:kClientIdPreferenceKey];
+ if (crashClientID) {
+ return crashClientID;
+ }
+
+ // Otherwise, if we have no client id, generate one!
+ srandom((int)[[NSDate date] timeIntervalSince1970]);
+ long clientId1 = random();
+ long clientId2 = random();
+ long clientId3 = random();
+ crashClientID = [NSString stringWithFormat:@"%x%x%x",
+ clientId1, clientId2, clientId3];
+
+ [ud setObject:crashClientID forKey:kClientIdPreferenceKey];
+ [ud synchronize];
+ return crashClientID;
+}
+
+//=============================================================================
+- (BOOL)readLogFileData {
+ unsigned int logFileCounter = 0;
+
+ NSString *logPath;
+ size_t logFileTailSize =
+ [[parameters_ objectForKey:@BREAKPAD_LOGFILE_UPLOAD_SIZE] intValue];
+
+ NSMutableArray *logFilenames; // An array of NSString, one per log file
+ logFilenames = [[NSMutableArray alloc] init];
+
+ char tmpDirTemplate[80] = "/tmp/CrashUpload-XXXXX";
+ char *tmpDir = mkdtemp(tmpDirTemplate);
+
+ // Construct key names for the keys we expect to contain log file paths
+ for(logFileCounter = 0;; logFileCounter++) {
+ NSString *logFileKey = [NSString stringWithFormat:@"%@%d",
+ @BREAKPAD_LOGFILE_KEY_PREFIX,
+ logFileCounter];
+
+ logPath = [parameters_ objectForKey:logFileKey];
+
+ // They should all be consecutive, so if we don't find one, assume
+ // we're done
+
+ if (!logPath) {
+ break;
+ }
+
+ NSData *entireLogFile = [[NSData alloc] initWithContentsOfFile:logPath];
+
+ if (entireLogFile == nil) {
+ continue;
+ }
+
+ NSRange fileRange;
+
+ // Truncate the log file, only if necessary
+
+ if ([entireLogFile length] <= logFileTailSize) {
+ fileRange = NSMakeRange(0, [entireLogFile length]);
+ } else {
+ fileRange = NSMakeRange([entireLogFile length] - logFileTailSize,
+ logFileTailSize);
+ }
+
+ char tmpFilenameTemplate[100];
+
+ // Generate a template based on the log filename
+ sprintf(tmpFilenameTemplate,"%s/%s-XXXX", tmpDir,
+ [[logPath lastPathComponent] fileSystemRepresentation]);
+
+ char *tmpFile = mktemp(tmpFilenameTemplate);
+
+ NSData *logSubdata = [entireLogFile subdataWithRange:fileRange];
+ NSString *tmpFileString = [NSString stringWithUTF8String:tmpFile];
+ [logSubdata writeToFile:tmpFileString atomically:NO];
+
+ [logFilenames addObject:[tmpFileString lastPathComponent]];
+ [entireLogFile release];
+ }
+
+ if ([logFilenames count] == 0) {
+ [logFilenames release];
+ logFileData_ = nil;
+ return NO;
+ }
+
+ // now, bzip all files into one
+ NSTask *tarTask = [[NSTask alloc] init];
+
+ [tarTask setCurrentDirectoryPath:[NSString stringWithUTF8String:tmpDir]];
+ [tarTask setLaunchPath:@"/usr/bin/tar"];
+
+ NSMutableArray *bzipArgs = [NSMutableArray arrayWithObjects:@"-cjvf",
+ @"log.tar.bz2",nil];
+ [bzipArgs addObjectsFromArray:logFilenames];
+
+ [logFilenames release];
+
+ [tarTask setArguments:bzipArgs];
+ [tarTask launch];
+ [tarTask waitUntilExit];
+ [tarTask release];
+
+ NSString *logTarFile = [NSString stringWithFormat:@"%s/log.tar.bz2",tmpDir];
+ logFileData_ = [[NSData alloc] initWithContentsOfFile:logTarFile];
+ if (logFileData_ == nil) {
+ GTMLoggerDebug(@"Cannot find temp tar log file: %@", logTarFile);
+ return NO;
+ }
+ return YES;
+}
+
+//=============================================================================
+- (BOOL)readMinidumpData {
+ NSString *minidumpDir =
+ [parameters_ objectForKey:@kReporterMinidumpDirectoryKey];
+ NSString *minidumpID = [parameters_ objectForKey:@kReporterMinidumpIDKey];
+
+ if (![minidumpID length])
+ return NO;
+
+ NSString *path = [minidumpDir stringByAppendingPathComponent:minidumpID];
+ path = [path stringByAppendingPathExtension:@"dmp"];
+
+ // check the size of the minidump and limit it to a reasonable size
+ // before attempting to load into memory and upload
+ const char *fileName = [path fileSystemRepresentation];
+ struct stat fileStatus;
+
+ BOOL success = YES;
+
+ if (!stat(fileName, &fileStatus)) {
+ if (fileStatus.st_size > kMinidumpFileLengthLimit) {
+ fprintf(stderr, "Breakpad Uploader: minidump file too large " \
+ "to upload : %d\n", (int)fileStatus.st_size);
+ success = NO;
+ }
+ } else {
+ fprintf(stderr, "Breakpad Uploader: unable to determine minidump " \
+ "file length\n");
+ success = NO;
+ }
+
+ if (success) {
+ minidumpContents_ = [[NSData alloc] initWithContentsOfFile:path];
+ success = ([minidumpContents_ length] ? YES : NO);
+ }
+
+ if (!success) {
+ // something wrong with the minidump file -- delete it
+ unlink(fileName);
+ }
+
+ return success;
+}
+
+#pragma mark -
+//=============================================================================
+
+- (void)createServerParameterDictionaries {
+ serverDictionary_ = [[NSMutableDictionary alloc] init];
+ socorroDictionary_ = [[NSMutableDictionary alloc] init];
+ googleDictionary_ = [[NSMutableDictionary alloc] init];
+ extraServerVars_ = [[NSMutableDictionary alloc] init];
+
+ [serverDictionary_ setObject:socorroDictionary_ forKey:kSocorroServerType];
+ [serverDictionary_ setObject:googleDictionary_ forKey:kGoogleServerType];
+
+ [googleDictionary_ setObject:@"ptime" forKey:@BREAKPAD_PROCESS_UP_TIME];
+ [googleDictionary_ setObject:@"email" forKey:@BREAKPAD_EMAIL];
+ [googleDictionary_ setObject:@"comments" forKey:@BREAKPAD_COMMENTS];
+ [googleDictionary_ setObject:@"prod" forKey:@BREAKPAD_PRODUCT];
+ [googleDictionary_ setObject:@"ver" forKey:@BREAKPAD_VERSION];
+
+ [socorroDictionary_ setObject:@"Comments" forKey:@BREAKPAD_COMMENTS];
+ [socorroDictionary_ setObject:@"CrashTime"
+ forKey:@BREAKPAD_PROCESS_CRASH_TIME];
+ [socorroDictionary_ setObject:@"StartupTime"
+ forKey:@BREAKPAD_PROCESS_START_TIME];
+ [socorroDictionary_ setObject:@"Version"
+ forKey:@BREAKPAD_VERSION];
+ [socorroDictionary_ setObject:@"ProductName"
+ forKey:@BREAKPAD_PRODUCT];
+ [socorroDictionary_ setObject:@"Email"
+ forKey:@BREAKPAD_EMAIL];
+}
+
+- (NSMutableDictionary *)dictionaryForServerType:(NSString *)serverType {
+ if (serverType == nil || [serverType length] == 0) {
+ return [serverDictionary_ objectForKey:kDefaultServerType];
+ }
+ return [serverDictionary_ objectForKey:serverType];
+}
+
+- (NSMutableDictionary *)urlParameterDictionary {
+ NSString *serverType = [parameters_ objectForKey:@BREAKPAD_SERVER_TYPE];
+ return [self dictionaryForServerType:serverType];
+
+}
+
+- (BOOL)populateServerDictionary:(NSMutableDictionary *)crashParameters {
+ NSDictionary *urlParameterNames = [self urlParameterDictionary];
+
+ id key;
+ NSEnumerator *enumerator = [parameters_ keyEnumerator];
+
+ while ((key = [enumerator nextObject])) {
+ // The key from parameters_ corresponds to a key in
+ // urlParameterNames. The value in parameters_ gets stored in
+ // crashParameters with a key that is the value in
+ // urlParameterNames.
+
+ // For instance, if parameters_ has [PRODUCT_NAME => "FOOBAR"] and
+ // urlParameterNames has [PRODUCT_NAME => "pname"] the final HTTP
+ // URL parameter becomes [pname => "FOOBAR"].
+ NSString *breakpadParameterName = (NSString *)key;
+ NSString *urlParameter = [urlParameterNames
+ objectForKey:breakpadParameterName];
+ if (urlParameter) {
+ [crashParameters setObject:[parameters_ objectForKey:key]
+ forKey:urlParameter];
+ }
+ }
+
+ // Now, add the parameters that were added by the application.
+ enumerator = [extraServerVars_ keyEnumerator];
+
+ while ((key = [enumerator nextObject])) {
+ NSString *urlParameterName = (NSString *)key;
+ NSString *urlParameterValue =
+ [extraServerVars_ objectForKey:urlParameterName];
+ [crashParameters setObject:urlParameterValue
+ forKey:urlParameterName];
+ }
+ return YES;
+}
+
+- (void)addServerParameter:(id)value forKey:(NSString *)key {
+ [extraServerVars_ setObject:value forKey:key];
+}
+
+//=============================================================================
+- (void)report {
+ NSURL *url = [NSURL URLWithString:[parameters_ objectForKey:@BREAKPAD_URL]];
+ HTTPMultipartUpload *upload = [[HTTPMultipartUpload alloc] initWithURL:url];
+ NSMutableDictionary *uploadParameters = [NSMutableDictionary dictionary];
+
+ if (![self populateServerDictionary:uploadParameters]) {
+ return;
+ }
+
+ [upload setParameters:uploadParameters];
+
+ // Add minidump file
+ if (minidumpContents_) {
+ [upload addFileContents:minidumpContents_ name:@"upload_file_minidump"];
+
+ // Send it
+ NSError *error = nil;
+ NSData *data = [upload send:&error];
+ NSString *result = [[NSString alloc] initWithData:data
+ encoding:NSUTF8StringEncoding];
+ const char *reportID = "ERR";
+
+ if (error) {
+ fprintf(stderr, "Breakpad Uploader: Send Error: %s\n",
+ [[error description] UTF8String]);
+ } else {
+ NSCharacterSet *trimSet =
+ [NSCharacterSet whitespaceAndNewlineCharacterSet];
+ reportID = [[result stringByTrimmingCharactersInSet:trimSet] UTF8String];
+ [self logUploadWithID:reportID];
+ }
+
+ // rename the minidump file according to the id returned from the server
+ NSString *minidumpDir =
+ [parameters_ objectForKey:@kReporterMinidumpDirectoryKey];
+ NSString *minidumpID = [parameters_ objectForKey:@kReporterMinidumpIDKey];
+
+ NSString *srcString = [NSString stringWithFormat:@"%@/%@.dmp",
+ minidumpDir, minidumpID];
+ NSString *destString = [NSString stringWithFormat:@"%@/%s.dmp",
+ minidumpDir, reportID];
+
+ const char *src = [srcString fileSystemRepresentation];
+ const char *dest = [destString fileSystemRepresentation];
+
+ if (rename(src, dest) == 0) {
+ GTMLoggerInfo(@"Breakpad Uploader: Renamed %s to %s after successful " \
+ "upload",src, dest);
+ }
+ else {
+ // can't rename - don't worry - it's not important for users
+ GTMLoggerDebug(@"Breakpad Uploader: successful upload report ID = %s\n",
+ reportID );
+ }
+ [result release];
+ }
+
+ if (logFileData_) {
+ HTTPMultipartUpload *logUpload =
+ [[HTTPMultipartUpload alloc] initWithURL:url];
+
+ [uploadParameters setObject:@"log" forKey:@"type"];
+ [logUpload setParameters:uploadParameters];
+ [logUpload addFileContents:logFileData_ name:@"log"];
+
+ NSError *error = nil;
+ NSData *data = [logUpload send:&error];
+ NSString *result = [[NSString alloc] initWithData:data
+ encoding:NSUTF8StringEncoding];
+ [result release];
+ [logUpload release];
+ }
+
+ [upload release];
+}
+
+- (void)logUploadWithID:(const char *)uploadID {
+ NSString *minidumpDir =
+ [parameters_ objectForKey:@kReporterMinidumpDirectoryKey];
+ NSString *logFilePath = [NSString stringWithFormat:@"%@/%s",
+ minidumpDir, kReporterLogFilename];
+ NSString *logLine = [NSString stringWithFormat:@"%0.f,%s\n",
+ [[NSDate date] timeIntervalSince1970], uploadID];
+ NSData *logData = [logLine dataUsingEncoding:NSUTF8StringEncoding];
+
+ NSFileManager *fileManager = [NSFileManager defaultManager];
+ if ([fileManager fileExistsAtPath:logFilePath]) {
+ NSFileHandle *logFileHandle =
+ [NSFileHandle fileHandleForWritingAtPath:logFilePath];
+ [logFileHandle seekToEndOfFile];
+ [logFileHandle writeData:logData];
+ [logFileHandle closeFile];
+ } else {
+ [fileManager createFileAtPath:logFilePath
+ contents:logData
+ attributes:nil];
+ }
+}
+
+//=============================================================================
+- (void)dealloc {
+ [parameters_ release];
+ [minidumpContents_ release];
+ [logFileData_ release];
+ [googleDictionary_ release];
+ [socorroDictionary_ release];
+ [serverDictionary_ release];
+ [extraServerVars_ release];
+ [super dealloc];
+}
+
+@end