Complete backup restore/import.
This commit is contained in:
parent
272a90d269
commit
16f7317579
|
@ -1,5 +1,5 @@
|
|||
//
|
||||
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "AppDelegate.h"
|
||||
|
@ -9,6 +9,7 @@
|
|||
#import "DebugLogger.h"
|
||||
#import "MainAppContext.h"
|
||||
#import "NotificationsManager.h"
|
||||
#import "OWSBackup.h"
|
||||
#import "OWSNavigationController.h"
|
||||
#import "Pastelog.h"
|
||||
#import "PushManager.h"
|
||||
|
@ -129,6 +130,10 @@ static NSString *const kURLHostVerifyPrefix = @"verify";
|
|||
|
||||
SetRandFunctionSeed();
|
||||
|
||||
// If a backup restore is in progress, try to complete it.
|
||||
// Otherwise, cleanup backup state.
|
||||
[OWSBackup applicationDidFinishLaunching];
|
||||
|
||||
// XXX - careful when moving this. It must happen before we initialize TSStorageManager.
|
||||
[self verifyDBKeysAvailableBeforeBackgroundLaunch];
|
||||
|
||||
|
|
|
@ -89,10 +89,6 @@
|
|||
self.title = NSLocalizedString(@"SETTINGS_NAV_BAR_TITLE", @"Title for settings activity");
|
||||
|
||||
[self updateTableContents];
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self showDebugUI];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated
|
||||
|
|
|
@ -1367,8 +1367,10 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
OWSBackupImportViewController *backupViewController = [OWSBackupImportViewController new];
|
||||
// TODO: Add support for restoring password-protected backups.
|
||||
[backupViewController importBackup:backupZipPath password:nil];
|
||||
UINavigationController *navigationController =
|
||||
[[UINavigationController alloc] initWithRootViewController:backupViewController];
|
||||
UIViewController *fromViewController = [[UIApplication sharedApplication] frontmostViewController];
|
||||
[fromViewController presentViewController:backupViewController animated:YES completion:nil];
|
||||
[fromViewController presentViewController:navigationController animated:YES completion:nil];
|
||||
}
|
||||
|
||||
- (void)handleTextLongPressGesture:(UILongPressGestureRecognizer *)sender
|
||||
|
|
|
@ -286,10 +286,6 @@ typedef NS_ENUM(NSInteger, CellState) { kArchiveState, kInboxState };
|
|||
}
|
||||
|
||||
[self updateBarButtonItems];
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self settingsButtonPressed:nil];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)updateBarButtonItems
|
||||
|
|
|
@ -130,43 +130,11 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
[subviews addObject:label];
|
||||
}
|
||||
|
||||
if (self.backup.backupPassword) {
|
||||
NSString *message = [NSString stringWithFormat:NSLocalizedString(@"BACKUP_IMPORT_PASSWORD_MESSAGE_FORMAT",
|
||||
@"Format for message indicating that backup import "
|
||||
@"is complete. Embeds: {{the backup password}}."),
|
||||
self.backup.backupPassword];
|
||||
|
||||
UILabel *label = [UILabel new];
|
||||
label.text = message;
|
||||
label.textColor = [UIColor blackColor];
|
||||
label.font = [UIFont ows_regularFontWithSize:14.f];
|
||||
label.textAlignment = NSTextAlignmentCenter;
|
||||
label.numberOfLines = 0;
|
||||
label.lineBreakMode = NSLineBreakByWordWrapping;
|
||||
[subviews addObject:label];
|
||||
}
|
||||
|
||||
[subviews addObject:[UIView new]];
|
||||
|
||||
if (self.backup.backupPassword) {
|
||||
[subviews
|
||||
addObject:[self makeButtonWithTitle:NSLocalizedString(@"BACKUP_IMPORT_COPY_PASSWORD_BUTTON",
|
||||
@"Label for button that copies backup password to the pasteboard.")
|
||||
selector:@selector(copyPassword)]];
|
||||
}
|
||||
|
||||
[subviews addObject:[self makeButtonWithTitle:NSLocalizedString(@"BACKUP_IMPORT_SHARE_BACKUP_BUTTON",
|
||||
@"Label for button that opens share UI for backup.")
|
||||
selector:@selector(shareBackup)]];
|
||||
|
||||
if (self.backup.currentThread) {
|
||||
[subviews
|
||||
addObject:[self makeButtonWithTitle:NSLocalizedString(@"BACKUP_IMPORT_SEND_BACKUP_BUTTON",
|
||||
@"Label for button that 'send backup' in the current conversation.")
|
||||
selector:@selector(sendBackup)]];
|
||||
}
|
||||
|
||||
// TODO: We should offer the option to save the backup to "Files", iCloud, Dropbox, etc.
|
||||
[subviews addObject:[self makeButtonWithTitle:NSLocalizedString(@"BACKUP_IMPORT_RESTART_BUTTON",
|
||||
@"Label for button that restarts app to complete restore.")
|
||||
selector:@selector(restartApp)]];
|
||||
|
||||
UIView *container = [UIView verticalStackWithSubviews:subviews spacing:10];
|
||||
[self.view addSubview:container];
|
||||
|
@ -218,7 +186,14 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
{
|
||||
[self.backup cancel];
|
||||
|
||||
[self.navigationController popViewControllerAnimated:YES];
|
||||
[self.navigationController dismissViewControllerAnimated:YES completion:nil];
|
||||
}
|
||||
|
||||
- (void)restartApp
|
||||
{
|
||||
DDLogInfo(@"%@ %s.", self.logTag, __PRETTY_FUNCTION__);
|
||||
|
||||
[NSException raise:@"OWSBackup_RestartAppToCompleteBackupRestore" format:@"Killing app to complete backup restore"];
|
||||
}
|
||||
|
||||
#pragma mark - OWSBackupDelegate
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
//
|
||||
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "RegistrationViewController.h"
|
||||
|
@ -439,8 +439,9 @@ NSString *const kKeychainKey_LastRegisteredPhoneNumber = @"kKeychainKey_LastRegi
|
|||
OWSCAssert(value.length > 0);
|
||||
|
||||
NSError *error;
|
||||
[SAMKeychain setPassword:value forService:kKeychainService_LastRegistered account:key error:&error];
|
||||
if (error) {
|
||||
[SAMKeychain setAccessibilityType:kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly];
|
||||
BOOL success = [SAMKeychain setPassword:value forService:kKeychainService_LastRegistered account:key error:&error];
|
||||
if (!success || error) {
|
||||
DDLogError(@"%@ Error persisting 'last registered' value in keychain: %@", self.logTag, error);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,6 +46,8 @@ typedef NS_ENUM(NSUInteger, OWSBackupState) {
|
|||
|
||||
- (void)cancel;
|
||||
|
||||
+ (void)applicationDidFinishLaunching;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#import "NSUserDefaults+OWS.h"
|
||||
#import "Signal-Swift.h"
|
||||
#import "zlib.h"
|
||||
#import <SAMKeychain/SAMKeychain.h>
|
||||
#import <SSZipArchive/SSZipArchive.h>
|
||||
#import <SignalMessaging/SignalMessaging-Swift.h>
|
||||
#import <SignalServiceKit/Cryptography.h>
|
||||
|
@ -14,18 +15,32 @@
|
|||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
// Hide the "import" directories from exports, etc. by prefixing their name with a period.
|
||||
NSString *const OWSBackup_DirNamePrefix = @".SignalBackup.";
|
||||
NSString *const OWSBackup_FileExtension = @".signalbackup";
|
||||
NSString *const OWSBackup_EncryptionKeyFilename = @"OWSBackup_EncryptionKeyFilename";
|
||||
NSString *const OWSBackup_DatabasePasswordFilename = @".databasePassword";
|
||||
NSString *const OWSBackup_StandardUserDefaultsFilename = @".standardUserDefaults";
|
||||
NSString *const OWSBackup_AppUserDefaultsFilename = @".appUserDefaults";
|
||||
NSString *const OWSBackup_AppDocumentDirName = @"appDocumentDirectoryPath";
|
||||
NSString *const OWSBackup_AppSharedDataDirName = @"appSharedDataDirectoryPath";
|
||||
|
||||
NSString *const NSUserDefaults_QueuedBackupPath = @"NSUserDefaults_QueuedBackupPath";
|
||||
|
||||
NSString *const Keychain_ImportBackupService = @"OWSKeychainService";
|
||||
NSString *const Keychain_ImportBackupKey = @"ImportBackupKey";
|
||||
|
||||
@interface OWSStorage (OWSBackup)
|
||||
|
||||
- (NSData *)databasePassword;
|
||||
|
||||
+ (void)storeDatabasePassword:(NSString *)password;
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@interface OWSBackup ()
|
||||
@interface OWSBackup () <SSZipArchiveDelegate>
|
||||
|
||||
@property (nonatomic) OWSBackupState backupState;
|
||||
|
||||
|
@ -38,6 +53,8 @@ NSString *const OWSBackup_EncryptionKeyFilename = @"OWSBackup_EncryptionKeyFilen
|
|||
@property (nonatomic) NSString *backupDirPath;
|
||||
@property (nonatomic) NSString *backupZipPath;
|
||||
|
||||
@property (nonatomic) OWSAES256Key *encryptionKey;
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
@ -46,35 +63,62 @@ NSString *const OWSBackup_EncryptionKeyFilename = @"OWSBackup_EncryptionKeyFilen
|
|||
|
||||
- (void)dealloc
|
||||
{
|
||||
OWSAssert(self.backupDirPath.length > 0);
|
||||
|
||||
DDLogInfo(@"%@ Cleaning up: %@", self.logTag, self.backupDirPath);
|
||||
[OWSFileSystem deleteFileIfExists:self.backupDirPath];
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[OWSBackup cleanupBackupState];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)setBackupState:(OWSBackupState)backupState
|
||||
{
|
||||
_backupState = backupState;
|
||||
|
||||
[self.delegate backupStateDidChange];
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self.delegate backupStateDidChange];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)setBackupProgress:(CGFloat)backupProgress
|
||||
{
|
||||
_backupProgress = backupProgress;
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self.delegate backupProgressDidChange];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)fail
|
||||
{
|
||||
DDLogInfo(@"%@ %s.", self.logTag, __PRETTY_FUNCTION__);
|
||||
|
||||
if (!self.isCancelledOrFailed) {
|
||||
self.backupState = OWSBackupState_Failed;
|
||||
}
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[OWSBackup cleanupBackupState];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)cancel
|
||||
{
|
||||
DDLogInfo(@"%@ %s.", self.logTag, __PRETTY_FUNCTION__);
|
||||
|
||||
if (!self.isCancelledOrFailed) {
|
||||
self.backupState = OWSBackupState_Cancelled;
|
||||
}
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[OWSBackup cleanupBackupState];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)complete
|
||||
{
|
||||
DDLogInfo(@"%@ %s.", self.logTag, __PRETTY_FUNCTION__);
|
||||
|
||||
if (!self.isCancelledOrFailed) {
|
||||
self.backupState = OWSBackupState_Complete;
|
||||
}
|
||||
|
@ -92,18 +136,20 @@ NSString *const OWSBackup_EncryptionKeyFilename = @"OWSBackup_EncryptionKeyFilen
|
|||
OWSAssertIsOnMainThread();
|
||||
OWSAssert(CurrentAppContext().isMainApp);
|
||||
|
||||
DDLogInfo(@"%@ %s.", self.logTag, __PRETTY_FUNCTION__);
|
||||
|
||||
self.currentThread = currentThread;
|
||||
self.backupState = OWSBackupState_InProgress;
|
||||
|
||||
if (skipPassword) {
|
||||
DDLogVerbose(@"%@ backup export without password", self.logTag);
|
||||
DDLogInfo(@"%@ backup export without password", self.logTag);
|
||||
} else {
|
||||
// TODO: Should the user pick a password?
|
||||
// If not, should probably generate something more user-friendly,
|
||||
// e.g. case-insensitive set of hexadecimal?
|
||||
NSString *backupPassword = [NSUUID UUID].UUIDString;
|
||||
self.backupPassword = backupPassword;
|
||||
DDLogVerbose(@"%@ backup export with password: %@", self.logTag, backupPassword);
|
||||
DDLogInfo(@"%@ backup export with password: %@", self.logTag, backupPassword);
|
||||
}
|
||||
|
||||
[self startExport];
|
||||
|
@ -124,8 +170,11 @@ NSString *const OWSBackup_EncryptionKeyFilename = @"OWSBackup_EncryptionKeyFilen
|
|||
|
||||
- (void)exportToFilesAndZip
|
||||
{
|
||||
DDLogInfo(@"%@ %s.", self.logTag, __PRETTY_FUNCTION__);
|
||||
|
||||
NSString *temporaryDirectory = NSTemporaryDirectory();
|
||||
NSString *rootDirPath = [temporaryDirectory stringByAppendingPathComponent:[NSUUID UUID].UUIDString];
|
||||
NSString *rootDirName = [OWSBackup_DirNamePrefix stringByAppendingString:[NSUUID UUID].UUIDString];
|
||||
NSString *rootDirPath = [temporaryDirectory stringByAppendingPathComponent:rootDirName];
|
||||
NSString *backupDirPath = [rootDirPath stringByAppendingPathComponent:@"Contents"];
|
||||
|
||||
NSDateFormatter *dateFormatter = [NSDateFormatter new];
|
||||
|
@ -154,13 +203,14 @@ NSString *const OWSBackup_EncryptionKeyFilename = @"OWSBackup_EncryptionKeyFilen
|
|||
}
|
||||
|
||||
OWSAES256Key *encryptionKey = [OWSAES256Key generateRandomKey];
|
||||
self.encryptionKey = encryptionKey;
|
||||
|
||||
NSData *databasePassword = [TSStorageManager sharedManager].databasePassword;
|
||||
|
||||
// TODO: We don't want this to reside unencrypted on disk even temporarily.
|
||||
// We need to encrypt this with a key that we hide in the keychain.
|
||||
if (![self writeData:databasePassword
|
||||
fileName:@"databasePassword"
|
||||
fileName:OWSBackup_DatabasePasswordFilename
|
||||
backupDirPath:backupDirPath
|
||||
encryptionKey:encryptionKey]) {
|
||||
return [self fail];
|
||||
|
@ -169,7 +219,7 @@ NSString *const OWSBackup_EncryptionKeyFilename = @"OWSBackup_EncryptionKeyFilen
|
|||
return;
|
||||
}
|
||||
if (![self writeUserDefaults:NSUserDefaults.standardUserDefaults
|
||||
fileName:@"standardUserDefaults"
|
||||
fileName:OWSBackup_StandardUserDefaultsFilename
|
||||
backupDirPath:backupDirPath
|
||||
encryptionKey:encryptionKey]) {
|
||||
return [self fail];
|
||||
|
@ -178,7 +228,7 @@ NSString *const OWSBackup_EncryptionKeyFilename = @"OWSBackup_EncryptionKeyFilen
|
|||
return;
|
||||
}
|
||||
if (![self writeUserDefaults:NSUserDefaults.appUserDefaults
|
||||
fileName:@"appUserDefaults"
|
||||
fileName:OWSBackup_AppUserDefaultsFilename
|
||||
backupDirPath:backupDirPath
|
||||
encryptionKey:encryptionKey]) {
|
||||
return [self fail];
|
||||
|
@ -192,7 +242,7 @@ NSString *const OWSBackup_EncryptionKeyFilename = @"OWSBackup_EncryptionKeyFilen
|
|||
[TSStorageManager.sharedManager.newDatabaseConnection
|
||||
readWriteWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) {
|
||||
if (![self copyDirectory:OWSFileSystem.appDocumentDirectoryPath
|
||||
dstDirName:@"appDocumentDirectoryPath"
|
||||
dstDirName:OWSBackup_AppDocumentDirName
|
||||
backupDirPath:backupDirPath]) {
|
||||
[self fail];
|
||||
return;
|
||||
|
@ -201,7 +251,7 @@ NSString *const OWSBackup_EncryptionKeyFilename = @"OWSBackup_EncryptionKeyFilen
|
|||
return;
|
||||
}
|
||||
if (![self copyDirectory:OWSFileSystem.appSharedDataDirectoryPath
|
||||
dstDirName:@"appSharedDataDirectoryPath"
|
||||
dstDirName:OWSBackup_AppSharedDataDirName
|
||||
backupDirPath:backupDirPath]) {
|
||||
[self fail];
|
||||
return;
|
||||
|
@ -219,7 +269,6 @@ NSString *const OWSBackup_EncryptionKeyFilename = @"OWSBackup_EncryptionKeyFilen
|
|||
[OWSFileSystem deleteFileIfExists:self.backupDirPath];
|
||||
}
|
||||
|
||||
// TODO: We
|
||||
- (BOOL)writeData:(NSData *)data
|
||||
fileName:(NSString *)fileName
|
||||
backupDirPath:(NSString *)backupDirPath
|
||||
|
@ -230,12 +279,15 @@ NSString *const OWSBackup_EncryptionKeyFilename = @"OWSBackup_EncryptionKeyFilen
|
|||
OWSAssert(backupDirPath.length > 0);
|
||||
OWSAssert(encryptionKey);
|
||||
|
||||
NSData *encryptedData = [Cryptography encryptAESGCMWithData:data key:encryptionKey];
|
||||
OWSAssert(encryptedData);
|
||||
NSData *_Nullable encryptedData = [Cryptography encryptAESGCMWithData:data key:encryptionKey];
|
||||
if (!encryptedData) {
|
||||
OWSFail(@"%@ failed to encrypt data: %@", self.logTag, fileName);
|
||||
return NO;
|
||||
}
|
||||
|
||||
NSString *filePath = [backupDirPath stringByAppendingPathComponent:fileName];
|
||||
|
||||
DDLogVerbose(@"%@ writeData: %@", self.logTag, filePath);
|
||||
DDLogInfo(@"%@ writeData: %@", self.logTag, filePath);
|
||||
|
||||
NSError *error;
|
||||
BOOL success = [encryptedData writeToFile:filePath options:NSDataWritingAtomic error:&error];
|
||||
|
@ -254,7 +306,7 @@ NSString *const OWSBackup_EncryptionKeyFilename = @"OWSBackup_EncryptionKeyFilen
|
|||
|
||||
NSString *dstDirPath = [backupDirPath stringByAppendingPathComponent:dstDirName];
|
||||
|
||||
DDLogVerbose(@"%@ copyDirectory: %@ -> %@", self.logTag, srcDirPath, dstDirPath);
|
||||
DDLogInfo(@"%@ copyDirectory: %@ -> %@", self.logTag, srcDirPath, dstDirPath);
|
||||
|
||||
// We "manually" copy the "root" items in the src directory.
|
||||
// Can't just use [NSFileManager copyItemAtPath:...] because the shared data container
|
||||
|
@ -270,7 +322,7 @@ NSString *const OWSBackup_EncryptionKeyFilename = @"OWSBackup_EncryptionKeyFilen
|
|||
NSString *srcFilePath = [srcDirPath stringByAppendingPathComponent:fileName];
|
||||
NSString *dstFilePath = [dstDirPath stringByAppendingPathComponent:fileName];
|
||||
if ([fileName hasPrefix:@"."]) {
|
||||
DDLogVerbose(@"%@ ignoring: %@", self.logTag, srcFilePath);
|
||||
DDLogInfo(@"%@ ignoring: %@", self.logTag, srcFilePath);
|
||||
continue;
|
||||
}
|
||||
BOOL success = [[NSFileManager defaultManager] copyItemAtPath:srcFilePath toPath:dstFilePath error:&error];
|
||||
|
@ -293,7 +345,7 @@ NSString *const OWSBackup_EncryptionKeyFilename = @"OWSBackup_EncryptionKeyFilen
|
|||
OWSAssert(backupDirPath.length > 0);
|
||||
OWSAssert(encryptionKey);
|
||||
|
||||
DDLogVerbose(@"%@ writeUserDefaults: %@", self.logTag, fileName);
|
||||
DDLogInfo(@"%@ writeUserDefaults: %@", self.logTag, fileName);
|
||||
|
||||
NSDictionary<NSString *, id> *dictionary = userDefaults.dictionaryRepresentation;
|
||||
if (!dictionary) {
|
||||
|
@ -317,31 +369,11 @@ NSString *const OWSBackup_EncryptionKeyFilename = @"OWSBackup_EncryptionKeyFilen
|
|||
OWSAssert(dstFilePath.length > 0);
|
||||
OWSAssert(encryptionKey);
|
||||
|
||||
DDLogInfo(@"%@ %s.", self.logTag, __PRETTY_FUNCTION__);
|
||||
|
||||
srcDirPath = [srcDirPath stringByStandardizingPath];
|
||||
OWSAssert(srcDirPath.length > 0);
|
||||
|
||||
// BOOL success = [SSZipArchive createZipFileAtPath:dstFilePath
|
||||
// withContentsOfDirectory:srcDirPath
|
||||
// keepParentDirectory:NO
|
||||
// compressionLevel:Z_DEFAULT_COMPRESSION
|
||||
// password:self.backupPassword
|
||||
// AES:self.backupPassword != nil
|
||||
// progressHandler:^(NSUInteger entryNumber, NSUInteger total) {
|
||||
// DDLogVerbose(@"%@ Zip progress: %zd / %zd = %f",
|
||||
// self.logTag,
|
||||
// entryNumber,
|
||||
// total,
|
||||
// entryNumber / (CGFloat)total);
|
||||
//
|
||||
// CGFloat progress = entryNumber / (CGFloat)total;
|
||||
// self.backupProgress = progress;
|
||||
// [self.delegate backupProgressDidChange];
|
||||
// }];
|
||||
// if (!success) {
|
||||
// OWSFail(@"%@ failed to write zip backup", self.logTag);
|
||||
// return NO;
|
||||
// }
|
||||
|
||||
NSError *error;
|
||||
NSArray<NSString *> *_Nullable srcFilePaths = [OWSFileSystem allFilesInDirectoryRecursive:srcDirPath error:&error];
|
||||
if (!srcFilePaths || error) {
|
||||
|
@ -349,20 +381,15 @@ NSString *const OWSBackup_EncryptionKeyFilename = @"OWSBackup_EncryptionKeyFilen
|
|||
return NO;
|
||||
}
|
||||
|
||||
// Don't use the SSZipArchive convenience methods so that we can add the
|
||||
// encryption key directly as data.
|
||||
SSZipArchive *zipArchive = [[SSZipArchive alloc] initWithPath:dstFilePath];
|
||||
if (![zipArchive open]) {
|
||||
OWSFail(@"%@ failed to open zip file.", self.logTag);
|
||||
return NO;
|
||||
}
|
||||
for (NSString *srcFilePath in srcFilePaths) {
|
||||
OWSAssert(srcFilePath.stringByStandardizingPath.length > 0);
|
||||
OWSAssert([srcFilePath.stringByStandardizingPath hasPrefix:srcDirPath]);
|
||||
NSString *relativePath = [srcFilePath.stringByStandardizingPath substringFromIndex:srcDirPath.length];
|
||||
NSString *separator = @"/";
|
||||
if ([relativePath hasPrefix:separator]) {
|
||||
relativePath = [relativePath substringFromIndex:separator.length];
|
||||
}
|
||||
OWSAssert(relativePath.length > 0);
|
||||
NSString *relativePath = [self relativePathforPath:srcFilePath basePath:srcDirPath];
|
||||
BOOL success = [zipArchive writeFileAtPath:srcFilePath
|
||||
withFileName:relativePath
|
||||
compressionLevel:Z_DEFAULT_COMPRESSION
|
||||
|
@ -396,12 +423,12 @@ NSString *const OWSBackup_EncryptionKeyFilename = @"OWSBackup_EncryptionKeyFilen
|
|||
OWSFail(@"%@ failed to get zip file size: %@", self.logTag, error);
|
||||
return NO;
|
||||
}
|
||||
DDLogVerbose(@"%@ Zip file size: %@", self.logTag, fileSize);
|
||||
DDLogInfo(@"%@ Zip file size: %@", self.logTag, fileSize);
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
#pragma mark - Import Backup
|
||||
#pragma mark - Import Backup, Part 1
|
||||
|
||||
- (void)importBackup:(NSString *)srcZipPath password:(NSString *_Nullable)password
|
||||
{
|
||||
|
@ -409,26 +436,28 @@ NSString *const OWSBackup_EncryptionKeyFilename = @"OWSBackup_EncryptionKeyFilen
|
|||
OWSAssert(srcZipPath.length > 0);
|
||||
OWSAssert(CurrentAppContext().isMainApp);
|
||||
|
||||
DDLogInfo(@"%@ %s.", self.logTag, __PRETTY_FUNCTION__);
|
||||
|
||||
self.backupPassword = password;
|
||||
|
||||
self.backupState = OWSBackupState_InProgress;
|
||||
|
||||
if (password.length == 0) {
|
||||
DDLogVerbose(@"%@ backup import without password", self.logTag);
|
||||
DDLogInfo(@"%@ backup import without password", self.logTag);
|
||||
} else {
|
||||
DDLogVerbose(@"%@ backup import with password: %@", self.logTag, password);
|
||||
DDLogInfo(@"%@ backup import with password: %@", self.logTag, password);
|
||||
}
|
||||
|
||||
[self startExport];
|
||||
[self startImport:srcZipPath];
|
||||
}
|
||||
|
||||
- (void)startExport:(NSString *)srcZipPath
|
||||
- (void)startImport:(NSString *)srcZipPath
|
||||
{
|
||||
OWSAssertIsOnMainThread();
|
||||
OWSAssert(srcZipPath.length > 0);
|
||||
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
[self unpackFiles:srcZipPath];
|
||||
[self prepareForImport:srcZipPath];
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self complete];
|
||||
|
@ -436,13 +465,14 @@ NSString *const OWSBackup_EncryptionKeyFilename = @"OWSBackup_EncryptionKeyFilen
|
|||
});
|
||||
}
|
||||
|
||||
- (void)unpackFiles:(NSString *)srcZipPath
|
||||
- (void)prepareForImport:(NSString *)srcZipPath
|
||||
{
|
||||
OWSAssert(srcZipPath.length > 0);
|
||||
|
||||
DDLogInfo(@"%@ %s.", self.logTag, __PRETTY_FUNCTION__);
|
||||
|
||||
NSString *documentDirectoryPath = OWSFileSystem.appDocumentDirectoryPath;
|
||||
// Hide the "import" directory from exports, etc. by prefixing with a period.
|
||||
NSString *rootDirName = [@"." stringByAppendingString:[NSUUID UUID].UUIDString];
|
||||
NSString *rootDirName = [OWSBackup_DirNamePrefix stringByAppendingString:[NSUUID UUID].UUIDString];
|
||||
NSString *rootDirPath = [documentDirectoryPath stringByAppendingPathComponent:rootDirName];
|
||||
NSString *backupDirPath = [rootDirPath stringByAppendingPathComponent:@"Contents"];
|
||||
NSString *backupZipPath = [rootDirPath stringByAppendingPathComponent:srcZipPath.lastPathComponent];
|
||||
|
@ -466,62 +496,408 @@ NSString *const OWSBackup_EncryptionKeyFilename = @"OWSBackup_EncryptionKeyFilen
|
|||
if (self.isCancelledOrFailed) {
|
||||
return;
|
||||
}
|
||||
if (![self unzipFilePath]) {
|
||||
return [self fail];
|
||||
}
|
||||
if (self.isCancelledOrFailed) {
|
||||
return;
|
||||
}
|
||||
if (![self extractEncryptionKey]) {
|
||||
return [self fail];
|
||||
}
|
||||
if (self.isCancelledOrFailed) {
|
||||
return;
|
||||
}
|
||||
if (![self isValidBackup]) {
|
||||
return [self fail];
|
||||
}
|
||||
if (self.isCancelledOrFailed) {
|
||||
return;
|
||||
}
|
||||
if (![self enqueueBackupRestore]) {
|
||||
return [self fail];
|
||||
}
|
||||
}
|
||||
|
||||
//// NSData *databasePassword = [TSStorageManager sharedManager].databasePassword;
|
||||
- (BOOL)extractEncryptionKey
|
||||
{
|
||||
DDLogInfo(@"%@ %s.", self.logTag, __PRETTY_FUNCTION__);
|
||||
|
||||
NSString *encryptionKeyFilePath =
|
||||
[self.backupDirPath stringByAppendingPathComponent:OWSBackup_EncryptionKeyFilename];
|
||||
if (![[NSFileManager defaultManager] fileExistsAtPath:encryptionKeyFilePath]) {
|
||||
return NO;
|
||||
}
|
||||
NSData *_Nullable encryptionKeyData = [NSData dataWithContentsOfFile:encryptionKeyFilePath];
|
||||
if (!encryptionKeyData) {
|
||||
return NO;
|
||||
}
|
||||
OWSAES256Key *encryptionKey = [OWSAES256Key keyWithData:encryptionKeyData];
|
||||
if (!encryptionKey) {
|
||||
return NO;
|
||||
}
|
||||
self.encryptionKey = encryptionKey;
|
||||
|
||||
NSError *error = nil;
|
||||
BOOL success = [[NSFileManager defaultManager] removeItemAtPath:encryptionKeyFilePath error:&error];
|
||||
if (!success || error) {
|
||||
OWSFail(@"%@ could not delete encryption key file: %@", self.logTag, error);
|
||||
return NO;
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)unzipFilePath
|
||||
{
|
||||
OWSAssert(self.backupZipPath.length > 0);
|
||||
OWSAssert(self.backupDirPath.length > 0);
|
||||
|
||||
DDLogInfo(@"%@ %s.", self.logTag, __PRETTY_FUNCTION__);
|
||||
|
||||
// Don't use the SSZipArchive convenience methods so that we can add the
|
||||
// encryption key directly as data.
|
||||
|
||||
// TODO: Should we use preserveAttributes?
|
||||
NSError *error = nil;
|
||||
BOOL success = [SSZipArchive unzipFileAtPath:self.backupZipPath
|
||||
toDestination:self.backupDirPath
|
||||
preserveAttributes:YES
|
||||
overwrite:YES
|
||||
nestedZipLevel:0
|
||||
password:self.backupPassword
|
||||
error:&error
|
||||
delegate:self
|
||||
progressHandler:^(NSString *entry, unz_file_info zipInfo, long entryNumber, long total) {
|
||||
DDLogInfo(@"%@ progressHandler: %ld %ld", self.logTag, entryNumber, total);
|
||||
|
||||
CGFloat progress = entryNumber / (CGFloat)total;
|
||||
self.backupProgress = progress;
|
||||
}
|
||||
completionHandler:^(NSString *path, BOOL succeeded, NSError *_Nullable completionError) {
|
||||
DDLogInfo(@"%@ completionHandler: %d %@", self.logTag, succeeded, completionError);
|
||||
}];
|
||||
if (!success || error) {
|
||||
OWSFail(@"%@ failed to unzip file: %@.", self.logTag, error);
|
||||
return NO;
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)isValidBackup
|
||||
{
|
||||
NSString *databasePasswordFilePath =
|
||||
[self.backupDirPath stringByAppendingPathComponent:OWSBackup_DatabasePasswordFilename];
|
||||
if (![[NSFileManager defaultManager] fileExistsAtPath:databasePasswordFilePath]) {
|
||||
return NO;
|
||||
}
|
||||
NSString *standardUserDefaultsFilePath =
|
||||
[self.backupDirPath stringByAppendingPathComponent:OWSBackup_StandardUserDefaultsFilename];
|
||||
if (![[NSFileManager defaultManager] fileExistsAtPath:standardUserDefaultsFilePath]) {
|
||||
return NO;
|
||||
}
|
||||
NSString *appUserDefaultsFilePath =
|
||||
[self.backupDirPath stringByAppendingPathComponent:OWSBackup_AppUserDefaultsFilename];
|
||||
if (![[NSFileManager defaultManager] fileExistsAtPath:appUserDefaultsFilePath]) {
|
||||
return NO;
|
||||
}
|
||||
// TODO: Verify that the primary database exists.
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)enqueueBackupRestore
|
||||
{
|
||||
DDLogInfo(@"%@ %s.", self.logTag, __PRETTY_FUNCTION__);
|
||||
|
||||
NSError *error = nil;
|
||||
[SAMKeychain setAccessibilityType:kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly];
|
||||
BOOL success = [SAMKeychain setPasswordData:self.encryptionKey.keyData
|
||||
forService:Keychain_ImportBackupService
|
||||
account:Keychain_ImportBackupKey
|
||||
error:&error];
|
||||
if (!success || error) {
|
||||
OWSFail(@"%@ Could not store encryption key for import backup: %@", self.logTag, error);
|
||||
return NO;
|
||||
}
|
||||
|
||||
NSString *documentDirectoryPath = OWSFileSystem.appDocumentDirectoryPath;
|
||||
NSString *relativePath = [self relativePathforPath:self.backupDirPath basePath:documentDirectoryPath];
|
||||
[[NSUserDefaults appUserDefaults] setObject:relativePath forKey:NSUserDefaults_QueuedBackupPath];
|
||||
[[NSUserDefaults appUserDefaults] synchronize];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
#pragma mark - Import Backup, Part 2
|
||||
|
||||
- (void)completeImportBackupIfPossible
|
||||
{
|
||||
OWSAssertIsOnMainThread();
|
||||
OWSAssert(CurrentAppContext().isMainApp);
|
||||
|
||||
DDLogInfo(@"%@ %s.", self.logTag, __PRETTY_FUNCTION__);
|
||||
|
||||
NSString *_Nullable queuedBackupRelativePath =
|
||||
[[NSUserDefaults appUserDefaults] stringForKey:NSUserDefaults_QueuedBackupPath];
|
||||
if (queuedBackupRelativePath.length == 0) {
|
||||
return;
|
||||
}
|
||||
NSString *documentDirectoryPath = OWSFileSystem.appDocumentDirectoryPath;
|
||||
NSString *_Nullable queuedBackupPath =
|
||||
[self joinRelativePath:queuedBackupRelativePath basePath:documentDirectoryPath];
|
||||
if (![[NSFileManager defaultManager] fileExistsAtPath:queuedBackupPath]) {
|
||||
OWSFail(@"%@ Missing import backup directory: %@.", self.logTag, queuedBackupPath);
|
||||
return;
|
||||
}
|
||||
self.backupDirPath = queuedBackupPath;
|
||||
self.backupState = OWSBackupState_InProgress;
|
||||
DDLogInfo(@"%@ queuedBackupPath: %@", self.logTag, queuedBackupPath);
|
||||
|
||||
[SAMKeychain setAccessibilityType:kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly];
|
||||
NSError *error;
|
||||
NSData *_Nullable encryptionKeyData =
|
||||
[SAMKeychain passwordDataForService:Keychain_ImportBackupService account:Keychain_ImportBackupKey error:&error];
|
||||
if (!encryptionKeyData || error) {
|
||||
OWSFail(@"%@ Could not retrieve encryption key for import backup: %@", self.logTag, error);
|
||||
return;
|
||||
}
|
||||
self.encryptionKey = [OWSAES256Key keyWithData:encryptionKeyData];
|
||||
|
||||
NSData *_Nullable databasePassword = [self readDataFromFileName:OWSBackup_DatabasePasswordFilename];
|
||||
if (!databasePassword) {
|
||||
OWSFail(@"%@ Could not retrieve database password.", self.logTag);
|
||||
return;
|
||||
}
|
||||
|
||||
// We can't restore a backup atomically, so we:
|
||||
//
|
||||
// if (![self writeData:databasePassword fileName:@"databasePassword" backupDirPath:backupDirPath]) {
|
||||
// return [self fail];
|
||||
// }
|
||||
// if (self.isCancelledOrFailed) {
|
||||
// return;
|
||||
// }
|
||||
// if (![self writeUserDefaults:NSUserDefaults.standardUserDefaults
|
||||
// fileName:@"standardUserDefaults"
|
||||
// backupDirPath:backupDirPath]) {
|
||||
// return [self fail];
|
||||
// }
|
||||
// if (self.isCancelledOrFailed) {
|
||||
// return;
|
||||
// }
|
||||
// if (![self writeUserDefaults:NSUserDefaults.appUserDefaults
|
||||
// fileName:@"appUserDefaults"
|
||||
// backupDirPath:backupDirPath]) {
|
||||
// return [self fail];
|
||||
// }
|
||||
// if (self.isCancelledOrFailed) {
|
||||
// return;
|
||||
// }
|
||||
// // Use a read/write transaction to acquire a file lock on the database files.
|
||||
// //
|
||||
// // TODO: If we use multiple database files, lock them too.
|
||||
// [TSStorageManager.sharedManager.newDatabaseConnection
|
||||
// readWriteWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) {
|
||||
// if (![self copyDirectory:OWSFileSystem.appDocumentDirectoryPath
|
||||
// dstDirName:@"appDocumentDirectoryPath"
|
||||
// backupDirPath:backupDirPath]) {
|
||||
// [self fail];
|
||||
// return;
|
||||
// }
|
||||
// if (self.isCancelledOrFailed) {
|
||||
// return;
|
||||
// }
|
||||
// if (![self copyDirectory:OWSFileSystem.appSharedDataDirectoryPath
|
||||
// dstDirName:@"appSharedDataDirectoryPath"
|
||||
// backupDirPath:backupDirPath]) {
|
||||
// [self fail];
|
||||
// return;
|
||||
// }
|
||||
// }];
|
||||
// if (self.isCancelledOrFailed) {
|
||||
// return;
|
||||
// }
|
||||
// if (![self zipDirectory:backupDirPath dstFilePath:backupZipPath]) {
|
||||
// return [self fail];
|
||||
// }
|
||||
//
|
||||
// [OWSFileSystem protectFolderAtPath:backupZipPath];
|
||||
//
|
||||
// [OWSFileSystem deleteFileIfExists:self.backupDirPath];
|
||||
// * Ensure the restore consists only of tiny writes, and file moves.
|
||||
// * Write the database password last.
|
||||
if (![self loadUserDefaults:NSUserDefaults.standardUserDefaults fileName:OWSBackup_StandardUserDefaultsFilename]) {
|
||||
return;
|
||||
}
|
||||
if (![self loadUserDefaults:NSUserDefaults.appUserDefaults fileName:OWSBackup_AppUserDefaultsFilename]) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (![self restoreDirectoryContents:OWSFileSystem.appDocumentDirectoryPath
|
||||
srcDirName:OWSBackup_AppDocumentDirName]) {
|
||||
return;
|
||||
}
|
||||
if (![self restoreDirectoryContents:OWSFileSystem.appSharedDataDirectoryPath
|
||||
srcDirName:OWSBackup_AppSharedDataDirName]) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Possibly verify database file location?
|
||||
|
||||
[OWSStorage storeDatabasePassword:[[NSString alloc] initWithData:databasePassword encoding:NSUTF8StringEncoding]];
|
||||
}
|
||||
|
||||
- (nullable NSData *)readDataFromFileName:(NSString *)fileName
|
||||
{
|
||||
OWSAssert(fileName.length > 0);
|
||||
OWSAssert(self.backupDirPath.length > 0);
|
||||
OWSAssert(self.encryptionKey);
|
||||
|
||||
NSString *filePath = [self.backupDirPath stringByAppendingPathComponent:fileName];
|
||||
|
||||
DDLogInfo(@"%@ readDataFromFileName: %@", self.logTag, filePath);
|
||||
|
||||
NSData *_Nullable encryptedData = [NSData dataWithContentsOfFile:filePath];
|
||||
if (!encryptedData) {
|
||||
OWSFail(@"%@ failed to read encrypted data: %@", self.logTag, fileName);
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSData *_Nullable data = [Cryptography decryptAESGCMWithData:encryptedData key:self.encryptionKey];
|
||||
if (!data) {
|
||||
OWSFail(@"%@ failed to decrypt data: %@", self.logTag, fileName);
|
||||
return nil;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
- (BOOL)loadUserDefaults:(NSUserDefaults *)userDefaults fileName:(NSString *)fileName
|
||||
{
|
||||
OWSAssert(userDefaults);
|
||||
OWSAssert(fileName.length > 0);
|
||||
OWSAssert(self.backupDirPath.length > 0);
|
||||
OWSAssert(self.encryptionKey);
|
||||
|
||||
DDLogInfo(@"%@ loadUserDefaults: %@", self.logTag, fileName);
|
||||
|
||||
NSData *_Nullable data = [self readDataFromFileName:fileName];
|
||||
if (!data) {
|
||||
OWSFail(@"%@ Could not retrieve user defaults: %@.", self.logTag, fileName);
|
||||
return NO;
|
||||
}
|
||||
|
||||
NSError *error;
|
||||
NSDictionary<NSString *, id> *_Nullable dictionary =
|
||||
[NSKeyedUnarchiver unarchiveTopLevelObjectWithData:data error:&error];
|
||||
if (!dictionary || error) {
|
||||
OWSFail(@"%@ Could not unarchive user defaults: %@", self.logTag, error);
|
||||
return NO;
|
||||
}
|
||||
if (![dictionary isKindOfClass:[NSDictionary class]]) {
|
||||
OWSFail(@"%@ Unexpected archived user defaults: %@", self.logTag, error);
|
||||
return NO;
|
||||
}
|
||||
|
||||
// TODO: this doesn't yet remove any keys, so you end up with the "union".
|
||||
for (NSString *key in dictionary) {
|
||||
id value = dictionary[key];
|
||||
OWSAssert(value);
|
||||
[userDefaults setObject:value forKey:key];
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)restoreDirectoryContents:(NSString *)dstDirPath srcDirName:(NSString *)srcDirName
|
||||
{
|
||||
OWSAssert(srcDirName.length > 0);
|
||||
OWSAssert(dstDirPath.length > 0);
|
||||
OWSAssert(self.backupDirPath.length > 0);
|
||||
|
||||
NSString *srcDirPath = [self.backupDirPath stringByAppendingPathComponent:srcDirName];
|
||||
|
||||
DDLogInfo(@"%@ restoreDirectoryContents: %@ -> %@", self.logTag, srcDirPath, dstDirPath);
|
||||
|
||||
if (![[NSFileManager defaultManager] fileExistsAtPath:srcDirPath]) {
|
||||
DDLogInfo(@"%@ Skipping restore directory: %@.", self.logTag, srcDirPath);
|
||||
return YES;
|
||||
}
|
||||
|
||||
NSError *error = nil;
|
||||
NSArray<NSString *> *fileNames = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:srcDirPath error:&error];
|
||||
if (error) {
|
||||
OWSFail(@"%@ failed to list directory: %@, %@", self.logTag, srcDirPath, error);
|
||||
return NO;
|
||||
}
|
||||
for (NSString *fileName in fileNames) {
|
||||
NSString *srcFilePath = [srcDirPath stringByAppendingPathComponent:fileName];
|
||||
NSString *dstFilePath = [dstDirPath stringByAppendingPathComponent:fileName];
|
||||
|
||||
if ([[NSFileManager defaultManager] fileExistsAtPath:dstFilePath]) {
|
||||
// To replace an existing file or directory, rename the existing item
|
||||
// by adding a date/time suffix.
|
||||
NSDateFormatter *dateFormatter = [NSDateFormatter new];
|
||||
[dateFormatter setLocale:[NSLocale currentLocale]];
|
||||
[dateFormatter setDateFormat:@".yyyy.MM.dd hh.mm.ss"];
|
||||
NSString *replacementDateTime = [dateFormatter stringFromDate:[NSDate new]];
|
||||
|
||||
NSString *oldFilePath = [dstFilePath stringByAppendingString:replacementDateTime];
|
||||
BOOL success = [[NSFileManager defaultManager] moveItemAtPath:dstFilePath toPath:oldFilePath error:&error];
|
||||
if (!success || error) {
|
||||
OWSFail(@"%@ failed to move directory item: %@, %@", self.logTag, dstFilePath, error);
|
||||
return NO;
|
||||
}
|
||||
if (![OWSFileSystem protectFolderAtPath:oldFilePath]) {
|
||||
OWSFail(@"%@ failed to protect old directory item: %@, %@", self.logTag, oldFilePath, error);
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
|
||||
BOOL success = [[NSFileManager defaultManager] moveItemAtPath:srcFilePath toPath:dstFilePath error:&error];
|
||||
if (!success || error) {
|
||||
OWSFail(@"%@ failed to move directory item: %@, %@", self.logTag, dstFilePath, error);
|
||||
return NO;
|
||||
}
|
||||
if (![OWSFileSystem protectFolderAtPath:dstFilePath]) {
|
||||
OWSFail(@"%@ failed to protect directory item: %@, %@", self.logTag, dstFilePath, error);
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
#pragma mark - Clean up
|
||||
|
||||
+ (void)cleanupBackupState
|
||||
{
|
||||
DDLogInfo(@"%@ %s.", self.logTag, __PRETTY_FUNCTION__);
|
||||
|
||||
[self cleanupBackupDirectoriesInDirectory:NSTemporaryDirectory()];
|
||||
[self cleanupBackupDirectoriesInDirectory:OWSFileSystem.appDocumentDirectoryPath];
|
||||
|
||||
[[NSUserDefaults appUserDefaults] removeObjectForKey:NSUserDefaults_QueuedBackupPath];
|
||||
[[NSUserDefaults appUserDefaults] synchronize];
|
||||
}
|
||||
|
||||
+ (void)cleanupBackupDirectoriesInDirectory:(NSString *)dirPath
|
||||
{
|
||||
OWSAssert(dirPath.length > 0);
|
||||
|
||||
NSError *error;
|
||||
NSArray<NSString *> *filenames = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:dirPath error:&error];
|
||||
if (error) {
|
||||
OWSFail(@"%@ could not find files in directory: %@", self.logTag, error);
|
||||
return;
|
||||
}
|
||||
|
||||
for (NSString *filename in filenames) {
|
||||
if (![filename hasPrefix:OWSBackup_DirNamePrefix]) {
|
||||
continue;
|
||||
}
|
||||
NSString *filePath = [dirPath stringByAppendingPathComponent:filename];
|
||||
BOOL success = [[NSFileManager defaultManager] removeItemAtPath:filePath error:&error];
|
||||
if (!success || error) {
|
||||
OWSFail(@"%@ could not clean up backup directory: %@", self.logTag, error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Utils
|
||||
|
||||
- (NSString *)relativePathforPath:(NSString *)filePath basePath:(NSString *)basePath
|
||||
{
|
||||
OWSAssert(filePath.stringByStandardizingPath.length > 0);
|
||||
OWSAssert([filePath.stringByStandardizingPath hasPrefix:basePath]);
|
||||
|
||||
NSString *relativePath = [filePath.stringByStandardizingPath substringFromIndex:basePath.length];
|
||||
NSString *separator = @"/";
|
||||
if ([relativePath hasPrefix:separator]) {
|
||||
relativePath = [relativePath substringFromIndex:separator.length];
|
||||
}
|
||||
OWSAssert(relativePath.length > 0);
|
||||
return relativePath;
|
||||
}
|
||||
|
||||
- (NSString *)joinRelativePath:(NSString *)relativePath basePath:(NSString *)basePath
|
||||
{
|
||||
OWSAssert(basePath.stringByStandardizingPath.length > 0);
|
||||
OWSAssert(relativePath.length > 0);
|
||||
|
||||
return [basePath stringByAppendingPathComponent:relativePath];
|
||||
}
|
||||
|
||||
#pragma mark - App Launch
|
||||
|
||||
+ (void)applicationDidFinishLaunching
|
||||
{
|
||||
[[OWSBackup new] completeImportBackupIfPossible];
|
||||
|
||||
// Always clean up backup state on disk, but defer so as not to interface with
|
||||
// app launch sequence.
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
|
||||
[OWSBackup cleanupBackupState];
|
||||
});
|
||||
}
|
||||
|
||||
#pragma mark - SSZipArchiveDelegate
|
||||
|
||||
- (void)zipArchiveProgressEvent:(unsigned long long)loaded total:(unsigned long long)total
|
||||
{
|
||||
DDLogInfo(@"%@ zipArchiveProgressEvent: %llu %llu", self.logTag, loaded, total);
|
||||
|
||||
CGFloat progress = loaded / (CGFloat)total;
|
||||
self.backupProgress = progress;
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -179,7 +179,7 @@
|
|||
"BACKUP_FILENAME_FORMAT" = "Signal Backup %@";
|
||||
|
||||
/* Message indicating that backup import is complete. */
|
||||
"BACKUP_IMPORT_COMPLETE_MESSAGE" = "BACKUP_IMPORT_COMPLETE_MESSAGE";
|
||||
"BACKUP_IMPORT_COMPLETE_MESSAGE" = "Restore Complete";
|
||||
|
||||
/* Label for button confirming backup import. */
|
||||
"BACKUP_IMPORT_CONFIRM_ALERT_BUTTON" = "Restore";
|
||||
|
@ -190,26 +190,17 @@
|
|||
/* Title for alert confirming backup import. */
|
||||
"BACKUP_IMPORT_CONFIRM_ALERT_TITLE" = "Restore Backup?";
|
||||
|
||||
/* Label for button that copies backup password to the pasteboard. */
|
||||
"BACKUP_IMPORT_COPY_PASSWORD_BUTTON" = "BACKUP_IMPORT_COPY_PASSWORD_BUTTON";
|
||||
|
||||
/* Message indicating that backup import failed. */
|
||||
"BACKUP_IMPORT_FAILED_MESSAGE" = "BACKUP_IMPORT_FAILED_MESSAGE";
|
||||
"BACKUP_IMPORT_FAILED_MESSAGE" = "Restore Failed";
|
||||
|
||||
/* Message indicating that backup import is in progress. */
|
||||
"BACKUP_IMPORT_IN_PROGRESS_MESSAGE" = "BACKUP_IMPORT_IN_PROGRESS_MESSAGE";
|
||||
"BACKUP_IMPORT_IN_PROGRESS_MESSAGE" = "Restoring Backup...";
|
||||
|
||||
/* Format for message indicating that backup import is complete. Embeds: {{the backup password}}. */
|
||||
"BACKUP_IMPORT_PASSWORD_MESSAGE_FORMAT" = "BACKUP_IMPORT_PASSWORD_MESSAGE_FORMAT";
|
||||
|
||||
/* Label for button that 'send backup' in the current conversation. */
|
||||
"BACKUP_IMPORT_SEND_BACKUP_BUTTON" = "BACKUP_IMPORT_SEND_BACKUP_BUTTON";
|
||||
|
||||
/* Label for button that opens share UI for backup. */
|
||||
"BACKUP_IMPORT_SHARE_BACKUP_BUTTON" = "BACKUP_IMPORT_SHARE_BACKUP_BUTTON";
|
||||
/* Label for button that restarts app to complete restore. */
|
||||
"BACKUP_IMPORT_RESTART_BUTTON" = "Restart App";
|
||||
|
||||
/* Title for the 'backup import' view. */
|
||||
"BACKUP_IMPORT_VIEW_TITLE" = "BACKUP_IMPORT_VIEW_TITLE";
|
||||
"BACKUP_IMPORT_VIEW_TITLE" = "Restore Backup";
|
||||
|
||||
/* An explanation of the consequences of blocking another user. */
|
||||
"BLOCK_BEHAVIOR_EXPLANATION" = "Blocked users will not be able to call you or send you messages.";
|
||||
|
|
|
@ -556,24 +556,11 @@ static NSString *keychainDBPassAccount = @"TSDatabasePass";
|
|||
|
||||
- (NSString *)createAndSetNewDatabasePassword
|
||||
{
|
||||
NSString *newDBPassword = [[Randomness generateRandomBytes:30] base64EncodedString];
|
||||
NSError *keySetError;
|
||||
[SAMKeychain setPassword:newDBPassword forService:keychainService account:keychainDBPassAccount error:&keySetError];
|
||||
if (keySetError) {
|
||||
OWSProdCritical([OWSAnalyticsEvents storageErrorCouldNotStoreDatabasePassword]);
|
||||
NSString *password = [[Randomness generateRandomBytes:30] base64EncodedString];
|
||||
|
||||
[OWSStorage deletePasswordFromKeychain];
|
||||
[OWSStorage storeDatabasePassword:password];
|
||||
|
||||
// Sleep to give analytics events time to be delivered.
|
||||
[NSThread sleepForTimeInterval:15.0f];
|
||||
|
||||
[NSException raise:OWSStorageExceptionName_DatabasePasswordUnwritable
|
||||
format:@"Setting DB password failed with error: %@", keySetError];
|
||||
} else {
|
||||
DDLogWarn(@"Succesfully set new DB password.");
|
||||
}
|
||||
|
||||
return newDBPassword;
|
||||
return password;
|
||||
}
|
||||
|
||||
- (void)backgroundedAppDatabasePasswordInaccessibleWithErrorDescription:(NSString *)errorDescription
|
||||
|
@ -610,6 +597,27 @@ static NSString *keychainDBPassAccount = @"TSDatabasePass";
|
|||
return fileSize;
|
||||
}
|
||||
|
||||
+ (void)storeDatabasePassword:(NSString *)password
|
||||
{
|
||||
NSError *error;
|
||||
[SAMKeychain setAccessibilityType:kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly];
|
||||
BOOL success =
|
||||
[SAMKeychain setPassword:password forService:keychainService account:keychainDBPassAccount error:&error];
|
||||
if (!success || error) {
|
||||
OWSProdCritical([OWSAnalyticsEvents storageErrorCouldNotStoreDatabasePassword]);
|
||||
|
||||
[OWSStorage deletePasswordFromKeychain];
|
||||
|
||||
// Sleep to give analytics events time to be delivered.
|
||||
[NSThread sleepForTimeInterval:15.0f];
|
||||
|
||||
[NSException raise:OWSStorageExceptionName_DatabasePasswordUnwritable
|
||||
format:@"Setting DB password failed with error: %@", error];
|
||||
} else {
|
||||
DDLogWarn(@"Succesfully set new DB password.");
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
|
|
@ -8,7 +8,8 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
|
||||
+ (void)protectFileOrFolderAtPath:(NSString *)path;
|
||||
// TODO: We shouldn't ignore the return value of this method.
|
||||
+ (BOOL)protectFileOrFolderAtPath:(NSString *)path;
|
||||
|
||||
+ (NSString *)appDocumentDirectoryPath;
|
||||
|
||||
|
|
|
@ -9,10 +9,10 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
|
||||
@implementation OWSFileSystem
|
||||
|
||||
+ (void)protectFileOrFolderAtPath:(NSString *)path
|
||||
+ (BOOL)protectFileOrFolderAtPath:(NSString *)path
|
||||
{
|
||||
if (![NSFileManager.defaultManager fileExistsAtPath:path]) {
|
||||
return;
|
||||
return NO;
|
||||
}
|
||||
|
||||
NSError *error;
|
||||
|
@ -26,7 +26,9 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
|
||||
if (error || !success) {
|
||||
OWSProdCritical([OWSAnalyticsEvents storageErrorFileProtection]);
|
||||
return NO;
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
|
||||
+ (NSString *)appDocumentDirectoryPath
|
||||
|
@ -139,6 +141,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
|
||||
NSArray<NSString *> *filenames = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:dirPath error:error];
|
||||
if (*error) {
|
||||
OWSFail(@"%@ could not find files in directory: %@", self.logTag, *error);
|
||||
return nil;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue