| // 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 <fcntl.h> |
| #include <stdio.h> |
| #import <sys/stat.h> |
| #include <TargetConditionals.h> |
| #import <unistd.h> |
| |
| #import <SystemConfiguration/SystemConfiguration.h> |
| |
| #import "common/mac/HTTPMultipartUpload.h" |
| |
| #import "client/apple/Framework/BreakpadDefines.h" |
| #import "client/mac/sender/uploader.h" |
| |
| const int kMinidumpFileLengthLimit = 2 * 1024 * 1024; // 2MB |
| |
| #define kApplePrefsSyncExcludeAllKey \ |
| @"com.apple.PreferenceSync.ExcludeAllSyncKeys" |
| |
| NSString *const kGoogleServerType = @"google"; |
| NSString *const kSocorroServerType = @"socorro"; |
| NSString *const kDefaultServerType = @"google"; |
| |
| #pragma mark - |
| |
| namespace { |
| // Read one line from the configuration file. |
| NSString *readString(int fileId) { |
| NSMutableString *str = [NSMutableString stringWithCapacity:32]; |
| char ch[2] = { 0 }; |
| |
| while (read(fileId, &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; |
| } |
| |
| //============================================================================= |
| // Read |length| of binary data from the configuration file. This method will |
| // returns |nil| in case of error. |
| NSData *readData(int fileId, ssize_t length) { |
| NSMutableData *data = [NSMutableData dataWithLength:length]; |
| char *bytes = (char *)[data bytes]; |
| |
| if (read(fileId, bytes, length) != length) |
| return nil; |
| |
| return data; |
| } |
| |
| //============================================================================= |
| // Read the configuration from the config file. |
| NSDictionary *readConfigurationData(const char *configFile) { |
| int fileId = open(configFile, O_RDONLY, 0600); |
| if (fileId == -1) { |
| fprintf(stderr, "Breakpad Uploader: 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)) { |
| fprintf(stderr, "Breakpad Uploader: Couldn't unlink config file %s - %s", |
| configFile, strerror(errno)); |
| } |
| |
| if (fileId == -1) { |
| return nil; |
| } |
| |
| NSMutableDictionary *config = [NSMutableDictionary dictionary]; |
| |
| while (1) { |
| NSString *key = readString(fileId); |
| |
| if (![key length]) |
| break; |
| |
| // Read the data. Try to convert to a UTF-8 string, or just save |
| // the data |
| NSString *lenStr = readString(fileId); |
| ssize_t len = [lenStr intValue]; |
| NSData *data = readData(fileId, len); |
| id value = [[NSString alloc] initWithData:data |
| encoding:NSUTF8StringEncoding]; |
| |
| [config setObject:(value ? value : data) forKey:key]; |
| [value release]; |
| } |
| |
| close(fileId); |
| return config; |
| } |
| } // namespace |
| |
| #pragma mark - |
| |
| @interface Uploader(PrivateMethods) |
| |
| // Update |parameters_| as well as the server parameters using |config|. |
| - (void)translateConfigurationData:(NSDictionary *)config; |
| |
| // Read the minidump referenced in |parameters_| and update |minidumpContents_| |
| // with its content. |
| - (BOOL)readMinidumpData; |
| |
| // Read the log files referenced in |parameters_| and update |logFileData_| |
| // with their content. |
| - (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; |
| |
| // Records the uploaded crash ID to the log file. |
| - (void)logUploadWithID:(const char *)uploadID; |
| |
| // Builds an URL parameter for a given dictionary key. Uses Uploader's |
| // parameters to provide its value. Returns nil if no item is stored for the |
| // given key. |
| - (NSURLQueryItem *)queryItemWithName:(NSString *)queryItemName |
| forParamKey:(NSString *)key; |
| @end |
| |
| @implementation Uploader |
| |
| //============================================================================= |
| - (id)initWithConfigFile:(const char *)configFile { |
| NSDictionary *config = readConfigurationData(configFile); |
| if (!config) |
| return nil; |
| |
| return [self initWithConfig:config]; |
| } |
| |
| //============================================================================= |
| - (id)initWithConfig:(NSDictionary *)config { |
| if ((self = [super init])) { |
| // 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]; |
| |
| [self translateConfigurationData:config]; |
| |
| // Read the minidump into memory. |
| [self readMinidumpData]; |
| [self readLogFileData]; |
| } |
| return self; |
| } |
| |
| //============================================================================= |
| + (NSDictionary *)readConfigurationDataFromFile:(NSString *)configFile { |
| return readConfigurationData([configFile fileSystemRepresentation]); |
| } |
| |
| //============================================================================= |
| - (void)translateConfigurationData:(NSDictionary *)config { |
| parameters_ = [[NSMutableDictionary alloc] init]; |
| |
| NSEnumerator *it = [config keyEnumerator]; |
| while (NSString *key = [it nextObject]) { |
| // 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]) { |
| id value = [config objectForKey:key]; |
| if ([value isKindOfClass:[NSString class]]) { |
| [self addServerParameter:(NSString *)value |
| forKey:urlParameterKey]; |
| } else { |
| [self addServerParameter:(NSData *)value |
| forKey:urlParameterKey]; |
| } |
| } |
| } else { |
| [parameters_ setObject:[config objectForKey:key] forKey:key]; |
| } |
| } |
| |
| // 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"]; |
| } |
| |
| // 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:@"%lx%lx%lx", |
| clientId1, clientId2, clientId3]; |
| |
| [ud setObject:crashClientID forKey:kClientIdPreferenceKey]; |
| [ud synchronize]; |
| return crashClientID; |
| } |
| |
| //============================================================================= |
| - (BOOL)readLogFileData { |
| #if TARGET_OS_IPHONE |
| return NO; |
| #else |
| 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) { |
| fprintf(stderr, "Breakpad Uploader: Cannot find temp tar log file: %s", |
| [logTarFile UTF8String]); |
| return NO; |
| } |
| return YES; |
| #endif // TARGET_OS_IPHONE |
| } |
| |
| //============================================================================= |
| - (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]; |
| [googleDictionary_ setObject:@"guid" forKey:@"guid"]; |
| |
| [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)handleNetworkResponse:(NSData *)data withError:(NSError *)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]; |
| } |
| if (uploadCompletion_) { |
| uploadCompletion_([NSString stringWithUTF8String:reportID], error); |
| } |
| |
| // 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) { |
| fprintf(stderr, |
| "Breakpad Uploader: Renamed %s to %s after successful upload", src, |
| dest); |
| } |
| else { |
| // can't rename - don't worry - it's not important for users |
| fprintf(stderr, "Breakpad Uploader: successful upload report ID = %s\n", |
| reportID); |
| } |
| [result release]; |
| } |
| |
| //============================================================================= |
| - (NSURLQueryItem *)queryItemWithName:(NSString *)queryItemName |
| forParamKey:(NSString *)key { |
| NSString *value = [parameters_ objectForKey:key]; |
| NSString *escapedValue = |
| [value stringByAddingPercentEncodingWithAllowedCharacters: |
| [NSCharacterSet URLQueryAllowedCharacterSet]]; |
| return [NSURLQueryItem queryItemWithName:queryItemName value:escapedValue]; |
| } |
| |
| //============================================================================= |
| - (void)setUploadCompletionBlock:(UploadCompletionBlock)uploadCompletion { |
| uploadCompletion_ = uploadCompletion; |
| } |
| |
| //============================================================================= |
| - (void)report { |
| NSURL *url = [NSURL URLWithString:[parameters_ objectForKey:@BREAKPAD_URL]]; |
| |
| NSString *serverType = [parameters_ objectForKey:@BREAKPAD_SERVER_TYPE]; |
| if ([serverType length] == 0 || |
| [serverType isEqualToString:kGoogleServerType]) { |
| // when communicating to Google's crash collecting service, add URL params |
| // which identify the product |
| NSURLComponents *urlComponents = [NSURLComponents componentsWithURL:url |
| resolvingAgainstBaseURL:false]; |
| NSMutableArray *queryItemsToAdd = [urlComponents.queryItems mutableCopy]; |
| if (queryItemsToAdd == nil) { |
| queryItemsToAdd = [[NSMutableArray alloc] init]; |
| } |
| |
| NSURLQueryItem *queryItemProduct = |
| [self queryItemWithName:@"product" forParamKey:@BREAKPAD_PRODUCT]; |
| NSURLQueryItem *queryItemVersion = |
| [self queryItemWithName:@"version" forParamKey:@BREAKPAD_VERSION]; |
| NSURLQueryItem *queryItemGuid = |
| [self queryItemWithName:@"guid" forParamKey:@"guid"]; |
| |
| if (queryItemProduct != nil) [queryItemsToAdd addObject:queryItemProduct]; |
| if (queryItemVersion != nil) [queryItemsToAdd addObject:queryItemVersion]; |
| if (queryItemGuid != nil) [queryItemsToAdd addObject:queryItemGuid]; |
| |
| urlComponents.queryItems = queryItemsToAdd; |
| url = [urlComponents URL]; |
| } |
| |
| HTTPMultipartUpload *upload = [[HTTPMultipartUpload alloc] initWithURL:url]; |
| NSMutableDictionary *uploadParameters = [NSMutableDictionary dictionary]; |
| |
| if (![self populateServerDictionary:uploadParameters]) { |
| [upload release]; |
| return; |
| } |
| |
| [upload setParameters:uploadParameters]; |
| |
| // Add minidump file |
| if (minidumpContents_) { |
| [upload addFileContents:minidumpContents_ name:@"upload_file_minidump"]; |
| |
| // If there is a log file, upload it together with the minidump. |
| if (logFileData_) { |
| [upload addFileContents:logFileData_ name:@"log"]; |
| } |
| |
| // Send it |
| NSError *error = nil; |
| NSData *data = [upload send:&error]; |
| |
| if (![url isFileURL]) { |
| [self handleNetworkResponse:data withError:error]; |
| } else { |
| if (error) { |
| fprintf(stderr, "Breakpad Uploader: Error writing request file: %s\n", |
| [[error description] UTF8String]); |
| } |
| } |
| |
| } else { |
| // Minidump is missing -- upload just the log file. |
| if (logFileData_) { |
| [self uploadData:logFileData_ name:@"log"]; |
| } |
| } |
| [upload release]; |
| } |
| |
| - (void)uploadData:(NSData *)data name:(NSString *)name { |
| NSURL *url = [NSURL URLWithString:[parameters_ objectForKey:@BREAKPAD_URL]]; |
| NSMutableDictionary *uploadParameters = [NSMutableDictionary dictionary]; |
| |
| if (![self populateServerDictionary:uploadParameters]) |
| return; |
| |
| HTTPMultipartUpload *upload = |
| [[HTTPMultipartUpload alloc] initWithURL:url]; |
| |
| [uploadParameters setObject:name forKey:@"type"]; |
| [upload setParameters:uploadParameters]; |
| [upload addFileContents:data name:name]; |
| |
| [upload send:nil]; |
| [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]; |
| } |
| } |
| |
| //============================================================================= |
| - (NSMutableDictionary *)parameters { |
| return parameters_; |
| } |
| |
| //============================================================================= |
| - (void)dealloc { |
| [parameters_ release]; |
| [minidumpContents_ release]; |
| [logFileData_ release]; |
| [googleDictionary_ release]; |
| [socorroDictionary_ release]; |
| [serverDictionary_ release]; |
| [extraServerVars_ release]; |
| [super dealloc]; |
| } |
| |
| @end |