Enable support for internal actions from URLs.
This commit is contained in:
		@@ -39,8 +39,9 @@
 | 
			
		||||
            askImportPassword:(NSString *( ^ )(NSString *userName))importPassword
 | 
			
		||||
              askUserPassword:(NSString *( ^ )(NSString *userName))userPassword
 | 
			
		||||
                       result:(void ( ^ )(NSError *error))resultBlock;
 | 
			
		||||
- (void)exportSitesRevealPasswords:(BOOL)revealPasswords
 | 
			
		||||
                 askExportPassword:(NSString *( ^ )(NSString *userName))askImportPassword
 | 
			
		||||
                            result:(void ( ^ )(NSString *exportedUser, NSError *error))resultBlock;
 | 
			
		||||
- (NSString *)exportSitesFor:(MPUserEntity *)user
 | 
			
		||||
             revealPasswords:(BOOL)revealPasswords
 | 
			
		||||
           askExportPassword:(NSString *( ^ )(NSString *userName))askExportPassword
 | 
			
		||||
                       error:(__autoreleasing NSError **)error;
 | 
			
		||||
 | 
			
		||||
@end
 | 
			
		||||
 
 | 
			
		||||
@@ -704,16 +704,17 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
 | 
			
		||||
    site.lastUsed = [NSDate dateWithTimeIntervalSince1970:importSite->lastUsed];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
- (void)exportSitesRevealPasswords:(BOOL)revealPasswords
 | 
			
		||||
                 askExportPassword:(NSString *( ^ )(NSString *userName))askImportPassword
 | 
			
		||||
                            result:(void ( ^ )(NSString *exportedUser, NSError *error))resultBlock {
 | 
			
		||||
 | 
			
		||||
    [MPAppDelegate_Shared managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
 | 
			
		||||
        MPUserEntity *user = [self activeUserInContext:context];
 | 
			
		||||
- (NSString *)exportSitesFor:(MPUserEntity *)user
 | 
			
		||||
             revealPasswords:(BOOL)revealPasswords
 | 
			
		||||
           askExportPassword:(NSString *( ^ )(NSString *userName))askExportPassword
 | 
			
		||||
                       error:(__autoreleasing NSError **)error {
 | 
			
		||||
 | 
			
		||||
    MPMarshalledUser *exportUser = NULL;
 | 
			
		||||
    MPMarshalledFile *exportFile = NULL;
 | 
			
		||||
    @try {
 | 
			
		||||
        inf( @"Exporting sites, %@, for user: %@", revealPasswords? @"revealing passwords": @"omitting passwords", user.userID );
 | 
			
		||||
        MPMarshalledUser *exportUser = mpw_marshal_user( user.name.UTF8String,
 | 
			
		||||
                mpw_masterKeyProvider_str( askImportPassword( user.name ).UTF8String ), user.algorithm.version );
 | 
			
		||||
        exportUser = mpw_marshal_user( user.name.UTF8String,
 | 
			
		||||
                mpw_masterKeyProvider_str( askExportPassword( user.name ).UTF8String ), user.algorithm.version );
 | 
			
		||||
        exportUser->redacted = !revealPasswords;
 | 
			
		||||
        exportUser->avatar = (unsigned int)user.avatar;
 | 
			
		||||
        exportUser->keyID = mpw_strdup( [user.keyID encodeHex].UTF8String );
 | 
			
		||||
@@ -737,22 +738,26 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
 | 
			
		||||
                mpw_marshal_question( exportSite, siteQuestion.keyword.UTF8String );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        MPMarshalledFile *exportFile = NULL;
 | 
			
		||||
        const char *export = mpw_marshal_write( MPMarshalFormatDefault, &exportFile, exportUser );
 | 
			
		||||
        NSString *exportedUser = nil;
 | 
			
		||||
        if (export && exportFile && exportFile->error.type == MPMarshalSuccess)
 | 
			
		||||
            exportedUser = [NSString stringWithCString:export encoding:NSUTF8StringEncoding];
 | 
			
		||||
        mpw_free_string( &export );
 | 
			
		||||
 | 
			
		||||
        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),
 | 
			
		||||
                              }] );
 | 
			
		||||
        if (error)
 | 
			
		||||
            *error = exportFile && exportFile->error.type == MPMarshalSuccess? nil:
 | 
			
		||||
                     [NSError errorWithDomain:MPErrorDomain code:MPErrorMarshalCode userInfo:@{
 | 
			
		||||
                             @"type"                  : @(exportFile? exportFile->error.type: MPMarshalErrorInternal),
 | 
			
		||||
                             NSLocalizedDescriptionKey: @(exportFile? exportFile->error.message: nil),
 | 
			
		||||
                     }];
 | 
			
		||||
 | 
			
		||||
        return exportedUser;
 | 
			
		||||
    }
 | 
			
		||||
    @finally {
 | 
			
		||||
        mpw_marshal_file_free( &exportFile );
 | 
			
		||||
        mpw_marshal_user_free( &exportUser );
 | 
			
		||||
        mpw_masterKeyProvider_free();
 | 
			
		||||
    }];
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@end
 | 
			
		||||
 
 | 
			
		||||
@@ -18,6 +18,7 @@
 | 
			
		||||
 | 
			
		||||
#import "MPNavigationController.h"
 | 
			
		||||
#import "MPWebViewController.h"
 | 
			
		||||
#import "MPiOSAppDelegate.h"
 | 
			
		||||
 | 
			
		||||
@implementation MPNavigationController
 | 
			
		||||
 | 
			
		||||
@@ -29,6 +30,16 @@
 | 
			
		||||
        [self performSegueWithIdentifier:@"setup" sender:self];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
- (void)performSegueWithIdentifier:(NSString *)identifier sender:(id)sender {
 | 
			
		||||
 | 
			
		||||
    if ([identifier isEqualToString:@"web"] && [[(NSURL *)sender scheme] isEqualToString:@"masterpassword"]) {
 | 
			
		||||
        [[MPiOSAppDelegate get] openURL:sender];
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    [super performSegueWithIdentifier:identifier sender:sender];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
 | 
			
		||||
 | 
			
		||||
    if ([segue.identifier isEqualToString:@"web"])
 | 
			
		||||
 
 | 
			
		||||
@@ -33,9 +33,8 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
 | 
			
		||||
    MPPasswordsBadNameTip = 1 << 0,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@interface MPSitesViewController()<NSFetchedResultsControllerDelegate, SKStoreProductViewControllerDelegate>
 | 
			
		||||
@interface MPSitesViewController()<NSFetchedResultsControllerDelegate>
 | 
			
		||||
 | 
			
		||||
@property(nonatomic, strong) SKStoreProductViewController *voltoViewController;
 | 
			
		||||
@property(nonatomic, strong) NSFetchedResultsController *fetchedResultsController;
 | 
			
		||||
@property(nonatomic, strong) NSArray *fuzzyGroups;
 | 
			
		||||
@property(nonatomic, strong) NSCharacterSet *siteNameAcceptableCharactersSet;
 | 
			
		||||
@@ -435,13 +434,6 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
 | 
			
		||||
    }                completion:completion];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#pragma mark - SKStoreProductViewControllerDelegate
 | 
			
		||||
 | 
			
		||||
- (void)productViewControllerDidFinish:(SKStoreProductViewController *)viewController {
 | 
			
		||||
 | 
			
		||||
    [viewController dismissViewControllerAnimated:YES completion:nil];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#pragma mark - Actions
 | 
			
		||||
 | 
			
		||||
- (IBAction)dismissPopdown:(id)sender {
 | 
			
		||||
@@ -454,45 +446,7 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
 | 
			
		||||
 | 
			
		||||
- (IBAction)upgradeVolto:(UIButton *)sender {
 | 
			
		||||
 | 
			
		||||
    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];
 | 
			
		||||
    [[MPiOSAppDelegate get] migrateFor:[MPiOSAppDelegate get].activeUserForMainThread];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#pragma mark - Private
 | 
			
		||||
@@ -505,23 +459,8 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
 | 
			
		||||
        self.voltoMigrateAlert.visible = YES;
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
        self.voltoInstallAlert.visible = NO;
 | 
			
		||||
        self.voltoInstallAlert.visible = [MPiOSAppDelegate get].voltoViewController != nil;
 | 
			
		||||
        self.voltoMigrateAlert.visible = NO;
 | 
			
		||||
        self.voltoViewController = [SKStoreProductViewController new];
 | 
			
		||||
        self.voltoViewController.delegate = self;
 | 
			
		||||
        [self.voltoViewController loadProductWithParameters:@{
 | 
			
		||||
                SKStoreProductParameterCampaignToken       : @"app-masterpassword.ios", /* Campaign:    From MasterPassword iOS */
 | 
			
		||||
                SKStoreProductParameterProviderToken       : @153897, /*                   Provider:    Maarten Billemont */
 | 
			
		||||
                SKStoreProductParameterITunesItemIdentifier: @510296984, /*                Application: MasterPassword iOS */
 | 
			
		||||
                //SKStoreProductParameterITunesItemIdentifier: @1500430196, /*             Application: Volto iOS */
 | 
			
		||||
        }                                   completionBlock:^(BOOL result, NSError *error) {
 | 
			
		||||
            if (error)
 | 
			
		||||
                err( @"Failed loading Volto product information: %@", error );
 | 
			
		||||
 | 
			
		||||
            [UIView animateWithDuration:0.3f animations:^{
 | 
			
		||||
                self.voltoInstallAlert.visible = result;
 | 
			
		||||
            }];
 | 
			
		||||
        }];
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -17,6 +17,7 @@
 | 
			
		||||
//==============================================================================
 | 
			
		||||
 | 
			
		||||
#import "MPWebViewController.h"
 | 
			
		||||
#import "MPiOSAppDelegate.h"
 | 
			
		||||
 | 
			
		||||
@implementation MPWebViewController
 | 
			
		||||
 | 
			
		||||
@@ -57,6 +58,18 @@
 | 
			
		||||
 | 
			
		||||
#pragma mark - WKNavigationDelegate
 | 
			
		||||
 | 
			
		||||
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction
 | 
			
		||||
decisionHandler:(void ( ^ )(WKNavigationActionPolicy))decisionHandler {
 | 
			
		||||
 | 
			
		||||
    if ([navigationAction.request.mainDocumentURL.scheme isEqualToString:@"masterpassword"]) {
 | 
			
		||||
        [[MPiOSAppDelegate get] openURL:navigationAction.request.mainDocumentURL];
 | 
			
		||||
        decisionHandler( WKNavigationActionPolicyCancel );
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    decisionHandler( WKNavigationActionPolicyAllow );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(null_unspecified WKNavigation *)navigation {
 | 
			
		||||
 | 
			
		||||
    self.webNavigationItem.title = webView.URL.host;
 | 
			
		||||
@@ -77,7 +90,7 @@
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    [self.webNavigationItem setLeftBarButtonItem:[[UIBarButtonItem alloc]
 | 
			
		||||
            initWithBarButtonSystemItem:UIBarButtonSystemItemAction target:self action:@selector(openURL:)]];
 | 
			
		||||
            initWithBarButtonSystemItem:UIBarButtonSystemItemAction target:self action:@selector( action: )]];
 | 
			
		||||
    [webView evaluateJavaScript:@"document.title" completionHandler:^(id o, NSError *error) {
 | 
			
		||||
        self.webNavigationItem.prompt = [o description];
 | 
			
		||||
    }];
 | 
			
		||||
@@ -85,7 +98,7 @@
 | 
			
		||||
 | 
			
		||||
#pragma mark - Actions
 | 
			
		||||
 | 
			
		||||
- (IBAction)openURL:(id)sender {
 | 
			
		||||
- (IBAction)action:(id)sender {
 | 
			
		||||
 | 
			
		||||
    UIAlertController *controller = [UIAlertController new];
 | 
			
		||||
    controller.title = self.webView.URL.host;
 | 
			
		||||
 
 | 
			
		||||
@@ -17,15 +17,22 @@
 | 
			
		||||
//==============================================================================
 | 
			
		||||
 | 
			
		||||
#import <UIKit/UIKit.h>
 | 
			
		||||
#import <StoreKit/StoreKit.h>
 | 
			
		||||
 | 
			
		||||
#import "MPAppDelegate_Shared.h"
 | 
			
		||||
 | 
			
		||||
@interface MPiOSAppDelegate : MPAppDelegate_Shared
 | 
			
		||||
@interface MPiOSAppDelegate : MPAppDelegate_Shared <SKStoreProductViewControllerDelegate>
 | 
			
		||||
 | 
			
		||||
@property(nonatomic, strong) SKStoreProductViewController *voltoViewController;
 | 
			
		||||
 | 
			
		||||
- (void)openURL:(NSURL *)url;
 | 
			
		||||
 | 
			
		||||
- (void)showFeedbackWithLogs:(BOOL)logs forVC:(UIViewController *)viewController;
 | 
			
		||||
- (void)openFeedbackWithLogs:(BOOL)logs forVC:(UIViewController *)viewController;
 | 
			
		||||
 | 
			
		||||
- (void)showExportForVC:(UIViewController *)viewController;
 | 
			
		||||
- (void)migrateFor:(MPUserEntity *)user;
 | 
			
		||||
 | 
			
		||||
- (void)changeMasterPasswordFor:(MPUserEntity *)user saveInContext:(NSManagedObjectContext *)moc didResetBlock:(void ( ^ )(void))didReset;
 | 
			
		||||
 | 
			
		||||
@end
 | 
			
		||||
 
 | 
			
		||||
@@ -35,7 +35,7 @@
 | 
			
		||||
@implementation CountlyPushNotifications(MPNotifications)
 | 
			
		||||
 | 
			
		||||
- (void)openURL:(NSString *)URLString {
 | 
			
		||||
    [UIApp.keyWindow.rootViewController performSegueWithIdentifier:@"web" sender:[NSURL URLWithString:URLString]];
 | 
			
		||||
    [[MPiOSAppDelegate get].navigationController performSegueWithIdentifier:@"web" sender:[NSURL URLWithString:URLString]];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@end
 | 
			
		||||
@@ -186,7 +186,26 @@
 | 
			
		||||
            }
 | 
			
		||||
        } );
 | 
			
		||||
 | 
			
		||||
        SKStoreProductViewController *migrateVC = [SKStoreProductViewController new];
 | 
			
		||||
        [migrateVC loadProductWithParameters:@{
 | 
			
		||||
                SKStoreProductParameterCampaignToken       : @"app-masterpassword.ios", /* Campaign:    From MasterPassword iOS */
 | 
			
		||||
                SKStoreProductParameterProviderToken       : @153897, /*                   Provider:    Maarten Billemont */
 | 
			
		||||
                SKStoreProductParameterITunesItemIdentifier: @510296984, /*                Application: MasterPassword iOS */
 | 
			
		||||
                //SKStoreProductParameterITunesItemIdentifier: @1500430196, /*             Application: Volto iOS */
 | 
			
		||||
        }                                   completionBlock:^(BOOL result, NSError *error) {
 | 
			
		||||
            if (error)
 | 
			
		||||
                err( @"Failed loading Volto product information: %@", error );
 | 
			
		||||
 | 
			
		||||
                if (result) {
 | 
			
		||||
                    self.voltoViewController = migrateVC;
 | 
			
		||||
                    self.voltoViewController.delegate = self;
 | 
			
		||||
                } else {
 | 
			
		||||
                    self.voltoViewController = nil;
 | 
			
		||||
                }
 | 
			
		||||
        }];
 | 
			
		||||
 | 
			
		||||
        PearlMainQueueOperation( ^{
 | 
			
		||||
            [self.navigationController performSegueWithIdentifier:@"web" sender:[NSURL URLWithString:@"masterpassword://foo?bar=quux"]];
 | 
			
		||||
            if ([[MPiOSConfig get].showSetup boolValue])
 | 
			
		||||
                [self.navigationController performSegueWithIdentifier:@"setup" sender:self];
 | 
			
		||||
 | 
			
		||||
@@ -207,6 +226,12 @@
 | 
			
		||||
    if (!url)
 | 
			
		||||
        return NO;
 | 
			
		||||
 | 
			
		||||
    // masterpassword: URLs.
 | 
			
		||||
    if ([url.scheme isEqualToString:@"masterpassword"]) {
 | 
			
		||||
        [self openURL:url];
 | 
			
		||||
        return YES;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Arbitrary URL to mpsites data.
 | 
			
		||||
    [[[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:
 | 
			
		||||
            ^(NSData *importedSitesData, NSURLResponse *response, NSError *error) {
 | 
			
		||||
@@ -444,6 +469,42 @@
 | 
			
		||||
 | 
			
		||||
#pragma mark - Behavior
 | 
			
		||||
 | 
			
		||||
- (void)openURL:(NSURL *)url {
 | 
			
		||||
    if ([url.scheme isEqualToString:@"masterpassword"]) {
 | 
			
		||||
        if ([url.host isEqualToString:@"open-url"]) {
 | 
			
		||||
            for (NSURLQueryItem *item in [NSURLComponents componentsWithString:[url absoluteString]].queryItems)
 | 
			
		||||
                if ([item.name isEqualToString:@"url"]) {
 | 
			
		||||
                    [UIApp openURL:[NSURL URLWithString:item.value]];
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
        }
 | 
			
		||||
        else if ([url.host isEqualToString:@"show-url"]) {
 | 
			
		||||
            for (NSURLQueryItem *item in [NSURLComponents componentsWithString:[url absoluteString]].queryItems)
 | 
			
		||||
                if ([item.name isEqualToString:@"url"]) {
 | 
			
		||||
                    [[MPiOSAppDelegate get].navigationController performSegueWithIdentifier:@"web" sender:[NSURL URLWithString:item.value]];
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
        }
 | 
			
		||||
        else if ([url.host isEqualToString:@"migrate"]) {
 | 
			
		||||
            for (NSURLQueryItem *item in [NSURLComponents componentsWithString:[url absoluteString]].queryItems)
 | 
			
		||||
                if ([item.name isEqualToString:@"fullName"]) {
 | 
			
		||||
                    [MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
 | 
			
		||||
                        NSFetchRequest
 | 
			
		||||
                                *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPUserEntity class] )];
 | 
			
		||||
                        fetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@", item.value];
 | 
			
		||||
                        NSArray *users = [context executeFetchRequest:fetchRequest error:nil];
 | 
			
		||||
                        [self migrateFor:users.firstObject];
 | 
			
		||||
                    }];
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
            [self migrateFor:nil];
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
    } else
 | 
			
		||||
        [UIApp openURL:url];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
- (void)showFeedbackWithLogs:(BOOL)logs forVC:(UIViewController *)viewController {
 | 
			
		||||
 | 
			
		||||
    if (![PearlEMail canSendMail]) {
 | 
			
		||||
@@ -564,89 +625,162 @@
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    [self exportSitesRevealPasswords:revealPasswords 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];
 | 
			
		||||
    [MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
 | 
			
		||||
        NSError *error = nil;
 | 
			
		||||
        NSString *exportedUser = [self exportSitesFor:[self activeUserInContext:context] revealPasswords:revealPasswords 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 mpsites." );
 | 
			
		||||
            PearlMainQueue( ^{
 | 
			
		||||
        } error:&error];
 | 
			
		||||
 | 
			
		||||
        PearlMainQueue( ^{
 | 
			
		||||
            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];
 | 
			
		||||
            } );
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            NSDateFormatter *exportDateFormatter = [NSDateFormatter new];
 | 
			
		||||
            [exportDateFormatter setDateFormat:@"yyyy'-'MM'-'dd"];
 | 
			
		||||
            NSString *exportFileName = strf( @"%@ (%@).mpsites",
 | 
			
		||||
                    [self activeUserForMainThread].name, [exportDateFormatter stringFromDate:[NSDate date]] );
 | 
			
		||||
 | 
			
		||||
            UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Export Destination" message:nil
 | 
			
		||||
                                                                    preferredStyle:UIAlertControllerStyleActionSheet];
 | 
			
		||||
            [alert addAction:[UIAlertAction actionWithTitle:@"Send As E-Mail" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
 | 
			
		||||
                NSString *message;
 | 
			
		||||
                if (revealPasswords)
 | 
			
		||||
                    message = strf( @"Export of Master Password sites with passwords included.\n\n"
 | 
			
		||||
                                    @"REMINDER: Make sure nobody else sees this file!  Passwords are visible!\n\n\n"
 | 
			
		||||
                                    @"--\n"
 | 
			
		||||
                                    @"%@\n"
 | 
			
		||||
                                    @"Master Password %@, build %@",
 | 
			
		||||
                            [self activeUserForMainThread].name,
 | 
			
		||||
                            [PearlInfoPlist get].CFBundleShortVersionString,
 | 
			
		||||
                            [PearlInfoPlist get].CFBundleVersion );
 | 
			
		||||
                else
 | 
			
		||||
                    message = strf( @"Backup of Master Password sites.\n\n\n"
 | 
			
		||||
                                    @"--\n"
 | 
			
		||||
                                    @"%@\n"
 | 
			
		||||
                                    @"Master Password %@, build %@",
 | 
			
		||||
                            [self activeUserForMainThread].name,
 | 
			
		||||
                            [PearlInfoPlist get].CFBundleShortVersionString,
 | 
			
		||||
                            [PearlInfoPlist get].CFBundleVersion );
 | 
			
		||||
 | 
			
		||||
                [PearlEMail sendEMailTo:nil fromVC:viewController subject:@"Master Password Export" body:message
 | 
			
		||||
                            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) {
 | 
			
		||||
                NSURL *applicationSupportURL = [[[NSFileManager defaultManager] URLsForDirectory:NSApplicationSupportDirectory
 | 
			
		||||
                                                                                       inDomains:NSUserDomainMask] lastObject];
 | 
			
		||||
                NSURL *exportURL = [[applicationSupportURL
 | 
			
		||||
                        URLByAppendingPathComponent:[NSBundle mainBundle].bundleIdentifier isDirectory:YES]
 | 
			
		||||
                        URLByAppendingPathComponent:exportFileName isDirectory:NO];
 | 
			
		||||
                NSError *writeError = nil;
 | 
			
		||||
                if (![[exportedUser dataUsingEncoding:NSUTF8StringEncoding]
 | 
			
		||||
                        writeToURL:exportURL options:NSDataWritingFileProtectionComplete error:&writeError])
 | 
			
		||||
                    MPError( writeError, @"Failed to write export data to URL %@.", exportURL );
 | 
			
		||||
                else {
 | 
			
		||||
                    self.interactionController = [UIDocumentInteractionController interactionControllerWithURL:exportURL];
 | 
			
		||||
                    self.interactionController.UTI = @"com.lyndir.masterpassword.sites";
 | 
			
		||||
                    self.interactionController.delegate = self;
 | 
			
		||||
                    [self.interactionController presentOpenInMenuFromRect:CGRectZero inView:viewController.view animated:YES];
 | 
			
		||||
                }
 | 
			
		||||
            }]];
 | 
			
		||||
            [alert addAction:[UIAlertAction actionWithTitle:@"Continue" style:UIAlertActionStyleCancel handler:nil]];
 | 
			
		||||
            [self.navigationController presentViewController:alert animated:YES completion:nil];
 | 
			
		||||
        } );
 | 
			
		||||
    }];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
- (void)migrateFor:(MPUserEntity *)user {
 | 
			
		||||
 | 
			
		||||
    if ([UIApp canOpenURL:[[NSURL alloc] initWithString:@"volto:"]]) {
 | 
			
		||||
        if (!user) {
 | 
			
		||||
            [MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
 | 
			
		||||
                NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPUserEntity class] )];
 | 
			
		||||
                NSArray *users = [context executeFetchRequest:fetchRequest error:nil];
 | 
			
		||||
                if (![users count])
 | 
			
		||||
                    return;
 | 
			
		||||
 | 
			
		||||
                UIAlertController *usersSheet = [UIAlertController alertControllerWithTitle:@"Migrate User"
 | 
			
		||||
                                                                                    message:@"Choose a user to migrate out to Volto."
 | 
			
		||||
                                                                             preferredStyle:UIAlertControllerStyleActionSheet];
 | 
			
		||||
                [usersSheet addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil]];
 | 
			
		||||
                for (MPUserEntity *user_ in users)
 | 
			
		||||
                    [usersSheet addAction:[UIAlertAction actionWithTitle:user_.name style:UIAlertActionStyleDefault handler:
 | 
			
		||||
                            ^(UIAlertAction *action) { [self migrateFor:user_]; }]];
 | 
			
		||||
 | 
			
		||||
                PearlMainQueue( ^{
 | 
			
		||||
                    [self.navigationController presentViewController:usersSheet animated:YES completion:nil];
 | 
			
		||||
                } );
 | 
			
		||||
            }];
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        NSDateFormatter *exportDateFormatter = [NSDateFormatter new];
 | 
			
		||||
        [exportDateFormatter setDateFormat:@"yyyy'-'MM'-'dd"];
 | 
			
		||||
        NSString *exportFileName = strf( @"%@ (%@).mpsites",
 | 
			
		||||
                [self activeUserForMainThread].name, [exportDateFormatter stringFromDate:[NSDate date]] );
 | 
			
		||||
        [MPAppDelegate_Shared managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
 | 
			
		||||
            NSError *error = nil;
 | 
			
		||||
            NSString *exportedUser = [[MPAppDelegate_Shared get] exportSitesFor:[MPUserEntity existingObjectWithID:user.objectID inContext:context]
 | 
			
		||||
                                                                revealPasswords: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];
 | 
			
		||||
                            } );
 | 
			
		||||
                        } );
 | 
			
		||||
                    } error:&error];
 | 
			
		||||
 | 
			
		||||
        UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Export Destination" message:nil
 | 
			
		||||
                                                                preferredStyle:UIAlertControllerStyleActionSheet];
 | 
			
		||||
        [alert addAction:[UIAlertAction actionWithTitle:@"Send As E-Mail" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
 | 
			
		||||
            NSString *message;
 | 
			
		||||
            if (revealPasswords)
 | 
			
		||||
                message = strf( @"Export of Master Password sites with passwords included.\n\n"
 | 
			
		||||
                                @"REMINDER: Make sure nobody else sees this file!  Passwords are visible!\n\n\n"
 | 
			
		||||
                                @"--\n"
 | 
			
		||||
                                @"%@\n"
 | 
			
		||||
                                @"Master Password %@, build %@",
 | 
			
		||||
                        [self activeUserForMainThread].name,
 | 
			
		||||
                        [PearlInfoPlist get].CFBundleShortVersionString,
 | 
			
		||||
                        [PearlInfoPlist get].CFBundleVersion );
 | 
			
		||||
            else
 | 
			
		||||
                message = strf( @"Backup of Master Password sites.\n\n\n"
 | 
			
		||||
                                @"--\n"
 | 
			
		||||
                                @"%@\n"
 | 
			
		||||
                                @"Master Password %@, build %@",
 | 
			
		||||
                        [self activeUserForMainThread].name,
 | 
			
		||||
                        [PearlInfoPlist get].CFBundleShortVersionString,
 | 
			
		||||
                        [PearlInfoPlist get].CFBundleVersion );
 | 
			
		||||
            PearlMainQueue( ^{
 | 
			
		||||
                if (!exportedUser || error) {
 | 
			
		||||
                    MPError( error, @"Failed to export user." );
 | 
			
		||||
                    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;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
            [PearlEMail sendEMailTo:nil fromVC:viewController subject:@"Master Password Export" body:message
 | 
			
		||||
                        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) {
 | 
			
		||||
            NSURL *applicationSupportURL = [[[NSFileManager defaultManager] URLsForDirectory:NSApplicationSupportDirectory
 | 
			
		||||
                                                                                   inDomains:NSUserDomainMask] lastObject];
 | 
			
		||||
            NSURL *exportURL = [[applicationSupportURL
 | 
			
		||||
                    URLByAppendingPathComponent:[NSBundle mainBundle].bundleIdentifier isDirectory:YES]
 | 
			
		||||
                    URLByAppendingPathComponent:exportFileName isDirectory:NO];
 | 
			
		||||
            NSError *writeError = nil;
 | 
			
		||||
            if (![[exportedUser dataUsingEncoding:NSUTF8StringEncoding]
 | 
			
		||||
                    writeToURL:exportURL options:NSDataWritingFileProtectionComplete error:&writeError])
 | 
			
		||||
                MPError( writeError, @"Failed to write export data to URL %@.", exportURL );
 | 
			
		||||
            else {
 | 
			
		||||
                self.interactionController = [UIDocumentInteractionController interactionControllerWithURL:exportURL];
 | 
			
		||||
                self.interactionController.UTI = @"com.lyndir.masterpassword.sites";
 | 
			
		||||
                self.interactionController.delegate = self;
 | 
			
		||||
                [self.interactionController presentOpenInMenuFromRect:CGRectZero inView:viewController.view animated:YES];
 | 
			
		||||
            }
 | 
			
		||||
        }]];
 | 
			
		||||
        [alert addAction:[UIAlertAction actionWithTitle:@"Continue" style:UIAlertActionStyleCancel handler:nil]];
 | 
			
		||||
        [self.navigationController presentViewController:alert animated:YES completion:nil];
 | 
			
		||||
    }];
 | 
			
		||||
                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.navigationController presentViewController:self.voltoViewController animated:YES completion:nil];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
- (void)changeMasterPasswordFor:(MPUserEntity *)user saveInContext:(NSManagedObjectContext *)moc didResetBlock:(void ( ^ )(void))didReset {
 | 
			
		||||
@@ -674,6 +808,13 @@
 | 
			
		||||
    } );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#pragma mark - SKStoreProductViewControllerDelegate
 | 
			
		||||
 | 
			
		||||
- (void)productViewControllerDidFinish:(SKStoreProductViewController *)viewController {
 | 
			
		||||
 | 
			
		||||
    [viewController dismissViewControllerAnimated:YES completion:nil];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#pragma mark - UIDocumentInteractionControllerDelegate
 | 
			
		||||
 | 
			
		||||
- (void)documentInteractionController:(UIDocumentInteractionController *)controller didEndSendingToApplication:(NSString *)application {
 | 
			
		||||
 
 | 
			
		||||
@@ -37,6 +37,21 @@
 | 
			
		||||
	<string>APPL</string>
 | 
			
		||||
	<key>CFBundleShortVersionString</key>
 | 
			
		||||
	<string>[auto]</string>
 | 
			
		||||
	<key>CFBundleURLTypes</key>
 | 
			
		||||
	<array>
 | 
			
		||||
		<dict>
 | 
			
		||||
			<key>CFBundleTypeRole</key>
 | 
			
		||||
			<string>Editor</string>
 | 
			
		||||
			<key>CFBundleURLIconFile</key>
 | 
			
		||||
			<string>Icon-320</string>
 | 
			
		||||
			<key>CFBundleURLName</key>
 | 
			
		||||
			<string>com.lyndir.masterpassword</string>
 | 
			
		||||
			<key>CFBundleURLSchemes</key>
 | 
			
		||||
			<array>
 | 
			
		||||
				<string>masterpassword</string>
 | 
			
		||||
			</array>
 | 
			
		||||
		</dict>
 | 
			
		||||
	</array>
 | 
			
		||||
	<key>CFBundleVersion</key>
 | 
			
		||||
	<string>[auto]</string>
 | 
			
		||||
	<key>LSApplicationQueriesSchemes</key>
 | 
			
		||||
@@ -44,6 +59,7 @@
 | 
			
		||||
		<string>firefox</string>
 | 
			
		||||
		<string>googlechrome</string>
 | 
			
		||||
		<string>opera-http</string>
 | 
			
		||||
		<string>volto</string>
 | 
			
		||||
	</array>
 | 
			
		||||
	<key>LSRequiresIPhoneOS</key>
 | 
			
		||||
	<true/>
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user