1 // Copyright (c) 2011, Google Inc.
2 // All rights reserved.
4 // Redistribution and use in source and binary forms, with or without
5 // modification, are permitted provided that the following conditions are
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
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.
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.
32 #include <TargetConditionals.h>
35 #import <SystemConfiguration/SystemConfiguration.h>
37 #import "common/mac/HTTPMultipartUpload.h"
39 #import "client/apple/Framework/BreakpadDefines.h"
40 #import "client/mac/sender/uploader.h"
41 #import "common/mac/GTMLogger.h"
43 const int kMinidumpFileLengthLimit = 2 * 1024 * 1024; // 2MB
45 #define kApplePrefsSyncExcludeAllKey \
46 @"com.apple.PreferenceSync.ExcludeAllSyncKeys"
48 NSString *const kGoogleServerType = @"google";
49 NSString *const kSocorroServerType = @"socorro";
50 NSString *const kDefaultServerType = @"google";
55 // Read one line from the configuration file.
56 NSString *readString(int fileId) {
57 NSMutableString *str = [NSMutableString stringWithCapacity:32];
60 while (read(fileId, &ch[0], 1) == 1) {
62 // Break if this is the first newline after reading some other string
67 [str appendString:[NSString stringWithUTF8String:ch]];
74 //=============================================================================
75 // Read |length| of binary data from the configuration file. This method will
76 // returns |nil| in case of error.
77 NSData *readData(int fileId, ssize_t length) {
78 NSMutableData *data = [NSMutableData dataWithLength:length];
79 char *bytes = (char *)[data bytes];
81 if (read(fileId, bytes, length) != length)
87 //=============================================================================
88 // Read the configuration from the config file.
89 NSDictionary *readConfigurationData(const char *configFile) {
90 int fileId = open(configFile, O_RDONLY, 0600);
92 GTMLoggerDebug(@"Couldn't open config file %s - %s",
97 // we want to avoid a build-up of old config files even if they
98 // have been incorrectly written by the framework
99 if (unlink(configFile)) {
100 GTMLoggerDebug(@"Couldn't unlink config file %s - %s",
109 NSMutableDictionary *config = [NSMutableDictionary dictionary];
112 NSString *key = readString(fileId);
117 // Read the data. Try to convert to a UTF-8 string, or just save
119 NSString *lenStr = readString(fileId);
120 ssize_t len = [lenStr intValue];
121 NSData *data = readData(fileId, len);
122 id value = [[NSString alloc] initWithData:data
123 encoding:NSUTF8StringEncoding];
125 [config setObject:(value ? value : data) forKey:key];
136 @interface Uploader(PrivateMethods)
138 // Update |parameters_| as well as the server parameters using |config|.
139 - (void)translateConfigurationData:(NSDictionary *)config;
141 // Read the minidump referenced in |parameters_| and update |minidumpContents_|
143 - (BOOL)readMinidumpData;
145 // Read the log files referenced in |parameters_| and update |logFileData_|
146 // with their content.
147 - (BOOL)readLogFileData;
149 // Returns a unique client id (user-specific), creating a persistent
150 // one in the user defaults, if necessary.
151 - (NSString*)clientID;
153 // Returns a dictionary that can be used to map Breakpad parameter names to
154 // URL parameter names.
155 - (NSMutableDictionary *)dictionaryForServerType:(NSString *)serverType;
157 // Helper method to set HTTP parameters based on server type. This is
158 // called right before the upload - crashParameters will contain, on exit,
159 // URL parameters that should be sent with the minidump.
160 - (BOOL)populateServerDictionary:(NSMutableDictionary *)crashParameters;
162 // Initialization helper to create dictionaries mapping Breakpad
163 // parameters to URL parameters
164 - (void)createServerParameterDictionaries;
166 // Accessor method for the URL parameter dictionary
167 - (NSMutableDictionary *)urlParameterDictionary;
169 // Records the uploaded crash ID to the log file.
170 - (void)logUploadWithID:(const char *)uploadID;
173 @implementation Uploader
175 //=============================================================================
176 - (id)initWithConfigFile:(const char *)configFile {
177 NSDictionary *config = readConfigurationData(configFile);
181 return [self initWithConfig:config];
184 //=============================================================================
185 - (id)initWithConfig:(NSDictionary *)config {
186 if ((self = [super init])) {
187 // Because the reporter is embedded in the framework (and many copies
188 // of the framework may exist) its not completely certain that the OS
189 // will obey the com.apple.PreferenceSync.ExcludeAllSyncKeys in our
190 // Info.plist. To make sure, also set the key directly if needed.
191 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
192 if (![ud boolForKey:kApplePrefsSyncExcludeAllKey]) {
193 [ud setBool:YES forKey:kApplePrefsSyncExcludeAllKey];
196 [self createServerParameterDictionaries];
198 [self translateConfigurationData:config];
200 // Read the minidump into memory.
201 [self readMinidumpData];
202 [self readLogFileData];
207 //=============================================================================
208 - (void)translateConfigurationData:(NSDictionary *)config {
209 parameters_ = [[NSMutableDictionary alloc] init];
211 NSEnumerator *it = [config keyEnumerator];
212 while (NSString *key = [it nextObject]) {
213 // If the keyname is prefixed by BREAKPAD_SERVER_PARAMETER_PREFIX
214 // that indicates that it should be uploaded to the server along
215 // with the minidump, so we treat it specially.
216 if ([key hasPrefix:@BREAKPAD_SERVER_PARAMETER_PREFIX]) {
217 NSString *urlParameterKey =
218 [key substringFromIndex:[@BREAKPAD_SERVER_PARAMETER_PREFIX length]];
219 if ([urlParameterKey length]) {
220 id value = [config objectForKey:key];
221 if ([value isKindOfClass:[NSString class]]) {
222 [self addServerParameter:(NSString *)value
223 forKey:urlParameterKey];
225 [self addServerParameter:(NSData *)value
226 forKey:urlParameterKey];
230 [parameters_ setObject:[config objectForKey:key] forKey:key];
234 // generate a unique client ID based on this host's MAC address
235 // then add a key/value pair for it
236 NSString *clientID = [self clientID];
237 [parameters_ setObject:clientID forKey:@"guid"];
240 // Per user per machine
241 - (NSString *)clientID {
242 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
243 NSString *crashClientID = [ud stringForKey:kClientIdPreferenceKey];
245 return crashClientID;
248 // Otherwise, if we have no client id, generate one!
249 srandom((int)[[NSDate date] timeIntervalSince1970]);
250 long clientId1 = random();
251 long clientId2 = random();
252 long clientId3 = random();
253 crashClientID = [NSString stringWithFormat:@"%lx%lx%lx",
254 clientId1, clientId2, clientId3];
256 [ud setObject:crashClientID forKey:kClientIdPreferenceKey];
258 return crashClientID;
261 //=============================================================================
262 - (BOOL)readLogFileData {
266 unsigned int logFileCounter = 0;
269 size_t logFileTailSize =
270 [[parameters_ objectForKey:@BREAKPAD_LOGFILE_UPLOAD_SIZE] intValue];
272 NSMutableArray *logFilenames; // An array of NSString, one per log file
273 logFilenames = [[NSMutableArray alloc] init];
275 char tmpDirTemplate[80] = "/tmp/CrashUpload-XXXXX";
276 char *tmpDir = mkdtemp(tmpDirTemplate);
278 // Construct key names for the keys we expect to contain log file paths
279 for(logFileCounter = 0;; logFileCounter++) {
280 NSString *logFileKey = [NSString stringWithFormat:@"%@%d",
281 @BREAKPAD_LOGFILE_KEY_PREFIX,
284 logPath = [parameters_ objectForKey:logFileKey];
286 // They should all be consecutive, so if we don't find one, assume
293 NSData *entireLogFile = [[NSData alloc] initWithContentsOfFile:logPath];
295 if (entireLogFile == nil) {
301 // Truncate the log file, only if necessary
303 if ([entireLogFile length] <= logFileTailSize) {
304 fileRange = NSMakeRange(0, [entireLogFile length]);
306 fileRange = NSMakeRange([entireLogFile length] - logFileTailSize,
310 char tmpFilenameTemplate[100];
312 // Generate a template based on the log filename
313 sprintf(tmpFilenameTemplate,"%s/%s-XXXX", tmpDir,
314 [[logPath lastPathComponent] fileSystemRepresentation]);
316 char *tmpFile = mktemp(tmpFilenameTemplate);
318 NSData *logSubdata = [entireLogFile subdataWithRange:fileRange];
319 NSString *tmpFileString = [NSString stringWithUTF8String:tmpFile];
320 [logSubdata writeToFile:tmpFileString atomically:NO];
322 [logFilenames addObject:[tmpFileString lastPathComponent]];
323 [entireLogFile release];
326 if ([logFilenames count] == 0) {
327 [logFilenames release];
332 // now, bzip all files into one
333 NSTask *tarTask = [[NSTask alloc] init];
335 [tarTask setCurrentDirectoryPath:[NSString stringWithUTF8String:tmpDir]];
336 [tarTask setLaunchPath:@"/usr/bin/tar"];
338 NSMutableArray *bzipArgs = [NSMutableArray arrayWithObjects:@"-cjvf",
340 [bzipArgs addObjectsFromArray:logFilenames];
342 [logFilenames release];
344 [tarTask setArguments:bzipArgs];
346 [tarTask waitUntilExit];
349 NSString *logTarFile = [NSString stringWithFormat:@"%s/log.tar.bz2",tmpDir];
350 logFileData_ = [[NSData alloc] initWithContentsOfFile:logTarFile];
351 if (logFileData_ == nil) {
352 GTMLoggerDebug(@"Cannot find temp tar log file: %@", logTarFile);
356 #endif // TARGET_OS_IPHONE
359 //=============================================================================
360 - (BOOL)readMinidumpData {
361 NSString *minidumpDir =
362 [parameters_ objectForKey:@kReporterMinidumpDirectoryKey];
363 NSString *minidumpID = [parameters_ objectForKey:@kReporterMinidumpIDKey];
365 if (![minidumpID length])
368 NSString *path = [minidumpDir stringByAppendingPathComponent:minidumpID];
369 path = [path stringByAppendingPathExtension:@"dmp"];
371 // check the size of the minidump and limit it to a reasonable size
372 // before attempting to load into memory and upload
373 const char *fileName = [path fileSystemRepresentation];
374 struct stat fileStatus;
378 if (!stat(fileName, &fileStatus)) {
379 if (fileStatus.st_size > kMinidumpFileLengthLimit) {
380 fprintf(stderr, "Breakpad Uploader: minidump file too large " \
381 "to upload : %d\n", (int)fileStatus.st_size);
385 fprintf(stderr, "Breakpad Uploader: unable to determine minidump " \
391 minidumpContents_ = [[NSData alloc] initWithContentsOfFile:path];
392 success = ([minidumpContents_ length] ? YES : NO);
396 // something wrong with the minidump file -- delete it
404 //=============================================================================
406 - (void)createServerParameterDictionaries {
407 serverDictionary_ = [[NSMutableDictionary alloc] init];
408 socorroDictionary_ = [[NSMutableDictionary alloc] init];
409 googleDictionary_ = [[NSMutableDictionary alloc] init];
410 extraServerVars_ = [[NSMutableDictionary alloc] init];
412 [serverDictionary_ setObject:socorroDictionary_ forKey:kSocorroServerType];
413 [serverDictionary_ setObject:googleDictionary_ forKey:kGoogleServerType];
415 [googleDictionary_ setObject:@"ptime" forKey:@BREAKPAD_PROCESS_UP_TIME];
416 [googleDictionary_ setObject:@"email" forKey:@BREAKPAD_EMAIL];
417 [googleDictionary_ setObject:@"comments" forKey:@BREAKPAD_COMMENTS];
418 [googleDictionary_ setObject:@"prod" forKey:@BREAKPAD_PRODUCT];
419 [googleDictionary_ setObject:@"ver" forKey:@BREAKPAD_VERSION];
420 [googleDictionary_ setObject:@"guid" forKey:@"guid"];
422 [socorroDictionary_ setObject:@"Comments" forKey:@BREAKPAD_COMMENTS];
423 [socorroDictionary_ setObject:@"CrashTime"
424 forKey:@BREAKPAD_PROCESS_CRASH_TIME];
425 [socorroDictionary_ setObject:@"StartupTime"
426 forKey:@BREAKPAD_PROCESS_START_TIME];
427 [socorroDictionary_ setObject:@"Version"
428 forKey:@BREAKPAD_VERSION];
429 [socorroDictionary_ setObject:@"ProductName"
430 forKey:@BREAKPAD_PRODUCT];
431 [socorroDictionary_ setObject:@"Email"
432 forKey:@BREAKPAD_EMAIL];
435 - (NSMutableDictionary *)dictionaryForServerType:(NSString *)serverType {
436 if (serverType == nil || [serverType length] == 0) {
437 return [serverDictionary_ objectForKey:kDefaultServerType];
439 return [serverDictionary_ objectForKey:serverType];
442 - (NSMutableDictionary *)urlParameterDictionary {
443 NSString *serverType = [parameters_ objectForKey:@BREAKPAD_SERVER_TYPE];
444 return [self dictionaryForServerType:serverType];
448 - (BOOL)populateServerDictionary:(NSMutableDictionary *)crashParameters {
449 NSDictionary *urlParameterNames = [self urlParameterDictionary];
452 NSEnumerator *enumerator = [parameters_ keyEnumerator];
454 while ((key = [enumerator nextObject])) {
455 // The key from parameters_ corresponds to a key in
456 // urlParameterNames. The value in parameters_ gets stored in
457 // crashParameters with a key that is the value in
458 // urlParameterNames.
460 // For instance, if parameters_ has [PRODUCT_NAME => "FOOBAR"] and
461 // urlParameterNames has [PRODUCT_NAME => "pname"] the final HTTP
462 // URL parameter becomes [pname => "FOOBAR"].
463 NSString *breakpadParameterName = (NSString *)key;
464 NSString *urlParameter = [urlParameterNames
465 objectForKey:breakpadParameterName];
467 [crashParameters setObject:[parameters_ objectForKey:key]
468 forKey:urlParameter];
472 // Now, add the parameters that were added by the application.
473 enumerator = [extraServerVars_ keyEnumerator];
475 while ((key = [enumerator nextObject])) {
476 NSString *urlParameterName = (NSString *)key;
477 NSString *urlParameterValue =
478 [extraServerVars_ objectForKey:urlParameterName];
479 [crashParameters setObject:urlParameterValue
480 forKey:urlParameterName];
485 - (void)addServerParameter:(id)value forKey:(NSString *)key {
486 [extraServerVars_ setObject:value forKey:key];
489 //=============================================================================
491 NSURL *url = [NSURL URLWithString:[parameters_ objectForKey:@BREAKPAD_URL]];
492 HTTPMultipartUpload *upload = [[HTTPMultipartUpload alloc] initWithURL:url];
493 NSMutableDictionary *uploadParameters = [NSMutableDictionary dictionary];
495 if (![self populateServerDictionary:uploadParameters]) {
500 [upload setParameters:uploadParameters];
503 if (minidumpContents_) {
504 [upload addFileContents:minidumpContents_ name:@"upload_file_minidump"];
506 // If there is a log file, upload it together with the minidump.
508 [upload addFileContents:logFileData_ name:@"log"];
512 NSError *error = nil;
513 NSData *data = [upload send:&error];
514 NSString *result = [[NSString alloc] initWithData:data
515 encoding:NSUTF8StringEncoding];
516 const char *reportID = "ERR";
519 fprintf(stderr, "Breakpad Uploader: Send Error: %s\n",
520 [[error description] UTF8String]);
522 NSCharacterSet *trimSet =
523 [NSCharacterSet whitespaceAndNewlineCharacterSet];
524 reportID = [[result stringByTrimmingCharactersInSet:trimSet] UTF8String];
525 [self logUploadWithID:reportID];
528 // rename the minidump file according to the id returned from the server
529 NSString *minidumpDir =
530 [parameters_ objectForKey:@kReporterMinidumpDirectoryKey];
531 NSString *minidumpID = [parameters_ objectForKey:@kReporterMinidumpIDKey];
533 NSString *srcString = [NSString stringWithFormat:@"%@/%@.dmp",
534 minidumpDir, minidumpID];
535 NSString *destString = [NSString stringWithFormat:@"%@/%s.dmp",
536 minidumpDir, reportID];
538 const char *src = [srcString fileSystemRepresentation];
539 const char *dest = [destString fileSystemRepresentation];
541 if (rename(src, dest) == 0) {
542 GTMLoggerInfo(@"Breakpad Uploader: Renamed %s to %s after successful " \
546 // can't rename - don't worry - it's not important for users
547 GTMLoggerDebug(@"Breakpad Uploader: successful upload report ID = %s\n",
552 // Minidump is missing -- upload just the log file.
554 [self uploadData:logFileData_ name:@"log"];
560 - (void)uploadData:(NSData *)data name:(NSString *)name {
561 NSURL *url = [NSURL URLWithString:[parameters_ objectForKey:@BREAKPAD_URL]];
562 NSMutableDictionary *uploadParameters = [NSMutableDictionary dictionary];
564 if (![self populateServerDictionary:uploadParameters])
567 HTTPMultipartUpload *upload =
568 [[HTTPMultipartUpload alloc] initWithURL:url];
570 [uploadParameters setObject:name forKey:@"type"];
571 [upload setParameters:uploadParameters];
572 [upload addFileContents:data name:name];
578 - (void)logUploadWithID:(const char *)uploadID {
579 NSString *minidumpDir =
580 [parameters_ objectForKey:@kReporterMinidumpDirectoryKey];
581 NSString *logFilePath = [NSString stringWithFormat:@"%@/%s",
582 minidumpDir, kReporterLogFilename];
583 NSString *logLine = [NSString stringWithFormat:@"%0.f,%s\n",
584 [[NSDate date] timeIntervalSince1970], uploadID];
585 NSData *logData = [logLine dataUsingEncoding:NSUTF8StringEncoding];
587 NSFileManager *fileManager = [NSFileManager defaultManager];
588 if ([fileManager fileExistsAtPath:logFilePath]) {
589 NSFileHandle *logFileHandle =
590 [NSFileHandle fileHandleForWritingAtPath:logFilePath];
591 [logFileHandle seekToEndOfFile];
592 [logFileHandle writeData:logData];
593 [logFileHandle closeFile];
595 [fileManager createFileAtPath:logFilePath
601 //=============================================================================
602 - (NSMutableDictionary *)parameters {
606 //=============================================================================
608 [parameters_ release];
609 [minidumpContents_ release];
610 [logFileData_ release];
611 [googleDictionary_ release];
612 [socorroDictionary_ release];
613 [serverDictionary_ release];
614 [extraServerVars_ release];