MPAlgorithm V2: handle multi-byte UTF-8 correctly by counting bytes, not characters.
This commit is contained in:
parent
466863f8fd
commit
9bb613a3b6
2
External/Pearl
vendored
2
External/Pearl
vendored
@ -1 +1 @@
|
|||||||
Subproject commit 82753ec57f65343bb86a3db86d84d933a7aa9caa
|
Subproject commit 5af61226a78398de136dd1663f9e6ac8624e7df5
|
@ -19,7 +19,7 @@
|
|||||||
#import "MPStoredSiteEntity.h"
|
#import "MPStoredSiteEntity.h"
|
||||||
#import "MPGeneratedSiteEntity.h"
|
#import "MPGeneratedSiteEntity.h"
|
||||||
|
|
||||||
#define MPAlgorithmDefaultVersion 1
|
#define MPAlgorithmDefaultVersion 2
|
||||||
#define MPAlgorithmDefault MPAlgorithmForVersion(MPAlgorithmDefaultVersion)
|
#define MPAlgorithmDefault MPAlgorithmForVersion(MPAlgorithmDefaultVersion)
|
||||||
|
|
||||||
id<MPAlgorithm> MPAlgorithmForVersion(NSUInteger version);
|
id<MPAlgorithm> MPAlgorithmForVersion(NSUInteger version);
|
||||||
@ -43,8 +43,8 @@ NSString *NSStringFromTimeToCrack(TimeToCrack timeToCrack);
|
|||||||
|
|
||||||
@required
|
@required
|
||||||
- (NSUInteger)version;
|
- (NSUInteger)version;
|
||||||
- (BOOL)migrateUser:(MPUserEntity *)user inContext:(NSManagedObjectContext *)moc;
|
- (BOOL)tryMigrateUser:(MPUserEntity *)user inContext:(NSManagedObjectContext *)moc;
|
||||||
- (BOOL)migrateSite:(MPSiteEntity *)site explicit:(BOOL)explicit;
|
- (BOOL)tryMigrateSite:(MPSiteEntity *)site explicit:(BOOL)explicit;
|
||||||
|
|
||||||
- (MPKey *)keyForPassword:(NSString *)password ofUserNamed:(NSString *)userName;
|
- (MPKey *)keyForPassword:(NSString *)password ofUserNamed:(NSString *)userName;
|
||||||
- (MPKey *)keyFromKeyData:(NSData *)keyData;
|
- (MPKey *)keyFromKeyData:(NSData *)keyData;
|
||||||
|
@ -35,6 +35,9 @@ id<MPAlgorithm> MPAlgorithmDefaultForBundleVersion(NSString *bundleVersion) {
|
|||||||
if (PearlCFBundleVersionCompare( bundleVersion, @"1.3" ) == NSOrderedAscending)
|
if (PearlCFBundleVersionCompare( bundleVersion, @"1.3" ) == NSOrderedAscending)
|
||||||
// Pre-1.3
|
// Pre-1.3
|
||||||
return MPAlgorithmForVersion( 0 );
|
return MPAlgorithmForVersion( 0 );
|
||||||
|
if (PearlCFBundleVersionCompare( bundleVersion, @"2.1" ) == NSOrderedAscending)
|
||||||
|
// Pre-2.1
|
||||||
|
return MPAlgorithmForVersion( 1 );
|
||||||
|
|
||||||
return MPAlgorithmDefault;
|
return MPAlgorithmDefault;
|
||||||
}
|
}
|
||||||
|
@ -73,7 +73,7 @@
|
|||||||
return [(id<MPAlgorithm>)other version] == [self version];
|
return [(id<MPAlgorithm>)other version] == [self version];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)migrateUser:(MPUserEntity *)user inContext:(NSManagedObjectContext *)moc {
|
- (BOOL)tryMigrateUser:(MPUserEntity *)user inContext:(NSManagedObjectContext *)moc {
|
||||||
|
|
||||||
NSError *error = nil;
|
NSError *error = nil;
|
||||||
NSFetchRequest *migrationRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPSiteEntity class] )];
|
NSFetchRequest *migrationRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPSiteEntity class] )];
|
||||||
@ -84,15 +84,15 @@
|
|||||||
return NO;
|
return NO;
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOL requiresExplicitMigration = NO;
|
BOOL success = YES;
|
||||||
for (MPSiteEntity *migrationSite in migrationSites)
|
for (MPSiteEntity *migrationSite in migrationSites)
|
||||||
if (![migrationSite migrateExplicitly:NO])
|
if (![migrationSite tryMigrateExplicitly:NO])
|
||||||
requiresExplicitMigration = YES;
|
success = NO;
|
||||||
|
|
||||||
return requiresExplicitMigration;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)migrateSite:(MPSiteEntity *)site explicit:(BOOL)explicit {
|
- (BOOL)tryMigrateSite:(MPSiteEntity *)site explicit:(BOOL)explicit {
|
||||||
|
|
||||||
if (site.version != [self version] - 1)
|
if (site.version != [self version] - 1)
|
||||||
// Only migrate from previous version.
|
// Only migrate from previous version.
|
||||||
@ -364,11 +364,11 @@
|
|||||||
- (NSString *)generateLoginForSiteNamed:(NSString *)name usingKey:(MPKey *)key {
|
- (NSString *)generateLoginForSiteNamed:(NSString *)name usingKey:(MPKey *)key {
|
||||||
|
|
||||||
return [self generateContentForSiteNamed:name ofType:MPSiteTypeGeneratedName withCounter:1
|
return [self generateContentForSiteNamed:name ofType:MPSiteTypeGeneratedName withCounter:1
|
||||||
variant:MPSiteVariantLogin context:nil usingKey:key];
|
variant:MPSiteVariantLogin context:nil usingKey:key];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSString *)generatePasswordForSiteNamed:(NSString *)name ofType:(MPSiteType)type withCounter:(NSUInteger)counter
|
- (NSString *)generatePasswordForSiteNamed:(NSString *)name ofType:(MPSiteType)type withCounter:(NSUInteger)counter
|
||||||
usingKey:(MPKey *)key {
|
usingKey:(MPKey *)key {
|
||||||
|
|
||||||
return [self generateContentForSiteNamed:name ofType:type withCounter:counter
|
return [self generateContentForSiteNamed:name ofType:type withCounter:counter
|
||||||
variant:MPSiteVariantPassword context:nil usingKey:key];
|
variant:MPSiteVariantPassword context:nil usingKey:key];
|
||||||
@ -381,7 +381,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (NSString *)generateContentForSiteNamed:(NSString *)name ofType:(MPSiteType)type withCounter:(NSUInteger)counter
|
- (NSString *)generateContentForSiteNamed:(NSString *)name ofType:(MPSiteType)type withCounter:(NSUInteger)counter
|
||||||
variant:(MPSiteVariant)variant context:(NSString *)context usingKey:(MPKey *)key {
|
variant:(MPSiteVariant)variant context:(NSString *)context usingKey:(MPKey *)key {
|
||||||
|
|
||||||
// Determine the seed whose bytes will be used for calculating a password
|
// Determine the seed whose bytes will be used for calculating a password
|
||||||
uint32_t ncounter = htonl( counter ), nnameLength = htonl( name.length ), ncontextLength = htonl( context.length );
|
uint32_t ncounter = htonl( counter ), nnameLength = htonl( name.length ), ncontextLength = htonl( context.length );
|
||||||
@ -396,9 +396,9 @@
|
|||||||
nameLengthBytes,
|
nameLengthBytes,
|
||||||
[name dataUsingEncoding:NSUTF8StringEncoding],
|
[name dataUsingEncoding:NSUTF8StringEncoding],
|
||||||
counterBytes,
|
counterBytes,
|
||||||
context? contextLengthBytes: nil,
|
context? contextLengthBytes: nil,
|
||||||
[context dataUsingEncoding:NSUTF8StringEncoding],
|
[context dataUsingEncoding:NSUTF8StringEncoding],
|
||||||
nil]
|
nil]
|
||||||
hmacWith:PearlHashSHA256 key:key.keyData];
|
hmacWith:PearlHashSHA256 key:key.keyData];
|
||||||
trc( @"seed is: %@", [seed encodeHex] );
|
trc( @"seed is: %@", [seed encodeHex] );
|
||||||
const char *seedBytes = seed.bytes;
|
const char *seedBytes = seed.bytes;
|
||||||
@ -678,7 +678,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (void)importProtectedPassword:(NSString *)protectedContent protectedByKey:(MPKey *)importKey
|
- (void)importProtectedPassword:(NSString *)protectedContent protectedByKey:(MPKey *)importKey
|
||||||
intoSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey {
|
intoSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey {
|
||||||
|
|
||||||
NSAssert( [siteKey.keyID isEqualToData:site.user.keyID], @"Site does not belong to current user." );
|
NSAssert( [siteKey.keyID isEqualToData:site.user.keyID], @"Site does not belong to current user." );
|
||||||
switch (site.type) {
|
switch (site.type) {
|
||||||
|
@ -25,7 +25,7 @@
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)migrateSite:(MPSiteEntity *)site explicit:(BOOL)explicit {
|
- (BOOL)tryMigrateSite:(MPSiteEntity *)site explicit:(BOOL)explicit {
|
||||||
|
|
||||||
if (site.version != [self version] - 1)
|
if (site.version != [self version] - 1)
|
||||||
// Only migrate from previous version.
|
// Only migrate from previous version.
|
||||||
@ -46,12 +46,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (NSString *)generateContentForSiteNamed:(NSString *)name ofType:(MPSiteType)type withCounter:(NSUInteger)counter
|
- (NSString *)generateContentForSiteNamed:(NSString *)name ofType:(MPSiteType)type withCounter:(NSUInteger)counter
|
||||||
variant:(MPSiteVariant)variant usingKey:(MPKey *)key {
|
variant:(MPSiteVariant)variant context:(NSString *)context usingKey:(MPKey *)key {
|
||||||
|
|
||||||
// Determine the seed whose bytes will be used for calculating a password
|
// Determine the seed whose bytes will be used for calculating a password
|
||||||
uint32_t ncounter = htonl( counter ), nnameLength = htonl( name.length );
|
uint32_t ncounter = htonl( counter ), nnameLength = htonl( name.length ), ncontextLength = htonl( context.length );
|
||||||
NSData *counterBytes = [NSData dataWithBytes:&ncounter length:sizeof( ncounter )];
|
NSData *counterBytes = [NSData dataWithBytes:&ncounter length:sizeof( ncounter )];
|
||||||
NSData *nameLengthBytes = [NSData dataWithBytes:&nnameLength length:sizeof( nnameLength )];
|
NSData *nameLengthBytes = [NSData dataWithBytes:&nnameLength length:sizeof( nnameLength )];
|
||||||
|
NSData *contextLengthBytes = [NSData dataWithBytes:&ncontextLength length:sizeof( ncontextLength )];
|
||||||
NSString *scope = [self scopeForVariant:variant];
|
NSString *scope = [self scopeForVariant:variant];
|
||||||
trc( @"seed from: hmac-sha256(%@, %@ | %@ | %@ | %@)",
|
trc( @"seed from: hmac-sha256(%@, %@ | %@ | %@ | %@)",
|
||||||
[[key keyID] encodeHex], scope, [nameLengthBytes encodeHex], name, [counterBytes encodeHex] );
|
[[key keyID] encodeHex], scope, [nameLengthBytes encodeHex], name, [counterBytes encodeHex] );
|
||||||
@ -60,6 +61,8 @@
|
|||||||
nameLengthBytes,
|
nameLengthBytes,
|
||||||
[name dataUsingEncoding:NSUTF8StringEncoding],
|
[name dataUsingEncoding:NSUTF8StringEncoding],
|
||||||
counterBytes,
|
counterBytes,
|
||||||
|
context? contextLengthBytes: nil,
|
||||||
|
[context dataUsingEncoding:NSUTF8StringEncoding],
|
||||||
nil]
|
nil]
|
||||||
hmacWith:PearlHashSHA256 key:key.keyData];
|
hmacWith:PearlHashSHA256 key:key.keyData];
|
||||||
trc( @"seed is: %@", [seed encodeHex] );
|
trc( @"seed is: %@", [seed encodeHex] );
|
||||||
|
21
MasterPassword/ObjC/MPAlgorithmV2.h
Normal file
21
MasterPassword/ObjC/MPAlgorithmV2.h
Normal 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 "MPAlgorithmV1.h"
|
||||||
|
|
||||||
|
@interface MPAlgorithmV2 : MPAlgorithmV1
|
||||||
|
@end
|
97
MasterPassword/ObjC/MPAlgorithmV2.m
Normal file
97
MasterPassword/ObjC/MPAlgorithmV2.m
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
/**
|
||||||
|
* 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 <objc/runtime.h>
|
||||||
|
#import "MPAlgorithmV2.h"
|
||||||
|
#import "MPEntities.h"
|
||||||
|
|
||||||
|
@implementation MPAlgorithmV2
|
||||||
|
|
||||||
|
- (NSUInteger)version {
|
||||||
|
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL)tryMigrateSite:(MPSiteEntity *)site explicit:(BOOL)explicit {
|
||||||
|
|
||||||
|
if (site.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.version = [self version];
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSString *)generateContentForSiteNamed:(NSString *)name ofType:(MPSiteType)type withCounter:(NSUInteger)counter
|
||||||
|
variant:(MPSiteVariant)variant context:(NSString *)context usingKey:(MPKey *)key {
|
||||||
|
|
||||||
|
// Determine the seed whose bytes will be used for calculating a password
|
||||||
|
NSData *nameBytes = [name dataUsingEncoding:NSUTF8StringEncoding];
|
||||||
|
NSData *contextBytes = [context dataUsingEncoding:NSUTF8StringEncoding];
|
||||||
|
uint32_t ncounter = htonl( counter ), nnameLength = htonl( nameBytes.length ), ncontextLength = htonl( contextBytes.length );
|
||||||
|
NSData *counterBytes = [NSData dataWithBytes:&ncounter length:sizeof( ncounter )];
|
||||||
|
NSData *nameLengthBytes = [NSData dataWithBytes:&nnameLength length:sizeof( nnameLength )];
|
||||||
|
NSData *contextLengthBytes = [NSData dataWithBytes:&ncontextLength length:sizeof( ncontextLength )];
|
||||||
|
NSString *scope = [self scopeForVariant:variant];
|
||||||
|
NSData *scopeBytes = [scope dataUsingEncoding:NSUTF8StringEncoding];
|
||||||
|
trc( @"seed from: hmac-sha256(%@, %@ | %@ | %@ | %@)",
|
||||||
|
[[key keyID] encodeHex], scope, [nameLengthBytes encodeHex], name, [counterBytes encodeHex] );
|
||||||
|
NSData *seed = [[NSData dataByConcatenatingDatas:
|
||||||
|
scopeBytes,
|
||||||
|
nameLengthBytes,
|
||||||
|
nameBytes,
|
||||||
|
counterBytes,
|
||||||
|
context? contextLengthBytes: nil,
|
||||||
|
contextBytes,
|
||||||
|
nil]
|
||||||
|
hmacWith:PearlHashSHA256 key:key.keyData];
|
||||||
|
trc( @"seed is: %@", [seed encodeHex] );
|
||||||
|
const unsigned char *seedBytes = seed.bytes;
|
||||||
|
|
||||||
|
// Determine the cipher from the first seed byte.
|
||||||
|
NSAssert( [seed length], @"Missing seed." );
|
||||||
|
NSArray *typeCiphers = [self ciphersForType:type];
|
||||||
|
NSString *cipher = typeCiphers[seedBytes[0] % [typeCiphers count]];
|
||||||
|
trc( @"type %@ (%lu), ciphers: %@, selected: %@", [self nameOfType:type], (unsigned long)type, typeCiphers, cipher );
|
||||||
|
|
||||||
|
// Encode the content, character by character, using subsequent seed bytes and the cipher.
|
||||||
|
NSAssert( [seed length] >= [cipher length] + 1, @"Insufficient seed bytes to encode cipher." );
|
||||||
|
NSMutableString *content = [NSMutableString stringWithCapacity:[cipher length]];
|
||||||
|
for (NSUInteger c = 0; c < [cipher length]; ++c) {
|
||||||
|
uint16_t keyByte = seedBytes[c + 1];
|
||||||
|
NSString *cipherClass = [cipher substringWithRange:NSMakeRange( c, 1 )];
|
||||||
|
NSString *cipherClassCharacters = [self charactersForCipherClass:cipherClass];
|
||||||
|
NSString *character = [cipherClassCharacters substringWithRange:NSMakeRange( keyByte % [cipherClassCharacters length], 1 )];
|
||||||
|
|
||||||
|
trc( @"class %@ has characters: %@, index: %u, selected: %@", cipherClass, cipherClassCharacters, keyByte, character );
|
||||||
|
[content appendString:character];
|
||||||
|
}
|
||||||
|
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
@ -36,7 +36,7 @@
|
|||||||
@property(readonly) id<MPAlgorithm> algorithm;
|
@property(readonly) id<MPAlgorithm> algorithm;
|
||||||
|
|
||||||
- (NSUInteger)use;
|
- (NSUInteger)use;
|
||||||
- (BOOL)migrateExplicitly:(BOOL)explicit;
|
- (BOOL)tryMigrateExplicitly:(BOOL)explicit;
|
||||||
- (NSString *)resolveLoginUsingKey:(MPKey *)key;
|
- (NSString *)resolveLoginUsingKey:(MPKey *)key;
|
||||||
- (NSString *)resolvePasswordUsingKey:(MPKey *)key;
|
- (NSString *)resolvePasswordUsingKey:(MPKey *)key;
|
||||||
- (void)resolveLoginUsingKey:(MPKey *)key result:(void ( ^ )(NSString *))result;
|
- (void)resolveLoginUsingKey:(MPKey *)key result:(void ( ^ )(NSString *))result;
|
||||||
|
@ -134,18 +134,20 @@
|
|||||||
self.loginName, self.requiresExplicitMigration );
|
self.loginName, self.requiresExplicitMigration );
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)migrateExplicitly:(BOOL)explicit {
|
- (BOOL)tryMigrateExplicitly:(BOOL)explicit {
|
||||||
|
|
||||||
while (self.version < MPAlgorithmDefaultVersion)
|
while (self.version < MPAlgorithmDefaultVersion) {
|
||||||
if ([MPAlgorithmForVersion( self.version + 1 ) migrateSite:self explicit:explicit])
|
NSUInteger toVersion = self.version + 1;
|
||||||
inf( @"%@ migration to version: %ld succeeded for site: %@",
|
if (![MPAlgorithmForVersion( toVersion ) tryMigrateSite:self explicit:explicit]) {
|
||||||
explicit? @"Explicit": @"Automatic", (long)self.version + 1, self );
|
|
||||||
else {
|
|
||||||
wrn( @"%@ migration to version: %ld failed for site: %@",
|
wrn( @"%@ migration to version: %ld failed for site: %@",
|
||||||
explicit? @"Explicit": @"Automatic", (long)self.version + 1, self );
|
explicit? @"Explicit": @"Automatic", (long)toVersion, self );
|
||||||
return NO;
|
return NO;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inf( @"%@ migration to version: %ld succeeded for site: %@",
|
||||||
|
explicit? @"Explicit": @"Automatic", (long)toVersion, self );
|
||||||
|
}
|
||||||
|
|
||||||
return YES;
|
return YES;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -317,7 +317,7 @@
|
|||||||
- (IBAction)doUpgrade:(UIButton *)sender {
|
- (IBAction)doUpgrade:(UIButton *)sender {
|
||||||
|
|
||||||
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||||
if (![[self siteInContext:context] migrateExplicitly:YES]) {
|
if (![[self siteInContext:context] tryMigrateExplicitly:YES]) {
|
||||||
[PearlOverlay showTemporaryOverlayWithTitle:@"Couldn't Upgrade Site" dismissAfter:2];
|
[PearlOverlay showTemporaryOverlayWithTitle:@"Couldn't Upgrade Site" dismissAfter:2];
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -74,6 +74,18 @@
|
|||||||
[self updatePasswords];
|
[self updatePasswords];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)viewDidAppear:(BOOL)animated {
|
||||||
|
|
||||||
|
[super viewDidAppear:animated];
|
||||||
|
|
||||||
|
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||||
|
MPUserEntity *activeUser = [[MPiOSAppDelegate get] activeUserInContext:context];
|
||||||
|
if (![MPAlgorithmDefault tryMigrateUser:activeUser inContext:context])
|
||||||
|
[PearlOverlay showTemporaryOverlayWithTitle:@"Some Sites Need Upgrade" dismissAfter:2];
|
||||||
|
[context saveToStore];
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
- (void)viewWillDisappear:(BOOL)animated {
|
- (void)viewWillDisappear:(BOOL)animated {
|
||||||
|
|
||||||
[super viewWillDisappear:animated];
|
[super viewWillDisappear:animated];
|
||||||
|
@ -35,6 +35,7 @@
|
|||||||
93D3992FA1546E01F498F665 /* PearlNavigationController.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D398567FD02DB2647B8CF3 /* PearlNavigationController.h */; };
|
93D3992FA1546E01F498F665 /* PearlNavigationController.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D398567FD02DB2647B8CF3 /* PearlNavigationController.h */; };
|
||||||
93D399433EA75E50656040CB /* Twitter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 93D394077F8FAB8167647187 /* Twitter.framework */; };
|
93D399433EA75E50656040CB /* Twitter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 93D394077F8FAB8167647187 /* Twitter.framework */; };
|
||||||
93D399D7E08A142776A74CB8 /* MPOverlayViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D395105935859D71679931 /* MPOverlayViewController.m */; };
|
93D399D7E08A142776A74CB8 /* MPOverlayViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D395105935859D71679931 /* MPOverlayViewController.m */; };
|
||||||
|
93D39A27F2506C6FEEF9C588 /* MPAlgorithmV2.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D399A8E3181B442D347CD7 /* MPAlgorithmV2.m */; };
|
||||||
93D39A53D76CA70786423458 /* UICollectionView+PearlReloadFromArray.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D39246FC21C6E63E35D615 /* UICollectionView+PearlReloadFromArray.h */; };
|
93D39A53D76CA70786423458 /* UICollectionView+PearlReloadFromArray.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D39246FC21C6E63E35D615 /* UICollectionView+PearlReloadFromArray.h */; };
|
||||||
93D39A5FF670957C0AF8298D /* MPPasswordCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39DEA995041A13DC9CAF7 /* MPPasswordCell.m */; };
|
93D39A5FF670957C0AF8298D /* MPPasswordCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39DEA995041A13DC9CAF7 /* MPPasswordCell.m */; };
|
||||||
93D39A8EA1C49CE43B63F47B /* PearlUICollectionView.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39D8A953779B35403AF6E /* PearlUICollectionView.m */; };
|
93D39A8EA1C49CE43B63F47B /* PearlUICollectionView.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39D8A953779B35403AF6E /* PearlUICollectionView.m */; };
|
||||||
@ -448,12 +449,14 @@
|
|||||||
93D3995B1D4DCE5A30D882BA /* MPCoachmarkViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPCoachmarkViewController.m; sourceTree = "<group>"; };
|
93D3995B1D4DCE5A30D882BA /* MPCoachmarkViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPCoachmarkViewController.m; sourceTree = "<group>"; };
|
||||||
93D39975CE5AEC99E3F086C7 /* MPPasswordCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPPasswordCell.h; sourceTree = "<group>"; };
|
93D39975CE5AEC99E3F086C7 /* MPPasswordCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPPasswordCell.h; sourceTree = "<group>"; };
|
||||||
93D3999693660C89A7465F4E /* MPCoachmarkViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPCoachmarkViewController.h; sourceTree = "<group>"; };
|
93D3999693660C89A7465F4E /* MPCoachmarkViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPCoachmarkViewController.h; sourceTree = "<group>"; };
|
||||||
|
93D399A8E3181B442D347CD7 /* MPAlgorithmV2.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPAlgorithmV2.m; sourceTree = "<group>"; };
|
||||||
93D399E571F61E50A9BF8FAF /* MPUsersViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPUsersViewController.m; sourceTree = "<group>"; };
|
93D399E571F61E50A9BF8FAF /* MPUsersViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPUsersViewController.m; sourceTree = "<group>"; };
|
||||||
93D399F244BB522A317811BB /* MPFixable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPFixable.h; sourceTree = "<group>"; };
|
93D399F244BB522A317811BB /* MPFixable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPFixable.h; sourceTree = "<group>"; };
|
||||||
93D39A1DDFA09AE2E14D26DC /* UIResponder+PearlFirstResponder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIResponder+PearlFirstResponder.m"; sourceTree = "<group>"; };
|
93D39A1DDFA09AE2E14D26DC /* UIResponder+PearlFirstResponder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIResponder+PearlFirstResponder.m"; sourceTree = "<group>"; };
|
||||||
93D39A28369954D147E239BA /* MPSetupViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPSetupViewController.m; sourceTree = "<group>"; };
|
93D39A28369954D147E239BA /* MPSetupViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPSetupViewController.m; sourceTree = "<group>"; };
|
||||||
93D39A4759186F6D2D34AA6B /* PearlSizedTextView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PearlSizedTextView.h; sourceTree = "<group>"; };
|
93D39A4759186F6D2D34AA6B /* PearlSizedTextView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PearlSizedTextView.h; sourceTree = "<group>"; };
|
||||||
93D39A813CA9D7E192261ED2 /* MPFixable.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPFixable.m; sourceTree = "<group>"; };
|
93D39A813CA9D7E192261ED2 /* MPFixable.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPFixable.m; sourceTree = "<group>"; };
|
||||||
|
93D39A97A7D48CB3B784194D /* MPAlgorithmV2.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPAlgorithmV2.h; sourceTree = "<group>"; };
|
||||||
93D39AA10CD00D05937671B1 /* UITextView+PearlAttributes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UITextView+PearlAttributes.h"; sourceTree = "<group>"; };
|
93D39AA10CD00D05937671B1 /* UITextView+PearlAttributes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UITextView+PearlAttributes.h"; sourceTree = "<group>"; };
|
||||||
93D39AA1EE2E1E7B81372240 /* NSDictionary+Indexing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSDictionary+Indexing.m"; sourceTree = "<group>"; };
|
93D39AA1EE2E1E7B81372240 /* NSDictionary+Indexing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSDictionary+Indexing.m"; sourceTree = "<group>"; };
|
||||||
93D39ACBA9F4878B6A1CC33B /* MPEmergencyViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPEmergencyViewController.m; sourceTree = "<group>"; };
|
93D39ACBA9F4878B6A1CC33B /* MPEmergencyViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPEmergencyViewController.m; sourceTree = "<group>"; };
|
||||||
@ -2343,6 +2346,8 @@
|
|||||||
93D39A813CA9D7E192261ED2 /* MPFixable.m */,
|
93D39A813CA9D7E192261ED2 /* MPFixable.m */,
|
||||||
93D394C78C7B879C9AD9152C /* MPAppDelegate_InApp.m */,
|
93D394C78C7B879C9AD9152C /* MPAppDelegate_InApp.m */,
|
||||||
93D39CECA10BCCB0BA581BF1 /* MPAppDelegate_InApp.h */,
|
93D39CECA10BCCB0BA581BF1 /* MPAppDelegate_InApp.h */,
|
||||||
|
93D399A8E3181B442D347CD7 /* MPAlgorithmV2.m */,
|
||||||
|
93D39A97A7D48CB3B784194D /* MPAlgorithmV2.h */,
|
||||||
);
|
);
|
||||||
name = ObjC;
|
name = ObjC;
|
||||||
path = ..;
|
path = ..;
|
||||||
@ -3325,6 +3330,7 @@
|
|||||||
93D39D38356F59DBEF934D70 /* MPAppDelegate_InApp.m in Sources */,
|
93D39D38356F59DBEF934D70 /* MPAppDelegate_InApp.m in Sources */,
|
||||||
93D390C1B93F9D3AE37DD0A5 /* MPAnswersViewController.m in Sources */,
|
93D390C1B93F9D3AE37DD0A5 /* MPAnswersViewController.m in Sources */,
|
||||||
93D399D7E08A142776A74CB8 /* MPOverlayViewController.m in Sources */,
|
93D399D7E08A142776A74CB8 /* MPOverlayViewController.m in Sources */,
|
||||||
|
93D39A27F2506C6FEEF9C588 /* MPAlgorithmV2.m in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 37 KiB |
Binary file not shown.
Before Width: | Height: | Size: 116 KiB After Width: | Height: | Size: 118 KiB |
Binary file not shown.
Before Width: | Height: | Size: 253 KiB After Width: | Height: | Size: 254 KiB |
Loading…
Reference in New Issue
Block a user