Fix login/result state import/export, keyID; Volto export; Pearl log sink.
This commit is contained in:
parent
b15417aa31
commit
131720eb8d
@ -87,7 +87,10 @@ NSString *NSStringFromTimeToCrack(TimeToCrack timeToCrack);
|
||||
|
||||
- (void)importPassword:(NSString *)protectedPassword protectedByKey:(MPKey *)importKey
|
||||
intoSite:(MPSiteEntity *)site usingKey:(MPKey *)key;
|
||||
- (void)importLogin:(NSString *)protectedLogin protectedByKey:(MPKey *)importKey
|
||||
intoSite:(MPSiteEntity *)site usingKey:(MPKey *)key;
|
||||
- (NSString *)exportPasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)key;
|
||||
- (NSString *)exportLoginForSite:(MPSiteEntity *)site usingKey:(MPKey *)key;
|
||||
|
||||
- (BOOL)timeToCrack:(out TimeToCrack *)timeToCrack passwordOfType:(MPResultType)type byAttacker:(MPAttacker)attacker;
|
||||
- (BOOL)timeToCrack:(out TimeToCrack *)timeToCrack passwordString:(NSString *)password byAttacker:(MPAttacker)attacker;
|
||||
|
@ -73,7 +73,7 @@ static NSOperationQueue *_mpwQueue = nil;
|
||||
return [(id<MPAlgorithm>)other version] == [self version];
|
||||
}
|
||||
|
||||
- (void)mpw_perform:(void ( ^ )(void))operationBlock {
|
||||
- (void)mpw_await:(void ( ^ )(void))operationBlock {
|
||||
|
||||
if ([NSOperationQueue currentQueue] == _mpwQueue) {
|
||||
operationBlock();
|
||||
@ -126,7 +126,7 @@ static NSOperationQueue *_mpwQueue = nil;
|
||||
- (NSData *)keyDataForFullName:(NSString *)fullName withMasterPassword:(NSString *)masterPassword {
|
||||
|
||||
__block NSData *keyData;
|
||||
[self mpw_perform:^{
|
||||
[self mpw_await:^{
|
||||
NSDate *start = [NSDate date];
|
||||
MPMasterKey masterKey = mpw_master_key( fullName.UTF8String, masterPassword.UTF8String, [self version] );
|
||||
if (masterKey) {
|
||||
@ -363,7 +363,7 @@ static NSOperationQueue *_mpwQueue = nil;
|
||||
usingKey:(MPKey *)key {
|
||||
|
||||
__block NSString *result = nil;
|
||||
[self mpw_perform:^{
|
||||
[self mpw_await:^{
|
||||
NSData *masterKey = [key keyForAlgorithm:self];
|
||||
char const *resultBytes = mpw_site_result( masterKey.bytes, name.UTF8String,
|
||||
counter, purpose, context.UTF8String, type, parameter.UTF8String, [self version] );
|
||||
@ -392,7 +392,7 @@ static NSOperationQueue *_mpwQueue = nil;
|
||||
|
||||
__block NSData *state = nil;
|
||||
if (plainText)
|
||||
[self mpw_perform:^{
|
||||
[self mpw_await:^{
|
||||
NSData *masterKey = [key keyForAlgorithm:self];
|
||||
char const *stateBytes = mpw_site_state( masterKey.bytes, site.name.UTF8String,
|
||||
MPCounterValueInitial, MPKeyPurposeAuthentication, NULL, site.type, plainText.UTF8String, [self version] );
|
||||
@ -589,6 +589,19 @@ static NSOperationQueue *_mpwQueue = nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)importLogin:(NSString *)cipherText protectedByKey:(MPKey *)importKey
|
||||
intoSite:(MPSiteEntity *)site usingKey:(MPKey *)key {
|
||||
|
||||
NSAssert( [[key keyIDForAlgorithm:site.user.algorithm] isEqualToData:site.user.keyID], @"Site does not belong to current user." );
|
||||
if (cipherText && cipherText.length) {
|
||||
NSString *plainText = [self mpwResultForSiteNamed:site.name ofType:MPResultTypeStatefulPersonal parameter:cipherText
|
||||
withCounter:MPCounterValueInitial variant:MPKeyPurposeIdentification context:nil
|
||||
usingKey:importKey];
|
||||
if (plainText)
|
||||
site.loginName = plainText;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSDictionary *)queryForSite:(MPSiteEntity *)site {
|
||||
|
||||
return [PearlKeyChain createQueryForClass:kSecClassGenericPassword attributes:@{
|
||||
@ -607,6 +620,26 @@ static NSOperationQueue *_mpwQueue = nil;
|
||||
return [state?: ((MPStoredSiteEntity *)site).contentObject encodeBase64];
|
||||
}
|
||||
|
||||
- (NSString *)exportLoginForSite:(MPSiteEntity *)site usingKey:(MPKey *)key {
|
||||
|
||||
if (!(site.type & MPSiteFeatureExportContent) || site.loginGenerated || ![site.loginName length])
|
||||
return nil;
|
||||
|
||||
__block NSData *state = nil;
|
||||
[self mpw_await:^{
|
||||
NSData *masterKey = [key keyForAlgorithm:self];
|
||||
char const *stateBytes = mpw_site_state( masterKey.bytes, site.name.UTF8String,
|
||||
MPCounterValueInitial, MPKeyPurposeIdentification, NULL, MPResultTypeStatefulPersonal,
|
||||
site.loginName.UTF8String, [self version] );
|
||||
if (stateBytes) {
|
||||
state = [[NSString stringWithCString:stateBytes encoding:NSUTF8StringEncoding] decodeBase64];
|
||||
mpw_free_string( &stateBytes );
|
||||
}
|
||||
}];
|
||||
|
||||
return [state encodeBase64];
|
||||
}
|
||||
|
||||
- (BOOL)timeToCrack:(out TimeToCrack *)timeToCrack passwordOfType:(MPResultType)type byAttacker:(MPAttacker)attacker {
|
||||
|
||||
if (!(type & MPResultTypeClassTemplate))
|
||||
|
@ -41,6 +41,6 @@
|
||||
result:(void ( ^ )(NSError *error))resultBlock;
|
||||
- (void)exportSitesRevealPasswords:(BOOL)revealPasswords
|
||||
askExportPassword:(NSString *( ^ )(NSString *userName))askImportPassword
|
||||
result:(void ( ^ )(NSString *mpsites, NSError *error))resultBlock;
|
||||
result:(void ( ^ )(NSString *exportedUser, NSError *error))resultBlock;
|
||||
|
||||
@end
|
||||
|
@ -700,14 +700,16 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
|
||||
usingKey:(MPKey *)userKey {
|
||||
|
||||
site.name = @(importSite->siteName);
|
||||
if (importSite->resultState)
|
||||
[site.algorithm importPassword:@(importSite->resultState) protectedByKey:importKey intoSite:site usingKey:userKey];
|
||||
site.type = importSite->resultType;
|
||||
site.algorithm = MPAlgorithmForVersion( importSite->algorithm );
|
||||
if ([site isKindOfClass:[MPGeneratedSiteEntity class]])
|
||||
((MPGeneratedSiteEntity *)site).counter = importSite->counter;
|
||||
site.algorithm = MPAlgorithmForVersion( importSite->algorithm );
|
||||
site.loginName = importSite->loginState? @(importSite->loginState): nil;
|
||||
site.loginGenerated = importSite->loginType & MPResultTypeClassTemplate;
|
||||
site.type = importSite->resultType;
|
||||
if (importSite->resultState)
|
||||
[site.algorithm importPassword:@(importSite->resultState) protectedByKey:importKey intoSite:site usingKey:userKey];
|
||||
site.loginGenerated = (importSite->loginType & MPResultTypeClassTemplate) == MPResultTypeClassTemplate;
|
||||
site.loginName = nil;
|
||||
if (importSite->loginState)
|
||||
[site.algorithm importLogin:@(importSite->loginState) protectedByKey:importKey intoSite:site usingKey:userKey];
|
||||
site.url = importSite->url? @(importSite->url): nil;
|
||||
site.uses = importSite->uses;
|
||||
site.lastUsed = [NSDate dateWithTimeIntervalSince1970:importSite->lastUsed];
|
||||
@ -715,7 +717,7 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
|
||||
|
||||
- (void)exportSitesRevealPasswords:(BOOL)revealPasswords
|
||||
askExportPassword:(NSString *( ^ )(NSString *userName))askImportPassword
|
||||
result:(void ( ^ )(NSString *mpsites, NSError *error))resultBlock {
|
||||
result:(void ( ^ )(NSString *exportedUser, NSError *error))resultBlock {
|
||||
|
||||
[MPAppDelegate_Shared managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||
MPUserEntity *user = [self activeUserInContext:context];
|
||||
@ -725,6 +727,7 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
|
||||
mpw_masterKeyProvider_str( askImportPassword( user.name ).UTF8String ), user.algorithm.version );
|
||||
exportUser->redacted = !revealPasswords;
|
||||
exportUser->avatar = (unsigned int)user.avatar;
|
||||
exportUser->keyID = mpw_strdup( [user.keyID encodeHex].UTF8String );
|
||||
exportUser->defaultType = user.defaultType;
|
||||
exportUser->lastUsed = (time_t)user.lastUsed.timeIntervalSince1970;
|
||||
|
||||
@ -732,16 +735,12 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
|
||||
MPCounterValue counter = MPCounterValueInitial;
|
||||
if ([site isKindOfClass:[MPGeneratedSiteEntity class]])
|
||||
counter = ((MPGeneratedSiteEntity *)site).counter;
|
||||
NSString *content = revealPasswords
|
||||
? [site.algorithm exportPasswordForSite:site usingKey:self.key]
|
||||
: [site.algorithm resolvePasswordForSite:site usingKey:self.key];
|
||||
|
||||
MPMarshalledSite *exportSite = mpw_marshal_site( exportUser,
|
||||
site.name.UTF8String, site.type, counter, site.algorithm.version );
|
||||
exportSite->resultState = content.UTF8String;
|
||||
exportSite->loginState = site.loginName.UTF8String;
|
||||
exportSite->resultState = mpw_strdup( [site.algorithm exportPasswordForSite:site usingKey:self.key].UTF8String );
|
||||
exportSite->loginState = mpw_strdup( [site.algorithm exportLoginForSite:site usingKey:self.key].UTF8String );
|
||||
exportSite->loginType = site.loginGenerated? MPResultTypeTemplateName: MPResultTypeStatefulPersonal;
|
||||
exportSite->url = site.url.UTF8String;
|
||||
exportSite->url = mpw_strdup( site.url.UTF8String );
|
||||
exportSite->uses = (unsigned int)site.uses;
|
||||
exportSite->lastUsed = (time_t)site.lastUsed.timeIntervalSince1970;
|
||||
|
||||
@ -750,14 +749,14 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
|
||||
}
|
||||
|
||||
MPMarshalledFile *exportFile = NULL;
|
||||
const char *export = mpw_marshal_write( MPMarshalFormatFlat, &exportFile, exportUser );
|
||||
NSString *mpsites = nil;
|
||||
const char *export = mpw_marshal_write( MPMarshalFormatDefault, &exportFile, exportUser );
|
||||
NSString *exportedUser = nil;
|
||||
if (export && exportFile && exportFile->error.type == MPMarshalSuccess)
|
||||
mpsites = [NSString stringWithCString:export encoding:NSUTF8StringEncoding];
|
||||
exportedUser = [NSString stringWithCString:export encoding:NSUTF8StringEncoding];
|
||||
mpw_free_string( &export );
|
||||
|
||||
resultBlock( mpsites, exportFile && exportFile->error.type == MPMarshalSuccess? nil:
|
||||
[NSError errorWithDomain:MPErrorDomain code:MPErrorMarshalCode userInfo:@{
|
||||
resultBlock( exportedUser, exportFile && exportFile->error.type == MPMarshalSuccess? nil:
|
||||
[NSError errorWithDomain:MPErrorDomain code:MPErrorMarshalCode userInfo:@{
|
||||
@"type" : @(exportFile? exportFile->error.type: MPMarshalErrorInternal),
|
||||
NSLocalizedDescriptionKey: @(exportFile? exportFile->error.message: nil),
|
||||
}] );
|
||||
|
@ -28,41 +28,11 @@ MP_LIBS_BEGIN
|
||||
#include <libgen.h>
|
||||
MP_LIBS_END
|
||||
|
||||
#define mpw_log_os(level, file, line, function, format, ...) \
|
||||
do { \
|
||||
if (mpw_verbosity < level) { \
|
||||
break; \
|
||||
} \
|
||||
\
|
||||
switch (level) { \
|
||||
case LogLevelTrace: \
|
||||
os_log_debug( OS_LOG_DEFAULT, "%30s:%-3ld TRC | " format, basename( (char *)file ), line, ##__VA_ARGS__ ); \
|
||||
break; \
|
||||
case LogLevelDebug: \
|
||||
os_log_debug( OS_LOG_DEFAULT, "%30s:%-3ld DBG | " format, basename( (char *)file ), line, ##__VA_ARGS__ ); \
|
||||
break; \
|
||||
case LogLevelInfo: \
|
||||
os_log_info( OS_LOG_DEFAULT, "%30s:%-3ld INF | " format, basename( (char *)file ), line, ##__VA_ARGS__ ); \
|
||||
break; \
|
||||
case LogLevelWarning: \
|
||||
os_log( OS_LOG_DEFAULT, "%30s:%-3ld WRN | " format, basename( (char *)file ), line, ##__VA_ARGS__ ); \
|
||||
break; \
|
||||
case LogLevelError: \
|
||||
os_log_error( OS_LOG_DEFAULT, "%30s:%-3ld ERR | " format, basename( (char *)file ), line, ##__VA_ARGS__ ); \
|
||||
break; \
|
||||
case LogLevelFatal: \
|
||||
os_log_fault( OS_LOG_DEFAULT, "%30s:%-3ld FTL | " format, basename( (char *)file ), line, ##__VA_ARGS__ ); \
|
||||
break; \
|
||||
} \
|
||||
\
|
||||
mpw_log_sink( level, file, line, function, format, ##__VA_ARGS__ ); \
|
||||
} while (0)
|
||||
|
||||
#ifdef __OBJC__
|
||||
|
||||
#import "Pearl-Prefix.pch"
|
||||
|
||||
#define MPW_LOG(level, file, line, function, format, ...) mpw_log_os(level, file, line, function, strf(format, ##__VA_ARGS__))
|
||||
#define MPW_LOG(level, file, line, function, format, ...) mpw_log_sink(level, file, line, function, strf(format, ##__VA_ARGS__))
|
||||
|
||||
#if TARGET_OS_IOS
|
||||
#import "MPTypes.h"
|
||||
@ -72,10 +42,6 @@ MP_LIBS_END
|
||||
#import "MPMacConfig.h"
|
||||
#endif
|
||||
|
||||
#else
|
||||
|
||||
#define MPW_LOG mpw_log_os
|
||||
|
||||
#endif
|
||||
|
||||
#include "mpw-util.h"
|
||||
|
@ -479,8 +479,43 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
|
||||
|
||||
- (IBAction)upgradeVolto:(UIButton *)sender {
|
||||
|
||||
if ([UIApp canOpenURL:[[NSURL alloc] initWithString:@"volto://"]])
|
||||
[UIApp openURL:[[NSURL alloc] initWithString:@"volto://"]];
|
||||
if ([UIApp canOpenURL:[[NSURL alloc] initWithString:@"volto:"]]) {
|
||||
[[MPiOSAppDelegate get] exportSitesRevealPasswords:NO askExportPassword:^NSString *(NSString *userName) {
|
||||
return PearlAwait( ^(void (^setResult)(id)) {
|
||||
PearlMainQueue( ^{
|
||||
UIAlertController *alert = [UIAlertController alertControllerWithTitle:strf( @"Master Password For:\n%@", userName )
|
||||
message:@"Enter your master password to export the user."
|
||||
preferredStyle:UIAlertControllerStyleAlert];
|
||||
[alert addTextFieldWithConfigurationHandler:^(UITextField *textField) {
|
||||
textField.secureTextEntry = YES;
|
||||
}];
|
||||
[alert addAction:[UIAlertAction actionWithTitle:@"Export" style:UIAlertActionStyleDefault handler:
|
||||
^(UIAlertAction *action) { setResult( alert.textFields.firstObject.text ); }]];
|
||||
[alert addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:
|
||||
^(UIAlertAction *action) { setResult( nil ); }]];
|
||||
[self.navigationController presentViewController:alert animated:YES completion:nil];
|
||||
} );
|
||||
} );
|
||||
} result:^(NSString *exportedUser, NSError *error) {
|
||||
if (!exportedUser || error) {
|
||||
MPError( error, @"Failed to export user." );
|
||||
PearlMainQueue( ^{
|
||||
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Export Error"
|
||||
message:[error localizedDescription]
|
||||
preferredStyle:UIAlertControllerStyleAlert];
|
||||
[alert addAction:[UIAlertAction actionWithTitle:@"Okay" style:UIAlertActionStyleCancel handler:nil]];
|
||||
[self.navigationController presentViewController:alert animated:YES completion:nil];
|
||||
} );
|
||||
return;
|
||||
}
|
||||
|
||||
NSURLComponents *components = [NSURLComponents new];
|
||||
components.scheme = @"volto";
|
||||
components.path = @"import";
|
||||
components.queryItems = @[ [[NSURLQueryItem alloc] initWithName:@"data" value:exportedUser] ];
|
||||
[UIApp openURL:components.URL];
|
||||
}];
|
||||
}
|
||||
else if (self.voltoViewController)
|
||||
[self presentViewController:self.voltoViewController animated:YES completion:nil];
|
||||
}
|
||||
@ -489,7 +524,7 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
|
||||
|
||||
- (void)updateVoltoAlerts {
|
||||
|
||||
BOOL voltoInstalled = [UIApp canOpenURL:[[NSURL alloc] initWithString:@"volto://"]];
|
||||
BOOL voltoInstalled = [UIApp canOpenURL:[[NSURL alloc] initWithString:@"volto:"]];
|
||||
if (voltoInstalled) {
|
||||
self.voltoInstallAlert.visible = NO;
|
||||
self.voltoMigrateAlert.visible = YES;
|
||||
|
@ -33,6 +33,35 @@
|
||||
|
||||
@end
|
||||
|
||||
MPLogSink mpw_log_sink_pearl;
|
||||
void mpw_log_sink_pearl(const MPLogEvent *record) {
|
||||
|
||||
PearlLogLevel level = PearlLogLevelInfo;
|
||||
switch (record->level) {
|
||||
case LogLevelTrace:
|
||||
level = PearlLogLevelDebug;
|
||||
break;
|
||||
case LogLevelDebug:
|
||||
level = PearlLogLevelDebug;
|
||||
break;
|
||||
case LogLevelInfo:
|
||||
level = PearlLogLevelInfo;
|
||||
break;
|
||||
case LogLevelWarning:
|
||||
level = PearlLogLevelWarn;
|
||||
break;
|
||||
case LogLevelError:
|
||||
level = PearlLogLevelError;
|
||||
break;
|
||||
case LogLevelFatal:
|
||||
level = PearlLogLevelFatal;
|
||||
break;
|
||||
}
|
||||
|
||||
[[PearlLogger get] inFile:[@(record->file) lastPathComponent] atLine:record->line fromFunction:@(record->function)
|
||||
withLevel:level text:@(record->message)];
|
||||
}
|
||||
|
||||
@implementation MPiOSAppDelegate
|
||||
|
||||
+ (void)initialize {
|
||||
@ -44,6 +73,9 @@
|
||||
#ifdef DEBUG
|
||||
[PearlLogger get].printLevel = PearlLogLevelDebug;
|
||||
#endif
|
||||
|
||||
mpw_verbosity = LogLevelTrace;
|
||||
mpw_log_sink_register( &mpw_log_sink_pearl );
|
||||
} );
|
||||
}
|
||||
|
||||
@ -503,27 +535,31 @@
|
||||
|
||||
[self exportSitesRevealPasswords:revealPasswords askExportPassword:^NSString *(NSString *userName) {
|
||||
return PearlAwait( ^(void (^setResult)(id)) {
|
||||
UIAlertController *alert = [UIAlertController alertControllerWithTitle:strf( @"Master Password For:\n%@", userName ) message:
|
||||
@"Enter the user's master password to create an export file."
|
||||
preferredStyle:UIAlertControllerStyleAlert];
|
||||
[alert addTextFieldWithConfigurationHandler:^(UITextField *textField) {
|
||||
textField.secureTextEntry = YES;
|
||||
}];
|
||||
[alert addAction:[UIAlertAction actionWithTitle:@"Export" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
|
||||
setResult( alert.textFields.firstObject.text );
|
||||
}]];
|
||||
[alert addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) {
|
||||
setResult( nil );
|
||||
}]];
|
||||
[self.navigationController presentViewController:alert animated:YES completion:nil];
|
||||
PearlMainQueue( ^{
|
||||
UIAlertController *alert = [UIAlertController alertControllerWithTitle:strf( @"Master Password For:\n%@", userName )
|
||||
message:@"Enter your master password to export the user."
|
||||
preferredStyle:UIAlertControllerStyleAlert];
|
||||
[alert addTextFieldWithConfigurationHandler:^(UITextField *textField) {
|
||||
textField.secureTextEntry = YES;
|
||||
}];
|
||||
[alert addAction:[UIAlertAction actionWithTitle:@"Export" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
|
||||
setResult( alert.textFields.firstObject.text );
|
||||
}]];
|
||||
[alert addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) {
|
||||
setResult( nil );
|
||||
}]];
|
||||
[self.navigationController presentViewController:alert animated:YES completion:nil];
|
||||
} );
|
||||
} );
|
||||
} result:^(NSString *mpsites, NSError *error) {
|
||||
if (!mpsites || error) {
|
||||
} result:^(NSString *exportedUser, NSError *error) {
|
||||
if (!exportedUser || error) {
|
||||
MPError( error, @"Failed to export mpsites." );
|
||||
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Export Error" message:[error localizedDescription]
|
||||
preferredStyle:UIAlertControllerStyleAlert];
|
||||
[alert addAction:[UIAlertAction actionWithTitle:@"Okay" style:UIAlertActionStyleCancel handler:nil]];
|
||||
[self.navigationController presentViewController:alert animated:YES completion:nil];
|
||||
PearlMainQueue( ^{
|
||||
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Export Error" message:[error localizedDescription]
|
||||
preferredStyle:UIAlertControllerStyleAlert];
|
||||
[alert addAction:[UIAlertAction actionWithTitle:@"Okay" style:UIAlertActionStyleCancel handler:nil]];
|
||||
[self.navigationController presentViewController:alert animated:YES completion:nil];
|
||||
} );
|
||||
return;
|
||||
}
|
||||
|
||||
@ -555,11 +591,9 @@
|
||||
[PearlInfoPlist get].CFBundleVersion );
|
||||
|
||||
[PearlEMail sendEMailTo:nil fromVC:viewController subject:@"Master Password Export" body:message
|
||||
attachments:[[PearlEMailAttachment alloc]
|
||||
initWithContent:[mpsites dataUsingEncoding:NSUTF8StringEncoding]
|
||||
mimeType:@"text/plain"
|
||||
fileName:exportFileName],
|
||||
nil];
|
||||
attachments:[[PearlEMailAttachment alloc] initWithContent:[exportedUser dataUsingEncoding:NSUTF8StringEncoding]
|
||||
mimeType:@"text/plain"
|
||||
fileName:exportFileName], nil];
|
||||
return;
|
||||
}]];
|
||||
[alert addAction:[UIAlertAction actionWithTitle:@"Share / Export" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
|
||||
@ -569,7 +603,7 @@
|
||||
URLByAppendingPathComponent:[NSBundle mainBundle].bundleIdentifier isDirectory:YES]
|
||||
URLByAppendingPathComponent:exportFileName isDirectory:NO];
|
||||
NSError *writeError = nil;
|
||||
if (![[mpsites dataUsingEncoding:NSUTF8StringEncoding]
|
||||
if (![[exportedUser dataUsingEncoding:NSUTF8StringEncoding]
|
||||
writeToURL:exportURL options:NSDataWritingFileProtectionComplete error:&writeError])
|
||||
MPError( writeError, @"Failed to write export data to URL %@.", exportURL );
|
||||
else {
|
||||
|
@ -9,10 +9,6 @@
|
||||
<key>CFBundleDocumentTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>mpsites</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFiles</key>
|
||||
<array>
|
||||
<string>Icon-Small</string>
|
||||
@ -20,7 +16,7 @@
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Master Password sites</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
<string>Alternate</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>com.lyndir.masterpassword.sites</string>
|
||||
|
@ -69,7 +69,7 @@
|
||||
</collectionViewFlowLayout>
|
||||
<cells>
|
||||
<collectionViewCell opaque="NO" multipleTouchEnabled="YES" contentMode="center" reuseIdentifier="MPAvatarCell" id="Zab-uQ-uk9" customClass="MPAvatarCell">
|
||||
<rect key="frame" x="80" y="115" width="215" height="667"/>
|
||||
<rect key="frame" x="80" y="114.5" width="215" height="667"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
|
||||
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center">
|
||||
<rect key="frame" x="0.0" y="0.0" width="215" height="667"/>
|
||||
|
Loading…
Reference in New Issue
Block a user