2
0

Re-organize the project into a better hierarchy.

This commit is contained in:
Maarten Billemont
2017-03-06 13:37:05 -05:00
parent 67e18895ab
commit c6b285a9c0
1452 changed files with 43 additions and 350 deletions

View File

@@ -0,0 +1 @@
Versions/Current/Crashlytics

View File

@@ -0,0 +1 @@
Versions/Current/Headers

View File

@@ -0,0 +1 @@
Versions/Current/Modules

View File

@@ -0,0 +1 @@
Versions/Current/Resources

Binary file not shown.

View File

@@ -0,0 +1,31 @@
//
// ANSCompatibility.h
// AnswersKit
//
// Copyright (c) 2015 Crashlytics, Inc. All rights reserved.
//
#pragma once
#if !__has_feature(nullability)
#define nonnull
#define nullable
#define _Nullable
#define _Nonnull
#endif
#ifndef NS_ASSUME_NONNULL_BEGIN
#define NS_ASSUME_NONNULL_BEGIN
#endif
#ifndef NS_ASSUME_NONNULL_END
#define NS_ASSUME_NONNULL_END
#endif
#if __has_feature(objc_generics)
#define ANS_GENERIC_NSARRAY(type) NSArray<type>
#define ANS_GENERIC_NSDICTIONARY(key_type,object_key) NSDictionary<key_type, object_key>
#else
#define ANS_GENERIC_NSARRAY(type) NSArray
#define ANS_GENERIC_NSDICTIONARY(key_type,object_key) NSDictionary
#endif

View File

@@ -0,0 +1,210 @@
//
// Answers.h
// Crashlytics
//
// Copyright (c) 2015 Crashlytics, Inc. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "ANSCompatibility.h"
NS_ASSUME_NONNULL_BEGIN
/**
* This class exposes the Answers Events API, allowing you to track key
* user user actions and metrics in your app.
*/
@interface Answers : NSObject
/**
* Log a Sign Up event to see users signing up for your app in real-time, understand how
* many users are signing up with different methods and their success rate signing up.
*
* @param signUpMethodOrNil The method by which a user logged in, e.g. Twitter or Digits.
* @param signUpSucceededOrNil The ultimate success or failure of the login
* @param customAttributesOrNil A dictionary of custom attributes to associate with this event.
*/
+ (void)logSignUpWithMethod:(nullable NSString *)signUpMethodOrNil
success:(nullable NSNumber *)signUpSucceededOrNil
customAttributes:(nullable ANS_GENERIC_NSDICTIONARY(NSString *, id) *)customAttributesOrNil;
/**
* Log an Log In event to see users logging into your app in real-time, understand how many
* users are logging in with different methods and their success rate logging into your app.
*
* @param loginMethodOrNil The method by which a user logged in, e.g. email, Twitter or Digits.
* @param loginSucceededOrNil The ultimate success or failure of the login
* @param customAttributesOrNil A dictionary of custom attributes to associate with this event.
*/
+ (void)logLoginWithMethod:(nullable NSString *)loginMethodOrNil
success:(nullable NSNumber *)loginSucceededOrNil
customAttributes:(nullable ANS_GENERIC_NSDICTIONARY(NSString *, id) *)customAttributesOrNil;
/**
* Log a Share event to see users sharing from your app in real-time, letting you
* understand what content they're sharing from the type or genre down to the specific id.
*
* @param shareMethodOrNil The method by which a user shared, e.g. email, Twitter, SMS.
* @param contentNameOrNil The human readable name for this piece of content.
* @param contentTypeOrNil The type of content shared.
* @param contentIdOrNil The unique identifier for this piece of content. Useful for finding the top shared item.
* @param customAttributesOrNil A dictionary of custom attributes to associate with this event.
*/
+ (void)logShareWithMethod:(nullable NSString *)shareMethodOrNil
contentName:(nullable NSString *)contentNameOrNil
contentType:(nullable NSString *)contentTypeOrNil
contentId:(nullable NSString *)contentIdOrNil
customAttributes:(nullable ANS_GENERIC_NSDICTIONARY(NSString *, id) *)customAttributesOrNil;
/**
* Log an Invite Event to track how users are inviting other users into
* your application.
*
* @param inviteMethodOrNil The method of invitation, e.g. GameCenter, Twitter, email.
* @param customAttributesOrNil A dictionary of custom attributes to associate with this event.
*/
+ (void)logInviteWithMethod:(nullable NSString *)inviteMethodOrNil
customAttributes:(nullable ANS_GENERIC_NSDICTIONARY(NSString *, id) *)customAttributesOrNil;
/**
* Log a Purchase event to see your revenue in real-time, understand how many users are making purchases, see which
* items are most popular, and track plenty of other important purchase-related metrics.
*
* @param itemPriceOrNil The purchased item's price.
* @param currencyOrNil The ISO4217 currency code. Example: USD
* @param purchaseSucceededOrNil Was the purchase succesful or unsuccesful
* @param itemNameOrNil The human-readable form of the item's name. Example:
* @param itemTypeOrNil The type, or genre of the item. Example: Song
* @param itemIdOrNil The machine-readable, unique item identifier Example: SKU
* @param customAttributesOrNil A dictionary of custom attributes to associate with this purchase.
*/
+ (void)logPurchaseWithPrice:(nullable NSDecimalNumber *)itemPriceOrNil
currency:(nullable NSString *)currencyOrNil
success:(nullable NSNumber *)purchaseSucceededOrNil
itemName:(nullable NSString *)itemNameOrNil
itemType:(nullable NSString *)itemTypeOrNil
itemId:(nullable NSString *)itemIdOrNil
customAttributes:(nullable ANS_GENERIC_NSDICTIONARY(NSString *, id) *)customAttributesOrNil;
/**
* Log a Level Start Event to track where users are in your game.
*
* @param levelNameOrNil The level name
* @param customAttributesOrNil A dictionary of custom attributes to associate with this level start event.
*/
+ (void)logLevelStart:(nullable NSString *)levelNameOrNil
customAttributes:(nullable ANS_GENERIC_NSDICTIONARY(NSString *, id) *)customAttributesOrNil;
/**
* Log a Level End event to track how users are completing levels in your game.
*
* @param levelNameOrNil The name of the level completed, E.G. "1" or "Training"
* @param scoreOrNil The score the user completed the level with.
* @param levelCompletedSuccesfullyOrNil A boolean representing whether or not the level was completed succesfully.
* @param customAttributesOrNil A dictionary of custom attributes to associate with this event.
*/
+ (void)logLevelEnd:(nullable NSString *)levelNameOrNil
score:(nullable NSNumber *)scoreOrNil
success:(nullable NSNumber *)levelCompletedSuccesfullyOrNil
customAttributes:(nullable ANS_GENERIC_NSDICTIONARY(NSString *, id) *)customAttributesOrNil;
/**
* Log an Add to Cart event to see users adding items to a shopping cart in real-time, understand how
* many users start the purchase flow, see which items are most popular, and track plenty of other important
* purchase-related metrics.
*
* @param itemPriceOrNil The purchased item's price.
* @param currencyOrNil The ISO4217 currency code. Example: USD
* @param itemNameOrNil The human-readable form of the item's name. Example:
* @param itemTypeOrNil The type, or genre of the item. Example: Song
* @param itemIdOrNil The machine-readable, unique item identifier Example: SKU
* @param customAttributesOrNil A dictionary of custom attributes to associate with this event.
*/
+ (void)logAddToCartWithPrice:(nullable NSDecimalNumber *)itemPriceOrNil
currency:(nullable NSString *)currencyOrNil
itemName:(nullable NSString *)itemNameOrNil
itemType:(nullable NSString *)itemTypeOrNil
itemId:(nullable NSString *)itemIdOrNil
customAttributes:(nullable ANS_GENERIC_NSDICTIONARY(NSString *, id) *)customAttributesOrNil;
/**
* Log a Start Checkout event to see users moving through the purchase funnel in real-time, understand how many
* users are doing this and how much they're spending per checkout, and see how it related to other important
* purchase-related metrics.
*
* @param totalPriceOrNil The total price of the cart.
* @param currencyOrNil The ISO4217 currency code. Example: USD
* @param itemCountOrNil The number of items in the cart.
* @param customAttributesOrNil A dictionary of custom attributes to associate with this event.
*/
+ (void)logStartCheckoutWithPrice:(nullable NSDecimalNumber *)totalPriceOrNil
currency:(nullable NSString *)currencyOrNil
itemCount:(nullable NSNumber *)itemCountOrNil
customAttributes:(nullable ANS_GENERIC_NSDICTIONARY(NSString *, id) *)customAttributesOrNil;
/**
* Log a Rating event to see users rating content within your app in real-time and understand what
* content is most engaging, from the type or genre down to the specific id.
*
* @param ratingOrNil The integer rating given by the user.
* @param contentNameOrNil The human readable name for this piece of content.
* @param contentTypeOrNil The type of content shared.
* @param contentIdOrNil The unique identifier for this piece of content. Useful for finding the top shared item.
* @param customAttributesOrNil A dictionary of custom attributes to associate with this event.
*/
+ (void)logRating:(nullable NSNumber *)ratingOrNil
contentName:(nullable NSString *)contentNameOrNil
contentType:(nullable NSString *)contentTypeOrNil
contentId:(nullable NSString *)contentIdOrNil
customAttributes:(nullable ANS_GENERIC_NSDICTIONARY(NSString *, id) *)customAttributesOrNil;
/**
* Log a Content View event to see users viewing content within your app in real-time and
* understand what content is most engaging, from the type or genre down to the specific id.
*
* @param contentNameOrNil The human readable name for this piece of content.
* @param contentTypeOrNil The type of content shared.
* @param contentIdOrNil The unique identifier for this piece of content. Useful for finding the top shared item.
* @param customAttributesOrNil A dictionary of custom attributes to associate with this event.
*/
+ (void)logContentViewWithName:(nullable NSString *)contentNameOrNil
contentType:(nullable NSString *)contentTypeOrNil
contentId:(nullable NSString *)contentIdOrNil
customAttributes:(nullable ANS_GENERIC_NSDICTIONARY(NSString *, id) *)customAttributesOrNil;
/**
* Log a Search event allows you to see users searching within your app in real-time and understand
* exactly what they're searching for.
*
* @param queryOrNil The user's query.
* @param customAttributesOrNil A dictionary of custom attributes to associate with this event.
*/
+ (void)logSearchWithQuery:(nullable NSString *)queryOrNil
customAttributes:(nullable ANS_GENERIC_NSDICTIONARY(NSString *, id) *)customAttributesOrNil;
/**
* Log a Custom Event to see user actions that are uniquely important for your app in real-time, to see how often
* they're performing these actions with breakdowns by different categories you add. Use a human-readable name for
* the name of the event, since this is how the event will appear in Answers.
*
* @param eventName The human-readable name for the event.
* @param customAttributesOrNil A dictionary of custom attributes to associate with this event. Attribute keys
* must be <code>NSString</code> and and values must be <code>NSNumber</code> or <code>NSString</code>.
* @discussion How we treat <code>NSNumbers</code>:
* We will provide information about the distribution of values over time.
*
* How we treat <code>NSStrings</code>:
* NSStrings are used as categorical data, allowing comparison across different category values.
* Strings are limited to a maximum length of 100 characters, attributes over this length will be
* truncated.
*
* When tracking the Tweet views to better understand user engagement, sending the tweet's length
* and the type of media present in the tweet allows you to track how tweet length and the type of media influence
* engagement.
*/
+ (void)logCustomEventWithName:(NSString *)eventName
customAttributes:(nullable ANS_GENERIC_NSDICTIONARY(NSString *, id) *)customAttributesOrNil;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,33 @@
//
// CLSAttributes.h
// Crashlytics
//
// Copyright (c) 2015 Crashlytics, Inc. All rights reserved.
//
#pragma once
#define CLS_DEPRECATED(x) __attribute__ ((deprecated(x)))
#if !__has_feature(nullability)
#define nonnull
#define nullable
#define _Nullable
#define _Nonnull
#endif
#ifndef NS_ASSUME_NONNULL_BEGIN
#define NS_ASSUME_NONNULL_BEGIN
#endif
#ifndef NS_ASSUME_NONNULL_END
#define NS_ASSUME_NONNULL_END
#endif
#if __has_feature(objc_generics)
#define CLS_GENERIC_NSARRAY(type) NSArray<type>
#define CLS_GENERIC_NSDICTIONARY(key_type,object_key) NSDictionary<key_type, object_key>
#else
#define CLS_GENERIC_NSARRAY(type) NSArray
#define CLS_GENERIC_NSDICTIONARY(key_type,object_key) NSDictionary
#endif

View File

@@ -0,0 +1,64 @@
//
// CLSLogging.h
// Crashlytics
//
// Copyright (c) 2015 Crashlytics, Inc. All rights reserved.
//
#ifdef __OBJC__
#import "CLSAttributes.h"
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
#endif
/**
*
* The CLS_LOG macro provides as easy way to gather more information in your log messages that are
* sent with your crash data. CLS_LOG prepends your custom log message with the function name and
* line number where the macro was used. If your app was built with the DEBUG preprocessor macro
* defined CLS_LOG uses the CLSNSLog function which forwards your log message to NSLog and CLSLog.
* If the DEBUG preprocessor macro is not defined CLS_LOG uses CLSLog only.
*
* Example output:
* -[AppDelegate login:] line 134 $ login start
*
* If you would like to change this macro, create a new header file, unset our define and then define
* your own version. Make sure this new header file is imported after the Crashlytics header file.
*
* #undef CLS_LOG
* #define CLS_LOG(__FORMAT__, ...) CLSNSLog...
*
**/
#ifdef __OBJC__
#ifdef DEBUG
#define CLS_LOG(__FORMAT__, ...) CLSNSLog((@"%s line %d $ " __FORMAT__), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__)
#else
#define CLS_LOG(__FORMAT__, ...) CLSLog((@"%s line %d $ " __FORMAT__), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__)
#endif
#endif
/**
*
* Add logging that will be sent with your crash data. This logging will not show up in the system.log
* and will only be visible in your Crashlytics dashboard.
*
**/
#ifdef __OBJC__
OBJC_EXTERN void CLSLog(NSString *format, ...) NS_FORMAT_FUNCTION(1,2);
OBJC_EXTERN void CLSLogv(NSString *format, va_list ap) NS_FORMAT_FUNCTION(1,0);
/**
*
* Add logging that will be sent with your crash data. This logging will show up in the system.log
* and your Crashlytics dashboard. It is not recommended for Release builds.
*
**/
OBJC_EXTERN void CLSNSLog(NSString *format, ...) NS_FORMAT_FUNCTION(1,2);
OBJC_EXTERN void CLSNSLogv(NSString *format, va_list ap) NS_FORMAT_FUNCTION(1,0);
NS_ASSUME_NONNULL_END
#endif

View File

@@ -0,0 +1,103 @@
//
// CLSReport.h
// Crashlytics
//
// Copyright (c) 2015 Crashlytics, Inc. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "CLSAttributes.h"
NS_ASSUME_NONNULL_BEGIN
/**
* The CLSCrashReport protocol is deprecated. See the CLSReport class and the CrashyticsDelegate changes for details.
**/
@protocol CLSCrashReport <NSObject>
@property (nonatomic, copy, readonly) NSString *identifier;
@property (nonatomic, copy, readonly) NSDictionary *customKeys;
@property (nonatomic, copy, readonly) NSString *bundleVersion;
@property (nonatomic, copy, readonly) NSString *bundleShortVersionString;
@property (nonatomic, copy, readonly) NSDate *crashedOnDate;
@property (nonatomic, copy, readonly) NSString *OSVersion;
@property (nonatomic, copy, readonly) NSString *OSBuildVersion;
@end
/**
* The CLSReport exposes an interface to the phsyical report that Crashlytics has created. You can
* use this class to get information about the event, and can also set some values after the
* event has occured.
**/
@interface CLSReport : NSObject <CLSCrashReport>
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;
/**
* Returns the session identifier for the report.
**/
@property (nonatomic, copy, readonly) NSString *identifier;
/**
* Returns the custom key value data for the report.
**/
@property (nonatomic, copy, readonly) NSDictionary *customKeys;
/**
* Returns the CFBundleVersion of the application that generated the report.
**/
@property (nonatomic, copy, readonly) NSString *bundleVersion;
/**
* Returns the CFBundleShortVersionString of the application that generated the report.
**/
@property (nonatomic, copy, readonly) NSString *bundleShortVersionString;
/**
* Returns the date that the report was created.
**/
@property (nonatomic, copy, readonly) NSDate *dateCreated;
/**
* Returns the os version that the application crashed on.
**/
@property (nonatomic, copy, readonly) NSString *OSVersion;
/**
* Returns the os build version that the application crashed on.
**/
@property (nonatomic, copy, readonly) NSString *OSBuildVersion;
/**
* Returns YES if the report contains any crash information, otherwise returns NO.
**/
@property (nonatomic, assign, readonly) BOOL isCrash;
/**
* You can use this method to set, after the event, additional custom keys. The rules
* and semantics for this method are the same as those documented in Crashlytics.h. Be aware
* that the maximum size and count of custom keys is still enforced, and you can overwrite keys
* and/or cause excess keys to be deleted by using this method.
**/
- (void)setObjectValue:(nullable id)value forKey:(NSString *)key;
/**
* Record an application-specific user identifier. See Crashlytics.h for details.
**/
@property (nonatomic, copy, nullable) NSString * userIdentifier;
/**
* Record a user name. See Crashlytics.h for details.
**/
@property (nonatomic, copy, nullable) NSString * userName;
/**
* Record a user email. See Crashlytics.h for details.
**/
@property (nonatomic, copy, nullable) NSString * userEmail;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,38 @@
//
// CLSStackFrame.h
// Crashlytics
//
// Copyright 2015 Crashlytics, Inc. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "CLSAttributes.h"
NS_ASSUME_NONNULL_BEGIN
/**
*
* This class is used in conjunction with -[Crashlytics recordCustomExceptionName:reason:frameArray:] to
* record information about non-ObjC/C++ exceptions. All information included here will be displayed
* in the Crashlytics UI, and can influence crash grouping. Be particularly careful with the use of the
* address property. If set, Crashlytics will attempt symbolication and could overwrite other properities
* in the process.
*
**/
@interface CLSStackFrame : NSObject
+ (instancetype)stackFrame;
+ (instancetype)stackFrameWithAddress:(NSUInteger)address;
+ (instancetype)stackFrameWithSymbol:(NSString *)symbol;
@property (nonatomic, copy, nullable) NSString *symbol;
@property (nonatomic, copy, nullable) NSString *rawSymbol;
@property (nonatomic, copy, nullable) NSString *library;
@property (nonatomic, copy, nullable) NSString *fileName;
@property (nonatomic, assign) uint32_t lineNumber;
@property (nonatomic, assign) uint64_t offset;
@property (nonatomic, assign) uint64_t address;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,266 @@
//
// Crashlytics.h
// Crashlytics
//
// Copyright (c) 2015 Crashlytics, Inc. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "CLSAttributes.h"
#import "CLSLogging.h"
#import "CLSReport.h"
#import "CLSStackFrame.h"
#import "Answers.h"
NS_ASSUME_NONNULL_BEGIN
@protocol CrashlyticsDelegate;
/**
* Crashlytics. Handles configuration and initialization of Crashlytics.
*
* Note: The Crashlytics class cannot be subclassed. If this is causing you pain for
* testing, we suggest using either a wrapper class or a protocol extension.
*/
@interface Crashlytics : NSObject
@property (nonatomic, readonly, copy) NSString *APIKey;
@property (nonatomic, readonly, copy) NSString *version;
@property (nonatomic, assign) BOOL debugMode;
/**
*
* The delegate can be used to influence decisions on reporting and behavior, as well as reacting
* to previous crashes.
*
* Make certain that the delegate is setup before starting Crashlytics with startWithAPIKey:... or
* via +[Fabric with:...]. Failure to do will result in missing any delegate callbacks that occur
* synchronously during start.
*
**/
@property (nonatomic, assign, nullable) id <CrashlyticsDelegate> delegate;
/**
* The recommended way to install Crashlytics into your application is to place a call to +startWithAPIKey:
* in your -application:didFinishLaunchingWithOptions: or -applicationDidFinishLaunching:
* method.
*
* Note: Starting with 3.0, the submission process has been significantly improved. The delay parameter
* is no longer required to throttle submissions on launch, performance will be great without it.
*
* @param apiKey The Crashlytics API Key for this app
*
* @return The singleton Crashlytics instance
*/
+ (Crashlytics *)startWithAPIKey:(NSString *)apiKey;
+ (Crashlytics *)startWithAPIKey:(NSString *)apiKey afterDelay:(NSTimeInterval)delay CLS_DEPRECATED("Crashlytics no longer needs or uses the delay parameter. Please use +startWithAPIKey: instead.");
/**
* If you need the functionality provided by the CrashlyticsDelegate protocol, you can use
* these convenience methods to activate the framework and set the delegate in one call.
*
* @param apiKey The Crashlytics API Key for this app
* @param delegate A delegate object which conforms to CrashlyticsDelegate.
*
* @return The singleton Crashlytics instance
*/
+ (Crashlytics *)startWithAPIKey:(NSString *)apiKey delegate:(nullable id<CrashlyticsDelegate>)delegate;
+ (Crashlytics *)startWithAPIKey:(NSString *)apiKey delegate:(nullable id<CrashlyticsDelegate>)delegate afterDelay:(NSTimeInterval)delay CLS_DEPRECATED("Crashlytics no longer needs or uses the delay parameter. Please use +startWithAPIKey:delegate: instead.");
/**
* Access the singleton Crashlytics instance.
*
* @return The singleton Crashlytics instance
*/
+ (Crashlytics *)sharedInstance;
/**
* The easiest way to cause a crash - great for testing!
*/
- (void)crash;
/**
* The easiest way to cause a crash with an exception - great for testing.
*/
- (void)throwException;
/**
* Specify a user identifier which will be visible in the Crashlytics UI.
*
* Many of our customers have requested the ability to tie crashes to specific end-users of their
* application in order to facilitate responses to support requests or permit the ability to reach
* out for more information. We allow you to specify up to three separate values for display within
* the Crashlytics UI - but please be mindful of your end-user's privacy.
*
* We recommend specifying a user identifier - an arbitrary string that ties an end-user to a record
* in your system. This could be a database id, hash, or other value that is meaningless to a
* third-party observer but can be indexed and queried by you.
*
* Optionally, you may also specify the end-user's name or username, as well as email address if you
* do not have a system that works well with obscured identifiers.
*
* Pursuant to our EULA, this data is transferred securely throughout our system and we will not
* disseminate end-user data unless required to by law. That said, if you choose to provide end-user
* contact information, we strongly recommend that you disclose this in your application's privacy
* policy. Data privacy is of our utmost concern.
*
* @param identifier An arbitrary user identifier string which ties an end-user to a record in your system.
*/
- (void)setUserIdentifier:(nullable NSString *)identifier;
/**
* Specify a user name which will be visible in the Crashlytics UI.
* Please be mindful of your end-user's privacy and see if setUserIdentifier: can fulfil your needs.
* @see setUserIdentifier:
*
* @param name An end user's name.
*/
- (void)setUserName:(nullable NSString *)name;
/**
* Specify a user email which will be visible in the Crashlytics UI.
* Please be mindful of your end-user's privacy and see if setUserIdentifier: can fulfil your needs.
*
* @see setUserIdentifier:
*
* @param email An end user's email address.
*/
- (void)setUserEmail:(nullable NSString *)email;
+ (void)setUserIdentifier:(nullable NSString *)identifier CLS_DEPRECATED("Please access this method via +sharedInstance");
+ (void)setUserName:(nullable NSString *)name CLS_DEPRECATED("Please access this method via +sharedInstance");
+ (void)setUserEmail:(nullable NSString *)email CLS_DEPRECATED("Please access this method via +sharedInstance");
/**
* Set a value for a for a key to be associated with your crash data which will be visible in the Crashlytics UI.
* When setting an object value, the object is converted to a string. This is typically done by calling
* -[NSObject description].
*
* @param value The object to be associated with the key
* @param key The key with which to associate the value
*/
- (void)setObjectValue:(nullable id)value forKey:(NSString *)key;
/**
* Set an int value for a key to be associated with your crash data which will be visible in the Crashlytics UI.
*
* @param value The integer value to be set
* @param key The key with which to associate the value
*/
- (void)setIntValue:(int)value forKey:(NSString *)key;
/**
* Set an BOOL value for a key to be associated with your crash data which will be visible in the Crashlytics UI.
*
* @param value The BOOL value to be set
* @param key The key with which to associate the value
*/
- (void)setBoolValue:(BOOL)value forKey:(NSString *)key;
/**
* Set an float value for a key to be associated with your crash data which will be visible in the Crashlytics UI.
*
* @param value The float value to be set
* @param key The key with which to associate the value
*/
- (void)setFloatValue:(float)value forKey:(NSString *)key;
+ (void)setObjectValue:(nullable id)value forKey:(NSString *)key CLS_DEPRECATED("Please access this method via +sharedInstance");
+ (void)setIntValue:(int)value forKey:(NSString *)key CLS_DEPRECATED("Please access this method via +sharedInstance");
+ (void)setBoolValue:(BOOL)value forKey:(NSString *)key CLS_DEPRECATED("Please access this method via +sharedInstance");
+ (void)setFloatValue:(float)value forKey:(NSString *)key CLS_DEPRECATED("Please access this method via +sharedInstance");
/**
* This method can be used to record a single exception structure in a report. This is particularly useful
* when your code interacts with non-native languages like Lua, C#, or Javascript. This call can be
* expensive and should only be used shortly before process termination. This API is not intended be to used
* to log NSException objects. All safely-reportable NSExceptions are automatically captured by
* Crashlytics.
*
* @param name The name of the custom exception
* @param reason The reason this exception occured
* @param frameArray An array of CLSStackFrame objects
*/
- (void)recordCustomExceptionName:(NSString *)name reason:(nullable NSString *)reason frameArray:(CLS_GENERIC_NSARRAY(CLSStackFrame *) *)frameArray;
/**
*
* This allows you to record a non-fatal event, described by an NSError object. These events will be grouped and
* displayed similarly to crashes. Keep in mind that this method can be expensive. Also, the total number of
* NSErrors that can be recorded during your app's life-cycle is limited by a fixed-size circular buffer. If the
* buffer is overrun, the oldest data is dropped. Errors are relayed to Crashlytics on a subsequent launch
* of your application.
*
* You can also use the -recordError:withAdditionalUserInfo: to include additional context not represented
* by the NSError instance itself.
*
**/
- (void)recordError:(NSError *)error;
- (void)recordError:(NSError *)error withAdditionalUserInfo:(nullable CLS_GENERIC_NSDICTIONARY(NSString *, id) *)userInfo;
- (void)logEvent:(NSString *)eventName CLS_DEPRECATED("Please refer to Answers +logCustomEventWithName:");
- (void)logEvent:(NSString *)eventName attributes:(nullable NSDictionary *) attributes CLS_DEPRECATED("Please refer to Answers +logCustomEventWithName:");
+ (void)logEvent:(NSString *)eventName CLS_DEPRECATED("Please refer to Answers +logCustomEventWithName:");
+ (void)logEvent:(NSString *)eventName attributes:(nullable NSDictionary *) attributes CLS_DEPRECATED("Please refer to Answers +logCustomEventWithName:");
@end
/**
*
* The CrashlyticsDelegate protocol provides a mechanism for your application to take
* action on events that occur in the Crashlytics crash reporting system. You can make
* use of these calls by assigning an object to the Crashlytics' delegate property directly,
* or through the convenience +startWithAPIKey:delegate: method.
*
*/
@protocol CrashlyticsDelegate <NSObject>
@optional
- (void)crashlyticsDidDetectCrashDuringPreviousExecution:(Crashlytics *)crashlytics CLS_DEPRECATED("Please refer to -crashlyticsDidDetectReportForLastExecution:");
- (void)crashlytics:(Crashlytics *)crashlytics didDetectCrashDuringPreviousExecution:(id <CLSCrashReport>)crash CLS_DEPRECATED("Please refer to -crashlyticsDidDetectReportForLastExecution:");
/**
*
* Called when a Crashlytics instance has determined that the last execution of the
* application ended in a crash. This is called synchronously on Crashlytics
* initialization. Your delegate must invoke the completionHandler, but does not need to do so
* synchronously, or even on the main thread. Invoking completionHandler with NO will cause the
* detected report to be deleted and not submitted to Crashlytics. This is useful for
* implementing permission prompts, or other more-complex forms of logic around submitting crashes.
*
* @warning Failure to invoke the completionHandler will prevent submissions from being reported. Watch out.
*
* @warning Just implementing this delegate method will disable all forms of synchronous report submission. This can
* impact the reliability of reporting crashes very early in application launch.
*
* @param report The CLSReport object representing the last detected crash
* @param completionHandler The completion handler to call when your logic has completed.
*
*/
- (void)crashlyticsDidDetectReportForLastExecution:(CLSReport *)report completionHandler:(void (^)(BOOL submit))completionHandler;
/**
* If your app is running on an OS that supports it (OS X 10.9+, iOS 7.0+), Crashlytics will submit
* most reports using out-of-process background networking operations. This results in a significant
* improvement in reliability of reporting, as well as power and performance wins for your users.
* If you don't want this functionality, you can disable by returning NO from this method.
*
* @warning Background submission is not supported for extensions on iOS or OS X.
*
* @param crashlytics The Crashlytics singleton instance
*
* @return Return NO if you don't want out-of-process background network operations.
*
*/
- (BOOL)crashlyticsCanUseBackgroundSessions:(Crashlytics *)crashlytics;
@end
/**
* `CrashlyticsKit` can be used as a parameter to `[Fabric with:@[CrashlyticsKit]];` in Objective-C. In Swift, use Crashlytics.sharedInstance()
*/
#define CrashlyticsKit [Crashlytics sharedInstance]
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,14 @@
framework module Crashlytics {
header "Crashlytics.h"
header "Answers.h"
header "ANSCompatibility.h"
header "CLSLogging.h"
header "CLSReport.h"
header "CLSStackFrame.h"
header "CLSAttributes.h"
export *
link "z"
link "c++"
}

View File

@@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>BuildMachineOSBuild</key>
<string>15F34</string>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleExecutable</key>
<string>Crashlytics</string>
<key>CFBundleIdentifier</key>
<string>com.twitter.crashlytics.mac</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>Crashlytics</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>3.7.2</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleSupportedPlatforms</key>
<array>
<string>MacOSX</string>
</array>
<key>CFBundleVersion</key>
<string>112</string>
<key>DTCompiler</key>
<string>com.apple.compilers.llvm.clang.1_0</string>
<key>DTPlatformBuild</key>
<string>7D175</string>
<key>DTPlatformVersion</key>
<string>GM</string>
<key>DTSDKBuild</key>
<string>15E60</string>
<key>DTSDKName</key>
<string>macosx10.11</string>
<key>DTXcode</key>
<string>0730</string>
<key>DTXcodeBuild</key>
<string>7D175</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2016 Crashlytics, Inc. All rights reserved.</string>
<key>UIDeviceFamily</key>
<array>
<integer>3</integer>
<integer>2</integer>
<integer>1</integer>
<integer>4</integer>
</array>
</dict>
</plist>

View File

@@ -0,0 +1 @@
A

View File

@@ -0,0 +1,28 @@
#!/bin/sh
# run
#
# Copyright (c) 2015 Crashlytics. All rights reserved.
# Figure out where we're being called from
DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
# Quote path in case of spaces or special chars
DIR="\"${DIR}"
PATH_SEP="/"
VALIDATE_COMMAND="uploadDSYM\" $@ validate run-script"
UPLOAD_COMMAND="uploadDSYM\" $@ run-script"
# Ensure params are as expected, run in sync mode to validate
eval $DIR$PATH_SEP$VALIDATE_COMMAND
return_code=$?
if [[ $return_code != 0 ]]; then
exit $return_code
fi
# Verification passed, upload dSYM in background to prevent Xcode from waiting
# Note: Validation is performed again before upload.
# Output can still be found in Console.app
eval $DIR$PATH_SEP$UPLOAD_COMMAND > /dev/null 2>&1 &

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1 @@
Versions/Current/Fabric

View File

@@ -0,0 +1 @@
Versions/Current/Headers

View File

@@ -0,0 +1 @@
Versions/Current/Modules

View File

@@ -0,0 +1 @@
Versions/Current/Resources

Binary file not shown.

View File

@@ -0,0 +1,60 @@
//
// FABAttributes.h
// Fabric
//
// Copyright (C) 2015 Twitter, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#pragma once
#define FAB_UNAVAILABLE(x) __attribute__((unavailable(x)))
#if __has_feature(nullability)
#define fab_nullable nullable
#define fab_nonnull nonnull
#define fab_null_unspecified null_unspecified
#define fab_null_resettable null_resettable
#define __fab_nullable __nullable
#define __fab_nonnull __nonnull
#define __fab_null_unspecified __null_unspecified
#else
#define fab_nullable
#define fab_nonnull
#define fab_null_unspecified
#define fab_null_resettable
#define __fab_nullable
#define __fab_nonnull
#define __fab_null_unspecified
#endif
#ifndef NS_ASSUME_NONNULL_BEGIN
#define NS_ASSUME_NONNULL_BEGIN
#endif
#ifndef NS_ASSUME_NONNULL_END
#define NS_ASSUME_NONNULL_END
#endif
/**
* The following macros are defined here to provide
* backwards compatability. If you are still using
* them you should migrate to the new versions that
* are defined above.
*/
#define FAB_NONNULL __fab_nonnull
#define FAB_NULLABLE __fab_nullable
#define FAB_START_NONNULL NS_ASSUME_NONNULL_BEGIN
#define FAB_END_NONNULL NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,77 @@
//
// Fabric.h
// Fabric
//
// Copyright (C) 2015 Twitter, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#import <Foundation/Foundation.h>
#import "FABAttributes.h"
NS_ASSUME_NONNULL_BEGIN
#if TARGET_OS_IPHONE
#if __IPHONE_OS_VERSION_MIN_REQUIRED < 60000
#error "Fabric's minimum iOS version is 6.0"
#endif
#else
#if __MAC_OS_X_VERSION_MIN_REQUIRED < 1070
#error "Fabric's minimum OS X version is 10.7"
#endif
#endif
/**
* Fabric Base. Coordinates configuration and starts all provided kits.
*/
@interface Fabric : NSObject
/**
* Initialize Fabric and all provided kits. Call this method within your App Delegate's `application:didFinishLaunchingWithOptions:` and provide the kits you wish to use.
*
* For example, in Objective-C:
*
* `[Fabric with:@[[Crashlytics class], [Twitter class], [Digits class], [MoPub class]]];`
*
* Swift:
*
* `Fabric.with([Crashlytics.self(), Twitter.self(), Digits.self(), MoPub.self()])`
*
* Only the first call to this method is honored. Subsequent calls are no-ops.
*
* @param kitClasses An array of kit Class objects
*
* @return Returns the shared Fabric instance. In most cases this can be ignored.
*/
+ (instancetype)with:(NSArray *)kitClasses;
/**
* Returns the Fabric singleton object.
*/
+ (instancetype)sharedSDK;
/**
* This BOOL enables or disables debug logging, such as kit version information. The default value is NO.
*/
@property (nonatomic, assign) BOOL debug;
/**
* Unavailable. Use `+sharedSDK` to retrieve the shared Fabric instance.
*/
- (id)init FAB_UNAVAILABLE("Use +sharedSDK to retrieve the shared Fabric instance.");
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,6 @@
framework module Fabric {
umbrella header "Fabric.h"
export *
module * { export * }
}

View File

@@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>BuildMachineOSBuild</key>
<string>14F1021</string>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>Fabric</string>
<key>CFBundleIdentifier</key>
<string>io.fabric.sdk.mac</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>Fabric</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.6.7</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleSupportedPlatforms</key>
<array>
<string>MacOSX</string>
</array>
<key>CFBundleVersion</key>
<string>53</string>
<key>DTCompiler</key>
<string>com.apple.compilers.llvm.clang.1_0</string>
<key>DTPlatformBuild</key>
<string>7C1002</string>
<key>DTPlatformVersion</key>
<string>GM</string>
<key>DTSDKBuild</key>
<string>15C43</string>
<key>DTSDKName</key>
<string>macosx10.11</string>
<key>DTXcode</key>
<string>0721</string>
<key>DTXcodeBuild</key>
<string>7C1002</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2015 Twitter. All rights reserved.</string>
<key>UIDeviceFamily</key>
<array>
<integer>3</integer>
<integer>2</integer>
<integer>1</integer>
<integer>4</integer>
</array>
</dict>
</plist>

View File

@@ -0,0 +1 @@
A

View File

@@ -0,0 +1,28 @@
#!/bin/sh
# run
#
# Copyright (c) 2015 Crashlytics. All rights reserved.
# Figure out where we're being called from
DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
# Quote path in case of spaces or special chars
DIR="\"${DIR}"
PATH_SEP="/"
VALIDATE_COMMAND="uploadDSYM\" $@ validate run-script"
UPLOAD_COMMAND="uploadDSYM\" $@ run-script"
# Ensure params are as expected, run in sync mode to validate
eval $DIR$PATH_SEP$VALIDATE_COMMAND
return_code=$?
if [[ $return_code != 0 ]]; then
exit $return_code
fi
# Verification passed, upload dSYM in background to prevent Xcode from waiting
# Note: Validation is performed again before upload.
# Output can still be found in Console.app
eval $DIR$PATH_SEP$UPLOAD_COMMAND > /dev/null 2>&1 &

Binary file not shown.

1
platform-ios/External/Pearl vendored Submodule

Binary file not shown.

View File

@@ -0,0 +1,31 @@
//
// ANSCompatibility.h
// AnswersKit
//
// Copyright (c) 2015 Crashlytics, Inc. All rights reserved.
//
#pragma once
#if !__has_feature(nullability)
#define nonnull
#define nullable
#define _Nullable
#define _Nonnull
#endif
#ifndef NS_ASSUME_NONNULL_BEGIN
#define NS_ASSUME_NONNULL_BEGIN
#endif
#ifndef NS_ASSUME_NONNULL_END
#define NS_ASSUME_NONNULL_END
#endif
#if __has_feature(objc_generics)
#define ANS_GENERIC_NSARRAY(type) NSArray<type>
#define ANS_GENERIC_NSDICTIONARY(key_type,object_key) NSDictionary<key_type, object_key>
#else
#define ANS_GENERIC_NSARRAY(type) NSArray
#define ANS_GENERIC_NSDICTIONARY(key_type,object_key) NSDictionary
#endif

View File

@@ -0,0 +1,210 @@
//
// Answers.h
// Crashlytics
//
// Copyright (c) 2015 Crashlytics, Inc. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "ANSCompatibility.h"
NS_ASSUME_NONNULL_BEGIN
/**
* This class exposes the Answers Events API, allowing you to track key
* user user actions and metrics in your app.
*/
@interface Answers : NSObject
/**
* Log a Sign Up event to see users signing up for your app in real-time, understand how
* many users are signing up with different methods and their success rate signing up.
*
* @param signUpMethodOrNil The method by which a user logged in, e.g. Twitter or Digits.
* @param signUpSucceededOrNil The ultimate success or failure of the login
* @param customAttributesOrNil A dictionary of custom attributes to associate with this event.
*/
+ (void)logSignUpWithMethod:(nullable NSString *)signUpMethodOrNil
success:(nullable NSNumber *)signUpSucceededOrNil
customAttributes:(nullable ANS_GENERIC_NSDICTIONARY(NSString *, id) *)customAttributesOrNil;
/**
* Log an Log In event to see users logging into your app in real-time, understand how many
* users are logging in with different methods and their success rate logging into your app.
*
* @param loginMethodOrNil The method by which a user logged in, e.g. email, Twitter or Digits.
* @param loginSucceededOrNil The ultimate success or failure of the login
* @param customAttributesOrNil A dictionary of custom attributes to associate with this event.
*/
+ (void)logLoginWithMethod:(nullable NSString *)loginMethodOrNil
success:(nullable NSNumber *)loginSucceededOrNil
customAttributes:(nullable ANS_GENERIC_NSDICTIONARY(NSString *, id) *)customAttributesOrNil;
/**
* Log a Share event to see users sharing from your app in real-time, letting you
* understand what content they're sharing from the type or genre down to the specific id.
*
* @param shareMethodOrNil The method by which a user shared, e.g. email, Twitter, SMS.
* @param contentNameOrNil The human readable name for this piece of content.
* @param contentTypeOrNil The type of content shared.
* @param contentIdOrNil The unique identifier for this piece of content. Useful for finding the top shared item.
* @param customAttributesOrNil A dictionary of custom attributes to associate with this event.
*/
+ (void)logShareWithMethod:(nullable NSString *)shareMethodOrNil
contentName:(nullable NSString *)contentNameOrNil
contentType:(nullable NSString *)contentTypeOrNil
contentId:(nullable NSString *)contentIdOrNil
customAttributes:(nullable ANS_GENERIC_NSDICTIONARY(NSString *, id) *)customAttributesOrNil;
/**
* Log an Invite Event to track how users are inviting other users into
* your application.
*
* @param inviteMethodOrNil The method of invitation, e.g. GameCenter, Twitter, email.
* @param customAttributesOrNil A dictionary of custom attributes to associate with this event.
*/
+ (void)logInviteWithMethod:(nullable NSString *)inviteMethodOrNil
customAttributes:(nullable ANS_GENERIC_NSDICTIONARY(NSString *, id) *)customAttributesOrNil;
/**
* Log a Purchase event to see your revenue in real-time, understand how many users are making purchases, see which
* items are most popular, and track plenty of other important purchase-related metrics.
*
* @param itemPriceOrNil The purchased item's price.
* @param currencyOrNil The ISO4217 currency code. Example: USD
* @param purchaseSucceededOrNil Was the purchase succesful or unsuccesful
* @param itemNameOrNil The human-readable form of the item's name. Example:
* @param itemTypeOrNil The type, or genre of the item. Example: Song
* @param itemIdOrNil The machine-readable, unique item identifier Example: SKU
* @param customAttributesOrNil A dictionary of custom attributes to associate with this purchase.
*/
+ (void)logPurchaseWithPrice:(nullable NSDecimalNumber *)itemPriceOrNil
currency:(nullable NSString *)currencyOrNil
success:(nullable NSNumber *)purchaseSucceededOrNil
itemName:(nullable NSString *)itemNameOrNil
itemType:(nullable NSString *)itemTypeOrNil
itemId:(nullable NSString *)itemIdOrNil
customAttributes:(nullable ANS_GENERIC_NSDICTIONARY(NSString *, id) *)customAttributesOrNil;
/**
* Log a Level Start Event to track where users are in your game.
*
* @param levelNameOrNil The level name
* @param customAttributesOrNil A dictionary of custom attributes to associate with this level start event.
*/
+ (void)logLevelStart:(nullable NSString *)levelNameOrNil
customAttributes:(nullable ANS_GENERIC_NSDICTIONARY(NSString *, id) *)customAttributesOrNil;
/**
* Log a Level End event to track how users are completing levels in your game.
*
* @param levelNameOrNil The name of the level completed, E.G. "1" or "Training"
* @param scoreOrNil The score the user completed the level with.
* @param levelCompletedSuccesfullyOrNil A boolean representing whether or not the level was completed succesfully.
* @param customAttributesOrNil A dictionary of custom attributes to associate with this event.
*/
+ (void)logLevelEnd:(nullable NSString *)levelNameOrNil
score:(nullable NSNumber *)scoreOrNil
success:(nullable NSNumber *)levelCompletedSuccesfullyOrNil
customAttributes:(nullable ANS_GENERIC_NSDICTIONARY(NSString *, id) *)customAttributesOrNil;
/**
* Log an Add to Cart event to see users adding items to a shopping cart in real-time, understand how
* many users start the purchase flow, see which items are most popular, and track plenty of other important
* purchase-related metrics.
*
* @param itemPriceOrNil The purchased item's price.
* @param currencyOrNil The ISO4217 currency code. Example: USD
* @param itemNameOrNil The human-readable form of the item's name. Example:
* @param itemTypeOrNil The type, or genre of the item. Example: Song
* @param itemIdOrNil The machine-readable, unique item identifier Example: SKU
* @param customAttributesOrNil A dictionary of custom attributes to associate with this event.
*/
+ (void)logAddToCartWithPrice:(nullable NSDecimalNumber *)itemPriceOrNil
currency:(nullable NSString *)currencyOrNil
itemName:(nullable NSString *)itemNameOrNil
itemType:(nullable NSString *)itemTypeOrNil
itemId:(nullable NSString *)itemIdOrNil
customAttributes:(nullable ANS_GENERIC_NSDICTIONARY(NSString *, id) *)customAttributesOrNil;
/**
* Log a Start Checkout event to see users moving through the purchase funnel in real-time, understand how many
* users are doing this and how much they're spending per checkout, and see how it related to other important
* purchase-related metrics.
*
* @param totalPriceOrNil The total price of the cart.
* @param currencyOrNil The ISO4217 currency code. Example: USD
* @param itemCountOrNil The number of items in the cart.
* @param customAttributesOrNil A dictionary of custom attributes to associate with this event.
*/
+ (void)logStartCheckoutWithPrice:(nullable NSDecimalNumber *)totalPriceOrNil
currency:(nullable NSString *)currencyOrNil
itemCount:(nullable NSNumber *)itemCountOrNil
customAttributes:(nullable ANS_GENERIC_NSDICTIONARY(NSString *, id) *)customAttributesOrNil;
/**
* Log a Rating event to see users rating content within your app in real-time and understand what
* content is most engaging, from the type or genre down to the specific id.
*
* @param ratingOrNil The integer rating given by the user.
* @param contentNameOrNil The human readable name for this piece of content.
* @param contentTypeOrNil The type of content shared.
* @param contentIdOrNil The unique identifier for this piece of content. Useful for finding the top shared item.
* @param customAttributesOrNil A dictionary of custom attributes to associate with this event.
*/
+ (void)logRating:(nullable NSNumber *)ratingOrNil
contentName:(nullable NSString *)contentNameOrNil
contentType:(nullable NSString *)contentTypeOrNil
contentId:(nullable NSString *)contentIdOrNil
customAttributes:(nullable ANS_GENERIC_NSDICTIONARY(NSString *, id) *)customAttributesOrNil;
/**
* Log a Content View event to see users viewing content within your app in real-time and
* understand what content is most engaging, from the type or genre down to the specific id.
*
* @param contentNameOrNil The human readable name for this piece of content.
* @param contentTypeOrNil The type of content shared.
* @param contentIdOrNil The unique identifier for this piece of content. Useful for finding the top shared item.
* @param customAttributesOrNil A dictionary of custom attributes to associate with this event.
*/
+ (void)logContentViewWithName:(nullable NSString *)contentNameOrNil
contentType:(nullable NSString *)contentTypeOrNil
contentId:(nullable NSString *)contentIdOrNil
customAttributes:(nullable ANS_GENERIC_NSDICTIONARY(NSString *, id) *)customAttributesOrNil;
/**
* Log a Search event allows you to see users searching within your app in real-time and understand
* exactly what they're searching for.
*
* @param queryOrNil The user's query.
* @param customAttributesOrNil A dictionary of custom attributes to associate with this event.
*/
+ (void)logSearchWithQuery:(nullable NSString *)queryOrNil
customAttributes:(nullable ANS_GENERIC_NSDICTIONARY(NSString *, id) *)customAttributesOrNil;
/**
* Log a Custom Event to see user actions that are uniquely important for your app in real-time, to see how often
* they're performing these actions with breakdowns by different categories you add. Use a human-readable name for
* the name of the event, since this is how the event will appear in Answers.
*
* @param eventName The human-readable name for the event.
* @param customAttributesOrNil A dictionary of custom attributes to associate with this event. Attribute keys
* must be <code>NSString</code> and and values must be <code>NSNumber</code> or <code>NSString</code>.
* @discussion How we treat <code>NSNumbers</code>:
* We will provide information about the distribution of values over time.
*
* How we treat <code>NSStrings</code>:
* NSStrings are used as categorical data, allowing comparison across different category values.
* Strings are limited to a maximum length of 100 characters, attributes over this length will be
* truncated.
*
* When tracking the Tweet views to better understand user engagement, sending the tweet's length
* and the type of media present in the tweet allows you to track how tweet length and the type of media influence
* engagement.
*/
+ (void)logCustomEventWithName:(NSString *)eventName
customAttributes:(nullable ANS_GENERIC_NSDICTIONARY(NSString *, id) *)customAttributesOrNil;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,33 @@
//
// CLSAttributes.h
// Crashlytics
//
// Copyright (c) 2015 Crashlytics, Inc. All rights reserved.
//
#pragma once
#define CLS_DEPRECATED(x) __attribute__ ((deprecated(x)))
#if !__has_feature(nullability)
#define nonnull
#define nullable
#define _Nullable
#define _Nonnull
#endif
#ifndef NS_ASSUME_NONNULL_BEGIN
#define NS_ASSUME_NONNULL_BEGIN
#endif
#ifndef NS_ASSUME_NONNULL_END
#define NS_ASSUME_NONNULL_END
#endif
#if __has_feature(objc_generics)
#define CLS_GENERIC_NSARRAY(type) NSArray<type>
#define CLS_GENERIC_NSDICTIONARY(key_type,object_key) NSDictionary<key_type, object_key>
#else
#define CLS_GENERIC_NSARRAY(type) NSArray
#define CLS_GENERIC_NSDICTIONARY(key_type,object_key) NSDictionary
#endif

View File

@@ -0,0 +1,64 @@
//
// CLSLogging.h
// Crashlytics
//
// Copyright (c) 2015 Crashlytics, Inc. All rights reserved.
//
#ifdef __OBJC__
#import "CLSAttributes.h"
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
#endif
/**
*
* The CLS_LOG macro provides as easy way to gather more information in your log messages that are
* sent with your crash data. CLS_LOG prepends your custom log message with the function name and
* line number where the macro was used. If your app was built with the DEBUG preprocessor macro
* defined CLS_LOG uses the CLSNSLog function which forwards your log message to NSLog and CLSLog.
* If the DEBUG preprocessor macro is not defined CLS_LOG uses CLSLog only.
*
* Example output:
* -[AppDelegate login:] line 134 $ login start
*
* If you would like to change this macro, create a new header file, unset our define and then define
* your own version. Make sure this new header file is imported after the Crashlytics header file.
*
* #undef CLS_LOG
* #define CLS_LOG(__FORMAT__, ...) CLSNSLog...
*
**/
#ifdef __OBJC__
#ifdef DEBUG
#define CLS_LOG(__FORMAT__, ...) CLSNSLog((@"%s line %d $ " __FORMAT__), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__)
#else
#define CLS_LOG(__FORMAT__, ...) CLSLog((@"%s line %d $ " __FORMAT__), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__)
#endif
#endif
/**
*
* Add logging that will be sent with your crash data. This logging will not show up in the system.log
* and will only be visible in your Crashlytics dashboard.
*
**/
#ifdef __OBJC__
OBJC_EXTERN void CLSLog(NSString *format, ...) NS_FORMAT_FUNCTION(1,2);
OBJC_EXTERN void CLSLogv(NSString *format, va_list ap) NS_FORMAT_FUNCTION(1,0);
/**
*
* Add logging that will be sent with your crash data. This logging will show up in the system.log
* and your Crashlytics dashboard. It is not recommended for Release builds.
*
**/
OBJC_EXTERN void CLSNSLog(NSString *format, ...) NS_FORMAT_FUNCTION(1,2);
OBJC_EXTERN void CLSNSLogv(NSString *format, va_list ap) NS_FORMAT_FUNCTION(1,0);
NS_ASSUME_NONNULL_END
#endif

View File

@@ -0,0 +1,103 @@
//
// CLSReport.h
// Crashlytics
//
// Copyright (c) 2015 Crashlytics, Inc. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "CLSAttributes.h"
NS_ASSUME_NONNULL_BEGIN
/**
* The CLSCrashReport protocol is deprecated. See the CLSReport class and the CrashyticsDelegate changes for details.
**/
@protocol CLSCrashReport <NSObject>
@property (nonatomic, copy, readonly) NSString *identifier;
@property (nonatomic, copy, readonly) NSDictionary *customKeys;
@property (nonatomic, copy, readonly) NSString *bundleVersion;
@property (nonatomic, copy, readonly) NSString *bundleShortVersionString;
@property (nonatomic, copy, readonly) NSDate *crashedOnDate;
@property (nonatomic, copy, readonly) NSString *OSVersion;
@property (nonatomic, copy, readonly) NSString *OSBuildVersion;
@end
/**
* The CLSReport exposes an interface to the phsyical report that Crashlytics has created. You can
* use this class to get information about the event, and can also set some values after the
* event has occured.
**/
@interface CLSReport : NSObject <CLSCrashReport>
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;
/**
* Returns the session identifier for the report.
**/
@property (nonatomic, copy, readonly) NSString *identifier;
/**
* Returns the custom key value data for the report.
**/
@property (nonatomic, copy, readonly) NSDictionary *customKeys;
/**
* Returns the CFBundleVersion of the application that generated the report.
**/
@property (nonatomic, copy, readonly) NSString *bundleVersion;
/**
* Returns the CFBundleShortVersionString of the application that generated the report.
**/
@property (nonatomic, copy, readonly) NSString *bundleShortVersionString;
/**
* Returns the date that the report was created.
**/
@property (nonatomic, copy, readonly) NSDate *dateCreated;
/**
* Returns the os version that the application crashed on.
**/
@property (nonatomic, copy, readonly) NSString *OSVersion;
/**
* Returns the os build version that the application crashed on.
**/
@property (nonatomic, copy, readonly) NSString *OSBuildVersion;
/**
* Returns YES if the report contains any crash information, otherwise returns NO.
**/
@property (nonatomic, assign, readonly) BOOL isCrash;
/**
* You can use this method to set, after the event, additional custom keys. The rules
* and semantics for this method are the same as those documented in Crashlytics.h. Be aware
* that the maximum size and count of custom keys is still enforced, and you can overwrite keys
* and/or cause excess keys to be deleted by using this method.
**/
- (void)setObjectValue:(nullable id)value forKey:(NSString *)key;
/**
* Record an application-specific user identifier. See Crashlytics.h for details.
**/
@property (nonatomic, copy, nullable) NSString * userIdentifier;
/**
* Record a user name. See Crashlytics.h for details.
**/
@property (nonatomic, copy, nullable) NSString * userName;
/**
* Record a user email. See Crashlytics.h for details.
**/
@property (nonatomic, copy, nullable) NSString * userEmail;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,38 @@
//
// CLSStackFrame.h
// Crashlytics
//
// Copyright 2015 Crashlytics, Inc. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "CLSAttributes.h"
NS_ASSUME_NONNULL_BEGIN
/**
*
* This class is used in conjunction with -[Crashlytics recordCustomExceptionName:reason:frameArray:] to
* record information about non-ObjC/C++ exceptions. All information included here will be displayed
* in the Crashlytics UI, and can influence crash grouping. Be particularly careful with the use of the
* address property. If set, Crashlytics will attempt symbolication and could overwrite other properities
* in the process.
*
**/
@interface CLSStackFrame : NSObject
+ (instancetype)stackFrame;
+ (instancetype)stackFrameWithAddress:(NSUInteger)address;
+ (instancetype)stackFrameWithSymbol:(NSString *)symbol;
@property (nonatomic, copy, nullable) NSString *symbol;
@property (nonatomic, copy, nullable) NSString *rawSymbol;
@property (nonatomic, copy, nullable) NSString *library;
@property (nonatomic, copy, nullable) NSString *fileName;
@property (nonatomic, assign) uint32_t lineNumber;
@property (nonatomic, assign) uint64_t offset;
@property (nonatomic, assign) uint64_t address;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,266 @@
//
// Crashlytics.h
// Crashlytics
//
// Copyright (c) 2015 Crashlytics, Inc. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "CLSAttributes.h"
#import "CLSLogging.h"
#import "CLSReport.h"
#import "CLSStackFrame.h"
#import "Answers.h"
NS_ASSUME_NONNULL_BEGIN
@protocol CrashlyticsDelegate;
/**
* Crashlytics. Handles configuration and initialization of Crashlytics.
*
* Note: The Crashlytics class cannot be subclassed. If this is causing you pain for
* testing, we suggest using either a wrapper class or a protocol extension.
*/
@interface Crashlytics : NSObject
@property (nonatomic, readonly, copy) NSString *APIKey;
@property (nonatomic, readonly, copy) NSString *version;
@property (nonatomic, assign) BOOL debugMode;
/**
*
* The delegate can be used to influence decisions on reporting and behavior, as well as reacting
* to previous crashes.
*
* Make certain that the delegate is setup before starting Crashlytics with startWithAPIKey:... or
* via +[Fabric with:...]. Failure to do will result in missing any delegate callbacks that occur
* synchronously during start.
*
**/
@property (nonatomic, assign, nullable) id <CrashlyticsDelegate> delegate;
/**
* The recommended way to install Crashlytics into your application is to place a call to +startWithAPIKey:
* in your -application:didFinishLaunchingWithOptions: or -applicationDidFinishLaunching:
* method.
*
* Note: Starting with 3.0, the submission process has been significantly improved. The delay parameter
* is no longer required to throttle submissions on launch, performance will be great without it.
*
* @param apiKey The Crashlytics API Key for this app
*
* @return The singleton Crashlytics instance
*/
+ (Crashlytics *)startWithAPIKey:(NSString *)apiKey;
+ (Crashlytics *)startWithAPIKey:(NSString *)apiKey afterDelay:(NSTimeInterval)delay CLS_DEPRECATED("Crashlytics no longer needs or uses the delay parameter. Please use +startWithAPIKey: instead.");
/**
* If you need the functionality provided by the CrashlyticsDelegate protocol, you can use
* these convenience methods to activate the framework and set the delegate in one call.
*
* @param apiKey The Crashlytics API Key for this app
* @param delegate A delegate object which conforms to CrashlyticsDelegate.
*
* @return The singleton Crashlytics instance
*/
+ (Crashlytics *)startWithAPIKey:(NSString *)apiKey delegate:(nullable id<CrashlyticsDelegate>)delegate;
+ (Crashlytics *)startWithAPIKey:(NSString *)apiKey delegate:(nullable id<CrashlyticsDelegate>)delegate afterDelay:(NSTimeInterval)delay CLS_DEPRECATED("Crashlytics no longer needs or uses the delay parameter. Please use +startWithAPIKey:delegate: instead.");
/**
* Access the singleton Crashlytics instance.
*
* @return The singleton Crashlytics instance
*/
+ (Crashlytics *)sharedInstance;
/**
* The easiest way to cause a crash - great for testing!
*/
- (void)crash;
/**
* The easiest way to cause a crash with an exception - great for testing.
*/
- (void)throwException;
/**
* Specify a user identifier which will be visible in the Crashlytics UI.
*
* Many of our customers have requested the ability to tie crashes to specific end-users of their
* application in order to facilitate responses to support requests or permit the ability to reach
* out for more information. We allow you to specify up to three separate values for display within
* the Crashlytics UI - but please be mindful of your end-user's privacy.
*
* We recommend specifying a user identifier - an arbitrary string that ties an end-user to a record
* in your system. This could be a database id, hash, or other value that is meaningless to a
* third-party observer but can be indexed and queried by you.
*
* Optionally, you may also specify the end-user's name or username, as well as email address if you
* do not have a system that works well with obscured identifiers.
*
* Pursuant to our EULA, this data is transferred securely throughout our system and we will not
* disseminate end-user data unless required to by law. That said, if you choose to provide end-user
* contact information, we strongly recommend that you disclose this in your application's privacy
* policy. Data privacy is of our utmost concern.
*
* @param identifier An arbitrary user identifier string which ties an end-user to a record in your system.
*/
- (void)setUserIdentifier:(nullable NSString *)identifier;
/**
* Specify a user name which will be visible in the Crashlytics UI.
* Please be mindful of your end-user's privacy and see if setUserIdentifier: can fulfil your needs.
* @see setUserIdentifier:
*
* @param name An end user's name.
*/
- (void)setUserName:(nullable NSString *)name;
/**
* Specify a user email which will be visible in the Crashlytics UI.
* Please be mindful of your end-user's privacy and see if setUserIdentifier: can fulfil your needs.
*
* @see setUserIdentifier:
*
* @param email An end user's email address.
*/
- (void)setUserEmail:(nullable NSString *)email;
+ (void)setUserIdentifier:(nullable NSString *)identifier CLS_DEPRECATED("Please access this method via +sharedInstance");
+ (void)setUserName:(nullable NSString *)name CLS_DEPRECATED("Please access this method via +sharedInstance");
+ (void)setUserEmail:(nullable NSString *)email CLS_DEPRECATED("Please access this method via +sharedInstance");
/**
* Set a value for a for a key to be associated with your crash data which will be visible in the Crashlytics UI.
* When setting an object value, the object is converted to a string. This is typically done by calling
* -[NSObject description].
*
* @param value The object to be associated with the key
* @param key The key with which to associate the value
*/
- (void)setObjectValue:(nullable id)value forKey:(NSString *)key;
/**
* Set an int value for a key to be associated with your crash data which will be visible in the Crashlytics UI.
*
* @param value The integer value to be set
* @param key The key with which to associate the value
*/
- (void)setIntValue:(int)value forKey:(NSString *)key;
/**
* Set an BOOL value for a key to be associated with your crash data which will be visible in the Crashlytics UI.
*
* @param value The BOOL value to be set
* @param key The key with which to associate the value
*/
- (void)setBoolValue:(BOOL)value forKey:(NSString *)key;
/**
* Set an float value for a key to be associated with your crash data which will be visible in the Crashlytics UI.
*
* @param value The float value to be set
* @param key The key with which to associate the value
*/
- (void)setFloatValue:(float)value forKey:(NSString *)key;
+ (void)setObjectValue:(nullable id)value forKey:(NSString *)key CLS_DEPRECATED("Please access this method via +sharedInstance");
+ (void)setIntValue:(int)value forKey:(NSString *)key CLS_DEPRECATED("Please access this method via +sharedInstance");
+ (void)setBoolValue:(BOOL)value forKey:(NSString *)key CLS_DEPRECATED("Please access this method via +sharedInstance");
+ (void)setFloatValue:(float)value forKey:(NSString *)key CLS_DEPRECATED("Please access this method via +sharedInstance");
/**
* This method can be used to record a single exception structure in a report. This is particularly useful
* when your code interacts with non-native languages like Lua, C#, or Javascript. This call can be
* expensive and should only be used shortly before process termination. This API is not intended be to used
* to log NSException objects. All safely-reportable NSExceptions are automatically captured by
* Crashlytics.
*
* @param name The name of the custom exception
* @param reason The reason this exception occured
* @param frameArray An array of CLSStackFrame objects
*/
- (void)recordCustomExceptionName:(NSString *)name reason:(nullable NSString *)reason frameArray:(CLS_GENERIC_NSARRAY(CLSStackFrame *) *)frameArray;
/**
*
* This allows you to record a non-fatal event, described by an NSError object. These events will be grouped and
* displayed similarly to crashes. Keep in mind that this method can be expensive. Also, the total number of
* NSErrors that can be recorded during your app's life-cycle is limited by a fixed-size circular buffer. If the
* buffer is overrun, the oldest data is dropped. Errors are relayed to Crashlytics on a subsequent launch
* of your application.
*
* You can also use the -recordError:withAdditionalUserInfo: to include additional context not represented
* by the NSError instance itself.
*
**/
- (void)recordError:(NSError *)error;
- (void)recordError:(NSError *)error withAdditionalUserInfo:(nullable CLS_GENERIC_NSDICTIONARY(NSString *, id) *)userInfo;
- (void)logEvent:(NSString *)eventName CLS_DEPRECATED("Please refer to Answers +logCustomEventWithName:");
- (void)logEvent:(NSString *)eventName attributes:(nullable NSDictionary *) attributes CLS_DEPRECATED("Please refer to Answers +logCustomEventWithName:");
+ (void)logEvent:(NSString *)eventName CLS_DEPRECATED("Please refer to Answers +logCustomEventWithName:");
+ (void)logEvent:(NSString *)eventName attributes:(nullable NSDictionary *) attributes CLS_DEPRECATED("Please refer to Answers +logCustomEventWithName:");
@end
/**
*
* The CrashlyticsDelegate protocol provides a mechanism for your application to take
* action on events that occur in the Crashlytics crash reporting system. You can make
* use of these calls by assigning an object to the Crashlytics' delegate property directly,
* or through the convenience +startWithAPIKey:delegate: method.
*
*/
@protocol CrashlyticsDelegate <NSObject>
@optional
- (void)crashlyticsDidDetectCrashDuringPreviousExecution:(Crashlytics *)crashlytics CLS_DEPRECATED("Please refer to -crashlyticsDidDetectReportForLastExecution:");
- (void)crashlytics:(Crashlytics *)crashlytics didDetectCrashDuringPreviousExecution:(id <CLSCrashReport>)crash CLS_DEPRECATED("Please refer to -crashlyticsDidDetectReportForLastExecution:");
/**
*
* Called when a Crashlytics instance has determined that the last execution of the
* application ended in a crash. This is called synchronously on Crashlytics
* initialization. Your delegate must invoke the completionHandler, but does not need to do so
* synchronously, or even on the main thread. Invoking completionHandler with NO will cause the
* detected report to be deleted and not submitted to Crashlytics. This is useful for
* implementing permission prompts, or other more-complex forms of logic around submitting crashes.
*
* @warning Failure to invoke the completionHandler will prevent submissions from being reported. Watch out.
*
* @warning Just implementing this delegate method will disable all forms of synchronous report submission. This can
* impact the reliability of reporting crashes very early in application launch.
*
* @param report The CLSReport object representing the last detected crash
* @param completionHandler The completion handler to call when your logic has completed.
*
*/
- (void)crashlyticsDidDetectReportForLastExecution:(CLSReport *)report completionHandler:(void (^)(BOOL submit))completionHandler;
/**
* If your app is running on an OS that supports it (OS X 10.9+, iOS 7.0+), Crashlytics will submit
* most reports using out-of-process background networking operations. This results in a significant
* improvement in reliability of reporting, as well as power and performance wins for your users.
* If you don't want this functionality, you can disable by returning NO from this method.
*
* @warning Background submission is not supported for extensions on iOS or OS X.
*
* @param crashlytics The Crashlytics singleton instance
*
* @return Return NO if you don't want out-of-process background network operations.
*
*/
- (BOOL)crashlyticsCanUseBackgroundSessions:(Crashlytics *)crashlytics;
@end
/**
* `CrashlyticsKit` can be used as a parameter to `[Fabric with:@[CrashlyticsKit]];` in Objective-C. In Swift, use Crashlytics.sharedInstance()
*/
#define CrashlyticsKit [Crashlytics sharedInstance]
NS_ASSUME_NONNULL_END

Binary file not shown.

View File

@@ -0,0 +1,14 @@
framework module Crashlytics {
header "Crashlytics.h"
header "Answers.h"
header "ANSCompatibility.h"
header "CLSLogging.h"
header "CLSReport.h"
header "CLSStackFrame.h"
header "CLSAttributes.h"
export *
link "z"
link "c++"
}

View File

@@ -0,0 +1,28 @@
#!/bin/sh
# run
#
# Copyright (c) 2015 Crashlytics. All rights reserved.
# Figure out where we're being called from
DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
# Quote path in case of spaces or special chars
DIR="\"${DIR}"
PATH_SEP="/"
VALIDATE_COMMAND="uploadDSYM\" $@ validate run-script"
UPLOAD_COMMAND="uploadDSYM\" $@ run-script"
# Ensure params are as expected, run in sync mode to validate
eval $DIR$PATH_SEP$VALIDATE_COMMAND
return_code=$?
if [[ $return_code != 0 ]]; then
exit $return_code
fi
# Verification passed, upload dSYM in background to prevent Xcode from waiting
# Note: Validation is performed again before upload.
# Output can still be found in Console.app
eval $DIR$PATH_SEP$UPLOAD_COMMAND > /dev/null 2>&1 &

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,60 @@
//
// FABAttributes.h
// Fabric
//
// Copyright (C) 2015 Twitter, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#pragma once
#define FAB_UNAVAILABLE(x) __attribute__((unavailable(x)))
#if __has_feature(nullability)
#define fab_nullable nullable
#define fab_nonnull nonnull
#define fab_null_unspecified null_unspecified
#define fab_null_resettable null_resettable
#define __fab_nullable __nullable
#define __fab_nonnull __nonnull
#define __fab_null_unspecified __null_unspecified
#else
#define fab_nullable
#define fab_nonnull
#define fab_null_unspecified
#define fab_null_resettable
#define __fab_nullable
#define __fab_nonnull
#define __fab_null_unspecified
#endif
#ifndef NS_ASSUME_NONNULL_BEGIN
#define NS_ASSUME_NONNULL_BEGIN
#endif
#ifndef NS_ASSUME_NONNULL_END
#define NS_ASSUME_NONNULL_END
#endif
/**
* The following macros are defined here to provide
* backwards compatability. If you are still using
* them you should migrate to the new versions that
* are defined above.
*/
#define FAB_NONNULL __fab_nonnull
#define FAB_NULLABLE __fab_nullable
#define FAB_START_NONNULL NS_ASSUME_NONNULL_BEGIN
#define FAB_END_NONNULL NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,77 @@
//
// Fabric.h
// Fabric
//
// Copyright (C) 2015 Twitter, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#import <Foundation/Foundation.h>
#import "FABAttributes.h"
NS_ASSUME_NONNULL_BEGIN
#if TARGET_OS_IPHONE
#if __IPHONE_OS_VERSION_MIN_REQUIRED < 60000
#error "Fabric's minimum iOS version is 6.0"
#endif
#else
#if __MAC_OS_X_VERSION_MIN_REQUIRED < 1070
#error "Fabric's minimum OS X version is 10.7"
#endif
#endif
/**
* Fabric Base. Coordinates configuration and starts all provided kits.
*/
@interface Fabric : NSObject
/**
* Initialize Fabric and all provided kits. Call this method within your App Delegate's `application:didFinishLaunchingWithOptions:` and provide the kits you wish to use.
*
* For example, in Objective-C:
*
* `[Fabric with:@[[Crashlytics class], [Twitter class], [Digits class], [MoPub class]]];`
*
* Swift:
*
* `Fabric.with([Crashlytics.self(), Twitter.self(), Digits.self(), MoPub.self()])`
*
* Only the first call to this method is honored. Subsequent calls are no-ops.
*
* @param kitClasses An array of kit Class objects
*
* @return Returns the shared Fabric instance. In most cases this can be ignored.
*/
+ (instancetype)with:(NSArray *)kitClasses;
/**
* Returns the Fabric singleton object.
*/
+ (instancetype)sharedSDK;
/**
* This BOOL enables or disables debug logging, such as kit version information. The default value is NO.
*/
@property (nonatomic, assign) BOOL debug;
/**
* Unavailable. Use `+sharedSDK` to retrieve the shared Fabric instance.
*/
- (id)init FAB_UNAVAILABLE("Use +sharedSDK to retrieve the shared Fabric instance.");
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,57 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>BuildMachineOSBuild</key>
<string>14F1021</string>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>Fabric</string>
<key>CFBundleIdentifier</key>
<string>io.fabric.sdk.ios</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>Fabric</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.6.7</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleSupportedPlatforms</key>
<array>
<string>iPhoneOS</string>
</array>
<key>CFBundleVersion</key>
<string>53</string>
<key>DTCompiler</key>
<string>com.apple.compilers.llvm.clang.1_0</string>
<key>DTPlatformBuild</key>
<string>13C75</string>
<key>DTPlatformName</key>
<string>iphoneos</string>
<key>DTPlatformVersion</key>
<string>9.2</string>
<key>DTSDKBuild</key>
<string>13C75</string>
<key>DTSDKName</key>
<string>iphoneos9.2</string>
<key>DTXcode</key>
<string>0721</string>
<key>DTXcodeBuild</key>
<string>7C1002</string>
<key>MinimumOSVersion</key>
<string>6.0</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2015 Twitter. All rights reserved.</string>
<key>UIDeviceFamily</key>
<array>
<integer>3</integer>
<integer>2</integer>
<integer>1</integer>
<integer>4</integer>
</array>
</dict>
</plist>

View File

@@ -0,0 +1,6 @@
framework module Fabric {
umbrella header "Fabric.h"
export *
module * { export * }
}

View File

@@ -0,0 +1,28 @@
#!/bin/sh
# run
#
# Copyright (c) 2015 Crashlytics. All rights reserved.
# Figure out where we're being called from
DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
# Quote path in case of spaces or special chars
DIR="\"${DIR}"
PATH_SEP="/"
VALIDATE_COMMAND="uploadDSYM\" $@ validate run-script"
UPLOAD_COMMAND="uploadDSYM\" $@ run-script"
# Ensure params are as expected, run in sync mode to validate
eval $DIR$PATH_SEP$VALIDATE_COMMAND
return_code=$?
if [[ $return_code != 0 ]]; then
exit $return_code
fi
# Verification passed, upload dSYM in background to prevent Xcode from waiting
# Note: Validation is performed again before upload.
# Output can still be found in Console.app
eval $DIR$PATH_SEP$UPLOAD_COMMAND > /dev/null 2>&1 &

Binary file not shown.

View File

@@ -0,0 +1 @@
/Applications/Reveal.app/Contents/SharedSupport/iOS-Libraries/Reveal.framework

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:MasterPassword/ObjC/iOS/MasterPassword-iOS.xcodeproj">
</FileRef>
<FileRef
location = "group:MasterPassword/ObjC/Mac/MasterPassword-Mac.xcodeproj">
</FileRef>
</Workspace>

View File

@@ -0,0 +1,161 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDESourceControlProjectFavoriteDictionaryKey</key>
<false/>
<key>IDESourceControlProjectIdentifier</key>
<string>1DC75A27-0030-4493-ACE8-E1D49AB9A549</string>
<key>IDESourceControlProjectName</key>
<string>MasterPassword</string>
<key>IDESourceControlProjectOriginsDictionary</key>
<dict>
<key>1712FC0BC3C9AABD8B7B5376E310E93FBDB3BCFA</key>
<string>git://github.com/lhunath/InAppSettingsKit.git</string>
<key>1AA8C0BE-EEC3-4FBC-A801-8939A1AC093A</key>
<string>git://github.com/Lyndir/love-lyndir.client.git</string>
<key>2A70319CE0F91B35406CA7D970AE7CB4957B0A75</key>
<string>github.com:Lyndir/Lyndir.git</string>
<key>2FE140B36B7D26140DC8D5E5C639DC5900EFCF35</key>
<string>git://github.com/lhunath/uicolor-utilities.git</string>
<key>304AD0F97EA7B4893D91DFB8C3413D4E627B9472</key>
<string>https://github.com/CFKevinRef/KCOrderedAccessorFix.git</string>
<key>3E67FB08419C920516AAC3B00DAAF23073B8CF77</key>
<string>git://github.com/lhunath/RHStatusItemView.git</string>
<key>3ED8592497DB6A564366943C9AAD5A46341B5076</key>
<string>https://github.com/dreamwieber/AttributedMarkdown.git</string>
<key>4DDCFFD91B41F00326AD14553BD66CFD366ABD91</key>
<string>ssh://github.com/Lyndir/Pearl.git</string>
<key>8A15A8EA0B3D0B497C4883425BC74DF995224BB3</key>
<string>git://github.com/jonmarimba/jrswizzle.git</string>
<key>E47DEC29CB0D0FDE3560EF46E1808FA1C723D657</key>
<string>git://github.com/lhunath/UbiquityStoreManager.git</string>
<key>F788B28042EDBEF29EFE34687DA79A778C2CC260</key>
<string>ssh://github.com/Lyndir/MasterPassword.git</string>
</dict>
<key>IDESourceControlProjectPath</key>
<string>MasterPassword.xcworkspace</string>
<key>IDESourceControlProjectRelativeInstallPathDictionary</key>
<dict>
<key>1712FC0BC3C9AABD8B7B5376E310E93FBDB3BCFA</key>
<string>../External/InAppSettingsKit</string>
<key>1AA8C0BE-EEC3-4FBC-A801-8939A1AC093A</key>
<string>../External/LoveLyndir</string>
<key>2A70319CE0F91B35406CA7D970AE7CB4957B0A75</key>
<string>../..</string>
<key>2FE140B36B7D26140DC8D5E5C639DC5900EFCF35</key>
<string>../External/Pearl/External/uicolor-utilities</string>
<key>304AD0F97EA7B4893D91DFB8C3413D4E627B9472</key>
<string>../External/KCOrderedAccessorFix</string>
<key>3E67FB08419C920516AAC3B00DAAF23073B8CF77</key>
<string>../External/RHStatusItemView</string>
<key>3ED8592497DB6A564366943C9AAD5A46341B5076</key>
<string>../External/AttributedMarkdown</string>
<key>4DDCFFD91B41F00326AD14553BD66CFD366ABD91</key>
<string>../External/Pearl</string>
<key>8A15A8EA0B3D0B497C4883425BC74DF995224BB3</key>
<string>../External/Pearl/External/jrswizzle</string>
<key>E47DEC29CB0D0FDE3560EF46E1808FA1C723D657</key>
<string>../External/UbiquityStoreManager</string>
<key>F788B28042EDBEF29EFE34687DA79A778C2CC260</key>
<string>..</string>
</dict>
<key>IDESourceControlProjectURL</key>
<string>ssh://github.com/Lyndir/MasterPassword.git</string>
<key>IDESourceControlProjectVersion</key>
<integer>111</integer>
<key>IDESourceControlProjectWCCIdentifier</key>
<string>F788B28042EDBEF29EFE34687DA79A778C2CC260</string>
<key>IDESourceControlProjectWCConfigurations</key>
<array>
<dict>
<key>IDESourceControlRepositoryExtensionIdentifierKey</key>
<string>public.vcs.git</string>
<key>IDESourceControlWCCIdentifierKey</key>
<string>2A70319CE0F91B35406CA7D970AE7CB4957B0A75</string>
<key>IDESourceControlWCCName</key>
<string></string>
</dict>
<dict>
<key>IDESourceControlRepositoryExtensionIdentifierKey</key>
<string>public.vcs.git</string>
<key>IDESourceControlWCCIdentifierKey</key>
<string>3ED8592497DB6A564366943C9AAD5A46341B5076</string>
<key>IDESourceControlWCCName</key>
<string>AttributedMarkdown</string>
</dict>
<dict>
<key>IDESourceControlRepositoryExtensionIdentifierKey</key>
<string>public.vcs.git</string>
<key>IDESourceControlWCCIdentifierKey</key>
<string>1712FC0BC3C9AABD8B7B5376E310E93FBDB3BCFA</string>
<key>IDESourceControlWCCName</key>
<string>InAppSettingsKit</string>
</dict>
<dict>
<key>IDESourceControlRepositoryExtensionIdentifierKey</key>
<string>public.vcs.git</string>
<key>IDESourceControlWCCIdentifierKey</key>
<string>8A15A8EA0B3D0B497C4883425BC74DF995224BB3</string>
<key>IDESourceControlWCCName</key>
<string>jrswizzle</string>
</dict>
<dict>
<key>IDESourceControlRepositoryExtensionIdentifierKey</key>
<string>public.vcs.git</string>
<key>IDESourceControlWCCIdentifierKey</key>
<string>304AD0F97EA7B4893D91DFB8C3413D4E627B9472</string>
<key>IDESourceControlWCCName</key>
<string>KCOrderedAccessorFix</string>
</dict>
<dict>
<key>IDESourceControlRepositoryExtensionIdentifierKey</key>
<string>public.vcs.git</string>
<key>IDESourceControlWCCIdentifierKey</key>
<string>1AA8C0BE-EEC3-4FBC-A801-8939A1AC093A</string>
<key>IDESourceControlWCCName</key>
<string>LoveLyndir</string>
</dict>
<dict>
<key>IDESourceControlRepositoryExtensionIdentifierKey</key>
<string>public.vcs.git</string>
<key>IDESourceControlWCCIdentifierKey</key>
<string>F788B28042EDBEF29EFE34687DA79A778C2CC260</string>
<key>IDESourceControlWCCName</key>
<string>MasterPassword</string>
</dict>
<dict>
<key>IDESourceControlRepositoryExtensionIdentifierKey</key>
<string>public.vcs.git</string>
<key>IDESourceControlWCCIdentifierKey</key>
<string>4DDCFFD91B41F00326AD14553BD66CFD366ABD91</string>
<key>IDESourceControlWCCName</key>
<string>Pearl</string>
</dict>
<dict>
<key>IDESourceControlRepositoryExtensionIdentifierKey</key>
<string>public.vcs.git</string>
<key>IDESourceControlWCCIdentifierKey</key>
<string>3E67FB08419C920516AAC3B00DAAF23073B8CF77</string>
<key>IDESourceControlWCCName</key>
<string>RHStatusItemView</string>
</dict>
<dict>
<key>IDESourceControlRepositoryExtensionIdentifierKey</key>
<string>public.vcs.git</string>
<key>IDESourceControlWCCIdentifierKey</key>
<string>E47DEC29CB0D0FDE3560EF46E1808FA1C723D657</string>
<key>IDESourceControlWCCName</key>
<string>UbiquityStoreManager</string>
</dict>
<dict>
<key>IDESourceControlRepositoryExtensionIdentifierKey</key>
<string>public.vcs.git</string>
<key>IDESourceControlWCCIdentifierKey</key>
<string>2FE140B36B7D26140DC8D5E5C639DC5900EFCF35</string>
<key>IDESourceControlWCCName</key>
<string>uicolor-utilities</string>
</dict>
</array>
</dict>
</plist>

View File

@@ -0,0 +1,93 @@
{
"DVTSourceControlWorkspaceBlueprintPrimaryRemoteRepositoryKey" : "F788B28042EDBEF29EFE34687DA79A778C2CC260",
"DVTSourceControlWorkspaceBlueprintWorkingCopyRepositoryLocationsKey" : {
},
"DVTSourceControlWorkspaceBlueprintWorkingCopyStatesKey" : {
"304AD0F97EA7B4893D91DFB8C3413D4E627B9472" : 0,
"2A70319CE0F91B35406CA7D970AE7CB4957B0A75" : 0,
"8A15A8EA0B3D0B497C4883425BC74DF995224BB3" : 0,
"1712FC0BC3C9AABD8B7B5376E310E93FBDB3BCFA" : 0,
"1AA8C0BE-EEC3-4FBC-A801-8939A1AC093A" : 0,
"3E67FB08419C920516AAC3B00DAAF23073B8CF77" : 0,
"3ED8592497DB6A564366943C9AAD5A46341B5076" : 0,
"4DDCFFD91B41F00326AD14553BD66CFD366ABD91" : 0,
"E47DEC29CB0D0FDE3560EF46E1808FA1C723D657" : 0,
"2FE140B36B7D26140DC8D5E5C639DC5900EFCF35" : 0,
"F788B28042EDBEF29EFE34687DA79A778C2CC260" : 0
},
"DVTSourceControlWorkspaceBlueprintIdentifierKey" : "1DC75A27-0030-4493-ACE8-E1D49AB9A549",
"DVTSourceControlWorkspaceBlueprintWorkingCopyPathsKey" : {
"304AD0F97EA7B4893D91DFB8C3413D4E627B9472" : "MasterPassword\/External\/KCOrderedAccessorFix\/",
"2A70319CE0F91B35406CA7D970AE7CB4957B0A75" : "",
"8A15A8EA0B3D0B497C4883425BC74DF995224BB3" : "MasterPassword\/External\/jrswizzle\/",
"1712FC0BC3C9AABD8B7B5376E310E93FBDB3BCFA" : "MasterPassword\/External\/InAppSettingsKit\/",
"1AA8C0BE-EEC3-4FBC-A801-8939A1AC093A" : "MasterPassword\/External\/LoveLyndir",
"3E67FB08419C920516AAC3B00DAAF23073B8CF77" : "MasterPassword\/External\/RHStatusItemView",
"3ED8592497DB6A564366943C9AAD5A46341B5076" : "MasterPassword\/External\/AttributedMarkdown\/",
"4DDCFFD91B41F00326AD14553BD66CFD366ABD91" : "MasterPassword\/External\/Pearl\/",
"E47DEC29CB0D0FDE3560EF46E1808FA1C723D657" : "MasterPassword\/External\/UbiquityStoreManager",
"2FE140B36B7D26140DC8D5E5C639DC5900EFCF35" : "MasterPassword\/External\/uicolor-utilities\/",
"F788B28042EDBEF29EFE34687DA79A778C2CC260" : "MasterPassword\/"
},
"DVTSourceControlWorkspaceBlueprintNameKey" : "MasterPassword",
"DVTSourceControlWorkspaceBlueprintVersion" : 204,
"DVTSourceControlWorkspaceBlueprintRelativePathToProjectKey" : "MasterPassword.xcworkspace",
"DVTSourceControlWorkspaceBlueprintRemoteRepositoriesKey" : [
{
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "git:\/\/github.com\/lhunath\/InAppSettingsKit.git",
"DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "1712FC0BC3C9AABD8B7B5376E310E93FBDB3BCFA"
},
{
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "git:\/\/github.com\/Lyndir\/love-lyndir.client.git",
"DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "1AA8C0BE-EEC3-4FBC-A801-8939A1AC093A"
},
{
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:Lyndir\/Lyndir.git",
"DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "2A70319CE0F91B35406CA7D970AE7CB4957B0A75"
},
{
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "git:\/\/github.com\/lhunath\/uicolor-utilities.git",
"DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "2FE140B36B7D26140DC8D5E5C639DC5900EFCF35"
},
{
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/CFKevinRef\/KCOrderedAccessorFix.git",
"DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "304AD0F97EA7B4893D91DFB8C3413D4E627B9472"
},
{
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "git:\/\/github.com\/lhunath\/RHStatusItemView.git",
"DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "3E67FB08419C920516AAC3B00DAAF23073B8CF77"
},
{
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/dreamwieber\/AttributedMarkdown.git",
"DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "3ED8592497DB6A564366943C9AAD5A46341B5076"
},
{
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "ssh:\/\/github.com\/Lyndir\/Pearl.git",
"DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "4DDCFFD91B41F00326AD14553BD66CFD366ABD91"
},
{
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "git:\/\/github.com\/jonmarimba\/jrswizzle.git",
"DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "8A15A8EA0B3D0B497C4883425BC74DF995224BB3"
},
{
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "git:\/\/github.com\/lhunath\/UbiquityStoreManager.git",
"DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "E47DEC29CB0D0FDE3560EF46E1808FA1C723D657"
},
{
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "ssh:\/\/github.com\/Lyndir\/MasterPassword.git",
"DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "F788B28042EDBEF29EFE34687DA79A778C2CC260"
}
]
}

View File

@@ -0,0 +1,98 @@
/**
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
*
* See the enclosed file LICENSE for license information (LGPLv3). If you did
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
*
* @author Maarten Billemont <lhunath@lyndir.com>
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
*/
//
// MPAlgorithm
//
// Created by Maarten Billemont on 16/07/12.
// Copyright 2012 lhunath (Maarten Billemont). All rights reserved.
//
#import "MPKey.h"
#import "MPStoredSiteEntity.h"
#import "MPGeneratedSiteEntity.h"
#import "MPSiteQuestionEntity.h"
#import "mpw-algorithm.h"
#define MPAlgorithmDefaultVersion MPAlgorithmVersionCurrent
#define MPAlgorithmDefault MPAlgorithmForVersion(MPAlgorithmDefaultVersion)
id<MPAlgorithm> MPAlgorithmForVersion(MPAlgorithmVersion version);
id<MPAlgorithm> MPAlgorithmDefaultForBundleVersion(NSString *bundleVersion);
PearlEnum( MPAttacker,
MPAttacker1, MPAttacker5K, MPAttacker20M, MPAttacker5B );
typedef struct TimeToCrack {
unsigned long long hours;
unsigned long long days;
unsigned long long weeks;
unsigned long long months;
unsigned long long years;
unsigned long long universes;
} TimeToCrack;
NSString *NSStringFromTimeToCrack(TimeToCrack timeToCrack);
@protocol MPAlgorithm<NSObject>
@required
- (MPAlgorithmVersion)version;
- (BOOL)tryMigrateUser:(MPUserEntity *)user inContext:(NSManagedObjectContext *)moc;
- (BOOL)tryMigrateSite:(MPSiteEntity *)site explicit:(BOOL)explicit;
- (NSData *)keyIDForKeyData:(NSData *)keyData;
- (NSData *)keyDataForFullName:(NSString *)fullName withMasterPassword:(NSString *)masterPassword;
- (NSString *)nameOfType:(MPSiteType)type;
- (NSString *)shortNameOfType:(MPSiteType)type;
- (NSString *)classNameOfType:(MPSiteType)type;
- (Class)classOfType:(MPSiteType)type;
- (NSArray *)allTypes;
- (NSArray *)allTypesStartingWith:(MPSiteType)startingType;
- (MPSiteType)nextType:(MPSiteType)type;
- (MPSiteType)previousType:(MPSiteType)type;
- (NSString *)generateLoginForSiteNamed:(NSString *)name usingKey:(MPKey *)key;
- (NSString *)generatePasswordForSiteNamed:(NSString *)name ofType:(MPSiteType)type withCounter:(NSUInteger)counter
usingKey:(MPKey *)key;
- (NSString *)generateAnswerForSiteNamed:(NSString *)name onQuestion:(NSString *)question usingKey:(MPKey *)key;
- (NSString *)generateContentForSiteNamed:(NSString *)name ofType:(MPSiteType)type withCounter:(NSUInteger)counter
variant:(MPSiteVariant)variant context:(NSString *)context usingKey:(MPKey *)key;
- (NSString *)storedLoginForSite:(MPStoredSiteEntity *)site usingKey:(MPKey *)key;
- (NSString *)storedPasswordForSite:(MPStoredSiteEntity *)site usingKey:(MPKey *)key;
- (BOOL)savePassword:(NSString *)clearPassword toSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey;
- (NSString *)resolveLoginForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey;
- (NSString *)resolvePasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey;
- (NSString *)resolveAnswerForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey;
- (NSString *)resolveAnswerForQuestion:(MPSiteQuestionEntity *)question usingKey:(MPKey *)siteKey;
- (void)resolveLoginForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey
result:(void ( ^ )(NSString *result))resultBlock;
- (void)resolvePasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey
result:(void ( ^ )(NSString *result))resultBlock;
- (void)resolveAnswerForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey
result:(void ( ^ )(NSString *result))resultBlock;
- (void)resolveAnswerForQuestion:(MPSiteQuestionEntity *)question usingKey:(MPKey *)siteKey
result:(void ( ^ )(NSString *result))resultBlock;
- (void)importProtectedPassword:(NSString *)protectedPassword protectedByKey:(MPKey *)importKey
intoSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey;
- (void)importClearTextPassword:(NSString *)clearPassword intoSite:(MPSiteEntity *)site
usingKey:(MPKey *)siteKey;
- (NSString *)exportPasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey;
- (BOOL)timeToCrack:(out TimeToCrack *)timeToCrack passwordOfType:(MPSiteType)type byAttacker:(MPAttacker)attacker;
- (BOOL)timeToCrack:(out TimeToCrack *)timeToCrack passwordString:(NSString *)password byAttacker:(MPAttacker)attacker;
@end

View File

@@ -0,0 +1,61 @@
/**
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
*
* See the enclosed file LICENSE for license information (LGPLv3). If you did
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
*
* @author Maarten Billemont <lhunath@lyndir.com>
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
*/
//
// MPAlgorithm
//
// Created by Maarten Billemont on 16/07/12.
// Copyright 2012 lhunath (Maarten Billemont). All rights reserved.
//
#import "MPAlgorithm.h"
id<MPAlgorithm> MPAlgorithmForVersion(MPAlgorithmVersion version) {
static NSMutableDictionary *versionToAlgorithm = nil;
if (!versionToAlgorithm)
versionToAlgorithm = [NSMutableDictionary dictionary];
id<MPAlgorithm> algorithm = versionToAlgorithm[@(version)];
if (!algorithm && (algorithm = (id<MPAlgorithm>)[NSClassFromString( strf( @"MPAlgorithmV%lu", (unsigned long)version ) ) new]))
versionToAlgorithm[@(version)] = algorithm;
return algorithm;
}
id<MPAlgorithm> MPAlgorithmDefaultForBundleVersion(NSString *bundleVersion) {
if (PearlCFBundleVersionCompare( bundleVersion, @"1.3" ) == NSOrderedAscending)
// Pre-1.3
return MPAlgorithmForVersion( 0 );
if (PearlCFBundleVersionCompare( bundleVersion, @"2.1" ) == NSOrderedAscending)
// Pre-2.1
return MPAlgorithmForVersion( 1 );
return MPAlgorithmDefault;
}
NSString *NSStringFromTimeToCrack(TimeToCrack timeToCrack) {
if (timeToCrack.universes > 1)
return strl( @"> age of the universe" );
else if (timeToCrack.years > 1)
return strl( @"%d years", timeToCrack.years );
else if (timeToCrack.months > 1)
return strl( @"%d months", timeToCrack.months );
else if (timeToCrack.weeks > 1)
return strl( @"%d weeks", timeToCrack.weeks );
else if (timeToCrack.days > 1)
return strl( @"%d days", timeToCrack.days );
else if (timeToCrack.hours > 1)
return strl( @"%d hours", timeToCrack.hours );
else
return strl( @"trivial" );
}

View File

@@ -0,0 +1,21 @@
/**
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
*
* See the enclosed file LICENSE for license information (LGPLv3). If you did
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
*
* @author Maarten Billemont <lhunath@lyndir.com>
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
*/
//
// MPAlgorithmV0
//
// Created by Maarten Billemont on 16/07/12.
// Copyright 2012 lhunath (Maarten Billemont). All rights reserved.
//
#import "MPAlgorithm.h"
@interface MPAlgorithmV0 : NSObject<MPAlgorithm>
@end

View File

@@ -0,0 +1,894 @@
/**
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
*
* See the enclosed file LICENSE for license information (LGPLv3). If you did
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
*
* @author Maarten Billemont <lhunath@lyndir.com>
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
*/
//
// MPAlgorithmV0
//
// Created by Maarten Billemont on 16/07/12.
// Copyright 2012 lhunath (Maarten Billemont). All rights reserved.
//
#ifndef trc
#error error
#endif
#import "MPAlgorithmV0.h"
#import "MPEntities.h"
#import "MPAppDelegate_Shared.h"
#import "MPAppDelegate_InApp.h"
#import "mpw-util.h"
#include <openssl/bn.h>
#include <openssl/err.h>
/* An AMD HD 7970 calculates 2495M SHA-1 hashes per second at a cost of ~350$ per GPU */
#define CRACKING_PER_SECOND 2495000000UL
#define CRACKING_PRICE 350
NSOperationQueue *_mpwQueue = nil;
@implementation MPAlgorithmV0 {
BN_CTX *_ctx;
}
- (id)init {
if (!(self = [super init]))
return nil;
_ctx = BN_CTX_new();
static dispatch_once_t once = 0;
dispatch_once( &once, ^{
_mpwQueue = [NSOperationQueue new];
_mpwQueue.maxConcurrentOperationCount = 1;
_mpwQueue.name = @"mpw queue";
} );
return self;
}
- (void)dealloc {
BN_CTX_free( _ctx );
_ctx = NULL;
}
- (MPAlgorithmVersion)version {
return MPAlgorithmVersion0;
}
- (NSString *)description {
return strf( @"V%lu", (unsigned long)self.version );
}
- (NSString *)debugDescription {
return strf( @"<%@: version=%lu>", NSStringFromClass( [self class] ), (unsigned long)self.version );
}
- (BOOL)isEqual:(id)other {
if (other == self)
return YES;
if (!other || ![other conformsToProtocol:@protocol(MPAlgorithm)])
return NO;
return [(id<MPAlgorithm>)other version] == [self version];
}
- (void)mpw_perform:(void ( ^ )(void))operationBlock {
if ([NSOperationQueue currentQueue] == _mpwQueue) {
operationBlock();
return;
}
NSOperation *operation = [NSBlockOperation blockOperationWithBlock:operationBlock];
if ([operation respondsToSelector:@selector( qualityOfService )])
operation.qualityOfService = NSQualityOfServiceUserInitiated;
[_mpwQueue addOperations:@[ operation ] waitUntilFinished:YES];
}
- (BOOL)tryMigrateUser:(MPUserEntity *)user inContext:(NSManagedObjectContext *)moc {
NSError *error = nil;
NSFetchRequest *migrationRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPSiteEntity class] )];
migrationRequest.predicate = [NSPredicate predicateWithFormat:@"version_ < %d AND user == %@", self.version, user];
NSArray *migrationSites = [moc executeFetchRequest:migrationRequest error:&error];
if (!migrationSites) {
err( @"While looking for sites to migrate: %@", [error fullDescription] );
return NO;
}
BOOL success = YES;
for (MPSiteEntity *migrationSite in migrationSites)
if (![migrationSite tryMigrateExplicitly:NO])
success = NO;
return success;
}
- (BOOL)tryMigrateSite:(MPSiteEntity *)site explicit:(BOOL)explicit {
if ([site.algorithm version] != [self version] - 1)
// Only migrate from previous version.
return NO;
if (!explicit) {
// This migration requires explicit permission.
site.requiresExplicitMigration = YES;
return NO;
}
// Apply migration.
site.requiresExplicitMigration = NO;
site.algorithm = self;
return YES;
}
- (NSData *)keyDataForFullName:(NSString *)fullName withMasterPassword:(NSString *)masterPassword {
__block NSData *keyData;
[self mpw_perform:^{
NSDate *start = [NSDate date];
uint8_t const *masterKeyBytes = mpw_masterKeyForUser( fullName.UTF8String, masterPassword.UTF8String, [self version] );
if (masterKeyBytes) {
keyData = [NSData dataWithBytes:masterKeyBytes length:MP_dkLen];
trc( @"User: %@, password: %@ derives to key ID: %@ (took %0.2fs)", //
fullName, masterPassword, [self keyIDForKeyData:keyData], -[start timeIntervalSinceNow] );
mpw_free( masterKeyBytes, MP_dkLen );
}
}];
return keyData;
}
- (NSData *)keyIDForKeyData:(NSData *)keyData {
return [keyData hashWith:PearlHashSHA256];
}
- (NSString *)nameOfType:(MPSiteType)type {
if (!type)
return nil;
switch (type) {
case MPSiteTypeGeneratedMaximum:
return @"Maximum Security Password";
case MPSiteTypeGeneratedLong:
return @"Long Password";
case MPSiteTypeGeneratedMedium:
return @"Medium Password";
case MPSiteTypeGeneratedBasic:
return @"Basic Password";
case MPSiteTypeGeneratedShort:
return @"Short Password";
case MPSiteTypeGeneratedPIN:
return @"PIN";
case MPSiteTypeGeneratedName:
return @"Name";
case MPSiteTypeGeneratedPhrase:
return @"Phrase";
case MPSiteTypeStoredPersonal:
return @"Personal Password";
case MPSiteTypeStoredDevicePrivate:
return @"Device Private Password";
}
Throw( @"Type not supported: %lu", (long)type );
}
- (NSString *)shortNameOfType:(MPSiteType)type {
if (!type)
return nil;
switch (type) {
case MPSiteTypeGeneratedMaximum:
return @"Maximum";
case MPSiteTypeGeneratedLong:
return @"Long";
case MPSiteTypeGeneratedMedium:
return @"Medium";
case MPSiteTypeGeneratedBasic:
return @"Basic";
case MPSiteTypeGeneratedShort:
return @"Short";
case MPSiteTypeGeneratedPIN:
return @"PIN";
case MPSiteTypeGeneratedName:
return @"Name";
case MPSiteTypeGeneratedPhrase:
return @"Phrase";
case MPSiteTypeStoredPersonal:
return @"Personal";
case MPSiteTypeStoredDevicePrivate:
return @"Device";
}
Throw( @"Type not supported: %lu", (long)type );
}
- (NSString *)classNameOfType:(MPSiteType)type {
return NSStringFromClass( [self classOfType:type] );
}
- (Class)classOfType:(MPSiteType)type {
if (!type)
Throw( @"No type given." );
switch (type) {
case MPSiteTypeGeneratedMaximum:
return [MPGeneratedSiteEntity class];
case MPSiteTypeGeneratedLong:
return [MPGeneratedSiteEntity class];
case MPSiteTypeGeneratedMedium:
return [MPGeneratedSiteEntity class];
case MPSiteTypeGeneratedBasic:
return [MPGeneratedSiteEntity class];
case MPSiteTypeGeneratedShort:
return [MPGeneratedSiteEntity class];
case MPSiteTypeGeneratedPIN:
return [MPGeneratedSiteEntity class];
case MPSiteTypeGeneratedName:
return [MPGeneratedSiteEntity class];
case MPSiteTypeGeneratedPhrase:
return [MPGeneratedSiteEntity class];
case MPSiteTypeStoredPersonal:
return [MPStoredSiteEntity class];
case MPSiteTypeStoredDevicePrivate:
return [MPStoredSiteEntity class];
}
Throw( @"Type not supported: %lu", (long)type );
}
- (NSArray *)allTypes {
return [self allTypesStartingWith:MPSiteTypeGeneratedPhrase];
}
- (NSArray *)allTypesStartingWith:(MPSiteType)startingType {
NSMutableArray *allTypes = [[NSMutableArray alloc] initWithCapacity:8];
MPSiteType currentType = startingType;
do {
[allTypes addObject:@(currentType)];
} while ((currentType = [self nextType:currentType]) != startingType);
return allTypes;
}
- (MPSiteType)nextType:(MPSiteType)type {
switch (type) {
case MPSiteTypeGeneratedPhrase:
return MPSiteTypeGeneratedName;
case MPSiteTypeGeneratedName:
return MPSiteTypeGeneratedMaximum;
case MPSiteTypeGeneratedMaximum:
return MPSiteTypeGeneratedLong;
case MPSiteTypeGeneratedLong:
return MPSiteTypeGeneratedMedium;
case MPSiteTypeGeneratedMedium:
return MPSiteTypeGeneratedBasic;
case MPSiteTypeGeneratedBasic:
return MPSiteTypeGeneratedShort;
case MPSiteTypeGeneratedShort:
return MPSiteTypeGeneratedPIN;
case MPSiteTypeGeneratedPIN:
return MPSiteTypeStoredPersonal;
case MPSiteTypeStoredPersonal:
return MPSiteTypeStoredDevicePrivate;
case MPSiteTypeStoredDevicePrivate:
return MPSiteTypeGeneratedPhrase;
default:
return MPSiteTypeGeneratedLong;
}
}
- (MPSiteType)previousType:(MPSiteType)type {
MPSiteType previousType = type, nextType = type;
while ((nextType = [self nextType:nextType]) != type)
previousType = nextType;
return previousType;
}
- (NSString *)generateLoginForSiteNamed:(NSString *)name usingKey:(MPKey *)key {
return [self generateContentForSiteNamed:name ofType:MPSiteTypeGeneratedName withCounter:1
variant:MPSiteVariantLogin context:nil usingKey:key];
}
- (NSString *)generatePasswordForSiteNamed:(NSString *)name ofType:(MPSiteType)type withCounter:(NSUInteger)counter
usingKey:(MPKey *)key {
return [self generateContentForSiteNamed:name ofType:type withCounter:counter
variant:MPSiteVariantPassword context:nil usingKey:key];
}
- (NSString *)generateAnswerForSiteNamed:(NSString *)name onQuestion:(NSString *)question usingKey:(MPKey *)key {
return [self generateContentForSiteNamed:name ofType:MPSiteTypeGeneratedPhrase withCounter:1
variant:MPSiteVariantAnswer context:question usingKey:key];
}
- (NSString *)generateContentForSiteNamed:(NSString *)name ofType:(MPSiteType)type withCounter:(NSUInteger)counter
variant:(MPSiteVariant)variant context:(NSString *)context usingKey:(MPKey *)key {
__block NSString *content;
[self mpw_perform:^{
char const *contentBytes = mpw_passwordForSite( [key keyDataForAlgorithm:self].bytes,
name.UTF8String, type, (uint32_t)counter, variant, context.UTF8String, [self version] );
if (contentBytes) {
content = [NSString stringWithCString:contentBytes encoding:NSUTF8StringEncoding];
mpw_free_string( contentBytes );
}
}];
return content;
}
- (NSString *)storedLoginForSite:(MPStoredSiteEntity *)site usingKey:(MPKey *)key {
return nil;
}
- (NSString *)storedPasswordForSite:(MPStoredSiteEntity *)site usingKey:(MPKey *)key {
return [self decryptContent:site.contentObject usingKey:key];
}
- (BOOL)savePassword:(NSString *)clearContent toSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey {
NSAssert( [[siteKey keyIDForAlgorithm:site.user.algorithm] isEqualToData:site.user.keyID], @"Site does not belong to current user." );
switch (site.type) {
case MPSiteTypeGeneratedMaximum:
case MPSiteTypeGeneratedLong:
case MPSiteTypeGeneratedMedium:
case MPSiteTypeGeneratedBasic:
case MPSiteTypeGeneratedShort:
case MPSiteTypeGeneratedPIN:
case MPSiteTypeGeneratedName:
case MPSiteTypeGeneratedPhrase: {
wrn( @"Cannot save content to site with generated type %lu.", (long)site.type );
return NO;
}
case MPSiteTypeStoredPersonal: {
if (![site isKindOfClass:[MPStoredSiteEntity class]]) {
wrn( @"Site with stored type %lu is not an MPStoredSiteEntity, but a %@.",
(long)site.type, [site class] );
return NO;
}
NSData *encryptionKey = [siteKey keyDataForAlgorithm:self trimmedLength:PearlCryptKeySize];
NSData *encryptedContent = [[clearContent dataUsingEncoding:NSUTF8StringEncoding]
encryptWithSymmetricKey:encryptionKey padding:YES];
if ([((MPStoredSiteEntity *)site).contentObject isEqualToData:encryptedContent])
return NO;
((MPStoredSiteEntity *)site).contentObject = encryptedContent;
return YES;
}
case MPSiteTypeStoredDevicePrivate: {
if (![site isKindOfClass:[MPStoredSiteEntity class]]) {
wrn( @"Site with stored type %lu is not an MPStoredSiteEntity, but a %@.",
(long)site.type, [site class] );
return NO;
}
NSData *encryptionKey = [siteKey keyDataForAlgorithm:self trimmedLength:PearlCryptKeySize];
NSData *encryptedContent = [[clearContent dataUsingEncoding:NSUTF8StringEncoding]
encryptWithSymmetricKey:encryptionKey padding:YES];
NSDictionary *siteQuery = [self queryForDevicePrivateSiteNamed:site.name];
if (!encryptedContent)
[PearlKeyChain deleteItemForQuery:siteQuery];
else
[PearlKeyChain addOrUpdateItemForQuery:siteQuery withAttributes:@{
(__bridge id)kSecValueData : encryptedContent,
#if TARGET_OS_IPHONE
(__bridge id)kSecAttrAccessible : (__bridge id)kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
#endif
}];
((MPStoredSiteEntity *)site).contentObject = nil;
return YES;
}
}
Throw( @"Unsupported type: %ld", (long)site.type );
}
- (NSString *)resolveLoginForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey {
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter( group );
__block NSString *result = nil;
[self resolveLoginForSite:site usingKey:siteKey result:^(NSString *result_) {
result = result_;
dispatch_group_leave( group );
}];
dispatch_group_wait( group, DISPATCH_TIME_FOREVER );
return result;
}
- (NSString *)resolvePasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey {
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter( group );
__block NSString *result = nil;
[self resolvePasswordForSite:site usingKey:siteKey result:^(NSString *result_) {
result = result_;
dispatch_group_leave( group );
}];
dispatch_group_wait( group, DISPATCH_TIME_FOREVER );
return result;
}
- (NSString *)resolveAnswerForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey {
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter( group );
__block NSString *result = nil;
[self resolveAnswerForSite:site usingKey:siteKey result:^(NSString *result_) {
result = result_;
dispatch_group_leave( group );
}];
dispatch_group_wait( group, DISPATCH_TIME_FOREVER );
return result;
}
- (NSString *)resolveAnswerForQuestion:(MPSiteQuestionEntity *)question usingKey:(MPKey *)siteKey {
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter( group );
__block NSString *result = nil;
[self resolveAnswerForQuestion:question usingKey:siteKey result:^(NSString *result_) {
result = result_;
dispatch_group_leave( group );
}];
dispatch_group_wait( group, DISPATCH_TIME_FOREVER );
return result;
}
- (void)resolveLoginForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey result:(void ( ^ )(NSString *result))resultBlock {
NSAssert( [[siteKey keyIDForAlgorithm:site.user.algorithm] isEqualToData:site.user.keyID], @"Site does not belong to current user." );
NSString *name = site.name;
BOOL loginGenerated = site.loginGenerated && [[MPAppDelegate_Shared get] isFeatureUnlocked:MPProductGenerateLogins];
NSString *loginName = site.loginName;
id<MPAlgorithm> algorithm = nil;
if (!name.length)
err( @"Missing name." );
else if (!siteKey)
err( @"Missing key." );
else
algorithm = site.algorithm;
dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{
resultBlock( loginName || !loginGenerated? loginName:
[algorithm generateLoginForSiteNamed:name usingKey:siteKey] );
} );
}
- (void)resolvePasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey result:(void ( ^ )(NSString *result))resultBlock {
NSAssert( [[siteKey keyIDForAlgorithm:site.user.algorithm] isEqualToData:site.user.keyID], @"Site does not belong to current user." );
switch (site.type) {
case MPSiteTypeGeneratedMaximum:
case MPSiteTypeGeneratedLong:
case MPSiteTypeGeneratedMedium:
case MPSiteTypeGeneratedBasic:
case MPSiteTypeGeneratedShort:
case MPSiteTypeGeneratedPIN:
case MPSiteTypeGeneratedName:
case MPSiteTypeGeneratedPhrase: {
if (![site isKindOfClass:[MPGeneratedSiteEntity class]]) {
wrn( @"Site with generated type %lu is not an MPGeneratedSiteEntity, but a %@.",
(long)site.type, [site class] );
break;
}
NSString *name = site.name;
MPSiteType type = site.type;
NSUInteger counter = ((MPGeneratedSiteEntity *)site).counter;
id<MPAlgorithm> algorithm = nil;
if (!site.name.length)
err( @"Missing name." );
else if (!siteKey)
err( @"Missing key." );
else
algorithm = site.algorithm;
dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{
NSString *result = [algorithm generatePasswordForSiteNamed:name ofType:type withCounter:counter usingKey:siteKey];
resultBlock( result );
} );
break;
}
case MPSiteTypeStoredPersonal: {
if (![site isKindOfClass:[MPStoredSiteEntity class]]) {
wrn( @"Site with stored type %lu is not an MPStoredSiteEntity, but a %@.",
(long)site.type, [site class] );
break;
}
NSData *encryptedContent = ((MPStoredSiteEntity *)site).contentObject;
dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{
NSString *result = [self decryptContent:encryptedContent usingKey:siteKey];
resultBlock( result );
} );
break;
}
case MPSiteTypeStoredDevicePrivate: {
NSAssert( [site isKindOfClass:[MPStoredSiteEntity class]],
@"Site with stored type %lu is not an MPStoredSiteEntity, but a %@.", (long)site.type,
[site class] );
NSDictionary *siteQuery = [self queryForDevicePrivateSiteNamed:site.name];
NSData *encryptedContent = [PearlKeyChain dataOfItemForQuery:siteQuery];
dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{
NSString *result = [self decryptContent:encryptedContent usingKey:siteKey];
resultBlock( result );
} );
break;
}
}
}
- (void)resolveAnswerForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey result:(void ( ^ )(NSString *result))resultBlock {
NSAssert( [[siteKey keyIDForAlgorithm:site.user.algorithm] isEqualToData:site.user.keyID], @"Site does not belong to current user." );
NSString *name = site.name;
id<MPAlgorithm> algorithm = nil;
if (!site.name.length)
err( @"Missing name." );
else if (!siteKey)
err( @"Missing key." );
else
algorithm = site.algorithm;
dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{
NSString *result = [algorithm generateAnswerForSiteNamed:name onQuestion:nil usingKey:siteKey];
resultBlock( result );
} );
}
- (void)resolveAnswerForQuestion:(MPSiteQuestionEntity *)question usingKey:(MPKey *)siteKey
result:(void ( ^ )(NSString *result))resultBlock {
NSAssert( [[siteKey keyIDForAlgorithm:question.site.user.algorithm] isEqualToData:question.site.user.keyID],
@"Site does not belong to current user." );
NSString *name = question.site.name;
NSString *keyword = question.keyword;
id<MPAlgorithm> algorithm = nil;
if (!name.length)
err( @"Missing name." );
else if (!siteKey)
err( @"Missing key." );
else if ([[MPAppDelegate_Shared get] isFeatureUnlocked:MPProductGenerateAnswers])
algorithm = question.site.algorithm;
dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{
NSString *result = [algorithm generateAnswerForSiteNamed:name onQuestion:keyword usingKey:siteKey];
resultBlock( result );
} );
}
- (void)importProtectedPassword:(NSString *)protectedContent protectedByKey:(MPKey *)importKey
intoSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey {
NSAssert( [[siteKey keyIDForAlgorithm:site.user.algorithm] isEqualToData:site.user.keyID], @"Site does not belong to current user." );
switch (site.type) {
case MPSiteTypeGeneratedMaximum:
case MPSiteTypeGeneratedLong:
case MPSiteTypeGeneratedMedium:
case MPSiteTypeGeneratedBasic:
case MPSiteTypeGeneratedShort:
case MPSiteTypeGeneratedPIN:
case MPSiteTypeGeneratedName:
case MPSiteTypeGeneratedPhrase:
break;
case MPSiteTypeStoredPersonal: {
if (![site isKindOfClass:[MPStoredSiteEntity class]]) {
wrn( @"Site with stored type %lu is not an MPStoredSiteEntity, but a %@.",
(long)site.type, [site class] );
break;
}
if ([[importKey keyIDForAlgorithm:self] isEqualToData:[siteKey keyIDForAlgorithm:self]])
((MPStoredSiteEntity *)site).contentObject = [protectedContent decodeBase64];
else {
NSString *clearContent = [self decryptContent:[protectedContent decodeBase64] usingKey:importKey];
[self importClearTextPassword:clearContent intoSite:site usingKey:siteKey];
}
break;
}
case MPSiteTypeStoredDevicePrivate:
break;
}
}
- (void)importClearTextPassword:(NSString *)clearContent intoSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey {
NSAssert( [[siteKey keyIDForAlgorithm:site.user.algorithm] isEqualToData:site.user.keyID], @"Site does not belong to current user." );
switch (site.type) {
case MPSiteTypeGeneratedMaximum:
case MPSiteTypeGeneratedLong:
case MPSiteTypeGeneratedMedium:
case MPSiteTypeGeneratedBasic:
case MPSiteTypeGeneratedShort:
case MPSiteTypeGeneratedPIN:
case MPSiteTypeGeneratedName:
case MPSiteTypeGeneratedPhrase:
break;
case MPSiteTypeStoredPersonal: {
[self savePassword:clearContent toSite:site usingKey:siteKey];
break;
}
case MPSiteTypeStoredDevicePrivate:
break;
}
}
- (NSString *)exportPasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey {
NSAssert( [[siteKey keyIDForAlgorithm:site.user.algorithm] isEqualToData:site.user.keyID], @"Site does not belong to current user." );
if (!(site.type & MPSiteFeatureExportContent))
return nil;
NSString *result = nil;
switch (site.type) {
case MPSiteTypeGeneratedMaximum:
case MPSiteTypeGeneratedLong:
case MPSiteTypeGeneratedMedium:
case MPSiteTypeGeneratedBasic:
case MPSiteTypeGeneratedShort:
case MPSiteTypeGeneratedPIN:
case MPSiteTypeGeneratedName:
case MPSiteTypeGeneratedPhrase: {
result = nil;
break;
}
case MPSiteTypeStoredPersonal: {
if (![site isKindOfClass:[MPStoredSiteEntity class]]) {
wrn( @"Site with stored type %lu is not an MPStoredSiteEntity, but a %@.",
(long)site.type, [site class] );
break;
}
result = [((MPStoredSiteEntity *)site).contentObject encodeBase64];
break;
}
case MPSiteTypeStoredDevicePrivate: {
result = nil;
break;
}
}
return result;
}
- (BOOL)migrateExplicitly:(BOOL)explicit {
return NO;
}
- (NSDictionary *)queryForDevicePrivateSiteNamed:(NSString *)name {
return [PearlKeyChain createQueryForClass:kSecClassGenericPassword
attributes:@{
(__bridge id)kSecAttrService : @"DevicePrivate",
(__bridge id)kSecAttrAccount : name
}
matches:nil];
}
- (NSString *)decryptContent:(NSData *)encryptedContent usingKey:(MPKey *)key {
if (!key)
return nil;
NSData *decryptedContent = nil;
if ([encryptedContent length]) {
NSData *encryptionKey = [key keyDataForAlgorithm:self trimmedLength:PearlCryptKeySize];
decryptedContent = [encryptedContent decryptWithSymmetricKey:encryptionKey padding:YES];
}
if (!decryptedContent)
return nil;
return [[NSString alloc] initWithBytes:decryptedContent.bytes length:decryptedContent.length encoding:NSUTF8StringEncoding];
}
- (BOOL)timeToCrack:(out TimeToCrack *)timeToCrack passwordOfType:(MPSiteType)type byAttacker:(MPAttacker)attacker {
if (!(type & MPSiteTypeClassGenerated))
return NO;
size_t count = 0;
const char **templates = mpw_templatesForType( type, &count );
if (!templates)
return NO;
BIGNUM *permutations = BN_new(), *templatePermutations = BN_new();
for (size_t t = 0; t < count; ++t) {
const char *template = templates[t];
BN_one( templatePermutations );
for (NSUInteger c = 0; c < strlen( template ); ++c)
BN_mul_word( templatePermutations,
(BN_ULONG)strlen( mpw_charactersInClass( template[c] ) ) );
BN_add( permutations, permutations, templatePermutations );
}
BN_free( templatePermutations );
free( templates );
return [self timeToCrack:timeToCrack permutations:permutations forAttacker:attacker];
}
- (BOOL)timeToCrack:(out TimeToCrack *)timeToCrack passwordString:(NSString *)password byAttacker:(MPAttacker)attacker {
BIGNUM *permutations = BN_new();
BN_one( permutations );
for (NSUInteger c = 0; c < [password length]; ++c) {
const char passwordCharacter = [password substringWithRange:NSMakeRange( c, 1 )].UTF8String[0];
unsigned int characterEntropy = 0;
for (NSString *characterClass in @[ @"v", @"c", @"a", @"x" ]) {
char const *charactersForClass = mpw_charactersInClass( characterClass.UTF8String[0] );
if (strchr( charactersForClass, passwordCharacter )) {
// Found class for password character.
characterEntropy = (BN_ULONG)strlen( charactersForClass );
break;
}
}
if (!characterEntropy)
characterEntropy = 256 /* a byte */;
BN_mul_word( permutations, characterEntropy );
}
return [self timeToCrack:timeToCrack permutations:permutations forAttacker:attacker];
}
- (BOOL)timeToCrack:(out TimeToCrack *)timeToCrack permutations:(BIGNUM *)permutations forAttacker:(MPAttacker)attacker {
// Determine base seconds needed to calculate the permutations.
BIGNUM *secondsToCrack = BN_dup( permutations );
BN_div_word( secondsToCrack, CRACKING_PER_SECOND );
// Modify seconds needed by applying our hardware budget.
switch (attacker) {
case MPAttacker1:
break;
case MPAttacker5K:
BN_mul_word( secondsToCrack, CRACKING_PRICE );
BN_div_word( secondsToCrack, 5000 );
break;
case MPAttacker20M:
BN_mul_word( secondsToCrack, CRACKING_PRICE );
BN_div_word( secondsToCrack, 20000000 );
break;
case MPAttacker5B:
BN_mul_word( secondsToCrack, CRACKING_PRICE );
BN_div_word( secondsToCrack, 5000 );
BN_div_word( secondsToCrack, 1000000 );
break;
}
BIGNUM *max = BN_new();
BN_set_word( max, (BN_ULONG)-1 );
BIGNUM *hoursToCrack = BN_dup( secondsToCrack );
BN_div_word( hoursToCrack, 3600 );
if (BN_cmp( hoursToCrack, max ) < 0)
timeToCrack->hours = BN_get_word( hoursToCrack );
else
timeToCrack->hours = (BN_ULONG)-1;
BIGNUM *daysToCrack = BN_dup( hoursToCrack );
BN_div_word( daysToCrack, 24 );
if (BN_cmp( daysToCrack, max ) < 0)
timeToCrack->days = BN_get_word( daysToCrack );
else
timeToCrack->days = (BN_ULONG)-1;
BIGNUM *weeksToCrack = BN_dup( daysToCrack );
BN_div_word( weeksToCrack, 7 );
if (BN_cmp( weeksToCrack, max ) < 0)
timeToCrack->weeks = BN_get_word( weeksToCrack );
else
timeToCrack->weeks = (BN_ULONG)-1;
BIGNUM *monthsToCrack = BN_dup( daysToCrack );
BN_div_word( monthsToCrack, 31 );
if (BN_cmp( monthsToCrack, max ) < 0)
timeToCrack->months = BN_get_word( monthsToCrack );
else
timeToCrack->months = (BN_ULONG)-1;
BIGNUM *yearsToCrack = BN_dup( daysToCrack );
BN_div_word( yearsToCrack, 356 );
if (BN_cmp( yearsToCrack, max ) < 0)
timeToCrack->years = BN_get_word( yearsToCrack );
else
timeToCrack->years = (BN_ULONG)-1;
BIGNUM *universesToCrack = BN_dup( yearsToCrack );
BN_div_word( universesToCrack, 14000 );
BN_div_word( universesToCrack, 1000000 );
if (BN_cmp( universesToCrack, max ) < 0)
timeToCrack->universes = BN_get_word( universesToCrack );
else
timeToCrack->universes = (BN_ULONG)-1;
for (unsigned long error = ERR_get_error(); error; error = ERR_get_error())
err( @"bignum error: %lu", error );
BN_free( max );
BN_free( permutations );
BN_free( secondsToCrack );
BN_free( hoursToCrack );
BN_free( daysToCrack );
BN_free( weeksToCrack );
BN_free( monthsToCrack );
BN_free( yearsToCrack );
BN_free( universesToCrack );
return YES;
}
@end

View File

@@ -0,0 +1,21 @@
/**
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
*
* See the enclosed file LICENSE for license information (LGPLv3). If you did
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
*
* @author Maarten Billemont <lhunath@lyndir.com>
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
*/
//
// MPAlgorithmV1
//
// Created by Maarten Billemont on 17/07/12.
// Copyright 2012 lhunath (Maarten Billemont). All rights reserved.
//
#import "MPAlgorithmV0.h"
@interface MPAlgorithmV1 : MPAlgorithmV0
@end

View File

@@ -0,0 +1,48 @@
/**
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
*
* See the enclosed file LICENSE for license information (LGPLv3). If you did
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
*
* @author Maarten Billemont <lhunath@lyndir.com>
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
*/
//
// MPAlgorithmV1
//
// Created by Maarten Billemont on 17/07/12.
// Copyright 2012 lhunath (Maarten Billemont). All rights reserved.
//
#import "MPAlgorithmV1.h"
#import "MPEntities.h"
@implementation MPAlgorithmV1
- (MPAlgorithmVersion)version {
return MPAlgorithmVersion1;
}
- (BOOL)tryMigrateSite:(MPSiteEntity *)site explicit:(BOOL)explicit {
if ([site.algorithm version] != [self version] - 1)
// Only migrate from previous version.
return NO;
if (!explicit) {
if (site.type & MPSiteTypeClassGenerated) {
// This migration requires explicit permission for types of the generated class.
site.requiresExplicitMigration = YES;
return NO;
}
}
// Apply migration.
site.requiresExplicitMigration = NO;
site.algorithm = self;
return YES;
}
@end

View File

@@ -0,0 +1,21 @@
/**
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
*
* See the enclosed file LICENSE for license information (LGPLv3). If you did
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
*
* @author Maarten Billemont <lhunath@lyndir.com>
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
*/
//
// MPAlgorithmV2
//
// Created by Maarten Billemont on 17/07/12.
// Copyright 2012 lhunath (Maarten Billemont). All rights reserved.
//
#import "MPAlgorithmV1.h"
@interface MPAlgorithmV2 : MPAlgorithmV1
@end

View File

@@ -0,0 +1,48 @@
/**
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
*
* See the enclosed file LICENSE for license information (LGPLv3). If you did
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
*
* @author Maarten Billemont <lhunath@lyndir.com>
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
*/
//
// MPAlgorithmV2
//
// Created by Maarten Billemont on 17/07/12.
// Copyright 2012 lhunath (Maarten Billemont). All rights reserved.
//
#import "MPAlgorithmV2.h"
#import "MPEntities.h"
@implementation MPAlgorithmV2
- (MPAlgorithmVersion)version {
return MPAlgorithmVersion2;
}
- (BOOL)tryMigrateSite:(MPSiteEntity *)site explicit:(BOOL)explicit {
if ([site.algorithm version] != [self version] - 1)
// Only migrate from previous version.
return NO;
if (!explicit) {
if (site.type & MPSiteTypeClassGenerated && site.name.length != [site.name dataUsingEncoding:NSUTF8StringEncoding].length) {
// This migration requires explicit permission for types of the generated class.
site.requiresExplicitMigration = YES;
return NO;
}
}
// Apply migration.
site.requiresExplicitMigration = NO;
site.algorithm = self;
return YES;
}
@end

View File

@@ -0,0 +1,21 @@
/**
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
*
* See the enclosed file LICENSE for license information (LGPLv3). If you did
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
*
* @author Maarten Billemont <lhunath@lyndir.com>
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
*/
//
// MPAlgorithmV3
//
// Created by Maarten Billemont on 13/01/15.
// Copyright 2015 lhunath (Maarten Billemont). All rights reserved.
//
#import "MPAlgorithmV2.h"
@interface MPAlgorithmV3 : MPAlgorithmV2
@end

View File

@@ -0,0 +1,49 @@
/**
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
*
* See the enclosed file LICENSE for license information (LGPLv3). If you did
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
*
* @author Maarten Billemont <lhunath@lyndir.com>
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
*/
//
// MPAlgorithmV3
//
// Created by Maarten Billemont on 13/01/15.
// Copyright 2015 lhunath (Maarten Billemont). All rights reserved.
//
#import "MPAlgorithmV3.h"
#import "MPEntities.h"
@implementation MPAlgorithmV3
- (MPAlgorithmVersion)version {
return MPAlgorithmVersion3;
}
- (BOOL)tryMigrateSite:(MPSiteEntity *)site explicit:(BOOL)explicit {
if ([site.algorithm version] != [self version] - 1)
// Only migrate from previous version.
return NO;
if (!explicit) {
if (site.type & MPSiteTypeClassGenerated &&
site.user.name.length != [site.user.name dataUsingEncoding:NSUTF8StringEncoding].length) {
// This migration requires explicit permission for types of the generated class.
site.requiresExplicitMigration = YES;
return NO;
}
}
// Apply migration.
site.requiresExplicitMigration = NO;
site.algorithm = self;
return YES;
}
@end

View File

@@ -0,0 +1,39 @@
//
// MPAppDelegate_Key.h
// MasterPassword
//
// Created by Maarten Billemont on 24/11/11.
// Copyright (c) 2011 Lyndir. All rights reserved.
//
#import <StoreKit/StoreKit.h>
#import "MPAppDelegate_Shared.h"
#define MPProductGenerateLogins @"com.lyndir.masterpassword.products.generatelogins"
#define MPProductGenerateAnswers @"com.lyndir.masterpassword.products.generateanswers"
#define MPProductOSIntegration @"com.lyndir.masterpassword.products.osintegration"
#define MPProductTouchID @"com.lyndir.masterpassword.products.touchid"
#define MPProductFuel @"com.lyndir.masterpassword.products.fuel"
#define MP_FUEL_HOURLY_RATE 30.f /* Tier 1 purchases/h ~> USD/h */
@protocol MPInAppDelegate
- (void)updateWithProducts:(NSArray /* SKProduct */ *)products;
- (void)updateWithTransaction:(SKPaymentTransaction *)transaction;
@end
@interface MPAppDelegate_Shared(InApp)
- (void)registerProductsObserver:(id<MPInAppDelegate>)delegate;
- (void)removeProductsObserver:(id<MPInAppDelegate>)delegate;
- (void)reloadProducts;
- (BOOL)canMakePayments;
- (BOOL)isFeatureUnlocked:(NSString *)productIdentifier;
- (void)restoreCompletedTransactions;
- (void)purchaseProductWithIdentifier:(NSString *)productIdentifier quantity:(NSInteger)quantity;
@end

View File

@@ -0,0 +1,181 @@
//
// MPAppDelegate.m
// MasterPassword
//
// Created by Maarten Billemont on 24/11/11.
// Copyright (c) 2011 Lyndir. All rights reserved.
//
#import "MPAppDelegate_InApp.h"
@interface MPAppDelegate_Shared(InApp_Private)<SKProductsRequestDelegate, SKPaymentTransactionObserver>
@end
@implementation MPAppDelegate_Shared(InApp)
PearlAssociatedObjectProperty( NSArray*, Products, products );
PearlAssociatedObjectProperty( NSMutableArray*, ProductObservers, productObservers );
- (void)registerProductsObserver:(id<MPInAppDelegate>)delegate {
if (!self.productObservers)
self.productObservers = [NSMutableArray array];
[self.productObservers addObject:delegate];
if (self.products)
[delegate updateWithProducts:self.products];
else
[self reloadProducts];
}
- (void)removeProductsObserver:(id<MPInAppDelegate>)delegate {
[self.productObservers removeObject:delegate];
}
- (void)reloadProducts {
SKProductsRequest *productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:
[[NSSet alloc] initWithObjects:MPProductGenerateLogins, MPProductGenerateAnswers, MPProductTouchID, MPProductFuel, nil]];
productsRequest.delegate = self;
[productsRequest start];
}
- (SKPaymentQueue *)paymentQueue {
static dispatch_once_t once = 0;
dispatch_once( &once, ^{
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
} );
return [SKPaymentQueue defaultQueue];
}
- (BOOL)canMakePayments {
return [SKPaymentQueue canMakePayments];
}
- (BOOL)isFeatureUnlocked:(NSString *)productIdentifier {
if (![productIdentifier length])
// Missing a product.
return NO;
if ([productIdentifier isEqualToString:MPProductFuel])
// Consumable product.
return NO;
#if ADHOC || DEBUG
// All features are unlocked for beta / debug / mac versions.
return YES;
#else
// Check if product is purchased.
return [[NSUserDefaults standardUserDefaults] objectForKey:productIdentifier] != nil;
#endif
}
- (void)restoreCompletedTransactions {
[[self paymentQueue] restoreCompletedTransactions];
}
- (void)purchaseProductWithIdentifier:(NSString *)productIdentifier quantity:(NSInteger)quantity {
#if TARGET_OS_IPHONE
if (![[MPAppDelegate_Shared get] canMakePayments]) {
[PearlAlert showAlertWithTitle:@"Store Not Set Up" message:
@"Try logging using the App Store or from Settings."
viewStyle:UIAlertViewStyleDefault initAlert:nil
tappedButtonBlock:nil cancelTitle:@"Thanks" otherTitles:nil];
return;
}
#endif
for (SKProduct *product in self.products)
if ([product.productIdentifier isEqualToString:productIdentifier]) {
SKMutablePayment *payment = [SKMutablePayment paymentWithProduct:product];
payment.quantity = quantity;
[[self paymentQueue] addPayment:payment];
return;
}
}
#pragma mark - SKProductsRequestDelegate
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {
inf( @"products: %@, invalid: %@", response.products, response.invalidProductIdentifiers );
self.products = response.products;
for (id<MPInAppDelegate> productObserver in self.productObservers)
[productObserver updateWithProducts:self.products];
}
- (void)request:(SKRequest *)request didFailWithError:(NSError *)error {
#if TARGET_OS_IPHONE
[PearlAlert showAlertWithTitle:@"Purchase Failed" message:
strf( @"%@\n\n%@", error.localizedDescription,
@"Ensure you are online and try logging out and back into iTunes from your device's Settings." )
viewStyle:UIAlertViewStyleDefault initAlert:nil tappedButtonBlock:nil
cancelTitle:@"OK" otherTitles:nil];
#else
#endif
err( @"StoreKit request (%@) failed: %@", request, [error fullDescription] );
}
- (void)requestDidFinish:(SKRequest *)request {
dbg( @"StoreKit request (%@) finished.", request );
}
#pragma mark - SKPaymentTransactionObserver
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions {
for (SKPaymentTransaction *transaction in transactions) {
dbg( @"transaction updated: %@ -> %d", transaction.payment.productIdentifier, (int)(transaction.transactionState) );
switch (transaction.transactionState) {
case SKPaymentTransactionStatePurchased: {
inf( @"purchased: %@", transaction.payment.productIdentifier );
if ([transaction.payment.productIdentifier isEqualToString:MPProductFuel]) {
float currentFuel = [[MPiOSConfig get].developmentFuelRemaining floatValue];
float purchasedFuel = transaction.payment.quantity / MP_FUEL_HOURLY_RATE;
[MPiOSConfig get].developmentFuelRemaining = @(currentFuel + purchasedFuel);
if (![MPiOSConfig get].developmentFuelChecked || currentFuel < DBL_EPSILON)
[MPiOSConfig get].developmentFuelChecked = [NSDate date];
}
[[NSUserDefaults standardUserDefaults] setObject:transaction.transactionIdentifier
forKey:transaction.payment.productIdentifier];
[queue finishTransaction:transaction];
break;
}
case SKPaymentTransactionStateRestored: {
inf( @"restored: %@", transaction.payment.productIdentifier );
[[NSUserDefaults standardUserDefaults] setObject:transaction.transactionIdentifier
forKey:transaction.payment.productIdentifier];
[queue finishTransaction:transaction];
break;
}
case SKPaymentTransactionStatePurchasing:
case SKPaymentTransactionStateDeferred:
break;
case SKPaymentTransactionStateFailed:
err( @"Transaction failed: %@, reason: %@", transaction.payment.productIdentifier, [transaction.error fullDescription] );
[queue finishTransaction:transaction];
break;
}
for (id<MPInAppDelegate> productObserver in self.productObservers)
[productObserver updateWithTransaction:transaction];
}
if (![[NSUserDefaults standardUserDefaults] synchronize])
wrn( @"Couldn't synchronize after transaction updates." );
}
- (void)paymentQueue:(SKPaymentQueue *)queue restoreCompletedTransactionsFailedWithError:(NSError *)error {
err( @"StoreKit restore failed: %@", [error fullDescription] );
}
@end

View File

@@ -0,0 +1,19 @@
//
// MPAppDelegate_Key.h
// MasterPassword
//
// Created by Maarten Billemont on 24/11/11.
// Copyright (c) 2011 Lyndir. All rights reserved.
//
#import "MPAppDelegate_Shared.h"
@interface MPAppDelegate_Shared(Key)
- (BOOL)signInAsUser:(MPUserEntity *)user saveInContext:(NSManagedObjectContext *)moc usingMasterPassword:(NSString *)password;
- (void)signOutAnimated:(BOOL)animated;
- (void)storeSavedKeyFor:(MPUserEntity *)user;
- (void)forgetSavedKeyFor:(MPUserEntity *)user;
@end

View File

@@ -0,0 +1,269 @@
//
// MPAppDelegate.m
// MasterPassword
//
// Created by Maarten Billemont on 24/11/11.
// Copyright (c) 2011 Lyndir. All rights reserved.
//
#import "MPAppDelegate_Key.h"
#import "MPAppDelegate_Store.h"
@interface MPAppDelegate_Shared()
@property(strong, nonatomic) MPKey *key;
@end
@implementation MPAppDelegate_Shared(Key)
static NSDictionary *createKeyQuery(MPUserEntity *user, BOOL newItem, MPKeyOrigin *keyOrigin) {
#if TARGET_OS_IPHONE
if (user.touchID && kSecUseAuthenticationUI) {
if (keyOrigin)
*keyOrigin = MPKeyOriginKeyChainBiometric;
CFErrorRef acError = NULL;
id accessControl = (__bridge_transfer id)SecAccessControlCreateWithFlags( kCFAllocatorDefault,
kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly, kSecAccessControlTouchIDCurrentSet, &acError );
if (!accessControl || acError)
err( @"Could not use TouchID on this device: %@", acError );
else
return [PearlKeyChain createQueryForClass:kSecClassGenericPassword
attributes:@{
(__bridge id)kSecAttrService : @"Saved Master Password",
(__bridge id)kSecAttrAccount : user.name?: @"",
(__bridge id)kSecAttrAccessControl : accessControl,
(__bridge id)kSecUseAuthenticationUI : (__bridge id)kSecUseAuthenticationUIAllow,
(__bridge id)kSecUseOperationPrompt :
strf( @"Access %@'s master password.", user.name ),
}
matches:nil];
}
#endif
if (keyOrigin)
*keyOrigin = MPKeyOriginKeyChain;
return [PearlKeyChain createQueryForClass:kSecClassGenericPassword
attributes:@{
(__bridge id)kSecAttrService : @"Saved Master Password",
(__bridge id)kSecAttrAccount : user.name?: @"",
#if TARGET_OS_IPHONE
(__bridge id)kSecAttrAccessible : (__bridge id)(kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly?: kSecAttrAccessibleWhenUnlockedThisDeviceOnly),
#endif
}
matches:nil];
}
- (MPKey *)loadSavedKeyFor:(MPUserEntity *)user {
MPKeyOrigin keyOrigin;
NSDictionary *keyQuery = createKeyQuery( user, NO, &keyOrigin );
NSData *keyData = [PearlKeyChain dataOfItemForQuery:keyQuery];
if (!keyData) {
inf( @"No key found in keychain for user: %@", user.userID );
return nil;
}
inf( @"Found key in keychain for user: %@", user.userID );
return [[MPKey alloc] initForFullName:user.name withKeyData:keyData forAlgorithm:user.algorithm keyOrigin:keyOrigin];
}
- (void)storeSavedKeyFor:(MPUserEntity *)user {
if (user.saveKey) {
NSData *keyData = [self.key keyDataForAlgorithm:user.algorithm];
if (keyData) {
[self forgetSavedKeyFor:user];
inf( @"Saving key in keychain for user: %@", user.userID );
[PearlKeyChain addOrUpdateItemForQuery:createKeyQuery( user, YES, nil )
withAttributes:@{ (__bridge id)kSecValueData : keyData }];
}
}
}
- (void)forgetSavedKeyFor:(MPUserEntity *)user {
OSStatus result = [PearlKeyChain deleteItemForQuery:createKeyQuery( user, NO, nil )];
if (result == noErr) {
inf( @"Removed key from keychain for user: %@", user.userID );
[[NSNotificationCenter defaultCenter] postNotificationName:MPKeyForgottenNotification object:self];
}
}
- (void)signOutAnimated:(BOOL)animated {
if (self.key)
self.key = nil;
self.activeUser = nil;
[[NSNotificationCenter defaultCenter] postNotificationName:MPSignedOutNotification object:self userInfo:@{ @"animated" : @(animated) }];
}
- (BOOL)signInAsUser:(MPUserEntity *)user saveInContext:(NSManagedObjectContext *)moc usingMasterPassword:(NSString *)password {
NSAssert( ![NSThread isMainThread], @"Authentication should not happen on the main thread." );
if (!user)
return NO;
MPKey *tryKey = nil;
// Method 1: When the user has no keyID set, set a new key from the given master password.
if (!user.keyID) {
if ([password length] && (tryKey = [[MPKey alloc] initForFullName:user.name withMasterPassword:password])) {
user.keyID = [tryKey keyIDForAlgorithm:MPAlgorithmDefault];
// Migrate existing sites.
[self migrateSitesForUser:user saveInContext:moc toKey:tryKey];
}
}
// Method 2: Depending on the user's saveKey, load or remove the key from the keychain.
if (!user.saveKey)
// Key should not be stored in keychain. Delete it.
[self forgetSavedKeyFor:user];
else if (!tryKey) {
// Key should be saved in keychain. Load it.
if ((tryKey = [self loadSavedKeyFor:user]) && ![user.keyID isEqual:[tryKey keyIDForAlgorithm:user.algorithm]]) {
// Loaded password doesn't match user's keyID. Forget saved password: it is incorrect.
inf( @"Saved password doesn't match keyID for user: %@", user.userID );
trc( @"user keyID: %@ (version: %d) != authentication keyID: %@",
user.keyID, user.algorithm.version, [tryKey keyIDForAlgorithm:user.algorithm] );
tryKey = nil;
[self forgetSavedKeyFor:user];
}
}
// Method 3: Check the given master password string.
if (!tryKey && [password length] && (tryKey = [[MPKey alloc] initForFullName:user.name withMasterPassword:password]) &&
![user.keyID isEqual:[tryKey keyIDForAlgorithm:user.algorithm]]) {
inf( @"Key derived from password doesn't match keyID for user: %@", user.userID );
trc( @"user keyID: %@ (version: %u) != authentication keyID: %@",
user.keyID, user.algorithm.version, [tryKey keyIDForAlgorithm:user.algorithm] );
tryKey = nil;
}
// No more methods left, fail if key still not known.
if (!tryKey) {
if (password)
inf( @"Password login failed for user: %@", user.userID );
else
dbg( @"Automatic login failed for user: %@", user.userID );
return NO;
}
inf( @"Logged in user: %@", user.userID );
if (![self.key isEqualToKey:tryKey]) {
// Upgrade the user's keyID if not at the default version yet.
if (user.algorithm.version != MPAlgorithmDefaultVersion) {
user.algorithm = MPAlgorithmDefault;
user.keyID = [tryKey keyIDForAlgorithm:user.algorithm];
inf( @"Upgraded keyID to version %u for user: %@", user.algorithm.version, user.userID );
}
self.key = tryKey;
// Update the key chain if necessary.
[self storeSavedKeyFor:user];
}
@try {
if ([[MPConfig get].sendInfo boolValue]) {
#ifdef CRASHLYTICS
[[Crashlytics sharedInstance] setObjectValue:user.userID forKey:@"username"];
[[Crashlytics sharedInstance] setUserName:user.userID];
#endif
}
}
@catch (id exception) {
err( @"While setting username: %@", exception );
}
user.lastUsed = [NSDate date];
self.activeUser = user;
[moc saveToStore];
// Perform a data sanity check now that we're logged in as the user to allow fixes that require the user's key.
if ([[MPConfig get].checkInconsistency boolValue])
[MPAppDelegate_Shared managedObjectContextPerformBlockAndWait:^(NSManagedObjectContext *context) {
[self findAndFixInconsistenciesSaveInContext:context];
}];
[[NSNotificationCenter defaultCenter] postNotificationName:MPSignedInNotification object:self];
return YES;
}
- (void)migrateSitesForUser:(MPUserEntity *)user saveInContext:(NSManagedObjectContext *)moc toKey:(MPKey *)newKey {
if (![user.sites count])
// Nothing to migrate.
return;
MPKey *recoverKey = newKey;
#ifdef PEARL_UIKIT
PearlOverlay *activityOverlay = [PearlOverlay showProgressOverlayWithTitle:PearlString( @"Migrating %ld sites...",
(long)[user.sites count] )];
#endif
for (MPSiteEntity *site in user.sites) {
if (site.type & MPSiteTypeClassStored) {
NSString *content;
while (!(content = [site.algorithm storedPasswordForSite:(MPStoredSiteEntity *)site usingKey:recoverKey])) {
// Failed to decrypt site with the current recoveryKey. Ask user for a new one to use.
__block NSString *masterPassword = nil;
#ifdef PEARL_UIKIT
dispatch_group_t recoverPasswordGroup = dispatch_group_create();
dispatch_group_enter( recoverPasswordGroup );
[PearlAlert showAlertWithTitle:@"Enter Old Master Password"
message:PearlString( @"Your old master password is required to migrate the stored password for %@",
site.name )
viewStyle:UIAlertViewStyleSecureTextInput
initAlert:nil tappedButtonBlock:^(UIAlertView *alert_, NSInteger buttonIndex_) {
@try {
if (buttonIndex_ == [alert_ cancelButtonIndex])
// Don't Migrate
return;
masterPassword = [alert_ textFieldAtIndex:0].text;
}
@finally {
dispatch_group_leave( recoverPasswordGroup );
}
} cancelTitle:@"Don't Migrate" otherTitles:@"Migrate", nil];
dispatch_group_wait( recoverPasswordGroup, DISPATCH_TIME_FOREVER );
#endif
if (!masterPassword)
// Don't Migrate
break;
recoverKey = [[MPKey alloc] initForFullName:user.name withMasterPassword:masterPassword];
}
if (!content)
// Don't Migrate
break;
if (![recoverKey isEqualToKey:newKey])
[site.algorithm savePassword:content toSite:site usingKey:newKey];
}
}
[moc saveToStore];
#ifdef PEARL_UIKIT
[activityOverlay cancelOverlayAnimated:YES];
#endif
}
@end

View File

@@ -0,0 +1,28 @@
//
// MPAppDelegate_Shared.h
// MasterPassword
//
// Created by Maarten Billemont on 24/11/11.
// Copyright (c) 2011 Lyndir. All rights reserved.
//
#import "MPEntities.h"
#if TARGET_OS_IPHONE
@interface MPAppDelegate_Shared : PearlAppDelegate
#else
@interface MPAppDelegate_Shared : NSObject <PearlConfigDelegate>
#endif
@property(strong, nonatomic, readonly) MPKey *key;
@property(strong, nonatomic, readonly) NSManagedObjectID *activeUserOID;
@property(strong, nonatomic, readonly) NSPersistentStoreCoordinator *storeCoordinator;
+ (instancetype)get;
- (MPUserEntity *)activeUserForMainThread;
- (MPUserEntity *)activeUserInContext:(NSManagedObjectContext *)context;
- (void)setActiveUser:(MPUserEntity *)activeUser;
- (void)handleCoordinatorError:(NSError *)error;
@end

View File

@@ -0,0 +1,78 @@
//
// MPAppDelegate.m
// MasterPassword
//
// Created by Maarten Billemont on 24/11/11.
// Copyright (c) 2011 Lyndir. All rights reserved.
//
#import "MPAppDelegate_Shared.h"
#import "MPAppDelegate_Store.h"
#import "MPAppDelegate_Key.h"
#import "NSManagedObjectModel+KCOrderedAccessorFix.h"
@interface MPAppDelegate_Shared ()
@property(strong, nonatomic) MPKey *key;
@property(strong, nonatomic) NSManagedObjectID *activeUserOID;
@property(strong, nonatomic) NSPersistentStoreCoordinator *storeCoordinator;
@end
@implementation MPAppDelegate_Shared
+ (MPAppDelegate_Shared *)get {
#if TARGET_OS_IPHONE
return (MPAppDelegate_Shared *)UIApp.delegate;
#elif defined (__MAC_OS_X_VERSION_MIN_REQUIRED)
return (MPAppDelegate_Shared *)[NSApplication sharedApplication].delegate;
#else
#error Unsupported OS.
#endif
}
- (instancetype)init {
if (!(self = [super init]))
return nil;
NSManagedObjectModel *model = [NSManagedObjectModel mergedModelFromBundles:nil];
[model kc_generateOrderedSetAccessors];
self.storeCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];
return self;
}
- (MPUserEntity *)activeUserForMainThread {
return [self activeUserInContext:[MPAppDelegate_Shared managedObjectContextForMainThreadIfReady]];
}
- (MPUserEntity *)activeUserInContext:(NSManagedObjectContext *)context {
NSManagedObjectID *activeUserOID = self.activeUserOID;
if (!activeUserOID || !context)
return nil;
MPUserEntity *activeUser = [MPUserEntity existingObjectWithID:activeUserOID inContext:context];
if (!activeUser)
[self signOutAnimated:YES];
return activeUser;
}
- (void)setActiveUser:(MPUserEntity *)activeUser {
NSError *error;
if (activeUser.objectID.isTemporaryID && ![activeUser.managedObjectContext obtainPermanentIDsForObjects:@[ activeUser ] error:&error])
err(@"Failed to obtain a permanent object ID after setting active user: %@", [error fullDescription]);
self.activeUserOID = activeUser.objectID;
}
- (void)handleCoordinatorError:(NSError *)error {
}
@end

View File

@@ -0,0 +1,40 @@
//
// MPAppDelegate_Key.h
// MasterPassword
//
// Created by Maarten Billemont on 24/11/11.
// Copyright (c) 2011 Lyndir. All rights reserved.
//
#import "MPAppDelegate_Shared.h"
#import "MPFixable.h"
typedef NS_ENUM( NSUInteger, MPImportResult ) {
MPImportResultSuccess,
MPImportResultCancelled,
MPImportResultInvalidPassword,
MPImportResultMalformedInput,
MPImportResultInternalError,
};
@interface MPAppDelegate_Shared(Store)
+ (NSManagedObjectContext *)managedObjectContextForMainThreadIfReady;
+ (BOOL)managedObjectContextForMainThreadPerformBlock:(void (^)(NSManagedObjectContext *mainContext))mocBlock;
+ (BOOL)managedObjectContextForMainThreadPerformBlockAndWait:(void (^)(NSManagedObjectContext *mainContext))mocBlock;
+ (BOOL)managedObjectContextPerformBlock:(void (^)(NSManagedObjectContext *context))mocBlock;
+ (BOOL)managedObjectContextPerformBlockAndWait:(void (^)(NSManagedObjectContext *context))mocBlock;
- (MPFixableResult)findAndFixInconsistenciesSaveInContext:(NSManagedObjectContext *)context;
- (void)deleteAndResetStore;
/** @param completion The block to execute after adding the site, executed from the main thread with the new site in the main MOC. */
- (void)addSiteNamed:(NSString *)siteName completion:(void ( ^ )(MPSiteEntity *site, NSManagedObjectContext *context))completion;
- (MPSiteEntity *)changeSite:(MPSiteEntity *)site saveInContext:(NSManagedObjectContext *)context toType:(MPSiteType)type;
- (MPImportResult)importSites:(NSString *)importedSitesString
askImportPassword:(NSString *(^)(NSString *userName))importPassword
askUserPassword:(NSString *(^)(NSString *userName, NSUInteger importCount, NSUInteger deleteCount))userPassword;
- (NSString *)exportSitesRevealPasswords:(BOOL)revealPasswords;
@end

View File

@@ -0,0 +1,833 @@
//
// MPAppDelegate.m
// MasterPassword
//
// Created by Maarten Billemont on 24/11/11.
// Copyright (c) 2011 Lyndir. All rights reserved.
//
#import "MPAppDelegate_Store.h"
#if TARGET_OS_IPHONE
#define STORE_OPTIONS NSPersistentStoreFileProtectionKey : NSFileProtectionComplete,
#else
#define STORE_OPTIONS
#endif
#define MPMigrationLevelLocalStoreKey @"MPMigrationLevelLocalStoreKey"
typedef NS_ENUM( NSInteger, MPStoreMigrationLevel ) {
MPStoreMigrationLevelV1,
MPStoreMigrationLevelV2,
MPStoreMigrationLevelV3,
MPStoreMigrationLevelCurrent = MPStoreMigrationLevelV3,
};
@implementation MPAppDelegate_Shared(Store)
PearlAssociatedObjectProperty( NSOperationQueue *, StoreQueue, storeQueue );
PearlAssociatedObjectProperty( NSManagedObjectContext*, PrivateManagedObjectContext, privateManagedObjectContext );
PearlAssociatedObjectProperty( NSManagedObjectContext*, MainManagedObjectContext, mainManagedObjectContext );
PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
#pragma mark - Core Data setup
+ (NSManagedObjectContext *)managedObjectContextForMainThreadIfReady {
NSAssert( [[NSThread currentThread] isMainThread], @"Can only access main MOC from the main thread." );
NSManagedObjectContext *mainManagedObjectContext = [[self get] mainManagedObjectContextIfReady];
if (!mainManagedObjectContext || ![[NSThread currentThread] isMainThread])
return nil;
return mainManagedObjectContext;
}
+ (BOOL)managedObjectContextForMainThreadPerformBlock:(void ( ^ )(NSManagedObjectContext *mainContext))mocBlock {
NSManagedObjectContext *mainManagedObjectContext = [[self get] mainManagedObjectContextIfReady];
if (!mainManagedObjectContext)
return NO;
[mainManagedObjectContext performBlock:^{
@try {
mocBlock( mainManagedObjectContext );
}
@catch (id exception) {
err( @"While performing managed block:\n%@", [exception fullDescription] );
}
}];
return YES;
}
+ (BOOL)managedObjectContextForMainThreadPerformBlockAndWait:(void ( ^ )(NSManagedObjectContext *mainContext))mocBlock {
NSManagedObjectContext *mainManagedObjectContext = [[self get] mainManagedObjectContextIfReady];
if (!mainManagedObjectContext)
return NO;
[mainManagedObjectContext performBlockAndWait:^{
@try {
mocBlock( mainManagedObjectContext );
}
@catch (NSException *exception) {
err( @"While performing managed block:\n%@", [exception fullDescription] );
}
}];
return YES;
}
+ (BOOL)managedObjectContextPerformBlock:(void ( ^ )(NSManagedObjectContext *context))mocBlock {
NSManagedObjectContext *privateManagedObjectContextIfReady = [[self get] privateManagedObjectContextIfReady];
if (!privateManagedObjectContextIfReady)
return NO;
NSManagedObjectContext *moc = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
moc.parentContext = privateManagedObjectContextIfReady;
[moc performBlock:^{
@try {
mocBlock( moc );
}
@catch (NSException *exception) {
err( @"While performing managed block:\n%@", [exception fullDescription] );
}
}];
return YES;
}
+ (BOOL)managedObjectContextPerformBlockAndWait:(void ( ^ )(NSManagedObjectContext *context))mocBlock {
NSManagedObjectContext *privateManagedObjectContextIfReady = [[self get] privateManagedObjectContextIfReady];
if (!privateManagedObjectContextIfReady)
return NO;
NSManagedObjectContext *moc = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
moc.parentContext = privateManagedObjectContextIfReady;
[moc performBlockAndWait:^{
@try {
mocBlock( moc );
}
@catch (NSException *exception) {
err( @"While performing managed block:\n%@", [exception fullDescription] );
}
}];
return YES;
}
- (NSManagedObjectContext *)mainManagedObjectContextIfReady {
[self loadStore];
return self.mainManagedObjectContext;
}
- (NSManagedObjectContext *)privateManagedObjectContextIfReady {
[self loadStore];
return self.privateManagedObjectContext;
}
- (NSURL *)localStoreURL {
NSURL *applicationSupportURL = [[[NSFileManager defaultManager] URLsForDirectory:NSApplicationSupportDirectory
inDomains:NSUserDomainMask] lastObject];
return [[[applicationSupportURL
URLByAppendingPathComponent:[NSBundle mainBundle].bundleIdentifier isDirectory:YES]
URLByAppendingPathComponent:@"MasterPassword" isDirectory:NO]
URLByAppendingPathExtension:@"sqlite"];
}
- (void)loadStore {
static dispatch_once_t once = 0;
dispatch_once( &once, ^{
(self.storeQueue = [NSOperationQueue new]).maxConcurrentOperationCount = 1;
} );
// Do nothing if already fully set up, otherwise (re-)load the store.
if (self.storeCoordinator && self.mainManagedObjectContext && self.privateManagedObjectContext)
return;
[self.storeQueue addOperationWithBlock:^{
// Do nothing if already fully set up, otherwise (re-)load the store.
if (self.storeCoordinator && self.mainManagedObjectContext && self.privateManagedObjectContext)
return;
// Unregister any existing observers and contexts.
PearlRemoveNotificationObserversFrom( self.mainManagedObjectContext );
[self.mainManagedObjectContext performBlockAndWait:^{
[self.mainManagedObjectContext reset];
self.mainManagedObjectContext = nil;
}];
[self.privateManagedObjectContext performBlockAndWait:^{
[self.privateManagedObjectContext reset];
self.privateManagedObjectContext = nil;
}];
// Don't load when the store is corrupted.
if ([self.storeCorrupted boolValue])
return;
// Check if migration is necessary.
[self migrateStore];
// Install managed object contexts and observers.
self.privateManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[self.privateManagedObjectContext performBlockAndWait:^{
self.privateManagedObjectContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy;
self.privateManagedObjectContext.persistentStoreCoordinator = self.storeCoordinator;
}];
self.mainManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
self.mainManagedObjectContext.parentContext = self.privateManagedObjectContext;
// When privateManagedObjectContext is saved, import the changes into mainManagedObjectContext.
PearlAddNotificationObserverTo( self.mainManagedObjectContext, NSManagedObjectContextDidSaveNotification,
self.privateManagedObjectContext, nil, ^(NSManagedObjectContext *mainManagedObjectContext, NSNotification *note) {
[mainManagedObjectContext performBlock:^{
@try {
[mainManagedObjectContext mergeChangesFromContextDidSaveNotification:note];
}
@catch (NSException *exception) {
err( @"While merging changes:\n%@", [exception fullDescription] );
}
}];
} );
// Create a new store coordinator.
NSError *error = nil;
NSURL *localStoreURL = [self localStoreURL];
if (![[NSFileManager defaultManager] createDirectoryAtURL:[localStoreURL URLByDeletingLastPathComponent]
withIntermediateDirectories:YES attributes:nil error:&error]) {
err( @"Couldn't create our application support directory: %@", [error fullDescription] );
return;
}
if (![self.storeCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:[self localStoreURL]
options:@{
NSMigratePersistentStoresAutomaticallyOption : @YES,
NSInferMappingModelAutomaticallyOption : @YES,
STORE_OPTIONS
} error:&error]) {
err( @"Failed to open store: %@", [error fullDescription] );
self.storeCorrupted = @YES;
[self handleCoordinatorError:error];
return;
}
self.storeCorrupted = @NO;
#if TARGET_OS_IPHONE
PearlAddNotificationObserver( UIApplicationWillResignActiveNotification, UIApp, [NSOperationQueue mainQueue],
#else
PearlAddNotificationObserver( NSApplicationWillResignActiveNotification, NSApp, [NSOperationQueue mainQueue],
#endif
^(MPAppDelegate_Shared *self, NSNotification *note) {
[self.mainManagedObjectContext saveToStore];
} );
// Perform a data sanity check on the newly loaded store to find and fix any issues.
if ([[MPConfig get].checkInconsistency boolValue])
[MPAppDelegate_Shared managedObjectContextPerformBlockAndWait:^(NSManagedObjectContext *context) {
[self findAndFixInconsistenciesSaveInContext:context];
}];
}];
}
- (void)deleteAndResetStore {
@synchronized (self) {
// Unregister any existing observers and contexts.
PearlRemoveNotificationObserversFrom( self.mainManagedObjectContext );
[self.mainManagedObjectContext performBlockAndWait:^{
[self.mainManagedObjectContext reset];
self.mainManagedObjectContext = nil;
}];
[self.privateManagedObjectContext performBlockAndWait:^{
[self.privateManagedObjectContext reset];
self.privateManagedObjectContext = nil;
}];
NSError *error = nil;
for (NSPersistentStore *store in self.storeCoordinator.persistentStores) {
if (![self.storeCoordinator removePersistentStore:store error:&error])
err( @"Couldn't remove persistence store from coordinator: %@", [error fullDescription] );
}
if (![[NSFileManager defaultManager] removeItemAtURL:self.localStoreURL error:&error])
err( @"Couldn't remove persistence store at URL %@: %@", self.localStoreURL, [error fullDescription] );
[self loadStore];
}
}
- (MPFixableResult)findAndFixInconsistenciesSaveInContext:(NSManagedObjectContext *)context {
NSError *error = nil;
NSFetchRequest *fetchRequest = [NSFetchRequest new];
fetchRequest.fetchBatchSize = 50;
MPFixableResult result = MPFixableResultNoProblems;
for (NSEntityDescription *entity in [context.persistentStoreCoordinator.managedObjectModel entities])
if (class_conformsToProtocol( NSClassFromString( entity.managedObjectClassName ), @protocol(MPFixable) )) {
fetchRequest.entity = entity;
NSArray *objects = [context executeFetchRequest:fetchRequest error:&error];
if (!objects) {
err( @"Failed to fetch %@ objects: %@", entity, [error fullDescription] );
continue;
}
for (NSManagedObject<MPFixable> *object in objects)
result = MPApplyFix( result, ^MPFixableResult {
return [object findAndFixInconsistenciesInContext:context];
} );
}
if (result == MPFixableResultNoProblems)
inf( @"Sanity check found no problems in store." );
else {
[context saveToStore];
[[NSNotificationCenter defaultCenter] postNotificationName:MPFoundInconsistenciesNotification object:nil userInfo:@{
MPInconsistenciesFixResultUserKey : @(result)
}];
}
return result;
}
- (void)migrateStore {
MPStoreMigrationLevel migrationLevel = (signed)[[NSUserDefaults standardUserDefaults] integerForKey:MPMigrationLevelLocalStoreKey];
if (migrationLevel >= MPStoreMigrationLevelCurrent)
// Local store up-to-date.
return;
inf( @"Local store migration level: %d (current %d)", (signed)migrationLevel, (signed)MPStoreMigrationLevelCurrent );
if (migrationLevel <= MPStoreMigrationLevelV1 && ![self migrateV1LocalStore]) {
inf( @"Failed to migrate old V1 to new local store." );
return;
}
if (migrationLevel <= MPStoreMigrationLevelV2 && ![self migrateV2LocalStore]) {
inf( @"Failed to migrate old V2 to new local store." );
return;
}
[[NSUserDefaults standardUserDefaults] setInteger:MPStoreMigrationLevelCurrent forKey:MPMigrationLevelLocalStoreKey];
inf( @"Successfully migrated old to new local store." );
if (![[NSUserDefaults standardUserDefaults] synchronize])
wrn( @"Couldn't synchronize after store migration." );
}
- (BOOL)migrateV1LocalStore {
NSURL *applicationFilesDirectory = [[[NSFileManager defaultManager]
URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
NSURL *oldLocalStoreURL = [[applicationFilesDirectory
URLByAppendingPathComponent:@"MasterPassword" isDirectory:NO] URLByAppendingPathExtension:@"sqlite"];
if (![[NSFileManager defaultManager] fileExistsAtPath:oldLocalStoreURL.path isDirectory:NULL]) {
inf( @"No V1 local store to migrate." );
return YES;
}
inf( @"Migrating V1 local store" );
NSURL *newLocalStoreURL = [self localStoreURL];
if (![[NSFileManager defaultManager] fileExistsAtPath:newLocalStoreURL.path isDirectory:NULL]) {
inf( @"New local store already exists." );
return YES;
}
NSError *error = nil;
if (![NSPersistentStore migrateStore:oldLocalStoreURL withOptions:@{ STORE_OPTIONS }
toStore:newLocalStoreURL withOptions:@{ STORE_OPTIONS }
model:nil error:&error]) {
err( @"Couldn't migrate the old store to the new location: %@", [error fullDescription] );
return NO;
}
return YES;
}
- (BOOL)migrateV2LocalStore {
NSURL *applicationSupportURL = [[[NSFileManager defaultManager] URLsForDirectory:NSApplicationSupportDirectory
inDomains:NSUserDomainMask] lastObject];
NSURL *oldLocalStoreURL;
// On iOS, each app is in a sandbox so we don't need to app-scope this directory.
#if TARGET_OS_IPHONE
oldLocalStoreURL = [[applicationSupportURL
URLByAppendingPathComponent:@"UbiquityStore" isDirectory:NO]
URLByAppendingPathExtension:@"sqlite"];
#else
// The directory is shared between all apps on the system so we need to scope it for the running app.
oldLocalStoreURL = [[[applicationSupportURL
URLByAppendingPathComponent:[NSRunningApplication currentApplication].bundleIdentifier isDirectory:YES]
URLByAppendingPathComponent:@"UbiquityStore" isDirectory:NO]
URLByAppendingPathExtension:@"sqlite"];
#endif
if (![[NSFileManager defaultManager] fileExistsAtPath:oldLocalStoreURL.path isDirectory:NULL]) {
inf( @"No V2 local store to migrate." );
return YES;
}
inf( @"Migrating V2 local store" );
NSURL *newLocalStoreURL = [self localStoreURL];
if ([[NSFileManager defaultManager] fileExistsAtPath:newLocalStoreURL.path isDirectory:NULL]) {
inf( @"New local store already exists." );
return YES;
}
NSError *error = nil;
if (![NSPersistentStore migrateStore:oldLocalStoreURL withOptions:@{
NSMigratePersistentStoresAutomaticallyOption : @YES,
NSInferMappingModelAutomaticallyOption : @YES,
STORE_OPTIONS
} toStore:newLocalStoreURL withOptions:@{
NSMigratePersistentStoresAutomaticallyOption : @YES,
NSInferMappingModelAutomaticallyOption : @YES,
STORE_OPTIONS
} model:nil error:&error]) {
err( @"Couldn't migrate the old store to the new location: %@", [error fullDescription] );
return NO;
}
return YES;
}
#pragma mark - Utilities
- (void)addSiteNamed:(NSString *)siteName completion:(void ( ^ )(MPSiteEntity *site, NSManagedObjectContext *context))completion {
if (![siteName length]) {
completion( nil, nil );
return;
}
[MPAppDelegate_Shared managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
MPUserEntity *activeUser = [self activeUserInContext:context];
NSAssert( activeUser, @"Missing user." );
if (!activeUser) {
completion( nil, nil );
return;
}
MPSiteType type = activeUser.defaultType;
id<MPAlgorithm> algorithm = MPAlgorithmDefault;
Class entityType = [algorithm classOfType:type];
MPSiteEntity *site = (MPSiteEntity *)[entityType insertNewObjectInContext:context];
site.name = siteName;
site.user = activeUser;
site.type = type;
site.lastUsed = [NSDate date];
site.algorithm = algorithm;
NSError *error = nil;
if (site.objectID.isTemporaryID && ![context obtainPermanentIDsForObjects:@[ site ] error:&error])
err( @"Failed to obtain a permanent object ID after creating new site: %@", [error fullDescription] );
[context saveToStore];
completion( site, context );
}];
}
- (MPSiteEntity *)changeSite:(MPSiteEntity *)site saveInContext:(NSManagedObjectContext *)context toType:(MPSiteType)type {
if (site.type == type)
return site;
if ([site.algorithm classOfType:type] == site.typeClass) {
site.type = type;
[context saveToStore];
}
else {
// Type requires a different class of site. Recreate the site.
Class entityType = [site.algorithm classOfType:type];
MPSiteEntity *newSite = (MPSiteEntity *)[entityType insertNewObjectInContext:context];
newSite.type = type;
newSite.name = site.name;
newSite.user = site.user;
newSite.uses = site.uses;
newSite.lastUsed = site.lastUsed;
newSite.algorithm = site.algorithm;
newSite.loginName = site.loginName;
NSError *error = nil;
if (![context obtainPermanentIDsForObjects:@[ newSite ] error:&error])
err( @"Failed to obtain a permanent object ID after changing object type: %@", [error fullDescription] );
[context deleteObject:site];
[context saveToStore];
[[NSNotificationCenter defaultCenter] postNotificationName:MPSiteUpdatedNotification object:site.objectID];
site = newSite;
}
[[NSNotificationCenter defaultCenter] postNotificationName:MPSiteUpdatedNotification object:site.objectID];
return site;
}
- (MPImportResult)importSites:(NSString *)importedSitesString
askImportPassword:(NSString *( ^ )(NSString *userName))importPassword
askUserPassword:(NSString *( ^ )(NSString *userName, NSUInteger importCount, NSUInteger deleteCount))userPassword {
NSAssert( ![[NSThread currentThread] isMainThread], @"This method should not be invoked from the main thread." );
__block MPImportResult result = MPImportResultCancelled;
do {
if ([MPAppDelegate_Shared managedObjectContextPerformBlockAndWait:^(NSManagedObjectContext *context) {
result = [self importSites:importedSitesString askImportPassword:importPassword askUserPassword:userPassword
saveInContext:context];
}])
break;
usleep( (useconds_t)(USEC_PER_SEC * 0.2) );
} while (YES);
return result;
}
- (MPImportResult)importSites:(NSString *)importedSitesString
askImportPassword:(NSString *( ^ )(NSString *userName))askImportPassword
askUserPassword:(NSString *( ^ )(NSString *userName, NSUInteger importCount, NSUInteger deleteCount))askUserPassword
saveInContext:(NSManagedObjectContext *)context {
// Compile patterns.
static NSRegularExpression *headerPattern;
static NSArray *sitePatterns;
NSError *error = nil;
if (!headerPattern) {
headerPattern = [[NSRegularExpression alloc]
initWithPattern:@"^#[[:space:]]*([^:]+): (.*)"
options:(NSRegularExpressionOptions)0 error:&error];
if (error) {
err( @"Error loading the header pattern: %@", [error fullDescription] );
return MPImportResultInternalError;
}
}
if (!sitePatterns) {
sitePatterns = @[
[[NSRegularExpression alloc] // Format 0
initWithPattern:@"^([^ ]+) +([[:digit:]]+) +([[:digit:]]+)(:[[:digit:]]+)? +([^\t]+)\t(.*)"
options:(NSRegularExpressionOptions)0 error:&error],
[[NSRegularExpression alloc] // Format 1
initWithPattern:@"^([^ ]+) +([[:digit:]]+) +([[:digit:]]+)(:[[:digit:]]+)?(:[[:digit:]]+)? +([^\t]*)\t *([^\t]+)\t(.*)"
options:(NSRegularExpressionOptions)0 error:&error]
];
if (error) {
err( @"Error loading the site patterns: %@", [error fullDescription] );
return MPImportResultInternalError;
}
}
// Parse import data.
inf( @"Importing sites." );
__block MPUserEntity *user = nil;
id<MPAlgorithm> importAlgorithm = nil;
NSUInteger importFormat = 0;
NSUInteger importAvatar = NSNotFound;
NSString *importBundleVersion = nil, *importUserName = nil;
NSData *importKeyID = nil;
BOOL headerStarted = NO, headerEnded = NO, clearText = NO;
NSArray *importedSiteLines = [importedSitesString componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]];
NSMutableSet *sitesToDelete = [NSMutableSet set];
NSMutableArray *importedSiteSites = [NSMutableArray arrayWithCapacity:[importedSiteLines count]];
NSFetchRequest *siteFetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPSiteEntity class] )];
for (NSString *importedSiteLine in importedSiteLines) {
if ([importedSiteLine hasPrefix:@"#"]) {
// Comment or header
if (!headerStarted) {
if ([importedSiteLine isEqualToString:@"##"])
headerStarted = YES;
continue;
}
if (headerEnded)
continue;
if ([importedSiteLine isEqualToString:@"##"]) {
headerEnded = YES;
continue;
}
// Header
if ([headerPattern numberOfMatchesInString:importedSiteLine options:(NSMatchingOptions)0
range:NSMakeRange( 0, [importedSiteLine length] )] != 1) {
err( @"Invalid header format in line: %@", importedSiteLine );
return MPImportResultMalformedInput;
}
NSTextCheckingResult *headerSites = [[headerPattern matchesInString:importedSiteLine options:(NSMatchingOptions)0
range:NSMakeRange( 0, [importedSiteLine length] )] lastObject];
NSString *headerName = [importedSiteLine substringWithRange:[headerSites rangeAtIndex:1]];
NSString *headerValue = [importedSiteLine substringWithRange:[headerSites rangeAtIndex:2]];
if ([headerName isEqualToString:@"User Name"]) {
importUserName = headerValue;
NSFetchRequest *userFetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPUserEntity class] )];
userFetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@", importUserName];
NSArray *users = [context executeFetchRequest:userFetchRequest error:&error];
if (!users) {
err( @"While looking for user: %@, error: %@", importUserName, [error fullDescription] );
return MPImportResultInternalError;
}
if ([users count] > 1) {
err( @"While looking for user: %@, found more than one: %lu", importUserName, (unsigned long)[users count] );
return MPImportResultInternalError;
}
user = [users lastObject];
dbg( @"Existing user? %@", [user debugDescription] );
}
if ([headerName isEqualToString:@"Key ID"])
importKeyID = [headerValue decodeHex];
if ([headerName isEqualToString:@"Version"]) {
importBundleVersion = headerValue;
importAlgorithm = MPAlgorithmDefaultForBundleVersion( importBundleVersion );
}
if ([headerName isEqualToString:@"Format"]) {
importFormat = (NSUInteger)[headerValue integerValue];
if (importFormat >= [sitePatterns count]) {
err( @"Unsupported import format: %lu", (unsigned long)importFormat );
return MPImportResultInternalError;
}
}
if ([headerName isEqualToString:@"Avatar"])
importAvatar = (NSUInteger)[headerValue integerValue];
if ([headerName isEqualToString:@"Passwords"]) {
if ([headerValue isEqualToString:@"VISIBLE"])
clearText = YES;
}
continue;
}
if (!headerEnded)
continue;
if (![importUserName length])
return MPImportResultMalformedInput;
if (![importedSiteLine length])
continue;
// Site
NSRegularExpression *sitePattern = sitePatterns[importFormat];
if ([sitePattern numberOfMatchesInString:importedSiteLine options:(NSMatchingOptions)0
range:NSMakeRange( 0, [importedSiteLine length] )] != 1) {
err( @"Invalid site format in line: %@", importedSiteLine );
return MPImportResultMalformedInput;
}
NSTextCheckingResult *siteElements = [[sitePattern matchesInString:importedSiteLine options:(NSMatchingOptions)0
range:NSMakeRange( 0, [importedSiteLine length] )] lastObject];
NSString *lastUsed, *uses, *type, *version, *counter, *siteName, *loginName, *exportContent;
switch (importFormat) {
case 0:
lastUsed = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:1]];
uses = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:2]];
type = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:3]];
version = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:4]];
if ([version length])
version = [version substringFromIndex:1]; // Strip the leading colon.
counter = @"";
loginName = @"";
siteName = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:5]];
exportContent = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:6]];
break;
case 1:
lastUsed = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:1]];
uses = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:2]];
type = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:3]];
version = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:4]];
if ([version length])
version = [version substringFromIndex:1]; // Strip the leading colon.
counter = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:5]];
if ([counter length])
counter = [counter substringFromIndex:1]; // Strip the leading colon.
loginName = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:6]];
siteName = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:7]];
exportContent = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:8]];
break;
default:
err( @"Unexpected import format: %lu", (unsigned long)importFormat );
return MPImportResultInternalError;
}
// Find existing site.
if (user) {
siteFetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@ AND user == %@", siteName, user];
NSArray *existingSites = [context executeFetchRequest:siteFetchRequest error:&error];
if (!existingSites) {
err( @"Lookup of existing sites failed for site: %@, user: %@, error: %@", siteName, user.userID, [error fullDescription] );
return MPImportResultInternalError;
}
if ([existingSites count]) {
dbg( @"Existing sites: %@", existingSites );
[sitesToDelete addObjectsFromArray:existingSites];
}
}
[importedSiteSites addObject:@[ lastUsed, uses, type, version, counter, loginName, siteName, exportContent ]];
dbg( @"Will import site: lastUsed=%@, uses=%@, type=%@, version=%@, counter=%@, loginName=%@, siteName=%@, exportContent=%@",
lastUsed, uses, type, version, counter, loginName, siteName, exportContent );
}
// Ask for confirmation to import these sites and the master password of the user.
inf( @"Importing %lu sites, deleting %lu sites, for user: %@", (unsigned long)[importedSiteSites count],
(unsigned long)[sitesToDelete count], [MPUserEntity idFor:importUserName] );
NSString *userMasterPassword = askUserPassword( user? user.name: importUserName, [importedSiteSites count],
[sitesToDelete count] );
if (!userMasterPassword) {
inf( @"Import cancelled." );
return MPImportResultCancelled;
}
MPKey *userKey = [[MPKey alloc] initForFullName:user? user.name: importUserName withMasterPassword:userMasterPassword];
if (user && ![[userKey keyIDForAlgorithm:user.algorithm] isEqualToData:user.keyID])
return MPImportResultInvalidPassword;
__block MPKey *importKey = userKey;
if (importKeyID && ![[importKey keyIDForAlgorithm:importAlgorithm] isEqualToData:importKeyID])
importKey = [[MPKey alloc] initForFullName:importUserName withMasterPassword:askImportPassword( importUserName )];
if (importKeyID && ![[importKey keyIDForAlgorithm:importAlgorithm] isEqualToData:importKeyID])
return MPImportResultInvalidPassword;
// Delete existing sites.
if (sitesToDelete.count)
[sitesToDelete enumerateObjectsUsingBlock:^(id obj, BOOL *stop) {
inf( @"Deleting site: %@, it will be replaced by an imported site.", [obj name] );
[context deleteObject:obj];
}];
// Make sure there is a user.
if (user) {
if (importAvatar != NSNotFound)
user.avatar = importAvatar;
dbg( @"Updating User: %@", [user debugDescription] );
}
else {
user = [MPUserEntity insertNewObjectInContext:context];
user.name = importUserName;
user.algorithm = MPAlgorithmDefault;
user.keyID = [userKey keyIDForAlgorithm:user.algorithm];
if (importAvatar != NSNotFound)
user.avatar = importAvatar;
dbg( @"Created User: %@", [user debugDescription] );
}
// Import new sites.
for (NSArray *siteElements in importedSiteSites) {
NSDate *lastUsed = [[NSDateFormatter rfc3339DateFormatter] dateFromString:siteElements[0]];
NSUInteger uses = (unsigned)[siteElements[1] integerValue];
MPSiteType type = (MPSiteType)[siteElements[2] integerValue];
MPAlgorithmVersion version = (MPAlgorithmVersion)[siteElements[3] integerValue];
NSUInteger counter = [siteElements[4] length]? (unsigned)[siteElements[4] integerValue]: NSNotFound;
NSString *loginName = [siteElements[5] length]? siteElements[5]: nil;
NSString *siteName = siteElements[6];
NSString *exportContent = siteElements[7];
// Create new site.
id<MPAlgorithm> algorithm = MPAlgorithmForVersion( version );
Class entityType = [algorithm classOfType:type];
if (!entityType) {
err( @"Invalid site type in import file: %@ has type %lu", siteName, (long)type );
return MPImportResultInternalError;
}
MPSiteEntity *site = (MPSiteEntity *)[entityType insertNewObjectInContext:context];
site.name = siteName;
site.loginName = loginName;
site.user = user;
site.type = type;
site.uses = uses;
site.lastUsed = lastUsed;
site.algorithm = algorithm;
if ([exportContent length]) {
if (clearText)
[site.algorithm importClearTextPassword:exportContent intoSite:site usingKey:userKey];
else
[site.algorithm importProtectedPassword:exportContent protectedByKey:importKey intoSite:site usingKey:userKey];
}
if ([site isKindOfClass:[MPGeneratedSiteEntity class]] && counter != NSNotFound)
((MPGeneratedSiteEntity *)site).counter = counter;
dbg( @"Created Site: %@", [site debugDescription] );
}
if (![context saveToStore])
return MPImportResultInternalError;
inf( @"Import completed successfully." );
[[NSNotificationCenter defaultCenter] postNotificationName:MPSitesImportedNotification object:nil userInfo:@{
MPSitesImportedNotificationUserKey : user
}];
return MPImportResultSuccess;
}
- (NSString *)exportSitesRevealPasswords:(BOOL)revealPasswords {
MPUserEntity *activeUser = [self activeUserForMainThread];
inf( @"Exporting sites, %@, for user: %@", revealPasswords? @"revealing passwords": @"omitting passwords", activeUser.userID );
// Header.
NSMutableString *export = [NSMutableString new];
[export appendFormat:@"# Master Password site export\n"];
if (revealPasswords)
[export appendFormat:@"# Export of site names and passwords in clear-text.\n"];
else
[export appendFormat:@"# Export of site names and stored passwords (unless device-private) encrypted with the master key.\n"];
[export appendFormat:@"# \n"];
[export appendFormat:@"##\n"];
[export appendFormat:@"# User Name: %@\n", activeUser.name];
[export appendFormat:@"# Avatar: %lu\n", (unsigned long)activeUser.avatar];
[export appendFormat:@"# Key ID: %@\n", [activeUser.keyID encodeHex]];
[export appendFormat:@"# Date: %@\n", [[NSDateFormatter rfc3339DateFormatter] stringFromDate:[NSDate date]]];
[export appendFormat:@"# Version: %@\n", [PearlInfoPlist get].CFBundleVersion];
[export appendFormat:@"# Format: 1\n"];
if (revealPasswords)
[export appendFormat:@"# Passwords: VISIBLE\n"];
else
[export appendFormat:@"# Passwords: PROTECTED\n"];
[export appendFormat:@"##\n"];
[export appendFormat:@"#\n"];
[export appendFormat:@"# Last Times Password Login\t Site\tSite\n"];
[export appendFormat:@"# used used type name\t name\tpassword\n"];
// Sites.
for (MPSiteEntity *site in activeUser.sites) {
NSDate *lastUsed = site.lastUsed;
NSUInteger uses = site.uses;
MPSiteType type = site.type;
id<MPAlgorithm> algorithm = site.algorithm;
NSUInteger counter = 0;
NSString *loginName = site.loginName;
NSString *siteName = site.name;
NSString *content = nil;
// Generated-specific
if ([site isKindOfClass:[MPGeneratedSiteEntity class]])
counter = ((MPGeneratedSiteEntity *)site).counter;
// Determine the content to export.
if (!(type & MPSiteFeatureDevicePrivate)) {
if (revealPasswords)
content = [site.algorithm resolvePasswordForSite:site usingKey:self.key];
else if (type & MPSiteFeatureExportContent)
content = [site.algorithm exportPasswordForSite:site usingKey:self.key];
}
NSString *lastUsedExport = [[NSDateFormatter rfc3339DateFormatter] stringFromDate:lastUsed];
long usesExport = (long)uses;
NSString *typeExport = strf( @"%lu:%lu:%lu", (long)type, (long)[algorithm version], (long)counter );
NSString *loginNameExport = loginName?: @"";
NSString *contentExport = content?: @"";
[export appendFormat:@"%@ %8ld %8S %25S\t%25S\t%@\n",
lastUsedExport, usesExport,
(const unsigned short *)[typeExport cStringUsingEncoding:NSUTF16StringEncoding],
(const unsigned short *)[loginNameExport cStringUsingEncoding:NSUTF16StringEncoding],
(const unsigned short *)[siteName cStringUsingEncoding:NSUTF16StringEncoding],
contentExport];
}
return export;
}
@end

View File

@@ -0,0 +1,21 @@
//
// MPConfig.h
// MasterPassword
//
// Created by Maarten Billemont on 02/01/12.
// Copyright (c) 2012 Lyndir. All rights reserved.
//
#import "Pearl.h"
@interface MPConfig : PearlConfig
@property(nonatomic, retain) NSNumber *sendInfo;
@property(nonatomic, retain) NSNumber *rememberLogin;
@property(nonatomic, retain) NSNumber *hidePasswords;
@property(nonatomic, retain) NSNumber *checkInconsistency;
@property(nonatomic, strong) NSNumber *siteAttacker;
@end

View File

@@ -0,0 +1,35 @@
//
// MPConfig.m
// MasterPassword
//
// Created by Maarten Billemont on 02/01/12.
// Copyright (c) 2012 Lyndir. All rights reserved.
//
#import "MPAppDelegate_Shared.h"
@implementation MPConfig
@dynamic sendInfo, rememberLogin, checkInconsistency, hidePasswords, siteAttacker;
- (id)init {
if (!(self = [super init]))
return nil;
[self.defaults registerDefaults:@{
NSStringFromSelector( @selector( askForReviews ) ) : @YES,
NSStringFromSelector( @selector( sendInfo ) ) : @NO,
NSStringFromSelector( @selector( rememberLogin ) ) : @NO,
NSStringFromSelector( @selector( hidePasswords ) ) : @NO,
NSStringFromSelector( @selector( checkInconsistency ) ) : @NO,
NSStringFromSelector( @selector( siteAttacker ) ) : @(MPAttacker1),
}];
self.delegate = [MPAppDelegate_Shared get];
return self;
}
@end

View File

@@ -0,0 +1,73 @@
//
// MPEntities.h
// MasterPassword-iOS
//
// Created by Maarten Billemont on 31/05/12.
// Copyright (c) 2012 Lyndir. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "MPSiteEntity.h"
#import "MPStoredSiteEntity.h"
#import "MPGeneratedSiteEntity.h"
#import "MPUserEntity.h"
#import "MPAlgorithm.h"
#import "MPFixable.h"
#define MPAvatarCount 19
@interface NSManagedObjectContext(MP)
- (BOOL)saveToStore;
@end
@interface MPSiteQuestionEntity(MP)
- (NSString *)resolveQuestionAnswerUsingKey:(MPKey *)key;
- (void)resolveQuestionAnswerUsingKey:(MPKey *)key result:(void ( ^ )(NSString *))result;
@end
@interface MPSiteEntity(MP)<MPFixable>
@property(assign) BOOL loginGenerated;
@property(assign) MPSiteType type;
@property(readonly) NSString *typeName;
@property(readonly) NSString *typeShortName;
@property(readonly) NSString *typeClassName;
@property(readonly) Class typeClass;
@property(assign) NSUInteger uses;
@property(assign) BOOL requiresExplicitMigration;
@property(strong) id<MPAlgorithm> algorithm;
- (NSUInteger)use;
- (BOOL)tryMigrateExplicitly:(BOOL)explicit;
- (NSString *)resolveLoginUsingKey:(MPKey *)key;
- (NSString *)resolvePasswordUsingKey:(MPKey *)key;
- (NSString *)resolveSiteAnswerUsingKey:(MPKey *)key;
- (void)resolveLoginUsingKey:(MPKey *)key result:(void ( ^ )(NSString *))result;
- (void)resolvePasswordUsingKey:(MPKey *)key result:(void ( ^ )(NSString *))result;
- (void)resolveSiteAnswerUsingKey:(MPKey *)key result:(void ( ^ )(NSString *))result;
- (void)resolveAnswerUsingKey:(MPKey *)key forQuestion:(NSString *)question result:(void ( ^ )(NSString *))result;
@end
@interface MPGeneratedSiteEntity(MP)
@property(assign) NSUInteger counter;
@end
@interface MPUserEntity(MP)
@property(assign) NSUInteger avatar;
@property(assign) BOOL saveKey;
@property(assign) BOOL touchID;
@property(assign) MPSiteType defaultType;
@property(readonly) NSString *userID;
@property(strong) id<MPAlgorithm> algorithm;
+ (NSString *)idFor:(NSString *)userName;
@end

View File

@@ -0,0 +1,364 @@
//
// MPEntities.m
// MasterPassword-iOS
//
// Created by Maarten Billemont on 31/05/12.
// Copyright (c) 2012 Lyndir. All rights reserved.
//
#import "MPEntities.h"
#import "MPAppDelegate_Shared.h"
#import "MPAppDelegate_Key.h"
#import "MPAppDelegate_InApp.h"
@implementation NSManagedObjectContext(MP)
- (BOOL)saveToStore {
__block BOOL success = YES;
if ([self hasChanges]) {
[self performBlockAndWait:^{
@try {
NSError *error = nil;
if (!(success = [self save:&error]))
err( @"While saving: %@", [error fullDescription] );
}
@catch (NSException *exception) {
success = NO;
err( @"While saving: %@", [exception fullDescription] );
}
}];
}
return success && (!self.parentContext || [self.parentContext saveToStore]);
}
@end
@implementation MPSiteQuestionEntity(MP)
- (NSString *)resolveQuestionAnswerUsingKey:(MPKey *)key {
return [self.site.algorithm resolveAnswerForQuestion:self usingKey:key];
}
- (void)resolveQuestionAnswerUsingKey:(MPKey *)key result:(void ( ^ )(NSString *))result {
[self.site.algorithm resolveAnswerForQuestion:self usingKey:key result:result];
}
@end
@implementation MPSiteEntity(MP)
- (MPFixableResult)findAndFixInconsistenciesInContext:(NSManagedObjectContext *)context {
return MPFixableResultNoProblems;
}
- (MPSiteType)type {
return (MPSiteType)[self.type_ unsignedIntegerValue];
}
- (void)setLoginGenerated:(BOOL)aLoginGenerated {
self.loginGenerated_ = @(aLoginGenerated);
}
- (BOOL)loginGenerated {
return [self.loginGenerated_ boolValue];
}
- (void)setType:(MPSiteType)aType {
self.type_ = @(aType);
}
- (NSString *)typeName {
return [self.algorithm nameOfType:self.type];
}
- (NSString *)typeShortName {
return [self.algorithm shortNameOfType:self.type];
}
- (NSString *)typeClassName {
return [self.algorithm classNameOfType:self.type];
}
- (Class)typeClass {
return [self.algorithm classOfType:self.type];
}
- (NSUInteger)uses {
return [self.uses_ unsignedIntegerValue];
}
- (void)setUses:(NSUInteger)anUses {
self.uses_ = @(anUses);
}
- (id<MPAlgorithm>)algorithm {
return MPAlgorithmForVersion(
MIN( MPAlgorithmVersionCurrent,
MAX( MPAlgorithmVersion0, (MPAlgorithmVersion)[self.version_ unsignedIntegerValue] ) ) );
}
- (void)setAlgorithm:(id<MPAlgorithm>)algorithm {
self.version_ = @([algorithm version]);
}
- (BOOL)requiresExplicitMigration {
return [self.requiresExplicitMigration_ boolValue];
}
- (void)setRequiresExplicitMigration:(BOOL)requiresExplicitMigration {
self.requiresExplicitMigration_ = @(requiresExplicitMigration);
}
- (NSUInteger)use {
self.lastUsed = [NSDate date];
return ++self.uses;
}
- (NSString *)description {
return strf( @"%@:%@", [self class], [self name] );
}
- (NSString *)debugDescription {
__block NSString *debugDescription = strf( @"{%@: [recursing]}", [self class] );
static BOOL recursing = NO;
PearlIfNotRecursing( &recursing, ^{
@try {
debugDescription = strf(
@"{%@: name=%@, user=%@, type=%lu, uses=%ld, lastUsed=%@, version=%ld, loginName=%@, requiresExplicitMigration=%d}",
NSStringFromClass( [self class] ), self.name, self.user.name, (long)self.type, (long)self.uses, self.lastUsed,
(long)[self.algorithm version],
self.loginName, self.requiresExplicitMigration );
}
@catch (NSException *exception) {
debugDescription = strf( @"{%@: inaccessible: %@}",
NSStringFromClass( [self class] ), [exception fullDescription] );
}
} );
return debugDescription;
}
- (BOOL)tryMigrateExplicitly:(BOOL)explicit {
MPAlgorithmVersion algorithmVersion;
while ((algorithmVersion = [self.algorithm version]) < MPAlgorithmDefaultVersion) {
MPAlgorithmVersion toVersion = algorithmVersion + 1;
if (![MPAlgorithmForVersion( toVersion ) tryMigrateSite:self explicit:explicit]) {
wrn( @"%@ migration to version: %ld failed for site: %@",
explicit? @"Explicit": @"Automatic", (long)toVersion, self );
return NO;
}
inf( @"%@ migration to version: %ld succeeded for site: %@",
explicit? @"Explicit": @"Automatic", (long)toVersion, self );
}
return YES;
}
- (NSString *)resolveLoginUsingKey:(MPKey *)key {
return [self.algorithm resolveLoginForSite:self usingKey:key];
}
- (NSString *)resolvePasswordUsingKey:(MPKey *)key {
return [self.algorithm resolvePasswordForSite:self usingKey:key];
}
- (NSString *)resolveSiteAnswerUsingKey:(MPKey *)key {
return [self.algorithm resolveAnswerForSite:self usingKey:key];
}
- (void)resolveLoginUsingKey:(MPKey *)key result:(void ( ^ )(NSString *))result {
[self.algorithm resolveLoginForSite:self usingKey:key result:result];
}
- (void)resolvePasswordUsingKey:(MPKey *)key result:(void ( ^ )(NSString *))result {
[self.algorithm resolvePasswordForSite:self usingKey:key result:result];
}
- (void)resolveSiteAnswerUsingKey:(MPKey *)key result:(void ( ^ )(NSString *))result {
[self.algorithm resolveAnswerForSite:self usingKey:key result:result];
}
- (void)resolveAnswerUsingKey:(MPKey *)key forQuestion:(NSString *)question result:(void ( ^ )(NSString *))result {
MPSiteQuestionEntity *questionEntity = [MPSiteQuestionEntity new];
questionEntity.site = self;
questionEntity.keyword = question;
[questionEntity resolveQuestionAnswerUsingKey:key result:result];
}
@end
@implementation MPGeneratedSiteEntity(MP)
- (MPFixableResult)findAndFixInconsistenciesInContext:(NSManagedObjectContext *)context {
MPFixableResult result = [super findAndFixInconsistenciesInContext:context];
if (!self.type || self.type == (MPSiteType)NSNotFound || ![[self.algorithm allTypes] containsObject:self.type_])
// Invalid self.type
result = MPApplyFix( result, ^MPFixableResult {
wrn( @"Invalid type for: %@ of %@, type: %ld. Will use %ld instead.",
self.name, self.user.name, (long)self.type, (long)self.user.defaultType );
self.type = self.user.defaultType;
return MPFixableResultProblemsFixed;
} );
if (!self.type || self.type == (MPSiteType)NSNotFound || ![[self.algorithm allTypes] containsObject:self.type_])
// Invalid self.user.defaultType
result = MPApplyFix( result, ^MPFixableResult {
wrn( @"Invalid type for: %@ of %@, type: %ld. Will use %ld instead.",
self.name, self.user.name, (long)self.type, (long)MPSiteTypeGeneratedLong );
self.type = MPSiteTypeGeneratedLong;
return MPFixableResultProblemsFixed;
} );
if (![self isKindOfClass:[self.algorithm classOfType:self.type]])
// Mismatch between self.type and self.class
result = MPApplyFix( result, ^MPFixableResult {
for (MPSiteType newType = self.type; self.type != (newType = [self.algorithm nextType:newType]);)
if ([self isKindOfClass:[self.algorithm classOfType:newType]]) {
wrn( @"Mismatching type for: %@ of %@, type: %lu, class: %@. Will use %ld instead.",
self.name, self.user.name, (long)self.type, self.class, (long)newType );
self.type = newType;
return MPFixableResultProblemsFixed;
}
err( @"Mismatching type for: %@ of %@, type: %lu, class: %@. Couldn't find a type to fix problem with.",
self.name, self.user.name, (long)self.type, self.class );
return MPFixableResultProblemsNotFixed;
} );
return result;
}
- (NSUInteger)counter {
return [self.counter_ unsignedIntegerValue];
}
- (void)setCounter:(NSUInteger)aCounter {
self.counter_ = @(aCounter);
}
@end
@implementation MPStoredSiteEntity(MP)
- (MPFixableResult)findAndFixInconsistenciesInContext:(NSManagedObjectContext *)context {
MPFixableResult result = [super findAndFixInconsistenciesInContext:context];
if (self.contentObject && ![self.contentObject isKindOfClass:[NSData class]])
result = MPApplyFix( result, ^MPFixableResult {
MPKey *key = [MPAppDelegate_Shared get].key;
if (key && [[MPAppDelegate_Shared get] activeUserInContext:context] == self.user) {
wrn( @"Content object not encrypted for: %@ of %@. Will re-encrypt.", self.name, self.user.name );
[self.algorithm savePassword:[self.contentObject description] toSite:self usingKey:key];
return MPFixableResultProblemsFixed;
}
err( @"Content object not encrypted for: %@ of %@. Couldn't fix, please sign in.", self.name, self.user.name );
return MPFixableResultProblemsNotFixed;
} );
return result;
}
@end
@implementation MPUserEntity(MP)
- (NSUInteger)avatar {
return [self.avatar_ unsignedIntegerValue];
}
- (void)setAvatar:(NSUInteger)anAvatar {
self.avatar_ = @(anAvatar);
}
- (BOOL)saveKey {
return [self.saveKey_ boolValue];
}
- (void)setSaveKey:(BOOL)aSaveKey {
self.saveKey_ = @(aSaveKey);
}
- (BOOL)touchID {
return [self.touchID_ boolValue] && [[MPAppDelegate_Shared get] isFeatureUnlocked:MPProductTouchID];
}
- (void)setTouchID:(BOOL)aTouchID {
self.touchID_ = @(aTouchID);
}
- (MPSiteType)defaultType {
return (MPSiteType)[self.defaultType_ unsignedIntegerValue]?: MPSiteTypeGeneratedLong;
}
- (void)setDefaultType:(MPSiteType)aDefaultType {
self.defaultType_ = @(aDefaultType);
}
- (id<MPAlgorithm>)algorithm {
return MPAlgorithmForVersion(
MIN( MPAlgorithmVersionCurrent,
MAX( MPAlgorithmVersion0, (MPAlgorithmVersion)[self.version_ unsignedIntegerValue] ) ) );
}
- (void)setAlgorithm:(id<MPAlgorithm>)version {
self.version_ = @([version version]);
[[MPAppDelegate_Shared get] forgetSavedKeyFor:self];
}
- (NSString *)userID {
return [MPUserEntity idFor:self.name];
}
+ (NSString *)idFor:(NSString *)userName {
return [[userName hashWith:PearlHashSHA1] encodeHex];
}
@end

View File

@@ -0,0 +1,33 @@
/**
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
*
* See the enclosed file LICENSE for license information (LGPLv3). If you did
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
*
* @author Maarten Billemont <lhunath@lyndir.com>
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
*/
//
// MPFixable.h
// MPFixable
//
// Created by lhunath on 2014-04-26.
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
//
#import <Foundation/Foundation.h>
typedef NS_ENUM( NSUInteger, MPFixableResult ) {
MPFixableResultNoProblems,
MPFixableResultProblemsFixed,
MPFixableResultProblemsNotFixed,
};
MPFixableResult MPApplyFix(MPFixableResult previousResult, MPFixableResult(^fixBlock)(void));
@protocol MPFixable<NSObject>
- (MPFixableResult)findAndFixInconsistenciesInContext:(NSManagedObjectContext *)context;
@end

View File

@@ -0,0 +1,40 @@
/**
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
*
* See the enclosed file LICENSE for license information (LGPLv3). If you did
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
*
* @author Maarten Billemont <lhunath@lyndir.com>
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
*/
//
// MPFixable.m
// MPFixable
//
// Created by lhunath on 2014-04-26.
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
//
#import "MPFixable.h"
MPFixableResult MPApplyFix(MPFixableResult previousResult, MPFixableResult(^fixBlock)(void)) {
MPFixableResult additionalResult = fixBlock();
switch (previousResult) {
case MPFixableResultNoProblems:
return additionalResult;
case MPFixableResultProblemsFixed:
switch (additionalResult) {
case MPFixableResultNoProblems:
case MPFixableResultProblemsFixed:
return previousResult;
case MPFixableResultProblemsNotFixed:
return additionalResult;
}
case MPFixableResultProblemsNotFixed:
return additionalResult;
}
Throw( @"Unexpected previous=%ld or additional=%ld result.", (long)previousResult, (long)additionalResult );
}

View File

@@ -0,0 +1,18 @@
//
// MPGeneratedSiteEntity.h
// MasterPassword-Mac
//
// Created by Maarten Billemont on 2014-09-21.
// Copyright (c) 2014 Lyndir. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
#import "MPSiteEntity.h"
@interface MPGeneratedSiteEntity : MPSiteEntity
@property (nonatomic, retain) NSNumber * counter_;
@end

View File

@@ -0,0 +1,16 @@
//
// MPGeneratedSiteEntity.m
// MasterPassword-Mac
//
// Created by Maarten Billemont on 2014-09-21.
// Copyright (c) 2014 Lyndir. All rights reserved.
//
#import "MPGeneratedSiteEntity.h"
@implementation MPGeneratedSiteEntity
@dynamic counter_;
@end

44
platform-ios/ObjC/MPKey.h Normal file
View File

@@ -0,0 +1,44 @@
/**
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
*
* See the enclosed file LICENSE for license information (LGPLv3). If you did
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
*
* @author Maarten Billemont <lhunath@lyndir.com>
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
*/
//
// MPKey
//
// Created by Maarten Billemont on 16/07/12.
// Copyright 2012 lhunath (Maarten Billemont). All rights reserved.
//
#import <Foundation/Foundation.h>
#import "MPAlgorithm.h"
@protocol MPAlgorithm;
typedef NS_ENUM(NSUInteger, MPKeyOrigin) {
MPKeyOriginMasterPassword,
MPKeyOriginKeyChain,
MPKeyOriginKeyChainBiometric,
};
@interface MPKey : NSObject
@property(nonatomic, readonly) NSString *fullName;
@property(nonatomic, readonly) MPKeyOrigin origin;
- (instancetype)initForFullName:(NSString *)fullName withMasterPassword:(NSString *)masterPassword;
- (instancetype)initForFullName:(NSString *)fullName withKeyData:(NSData *)keyData
forAlgorithm:(id<MPAlgorithm>)algorithm keyOrigin:(MPKeyOrigin)origin;
- (NSData *)keyIDForAlgorithm:(id<MPAlgorithm>)algorithm;
- (NSData *)keyDataForAlgorithm:(id<MPAlgorithm>)algorithm;
- (NSData *)keyDataForAlgorithm:(id<MPAlgorithm>)algorithm trimmedLength:(NSUInteger)subKeyLength;
- (BOOL)isEqualToKey:(MPKey *)key;
@end

94
platform-ios/ObjC/MPKey.m Normal file
View File

@@ -0,0 +1,94 @@
/**
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
*
* See the enclosed file LICENSE for license information (LGPLv3). If you did
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
*
* @author Maarten Billemont <lhunath@lyndir.com>
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
*/
//
// MPKey
//
// Created by Maarten Billemont on 16/07/12.
// Copyright 2012 lhunath (Maarten Billemont). All rights reserved.
//
#import "MPAlgorithm.h"
@interface MPKey()
@property(nonatomic) NSString *fullName;
@property(nonatomic) MPKeyOrigin origin;
@property(nonatomic) NSString *masterPassword;
@end
@implementation MPKey {
NSCache *_keyCache;
};
- (instancetype)initForFullName:(NSString *)fullName withMasterPassword:(NSString *)masterPassword {
if (!(self = [super init]))
return nil;
_keyCache = [NSCache new];
self.fullName = fullName;
self.origin = MPKeyOriginMasterPassword;
self.masterPassword = masterPassword;
return self;
}
- (instancetype)initForFullName:(NSString *)fullName withKeyData:(NSData *)keyData
forAlgorithm:(id<MPAlgorithm>)algorithm keyOrigin:(MPKeyOrigin)origin {
if (!(self = [self initForFullName:fullName withMasterPassword:nil]))
return nil;
self.origin = origin;
[_keyCache setObject:keyData forKey:algorithm];
return self;
}
- (NSData *)keyIDForAlgorithm:(id<MPAlgorithm>)algorithm {
return [algorithm keyIDForKeyData:[self keyDataForAlgorithm:algorithm]];
}
- (NSData *)keyDataForAlgorithm:(id<MPAlgorithm>)algorithm {
NSData *keyData = [_keyCache objectForKey:algorithm];
if (keyData)
return keyData;
keyData = [algorithm keyDataForFullName:self.fullName withMasterPassword:self.masterPassword];
if (keyData)
[_keyCache setObject:keyData forKey:algorithm];
return keyData;
}
- (NSData *)keyDataForAlgorithm:(id<MPAlgorithm>)algorithm trimmedLength:(NSUInteger)subKeyLength {
NSData *keyData = [self keyDataForAlgorithm:algorithm];
return [keyData subdataWithRange:NSMakeRange( 0, MIN( subKeyLength, keyData.length ) )];
}
- (BOOL)isEqualToKey:(MPKey *)key {
return [self.fullName isEqualToString:key.fullName] && [self.masterPassword isEqualToString:self.masterPassword];
}
- (BOOL)isEqual:(id)object {
if (![object isKindOfClass:[MPKey class]])
return NO;
return [self isEqualToKey:object];
}
@end

View File

@@ -0,0 +1,41 @@
//
// MPSiteEntity.h
// MasterPassword-Mac
//
// Created by Maarten Billemont on 2014-09-21.
// Copyright (c) 2014 Lyndir. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
@class MPSiteQuestionEntity, MPUserEntity;
@interface MPSiteEntity : NSManagedObject
//@property (nonatomic, retain) id content; // Hide here, reveal in MPStoredSiteEntity
@property (nonatomic, retain) NSDate * lastUsed;
@property (nonatomic, retain) NSNumber * loginGenerated_;
@property (nonatomic, retain) NSString * loginName;
@property (nonatomic, retain) NSString * name;
@property (nonatomic, retain) NSNumber * requiresExplicitMigration_;
@property (nonatomic, retain) NSNumber * type_;
@property (nonatomic, retain) NSNumber * uses_;
@property (nonatomic, retain) NSNumber * version_;
@property (nonatomic, retain) NSOrderedSet *questions;
@property (nonatomic, retain) MPUserEntity *user;
@end
@interface MPSiteEntity (CoreDataGeneratedAccessors)
- (void)insertObject:(MPSiteQuestionEntity *)value inQuestionsAtIndex:(NSUInteger)idx;
- (void)removeObjectFromQuestionsAtIndex:(NSUInteger)idx;
- (void)insertQuestions:(NSArray *)value atIndexes:(NSIndexSet *)indexes;
- (void)removeQuestionsAtIndexes:(NSIndexSet *)indexes;
- (void)replaceObjectInQuestionsAtIndex:(NSUInteger)idx withObject:(MPSiteQuestionEntity *)value;
- (void)replaceQuestionsAtIndexes:(NSIndexSet *)indexes withQuestions:(NSArray *)values;
- (void)addQuestionsObject:(MPSiteQuestionEntity *)value;
- (void)removeQuestionsObject:(MPSiteQuestionEntity *)value;
- (void)addQuestions:(NSOrderedSet *)values;
- (void)removeQuestions:(NSOrderedSet *)values;
@end

View File

@@ -0,0 +1,28 @@
//
// MPSiteEntity.m
// MasterPassword-Mac
//
// Created by Maarten Billemont on 2014-09-21.
// Copyright (c) 2014 Lyndir. All rights reserved.
//
#import "MPSiteEntity.h"
#import "MPSiteQuestionEntity.h"
#import "MPUserEntity.h"
@implementation MPSiteEntity
//@dynamic content;
@dynamic lastUsed;
@dynamic loginGenerated_;
@dynamic loginName;
@dynamic name;
@dynamic requiresExplicitMigration_;
@dynamic type_;
@dynamic uses_;
@dynamic version_;
@dynamic questions;
@dynamic user;
@end

View File

@@ -0,0 +1,19 @@
//
// MPSiteQuestionEntity.h
// MasterPassword-iOS
//
// Created by Maarten Billemont on 2014-09-27.
// Copyright (c) 2014 Lyndir. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
@class MPSiteEntity;
@interface MPSiteQuestionEntity : NSManagedObject
@property (nonatomic, retain) NSString * keyword;
@property (nonatomic, retain) MPSiteEntity *site;
@end

View File

@@ -0,0 +1,18 @@
//
// MPSiteQuestionEntity.m
// MasterPassword-iOS
//
// Created by Maarten Billemont on 2014-09-27.
// Copyright (c) 2014 Lyndir. All rights reserved.
//
#import "MPSiteQuestionEntity.h"
#import "MPSiteEntity.h"
@implementation MPSiteQuestionEntity
@dynamic keyword;
@dynamic site;
@end

View File

@@ -0,0 +1,18 @@
//
// MPStoredSiteEntity.h
// MasterPassword-Mac
//
// Created by Maarten Billemont on 2014-09-21.
// Copyright (c) 2014 Lyndir. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
#import "MPSiteEntity.h"
@interface MPStoredSiteEntity : MPSiteEntity
@property (nonatomic, retain) NSData *contentObject;
@end

View File

@@ -0,0 +1,16 @@
//
// MPStoredSiteEntity.m
// MasterPassword-Mac
//
// Created by Maarten Billemont on 2014-09-21.
// Copyright (c) 2014 Lyndir. All rights reserved.
//
#import "MPStoredSiteEntity.h"
@implementation MPStoredSiteEntity
@dynamic contentObject;
@end

View File

@@ -0,0 +1,22 @@
//
// MPTypes.h
// MasterPassword
//
// Created by Maarten Billemont on 02/01/12.
// Copyright (c) 2012 Lyndir. All rights reserved.
//
__BEGIN_DECLS
extern NSString *const MPErrorDomain;
extern NSString *const MPSignedInNotification;
extern NSString *const MPSignedOutNotification;
extern NSString *const MPKeyForgottenNotification;
extern NSString *const MPSiteUpdatedNotification;
extern NSString *const MPCheckConfigNotification;
extern NSString *const MPSitesImportedNotification;
extern NSString *const MPFoundInconsistenciesNotification;
extern NSString *const MPSitesImportedNotificationUserKey;
extern NSString *const MPInconsistenciesFixResultUserKey;
__END_DECLS

View File

@@ -0,0 +1,22 @@
//
// MPTypes.c
// MasterPassword
//
// Created by Maarten Billemont on 02/01/12.
// Copyright (c) 2012 Lyndir. All rights reserved.
//
#import "MPTypes.h"
NSString *const MPErrorDomain = @"MPErrorDomain";
NSString *const MPSignedInNotification = @"MPSignedInNotification";
NSString *const MPSignedOutNotification = @"MPSignedOutNotification";
NSString *const MPKeyForgottenNotification = @"MPKeyForgottenNotification";
NSString *const MPSiteUpdatedNotification = @"MPSiteUpdatedNotification";
NSString *const MPCheckConfigNotification = @"MPCheckConfigNotification";
NSString *const MPSitesImportedNotification = @"MPSitesImportedNotification";
NSString *const MPFoundInconsistenciesNotification = @"MPFoundInconsistenciesNotification";
NSString *const MPSitesImportedNotificationUserKey = @"MPSitesImportedNotificationUserKey";
NSString *const MPInconsistenciesFixResultUserKey = @"MPInconsistenciesFixResultUserKey";

View File

@@ -0,0 +1,34 @@
//
// MPUserEntity.h
// MasterPassword-Mac
//
// Created by Maarten Billemont on 2014-09-21.
// Copyright (c) 2014 Lyndir. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
@class MPSiteEntity;
@interface MPUserEntity : NSManagedObject
@property (nonatomic, retain) NSNumber * avatar_;
@property (nonatomic, retain) NSNumber * defaultType_;
@property (nonatomic, retain) NSData * keyID;
@property (nonatomic, retain) NSDate * lastUsed;
@property (nonatomic, retain) NSString * name;
@property (nonatomic, retain) NSNumber * saveKey_;
@property (nonatomic, retain) NSNumber * touchID_;
@property (nonatomic, retain) NSNumber * version_;
@property (nonatomic, retain) NSSet *sites;
@end
@interface MPUserEntity (CoreDataGeneratedAccessors)
- (void)addSitesObject:(MPSiteEntity *)value;
- (void)removeSitesObject:(MPSiteEntity *)value;
- (void)addSites:(NSSet *)values;
- (void)removeSites:(NSSet *)values;
@end

View File

@@ -0,0 +1,25 @@
//
// MPUserEntity.m
// MasterPassword-Mac
//
// Created by Maarten Billemont on 2014-09-21.
// Copyright (c) 2014 Lyndir. All rights reserved.
//
#import "MPUserEntity.h"
#import "MPSiteEntity.h"
@implementation MPUserEntity
@dynamic avatar_;
@dynamic defaultType_;
@dynamic keyID;
@dynamic lastUsed;
@dynamic name;
@dynamic saveKey_;
@dynamic touchID_;
@dynamic version_;
@dynamic sites;
@end

View File

@@ -0,0 +1,121 @@
<?xml version="1.0" encoding="UTF-8"?>
<archive type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="8.00">
<data>
<int key="IBDocument.SystemTarget">1070</int>
<string key="IBDocument.SystemVersion">11C42</string>
<string key="IBDocument.InterfaceBuilderVersion">1938</string>
<string key="IBDocument.AppKitVersion">1138.17</string>
<string key="IBDocument.HIToolboxVersion">567.00</string>
<object class="NSMutableDictionary" key="IBDocument.PluginVersions">
<string key="NS.key.0">com.apple.InterfaceBuilder.CocoaPlugin</string>
<string key="NS.object.0">1938</string>
</object>
<array key="IBDocument.IntegratedClassDependencies">
<string>NSWindowTemplate</string>
<string>NSView</string>
<string>NSCustomObject</string>
</array>
<array key="IBDocument.PluginDependencies">
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
</array>
<object class="NSMutableDictionary" key="IBDocument.Metadata">
<string key="NS.key.0">PluginDependencyRecalculationVersion</string>
<integer value="1" key="NS.object.0"/>
</object>
<array class="NSMutableArray" key="IBDocument.RootObjects" id="1000">
<object class="NSCustomObject" id="1001">
<string key="NSClassName">NSObject</string>
</object>
<object class="NSCustomObject" id="1003">
<string key="NSClassName">FirstResponder</string>
</object>
<object class="NSCustomObject" id="1004">
<string key="NSClassName">NSApplication</string>
</object>
<object class="NSWindowTemplate" id="1005">
<int key="NSWindowStyleMask">15</int>
<int key="NSWindowBacking">2</int>
<string key="NSWindowRect">{{196, 240}, {480, 270}}</string>
<int key="NSWTFlags">544735232</int>
<string key="NSWindowTitle">Window</string>
<string key="NSWindowClass">NSWindow</string>
<nil key="NSViewClass"/>
<nil key="NSUserInterfaceItemIdentifier"/>
<object class="NSView" key="NSWindowView" id="1006">
<reference key="NSNextResponder"/>
<int key="NSvFlags">256</int>
<string key="NSFrameSize">{480, 270}</string>
<reference key="NSSuperview"/>
<reference key="NSWindow"/>
</object>
<string key="NSScreenRect">{{0, 0}, {1680, 1028}}</string>
<string key="NSMaxSize">{10000000000000, 10000000000000}</string>
<bool key="NSWindowIsRestorable">YES</bool>
</object>
</array>
<object class="IBObjectContainer" key="IBDocument.Objects">
<array class="NSMutableArray" key="connectionRecords"/>
<object class="IBMutableOrderedSet" key="objectRecords">
<array key="orderedObjects">
<object class="IBObjectRecord">
<int key="objectID">0</int>
<array key="object" id="0"/>
<reference key="children" ref="1000"/>
<nil key="parent"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">-2</int>
<reference key="object" ref="1001"/>
<reference key="parent" ref="0"/>
<string key="objectName">File's Owner</string>
</object>
<object class="IBObjectRecord">
<int key="objectID">-1</int>
<reference key="object" ref="1003"/>
<reference key="parent" ref="0"/>
<string key="objectName">First Responder</string>
</object>
<object class="IBObjectRecord">
<int key="objectID">-3</int>
<reference key="object" ref="1004"/>
<reference key="parent" ref="0"/>
<string key="objectName">Application</string>
</object>
<object class="IBObjectRecord">
<int key="objectID">1</int>
<reference key="object" ref="1005"/>
<array class="NSMutableArray" key="children">
<reference ref="1006"/>
</array>
<reference key="parent" ref="0"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">2</int>
<reference key="object" ref="1006"/>
<reference key="parent" ref="1005"/>
</object>
</array>
</object>
<dictionary class="NSMutableDictionary" key="flattenedProperties">
<string key="-1.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
<string key="-2.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
<string key="-3.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
<string key="1.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
<string key="1.IBWindowTemplateEditedContentRect">{{357, 418}, {480, 270}}</string>
<integer value="1" key="1.NSWindowTemplate.visibleAtLaunch"/>
<string key="2.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
</dictionary>
<dictionary class="NSMutableDictionary" key="unlocalizedProperties"/>
<nil key="activeLocalization"/>
<dictionary class="NSMutableDictionary" key="localizations"/>
<nil key="sourceID"/>
<int key="maxID">2</int>
</object>
<object class="IBClassDescriber" key="IBDocument.Classes"/>
<int key="IBDocument.localizationMode">0</int>
<string key="IBDocument.TargetRuntimeIdentifier">IBCocoaFramework</string>
<bool key="IBDocument.PluginDeclaredDependenciesTrackSystemTargetVersion">YES</bool>
<int key="IBDocument.defaultPropertyAccessControl">3</int>
<bool key="IBDocument.UseAutolayout">YES</bool>
</data>
</archive>

View File

@@ -0,0 +1,16 @@
//
// Created by Maarten Billemont on 2016-05-15.
// Copyright (c) 2016 Lyndir. All rights reserved.
//
#import <Foundation/Foundation.h>
IB_DESIGNABLE
@interface MPGradientView : NSView
@property(nonatomic, retain) IBInspectable NSColor *startingColor;
@property(nonatomic, retain) IBInspectable NSColor *endingColor;
@property(nonatomic, assign) IBInspectable NSInteger angle;
@property(nonatomic, assign) IBInspectable CGFloat ratio;
@end

Some files were not shown because too many files have changed in this diff Show More