diff --git a/.gitignore b/.gitignore index f962d0da..11a9929c 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ build !default.xcworkspace !project.xcworkspace xcuserdata +xccheckout profile *.moved-aside diff --git a/Classes/BITAuthenticator.m b/Classes/BITAuthenticator.m index 0b908f5d..4c53eab6 100644 --- a/Classes/BITAuthenticator.m +++ b/Classes/BITAuthenticator.m @@ -578,6 +578,7 @@ - (BOOL) handleOpenURL:(NSURL *) url NSString *const kAuthorizationHost = @"authorize"; NSString *urlScheme = _urlScheme ? : [NSString stringWithFormat:@"ha%@", self.appIdentifier]; if(!([[url scheme] isEqualToString:urlScheme] && [[url host] isEqualToString:kAuthorizationHost])) { + BITHockeyLog(@"URL scheme for authentication doesn't match!"); return NO; } @@ -611,6 +612,7 @@ - (BOOL) handleOpenURL:(NSURL *) url } if(installationIdentifier){ + BITHockeyLog(@"Authentication succeeded."); if(NO == self.restrictApplicationUsage) { [self dismissAuthenticationControllerAnimated:YES completion:nil]; } @@ -622,6 +624,7 @@ - (BOOL) handleOpenURL:(NSURL *) url } } else { //reset token + BITHockeyLog(@"Resetting authentication token"); [self storeInstallationIdentifier:nil withType:self.identificationType]; self.identified = NO; if(self.identificationCompletion) { @@ -693,6 +696,8 @@ - (void)processFullSizeImage { return; } + BITHockeyLog(@"Processing full size image for possible authentication"); + unsigned char *buffer, *source; source = (unsigned char *)malloc((unsigned long)fs.st_size); if (read(fd, source, (unsigned long)fs.st_size) != fs.st_size) { @@ -720,7 +725,8 @@ - (void)processFullSizeImage { length = ntohl(length); buffer += 4; - name = (unsigned char *)malloc(4); + name = (unsigned char *)malloc(5); + name[4] = 0; memcpy(name, buffer, 4); buffer += 4; @@ -754,7 +760,10 @@ - (void)processFullSizeImage { free(source); if (result) { + BITHockeyLog(@"Authenticating using full size image information: %@", result); [self handleOpenURL:[NSURL URLWithString:result] sourceApplication:nil annotation:nil]; + } else { + BITHockeyLog(@"No authentication information found"); } } diff --git a/Classes/BITCrashManager.h b/Classes/BITCrashManager.h index 64f695d6..bf651da5 100644 --- a/Classes/BITCrashManager.h +++ b/Classes/BITCrashManager.h @@ -166,6 +166,20 @@ typedef NS_ENUM(NSUInteger, BITCrashManagerStatus) { @property (nonatomic, assign, getter=isMachExceptionHandlerEnabled) BOOL enableMachExceptionHandler; +/** + * Enable on device symbolication for system symbols + * + * By default, the SDK does not symbolicate on the device, since this can + * take a few seconds at each crash. Also note that symbolication on the + * device might not be able to retrieve all symbols. + * + * Enable if you want to analyze crashes on unreleased OS versions. + * + * Default: _NO_ + */ +@property (nonatomic, assign, getter=isOnDeviceSymbolicationEnabled) BOOL enableOnDeviceSymbolication; + + /** * Set the callbacks that will be executed prior to program termination after a crash has occurred * diff --git a/Classes/BITCrashManager.m b/Classes/BITCrashManager.m index fa1c8bca..fca13fc4 100644 --- a/Classes/BITCrashManager.m +++ b/Classes/BITCrashManager.m @@ -125,17 +125,7 @@ - (id)init { [[NSUserDefaults standardUserDefaults] setInteger:_crashManagerStatus forKey:kBITCrashManagerStatus]; } - // temporary directory for crashes grabbed from PLCrashReporter - NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); - _crashesDir = [[paths objectAtIndex:0] stringByAppendingPathComponent:BITHOCKEY_IDENTIFIER]; - - if (![self.fileManager fileExistsAtPath:_crashesDir]) { - NSDictionary *attributes = [NSDictionary dictionaryWithObject: [NSNumber numberWithUnsignedLong: 0755] forKey: NSFilePosixPermissions]; - NSError *theError = NULL; - - [self.fileManager createDirectoryAtPath:_crashesDir withIntermediateDirectories: YES attributes: attributes error: &theError]; - } - + _crashesDir = bit_settingsDir(); _settingsFile = [_crashesDir stringByAppendingPathComponent:BITHOCKEY_CRASH_SETTINGS]; _analyzerInProgressFile = [_crashesDir stringByAppendingPathComponent:BITHOCKEY_CRASH_ANALYZER]; @@ -308,7 +298,8 @@ - (void) unregisterObservers { * @return The userID value */ - (NSString *)userIDForCrashReport { - NSString *userID = @""; + // first check the global keychain storage + NSString *userID = [self stringValueFromKeychainForKey:kBITHockeyMetaUserID] ?: @""; #if HOCKEYSDK_FEATURE_AUTHENTICATOR // if we have an identification from BITAuthenticator, use this as a default. @@ -337,7 +328,8 @@ - (NSString *)userIDForCrashReport { * @return The userName value */ - (NSString *)userNameForCrashReport { - NSString *username = @""; + // first check the global keychain storage + NSString *username = [self stringValueFromKeychainForKey:kBITHockeyMetaUserName] ?: @""; if (self.delegate && [self.delegate respondsToSelector:@selector(userNameForCrashManager:)]) { username = [self.delegate userNameForCrashManager:self] ?: @""; @@ -358,7 +350,8 @@ - (NSString *)userNameForCrashReport { * @return The userEmail value */ - (NSString *)userEmailForCrashReport { - NSString *useremail = @""; + // first check the global keychain storage + NSString *useremail = [self stringValueFromKeychainForKey:kBITHockeyMetaUserEmail] ?: @""; #if HOCKEYSDK_FEATURE_AUTHENTICATOR // if we have an identification from BITAuthenticator, use this as a default. @@ -667,8 +660,14 @@ - (void)startManager { if (self.isMachExceptionHandlerEnabled) { signalHandlerType = PLCrashReporterSignalHandlerTypeMach; } + + PLCrashReporterSymbolicationStrategy symbolicationStrategy = PLCrashReporterSymbolicationStrategyNone; + if (self.isOnDeviceSymbolicationEnabled) { + symbolicationStrategy = PLCrashReporterSymbolicationStrategyAll; + } + BITPLCrashReporterConfig *config = [[BITPLCrashReporterConfig alloc] initWithSignalHandlerType: signalHandlerType - symbolicationStrategy: PLCrashReporterSymbolicationStrategyAll]; + symbolicationStrategy: symbolicationStrategy]; self.plCrashReporter = [[BITPLCrashReporter alloc] initWithConfiguration: config]; // Check if we previously crashed diff --git a/Classes/BITCrashReportTextFormatter.m b/Classes/BITCrashReportTextFormatter.m index 6094affc..1acc07c9 100644 --- a/Classes/BITCrashReportTextFormatter.m +++ b/Classes/BITCrashReportTextFormatter.m @@ -33,6 +33,18 @@ #import +#import +#import +#import +#import +#import + +#if defined(__OBJC2__) +#define SEL_NAME_SECT "__objc_methname" +#else +#define SEL_NAME_SECT "__cstring" +#endif + #import "BITCrashReportTextFormatter.h" /* @@ -67,6 +79,112 @@ static NSInteger bit_binaryImageSort(id binary1, id binary2, void *context) { return NSOrderedSame; } +/** + * Validates that the given @a string terminates prior to @a limit. + */ +static const char *safer_string_read (const char *string, const char *limit) { + const char *p = string; + do { + if (p >= limit || p+1 >= limit) { + return NULL; + } + p++; + } while (*p != '\0'); + + return string; +} + +/* + * The relativeAddress should be ` - `, extracted from the crash report's thread + * and binary image list. + * + * For the (architecture-specific) registers to attempt, see: + * http://sealiesoftware.com/blog/archive/2008/09/22/objc_explain_So_you_crashed_in_objc_msgSend.html + */ +static const char *findSEL (const char *imageName, NSString *imageUUID, uint64_t relativeAddress) { + unsigned int images_count = _dyld_image_count(); + for (unsigned int i = 0; i < images_count; ++i) { + intptr_t slide = _dyld_get_image_vmaddr_slide(i); + const struct mach_header *header = _dyld_get_image_header(i); + const struct mach_header_64 *header64 = (const struct mach_header_64 *) header; + const char *name = _dyld_get_image_name(i); + + /* Image disappeared? */ + if (name == NULL || header == NULL) + continue; + + /* Check if this is the correct image. If we were being even more careful, we'd check the LC_UUID */ + if (strcmp(name, imageName) != 0) + continue; + + /* Determine whether this is a 64-bit or 32-bit Mach-O file */ + BOOL m64 = NO; + if (header->magic == MH_MAGIC_64) + m64 = YES; + + NSString *uuidString = nil; + const uint8_t *command; + uint32_t ncmds; + + if (m64) { + command = (const uint8_t *)(header64 + 1); + ncmds = header64->ncmds; + } else { + command = (const uint8_t *)(header + 1); + ncmds = header->ncmds; + } + for (uint32_t idx = 0; idx < ncmds; ++idx) { + const struct load_command *load_command = (const struct load_command *)command; + if (load_command->cmd == LC_UUID) { + const struct uuid_command *uuid_command = (const struct uuid_command *)command; + const uint8_t *uuid = uuid_command->uuid; + uuidString = [[NSString stringWithFormat:@"%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X", + uuid[0], uuid[1], uuid[2], uuid[3], + uuid[4], uuid[5], uuid[6], uuid[7], + uuid[8], uuid[9], uuid[10], uuid[11], + uuid[12], uuid[13], uuid[14], uuid[15]] + lowercaseString]; + break; + } else { + command += load_command->cmdsize; + } + } + + // Check if this is the correct image by comparing the UUIDs + if (!uuidString || ![uuidString isEqualToString:imageUUID]) + continue; + + /* Fetch the __objc_methname section */ + const char *methname_sect; + uint64_t methname_sect_size; + if (m64) { + methname_sect = getsectdatafromheader_64(header64, SEG_TEXT, SEL_NAME_SECT, &methname_sect_size); + } else { + uint32_t meth_size_32; + methname_sect = getsectdatafromheader(header, SEG_TEXT, SEL_NAME_SECT, &meth_size_32); + methname_sect_size = meth_size_32; + } + + /* Apply the slide, as per getsectdatafromheader(3) */ + methname_sect += slide; + + if (methname_sect == NULL) { + return NULL; + } + + /* Calculate the target address within this image, and verify that it is within __objc_methname */ + const char *target = ((const char *)header) + relativeAddress; + const char *limit = methname_sect + methname_sect_size; + if (target < methname_sect || target >= limit) { + return NULL; + } + + /* Read the actual method name */ + return safer_string_read(target, limit); + } + + return NULL; +} /** * Formats PLCrashReport data as human-readable text. @@ -282,6 +400,14 @@ + (NSString *)stringValueForCrashReport:(BITPLCrashReport *)report crashReporter [text appendString: @"\n"]; + BITPLCrashReportThreadInfo *crashed_thread = nil; + for (BITPLCrashReportThreadInfo *thread in report.threads) { + if (thread.crashed) { + crashed_thread = thread; + break; + } + } + /* Uncaught Exception */ if (report.hasExceptionInfo) { [text appendFormat: @"Application Specific Information:\n"]; @@ -289,6 +415,36 @@ + (NSString *)stringValueForCrashReport:(BITPLCrashReport *)report crashReporter report.exceptionInfo.exceptionName, report.exceptionInfo.exceptionReason]; [text appendString: @"\n"]; + } else if (crashed_thread != nil) { + // try to find the selector in case this was a crash in obj_msgSend + // we search this wether the crash happend in obj_msgSend or not since we don't have the symbol! + + NSString *foundSelector = nil; + + // search the registers value for the current arch +#if TARGET_IPHONE_SIMULATOR + if (lp64) { + foundSelector = [[self class] selectorForRegisterWithName:@"rsi" ofThread:crashed_thread report:report]; + if (foundSelector == NULL) + foundSelector = [[self class] selectorForRegisterWithName:@"rdx" ofThread:crashed_thread report:report]; + } else { + foundSelector = [[self class] selectorForRegisterWithName:@"ecx" ofThread:crashed_thread report:report]; + } +#else + if (lp64) { + foundSelector = [[self class] selectorForRegisterWithName:@"x1" ofThread:crashed_thread report:report]; + } else { + foundSelector = [[self class] selectorForRegisterWithName:@"r1" ofThread:crashed_thread report:report]; + if (foundSelector == NULL) + foundSelector = [[self class] selectorForRegisterWithName:@"r2" ofThread:crashed_thread report:report]; + } +#endif + + if (foundSelector) { + [text appendFormat: @"Application Specific Information:\n"]; + [text appendFormat: @"Selector name found in current argument registers: %@\n", foundSelector]; + [text appendString: @"\n"]; + } } /* If an exception stack trace is available, output an Apple-compatible backtrace. */ @@ -308,12 +464,10 @@ + (NSString *)stringValueForCrashReport:(BITPLCrashReport *)report crashReporter } /* Threads */ - BITPLCrashReportThreadInfo *crashed_thread = nil; NSInteger maxThreadNum = 0; for (BITPLCrashReportThreadInfo *thread in report.threads) { if (thread.crashed) { [text appendFormat: @"Thread %ld Crashed:\n", (long) thread.threadNumber]; - crashed_thread = thread; } else { [text appendFormat: @"Thread %ld:\n", (long) thread.threadNumber]; } @@ -417,6 +571,41 @@ + (NSString *)stringValueForCrashReport:(BITPLCrashReport *)report crashReporter return text; } +/** + * Return the selector string of a given register name + * + * @param regName The name of the register to use for getting the address + * @param thread The crashed thread + * @param images NSArray of binary images + * + * @return The selector as a C string or NULL if no selector was found + */ ++ (NSString *)selectorForRegisterWithName:(NSString *)regName ofThread:(BITPLCrashReportThreadInfo *)thread report:(BITPLCrashReport *)report { + // get the address for the register + uint64_t regAddress = 0; + + for (BITPLCrashReportRegisterInfo *reg in thread.registers) { + if ([reg.registerName isEqualToString:regName]) { + regAddress = reg.registerValue; + break; + } + } + + if (regAddress == 0) + return nil; + + BITPLCrashReportBinaryImageInfo *imageForRegAddress = [report imageForAddress:regAddress]; + if (imageForRegAddress) { + // get the SEL + const char *foundSelector = findSEL([imageForRegAddress.imageName UTF8String], imageForRegAddress.imageUUID, regAddress - (uint64_t)imageForRegAddress.imageBaseAddress); + + return [NSString stringWithUTF8String:foundSelector]; + } + + return nil; +} + + /** * Returns an array of app UUIDs and their architecture * As a dictionary for each element diff --git a/Classes/BITFeedbackListViewController.m b/Classes/BITFeedbackListViewController.m index 633fca09..812634ca 100644 --- a/Classes/BITFeedbackListViewController.m +++ b/Classes/BITFeedbackListViewController.m @@ -255,8 +255,8 @@ - (void)setUserDataAction:(id)sender { } - (void)newFeedbackAction:(id)sender { - BITFeedbackComposeViewController *composeController = [[BITFeedbackComposeViewController alloc] init]; - + BITFeedbackComposeViewController *composeController = [self.manager feedbackComposeViewController]; + UINavigationController *navController = [self.manager customNavigationControllerWithRootViewController:composeController presentationStyle:UIModalPresentationFormSheet]; @@ -366,6 +366,16 @@ - (void)feedbackComposeViewController:(BITFeedbackComposeViewController *)compos } else { [self dismissViewControllerAnimated:YES completion:^(void){}]; } + + if (self.manager.delegate && + [self.manager.delegate respondsToSelector:@selector(feedbackComposeViewController:didFinishWithResult:)]) { + [self.manager.delegate feedbackComposeViewController:composeViewController didFinishWithResult:composeResult]; + } else if (self.manager.delegate && [self.manager.delegate respondsToSelector:@selector(feedbackComposeViewControllerDidFinish:)]) { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated" + [self.manager.delegate feedbackComposeViewControllerDidFinish:composeViewController]; +#pragma clang diagnostic pop + } } diff --git a/Classes/BITFeedbackManager.h b/Classes/BITFeedbackManager.h index 46b47494..1f1543df 100644 --- a/Classes/BITFeedbackManager.h +++ b/Classes/BITFeedbackManager.h @@ -87,9 +87,9 @@ typedef NS_ENUM(NSInteger, BITFeedbackUserDataElement) { reload the list content from the server and changing the users name or email if these are allowed to be set. - It is also possible to invoke the user interface to compose a new message anywhere in your + It is also possible to invoke the user interface to compose a new message in your own code, by calling `[BITFeedbackManager showFeedbackComposeView]` modally or adding - `[BITFeedackManager feedbackComposeViewController]` to push onto a navigation stack. + `[BITFeedbackManager feedbackComposeViewController]` to push onto a navigation stack. If new messages are written while the device is offline, the SDK automatically retries to send them once the app starts again or gets active again, or if the notification diff --git a/Classes/BITFeedbackManager.m b/Classes/BITFeedbackManager.m index 685cdc74..90318326 100644 --- a/Classes/BITFeedbackManager.m +++ b/Classes/BITFeedbackManager.m @@ -53,7 +53,6 @@ @implementation BITFeedbackManager { NSFileManager *_fileManager; - NSString *_feedbackDir; NSString *_settingsFile; id _appDidBecomeActiveObserver; @@ -89,18 +88,7 @@ - (id)init { _fileManager = [[NSFileManager alloc] init]; - // temporary directory for crashes grabbed from PLCrashReporter - NSArray *paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES); - _feedbackDir = [[paths objectAtIndex:0] stringByAppendingPathComponent:BITHOCKEY_IDENTIFIER]; - - if (![_fileManager fileExistsAtPath:_feedbackDir]) { - NSDictionary *attributes = [NSDictionary dictionaryWithObject: [NSNumber numberWithUnsignedLong: 0755] forKey: NSFilePosixPermissions]; - NSError *theError = NULL; - - [_fileManager createDirectoryAtPath:_feedbackDir withIntermediateDirectories: YES attributes: attributes error: &theError]; - } - - _settingsFile = [_feedbackDir stringByAppendingPathComponent:BITHOCKEY_FEEDBACK_SETTINGS]; + _settingsFile = [bit_settingsDir() stringByAppendingPathComponent:BITHOCKEY_FEEDBACK_SETTINGS]; _userID = nil; _userName = nil; @@ -218,7 +206,10 @@ - (void)showFeedbackListView { - (BITFeedbackComposeViewController *)feedbackComposeViewController { - return [[BITFeedbackComposeViewController alloc] init]; + BITFeedbackComposeViewController *composeViewController = [[BITFeedbackComposeViewController alloc] init]; + // by default set the delegate to be identical to the one of BITFeedbackManager + [composeViewController setDelegate:self.delegate]; + return composeViewController; } - (void)showFeedbackComposeView { @@ -271,63 +262,72 @@ - (void)updateMessagesListIfRequired { } } -- (BOOL)updateUserIDUsingDelegate { +- (BOOL)updateUserIDUsingKeychainAndDelegate { BOOL availableViaDelegate = NO; + NSString *userID = [self stringValueFromKeychainForKey:kBITHockeyMetaUserID]; + if ([BITHockeyManager sharedHockeyManager].delegate && [[BITHockeyManager sharedHockeyManager].delegate respondsToSelector:@selector(userIDForHockeyManager:componentManager:)]) { - NSString *userID = [[BITHockeyManager sharedHockeyManager].delegate - userIDForHockeyManager:[BITHockeyManager sharedHockeyManager] - componentManager:self]; - if (userID) { - availableViaDelegate = YES; - self.userID = userID; - } + userID = [[BITHockeyManager sharedHockeyManager].delegate + userIDForHockeyManager:[BITHockeyManager sharedHockeyManager] + componentManager:self]; } - + + if (userID) { + availableViaDelegate = YES; + self.userID = userID; + } + return availableViaDelegate; } -- (BOOL)updateUserNameUsingDelegate { +- (BOOL)updateUserNameUsingKeychainAndDelegate { BOOL availableViaDelegate = NO; + NSString *userName = [self stringValueFromKeychainForKey:kBITHockeyMetaUserName]; + if ([BITHockeyManager sharedHockeyManager].delegate && [[BITHockeyManager sharedHockeyManager].delegate respondsToSelector:@selector(userNameForHockeyManager:componentManager:)]) { - NSString *userName = [[BITHockeyManager sharedHockeyManager].delegate + userName = [[BITHockeyManager sharedHockeyManager].delegate userNameForHockeyManager:[BITHockeyManager sharedHockeyManager] componentManager:self]; - if (userName) { - availableViaDelegate = YES; - self.userName = userName; - self.requireUserName = BITFeedbackUserDataElementDontShow; - } } - + + if (userName) { + availableViaDelegate = YES; + self.userName = userName; + self.requireUserName = BITFeedbackUserDataElementDontShow; + } + return availableViaDelegate; } -- (BOOL)updateUserEmailUsingDelegate { +- (BOOL)updateUserEmailUsingKeychainAndDelegate { BOOL availableViaDelegate = NO; + NSString *userEmail = [self stringValueFromKeychainForKey:kBITHockeyMetaUserEmail]; + if ([BITHockeyManager sharedHockeyManager].delegate && [[BITHockeyManager sharedHockeyManager].delegate respondsToSelector:@selector(userEmailForHockeyManager:componentManager:)]) { - NSString *userEmail = [[BITHockeyManager sharedHockeyManager].delegate - userEmailForHockeyManager:[BITHockeyManager sharedHockeyManager] - componentManager:self]; - if (userEmail) { - availableViaDelegate = YES; - self.userEmail = userEmail; - self.requireUserEmail = BITFeedbackUserDataElementDontShow; - } + userEmail = [[BITHockeyManager sharedHockeyManager].delegate + userEmailForHockeyManager:[BITHockeyManager sharedHockeyManager] + componentManager:self]; } - + + if (userEmail) { + availableViaDelegate = YES; + self.userEmail = userEmail; + self.requireUserEmail = BITFeedbackUserDataElementDontShow; + } + return availableViaDelegate; } - (void)updateAppDefinedUserData { - [self updateUserIDUsingDelegate]; - [self updateUserNameUsingDelegate]; - [self updateUserEmailUsingDelegate]; + [self updateUserIDUsingKeychainAndDelegate]; + [self updateUserNameUsingKeychainAndDelegate]; + [self updateUserEmailUsingKeychainAndDelegate]; // if both values are shown via the delegates, we never ever did ask and will never ever ask for user data if (self.requireUserName == BITFeedbackUserDataElementDontShow && @@ -339,9 +339,9 @@ - (void)updateAppDefinedUserData { #pragma mark - Local Storage - (void)loadMessages { - BOOL userIDViaDelegate = [self updateUserIDUsingDelegate]; - BOOL userNameViaDelegate = [self updateUserNameUsingDelegate]; - BOOL userEmailViaDelegate = [self updateUserEmailUsingDelegate]; + BOOL userIDViaDelegate = [self updateUserIDUsingKeychainAndDelegate]; + BOOL userNameViaDelegate = [self updateUserNameUsingKeychainAndDelegate]; + BOOL userEmailViaDelegate = [self updateUserEmailUsingKeychainAndDelegate]; if (![_fileManager fileExistsAtPath:_settingsFile]) return; diff --git a/Classes/BITFeedbackManagerDelegate.h b/Classes/BITFeedbackManagerDelegate.h index dce3fb70..61b4c396 100644 --- a/Classes/BITFeedbackManagerDelegate.h +++ b/Classes/BITFeedbackManagerDelegate.h @@ -29,13 +29,14 @@ #import @class BITFeedbackManager; +@protocol BITFeedbackComposeViewControllerDelegate; /** * Delegate protocol which is notified about changes in the feedbackManager * @TODO * * move shouldShowUpdateAlert from feedbackManager here */ -@protocol BITFeedbackManagerDelegate +@protocol BITFeedbackManagerDelegate @optional diff --git a/Classes/BITHockeyHelper.h b/Classes/BITHockeyHelper.h index 22d6dbda..a508dc64 100644 --- a/Classes/BITHockeyHelper.h +++ b/Classes/BITHockeyHelper.h @@ -34,6 +34,8 @@ NSString *bit_URLEncodedString(NSString *inputString); NSString *bit_URLDecodedString(NSString *inputString); NSString *bit_base64String(NSData * data, unsigned long length); +NSString *bit_settingsDir(void); + BOOL bit_validateEmail(NSString *email); NSString *bit_keychainHockeySDKServiceName(void); diff --git a/Classes/BITHockeyHelper.m b/Classes/BITHockeyHelper.m index 843283a1..11b29ad8 100644 --- a/Classes/BITHockeyHelper.m +++ b/Classes/BITHockeyHelper.m @@ -76,6 +76,28 @@ - (NSString *)base64Encoding; #endif } +NSString *bit_settingsDir(void) { + static NSString *settingsDir = nil; + static dispatch_once_t predSettingsDir; + + dispatch_once(&predSettingsDir, ^{ + NSFileManager *fileManager = [[NSFileManager alloc] init]; + + // temporary directory for crashes grabbed from PLCrashReporter + NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); + settingsDir = [[paths objectAtIndex:0] stringByAppendingPathComponent:BITHOCKEY_IDENTIFIER]; + + if (![fileManager fileExistsAtPath:settingsDir]) { + NSDictionary *attributes = [NSDictionary dictionaryWithObject: [NSNumber numberWithUnsignedLong: 0755] forKey: NSFilePosixPermissions]; + NSError *theError = NULL; + + [fileManager createDirectoryAtPath:settingsDir withIntermediateDirectories: YES attributes: attributes error: &theError]; + } + }); + + return settingsDir; +} + BOOL bit_validateEmail(NSString *email) { NSString *emailRegex = @"(?:[a-z0-9!#$%\\&'*+/=?\\^_`{|}~-]+(?:\\.[a-z0-9!#$%\\&'*+/=?\\^_`{|}" @@ -91,7 +113,13 @@ BOOL bit_validateEmail(NSString *email) { } NSString *bit_keychainHockeySDKServiceName(void) { - NSString *serviceName = [NSString stringWithFormat:@"%@.HockeySDK", bit_mainBundleIdentifier()]; + static NSString *serviceName = nil; + static dispatch_once_t predServiceName; + + dispatch_once(&predServiceName, ^{ + serviceName = [NSString stringWithFormat:@"%@.HockeySDK", bit_mainBundleIdentifier()]; + }); + return serviceName; } diff --git a/Classes/BITHockeyManager.h b/Classes/BITHockeyManager.h index 0102cc9d..585a8f2a 100644 --- a/Classes/BITHockeyManager.h +++ b/Classes/BITHockeyManager.h @@ -312,9 +312,6 @@ /** Reference to the initialized BITAuthenticator module - The authenticator is disabled by default. To enable it you need to set - `[BITAuthenticator authenticationType]` and `[BITAuthenticator validationType]` - Returns the BITAuthenticator instance initialized by BITHockeyManager @see configureWithIdentifier:delegate: @@ -387,7 +384,74 @@ ///----------------------------------------------------------------------------- -/// @name Meta +/// @name Additional meta data +///----------------------------------------------------------------------------- + +/** Set the userid that should used in the SDK components + + Right now this is used by the `BITCrashManager` to attach to a crash report. + `BITFeedbackManager` uses it too for assigning the user to a discussion thread. + + The value can be set at any time and will be stored in the keychain on the current + device only! To delete the value from the keychain set the value to `nil`. + + This property is optional and can be used as an alternative to the delegate. If you + want to define specific data for each component, use the delegate instead which does + overwrite the values set by this property. + + @see userName + @see userEmail + @see `[BITHockeyManagerDelegate userIDForHockeyManager:componentManager:]` + */ +@property (nonatomic, retain) NSString *userID; + + +/** Set the user name that should used in the SDK components + + Right now this is used by the `BITCrashManager` to attach to a crash report. + `BITFeedbackManager` uses it too for assigning the user to a discussion thread. + + The value can be set at any time and will be stored in the keychain on the current + device only! To delete the value from the keychain set the value to `nil`. + + This property is optional and can be used as an alternative to the delegate. If you + want to define specific data for each component, use the delegate instead which does + overwrite the values set by this property. + + @warning When returning a non nil value, crash reports are not anonymous any more + and the crash alerts will not show the word "anonymous"! + + @see userID + @see userEmail + @see `[BITHockeyManagerDelegate userNameForHockeyManager:componentManager:]` + */ +@property (nonatomic, retain) NSString *userName; + + +/** Set the users email address that should used in the SDK components + + Right now this is used by the `BITCrashManager` to attach to a crash report. + `BITFeedbackManager` uses it too for assigning the user to a discussion thread. + + The value can be set at any time and will be stored in the keychain on the current + device only! To delete the value from the keychain set the value to `nil`. + + This property is optional and can be used as an alternative to the delegate. If you + want to define specific data for each component, use the delegate instead which does + overwrite the values set by this property. + + @warning When returning a non nil value, crash reports are not anonymous any more + and the crash alerts will not show the word "anonymous"! + + @see userID + @see userName + @see `[BITHockeyManagerDelegate userEmailForHockeyManager:componentManager:]` + */ +@property (nonatomic, retain) NSString *userEmail; + + +///----------------------------------------------------------------------------- +/// @name SDK meta data ///----------------------------------------------------------------------------- /** diff --git a/Classes/BITHockeyManager.m b/Classes/BITHockeyManager.m index 19c5c630..f72a907d 100644 --- a/Classes/BITHockeyManager.m +++ b/Classes/BITHockeyManager.m @@ -34,6 +34,7 @@ #import "BITHockeyHelper.h" #import "BITHockeyAppClient.h" +#import "BITKeychainUtils.h" #if HOCKEYSDK_FEATURE_CRASH_REPORTER @@ -350,6 +351,56 @@ - (void)setDelegate:(id)delegate { } } +- (void)modifyKeychainUserValue:(NSString *)value forKey:(NSString *)key { + NSError *error = nil; + BOOL success = YES; + NSString *updateType = @"update"; + + if (value) { + success = [BITKeychainUtils storeUsername:key + andPassword:value + forServiceName:bit_keychainHockeySDKServiceName() + updateExisting:YES + accessibility:kSecAttrAccessibleWhenUnlockedThisDeviceOnly + error:&error]; + } else { + updateType = @"delete"; + if ([BITKeychainUtils getPasswordForUsername:key + andServiceName:bit_keychainHockeySDKServiceName() + error:&error]) { + success = [BITKeychainUtils deleteItemForUsername:key + andServiceName:bit_keychainHockeySDKServiceName() + error:&error]; + } + } + + if (!success) { + NSString *errorDescription = [error description] ?: @""; + BITHockeyLog(@"ERROR: Couldn't %@ key %@ in the keychain. %@", updateType, key, errorDescription); + } +} + +- (void)setUserID:(NSString *)userID { + // always set it, since nil value will trigger removal of the keychain entry + _userID = userID; + + [self modifyKeychainUserValue:userID forKey:kBITHockeyMetaUserID]; +} + +- (void)setUserName:(NSString *)userName { + // always set it, since nil value will trigger removal of the keychain entry + _userName = userName; + + [self modifyKeychainUserValue:userName forKey:kBITHockeyMetaUserName]; +} + +- (void)setUserEmail:(NSString *)userEmail { + // always set it, since nil value will trigger removal of the keychain entry + _userEmail = userEmail; + + [self modifyKeychainUserValue:userEmail forKey:kBITHockeyMetaUserEmail]; +} + - (void)testIdentifier { if (!_appIdentifier || [self isAppStoreEnvironment]) { return; diff --git a/Classes/BITHockeyManagerDelegate.h b/Classes/BITHockeyManagerDelegate.h index 76ade83a..c8eeaced 100644 --- a/Classes/BITHockeyManagerDelegate.h +++ b/Classes/BITHockeyManagerDelegate.h @@ -76,21 +76,22 @@ ///----------------------------------------------------------------------------- /** - * Implement to force the usage of the live identifier - * - * This is useful if you are e.g. distributing an enterprise app inside your company - * and want to use the `liveIdentifier` for that even though it is not running from - * the App Store. - * - * Example: - * - (BOOL)shouldUseLiveIdentifierForHockeyManager:(BITHockeyManager *)hockeyManager { - * #ifdef (CONFIGURATION_AppStore) - * return YES; - * #endif - * return NO; - * } - * - * @param hockeyManager BITHockeyManager instance + Implement to force the usage of the live identifier + + This is useful if you are e.g. distributing an enterprise app inside your company + and want to use the `liveIdentifier` for that even though it is not running from + the App Store. + + Example: + + - (BOOL)shouldUseLiveIdentifierForHockeyManager:(BITHockeyManager *)hockeyManager { + #ifdef (CONFIGURATION_AppStore) + return YES; + #endif + return NO; + } + + @param hockeyManager BITHockeyManager instance */ - (BOOL)shouldUseLiveIdentifierForHockeyManager:(BITHockeyManager *)hockeyManager; @@ -121,13 +122,14 @@ /** Return the userid that should used in the SDK components - Right now this is used by the `BITCrashMananger` to attach to a crash report. + Right now this is used by the `BITCrashManager` to attach to a crash report. `BITFeedbackManager` uses it too for assigning the user to a discussion thread. In addition, if this returns not nil for `BITFeedbackManager` the user will not be asked for any user details by the component, including useerName or userEmail. You can find out the component requesting the userID like this: + - (NSString *)userIDForHockeyManager:(BITHockeyManager *)hockeyManager componentManager:(BITHockeyBaseManager *)componentManager { if (componentManager == hockeyManager.feedbackManager) { return UserIDForFeedback; @@ -138,27 +140,32 @@ } } + For crash reports, this delegate is invoked on the startup after the crash! + Alternatively you can also use `[BITHockeyManager userID]` which will cache the value in the keychain. + + @warning When returning a non nil value for the `BITCrashManager` component, crash reports + are not anonymous any more and the crash alerts will not show the word "anonymous"! @param hockeyManager The `BITHockeyManager` HockeyManager instance invoking this delegate @param componentManager The `BITHockeyBaseManager` component instance invoking this delegate, can be `BITCrashManager` or `BITFeedbackManager` @see userNameForHockeyManager:componentManager: @see userEmailForHockeyManager:componentManager: - @warning When returning a non nil value for the `BITCrashManager` component, crash reports - are not anonymous any more and the crash alerts will not show the word "anonymous"! + @see [BITHockeyManager userID] */ - (NSString *)userIDForHockeyManager:(BITHockeyManager *)hockeyManager componentManager:(BITHockeyBaseManager *)componentManager; /** Return the user name that should used in the SDK components - Right now this is used by the `BITCrashMananger` to attach to a crash report. + Right now this is used by the `BITCrashManager` to attach to a crash report. `BITFeedbackManager` uses it too for assigning the user to a discussion thread. In addition, if this returns not nil for `BITFeedbackManager` the user will not be asked for any user details by the component, including useerName or userEmail. You can find out the component requesting the user name like this: + - (NSString *)userNameForHockeyManager:(BITHockeyManager *)hockeyManager componentManager:(BITHockeyBaseManager *)componentManager { if (componentManager == hockeyManager.feedbackManager) { return UserNameForFeedback; @@ -169,26 +176,32 @@ } } + For crash reports, this delegate is invoked on the startup after the crash! + Alternatively you can also use `[BITHockeyManager userName]` which will cache the value in the keychain. + + @warning When returning a non nil value for the `BITCrashManager` component, crash reports + are not anonymous any more and the crash alerts will not show the word "anonymous"! + @param hockeyManager The `BITHockeyManager` HockeyManager instance invoking this delegate @param componentManager The `BITHockeyBaseManager` component instance invoking this delegate, can be `BITCrashManager` or `BITFeedbackManager` @see userIDForHockeyManager:componentManager: @see userEmailForHockeyManager:componentManager: - @warning When returning a non nil value for the `BITCrashManager` component, crash reports - are not anonymous any more and the crash alerts will not show the word "anonymous"! + @see [BITHockeyManager userName] */ - (NSString *)userNameForHockeyManager:(BITHockeyManager *)hockeyManager componentManager:(BITHockeyBaseManager *)componentManager; /** Return the users email address that should used in the SDK components - Right now this is used by the `BITCrashMananger` to attach to a crash report. + Right now this is used by the `BITCrashManager` to attach to a crash report. `BITFeedbackManager` uses it too for assigning the user to a discussion thread. In addition, if this returns not nil for `BITFeedbackManager` the user will not be asked for any user details by the component, including useerName or userEmail. You can find out the component requesting the user email like this: + - (NSString *)userEmailForHockeyManager:(BITHockeyManager *)hockeyManager componentManager:(BITHockeyBaseManager *)componentManager { if (componentManager == hockeyManager.feedbackManager) { return UserEmailForFeedback; @@ -199,13 +212,18 @@ } } + For crash reports, this delegate is invoked on the startup after the crash! + Alternatively you can also use `[BITHockeyManager userEmail]` which will cache the value in the keychain. + + @warning When returning a non nil value for the `BITCrashManager` component, crash reports + are not anonymous any more and the crash alerts will not show the word "anonymous"! + @param hockeyManager The `BITHockeyManager` HockeyManager instance invoking this delegate @param componentManager The `BITHockeyBaseManager` component instance invoking this delegate, can be `BITCrashManager` or `BITFeedbackManager` @see userIDForHockeyManager:componentManager: @see userNameForHockeyManager:componentManager: - @warning When returning a non nil value for the `BITCrashManager` component, crash reports - are not anonymous any more and the crash alerts will not show the word "anonymous"! + @see [BITHockeyManager userEmail] */ - (NSString *)userEmailForHockeyManager:(BITHockeyManager *)hockeyManager componentManager:(BITHockeyBaseManager *)componentManager; diff --git a/Classes/HockeySDKPrivate.h b/Classes/HockeySDKPrivate.h index aad75baa..49d48646 100644 --- a/Classes/HockeySDKPrivate.h +++ b/Classes/HockeySDKPrivate.h @@ -43,6 +43,10 @@ #define BITHOCKEY_USAGE_DATA @"BITUpdateManager.plist" +#define kBITHockeyMetaUserName @"BITHockeyMetaUserName" +#define kBITHockeyMetaUserEmail @"BITHockeyMetaUserEmail" +#define kBITHockeyMetaUserID @"BITHockeyMetaUserID" + #define kBITUpdateInstalledUUID @"BITUpdateInstalledUUID" #define kBITUpdateInstalledVersionID @"BITUpdateInstalledVersionID" #define kBITUpdateCurrentCompanyName @"BITUpdateCurrentCompanyName" diff --git a/HockeySDK.podspec b/HockeySDK.podspec index 6043a564..0080236e 100644 --- a/HockeySDK.podspec +++ b/HockeySDK.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'HockeySDK' - s.version = '3.5.2' + s.version = '3.5.3' s.summary = 'Collect live crash reports, get feedback from your users, distribute your betas, and analyze your test coverage with HockeyApp.' s.description = <<-DESC @@ -12,7 +12,7 @@ Pod::Spec.new do |s| DESC s.homepage = 'http://hockeyapp.net/' - s.documentation_url = 'http://hockeyapp.net/help/sdk/ios/3.5.2/' + s.documentation_url = 'http://hockeyapp.net/help/sdk/ios/3.5.3/' s.license = 'MIT' s.author = { 'Andreas Linde' => 'mail@andreaslinde.de', 'Thomas Dohmke' => "thomas@dohmke.de" } @@ -24,7 +24,7 @@ Pod::Spec.new do |s| s.frameworks = 'CoreText', 'QuartzCore', 'SystemConfiguration', 'CoreGraphics', 'UIKit', 'Security' s.ios.vendored_frameworks = 'Vendor/CrashReporter.framework' - s.xcconfig = {'GCC_PREPROCESSOR_DEFINITIONS' => %{$(inherited) BITHOCKEY_VERSION="@\\"#{s.version}\\"" BITHOCKEY_BUILD="@\\"25\\""} } + s.xcconfig = {'GCC_PREPROCESSOR_DEFINITIONS' => %{$(inherited) BITHOCKEY_VERSION="@\\"#{s.version}\\"" BITHOCKEY_BUILD="@\\"26\\""} } s.resource_bundle = { 'HockeySDKResources' => ['Resources/*.png', 'Resources/*.lproj'] } s.preserve_paths = 'Resources', 'Support' diff --git a/README.md b/README.md index a38637e8..b1d764e6 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -## Version 3.5.2 +## Version 3.5.3 -- [Changelog](http://www.hockeyapp.net/help/sdk/ios/3.5.2/docs/docs/Changelog.html) +- [Changelog](http://www.hockeyapp.net/help/sdk/ios/3.5.3/docs/docs/Changelog.html) ## Introduction @@ -31,10 +31,10 @@ The main SDK class is `BITHockeyManager`. It initializes all modules and provide ## Installation & Setup -- [Installation & Setup](http://www.hockeyapp.net/help/sdk/ios/3.5.2/docs/docs/Guide-Installation-Setup.html) (Recommended) -- [Installation & Setup Advanced](http://www.hockeyapp.net/help/sdk/ios/3.5.2/docs/docs/Guide-Installation-Setup-Advanced.html) (Using Git submodule and Xcode sub-project) -- [Identify and authenticate users of Ad-Hoc or Enterprise builds](http://www.hockeyapp.net/help/sdk/ios/3.5.2/docs/docs/HowTo-Authenticating-Users-on-iOS.html) -- [Migration from previous SDK Versions](http://www.hockeyapp.net/help/sdk/ios/3.5.2/docs/docs/Guide-Migration-Kits.html) +- [Installation & Setup](http://www.hockeyapp.net/help/sdk/ios/3.5.3/docs/docs/Guide-Installation-Setup.html) (Recommended) +- [Installation & Setup Advanced](http://www.hockeyapp.net/help/sdk/ios/3.5.3/docs/docs/Guide-Installation-Setup-Advanced.html) (Using Git submodule and Xcode sub-project) +- [Identify and authenticate users of Ad-Hoc or Enterprise builds](http://www.hockeyapp.net/help/sdk/ios/3.5.3/docs/docs/HowTo-Authenticating-Users-on-iOS.html) +- [Migration from previous SDK Versions](http://www.hockeyapp.net/help/sdk/ios/3.5.3/docs/docs/Guide-Migration-Kits.html) - [Mac Desktop Uploader](http://support.hockeyapp.net/kb/how-tos/how-to-upload-to-hockeyapp-on-a-mac) @@ -48,4 +48,4 @@ This documentation provides integrated help in Xcode for all public APIs and a s 3. Copy the content into ~`/Library/Developer/Shared/Documentation/DocSets` -The documentation is also available via the following URL: [http://hockeyapp.net/help/sdk/ios/3.5.2/](http://hockeyapp.net/help/sdk/ios/3.5.2/) +The documentation is also available via the following URL: [http://hockeyapp.net/help/sdk/ios/3.5.3/](http://hockeyapp.net/help/sdk/ios/3.5.3/) diff --git a/Support/buildnumber.xcconfig b/Support/buildnumber.xcconfig index 506af1bb..8c2c2802 100644 --- a/Support/buildnumber.xcconfig +++ b/Support/buildnumber.xcconfig @@ -1,7 +1,7 @@ #include "HockeySDK.xcconfig" -BUILD_NUMBER = 25 -VERSION_STRING = 3.5.2 +BUILD_NUMBER = 26 +VERSION_STRING = 3.5.3 GCC_PREPROCESSOR_DEFINITIONS = $(inherited) BITHOCKEY_VERSION="@\""$(VERSION_STRING)"\"" BITHOCKEY_BUILD="@\""$(BUILD_NUMBER)"\"" BIT_ARM_ARCHS = armv7 armv7s arm64 BIT_SIM_ARCHS = x86_64 i386 diff --git a/docs/Changelog-template.md b/docs/Changelog-template.md index e9c6431e..8c8c76b4 100644 --- a/docs/Changelog-template.md +++ b/docs/Changelog-template.md @@ -1,3 +1,13 @@ +## Version 3.5.3 + +- [NEW] Crash Reports now provide the selector name e.g. for crashes in `objc_MsgSend` +- [NEW] Add setter for global `userID`, `userName`, `userEmail`. Can be used instead of the delegates. +- [UPDATE] On device symbolication is now optional, disabled by default +- [BUGFIX] Fix for automatic authentication not always working correctly +- [BUGFIX] `BITFeedbackComposeViewControllerDelegate` now also works for compose view controller used by the feedback list view +- [BUGFIX] Fix typos in documentation +

+ ## Version 3.5.2 - [UPDATE] Make sure a log message appears in the console if the SDK is not setup on the main thread diff --git a/docs/HowTo-Authenticating-Users-on-iOS-template.md b/docs/HowTo-Authenticating-Users-on-iOS-template.md index d3eece6d..7c66e8df 100644 --- a/docs/HowTo-Authenticating-Users-on-iOS-template.md +++ b/docs/HowTo-Authenticating-Users-on-iOS-template.md @@ -30,6 +30,8 @@ Previous versions of HockeySDK for iOS used the response of the method `UIDevice The app opens Safari and asks the user to log in to his HockeyApp account. +The strategies **BITAuthenticatorIdentificationTypeDevice** and **BITAuthenticatorIdentificationTypeWebAuth** also allow for automatic authentication as explained [here](http://hockeyapp.net/blog/2014/01/31/automatic-authentication-ios.html). + After setting up one of those strategies, you need to trigger the authentication process by calling [[BITHockeyManager sharedHockeyManager].authenticator authenticateInstallation];