diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8938ffebef..f6b2c03bd2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,9 +2,9 @@ name: Build & Tests on: push: - branches: [ 2.x, 2.2.2-dev, master ] + branches: [ 2.x, 2.2.x, master ] pull_request: - branches: [ 2.x, 2.2.2-dev, master ] + branches: [ 2.x, 2.2.x, master ] jobs: build: diff --git a/Autoupdate/AppInstaller.m b/Autoupdate/AppInstaller.m index 2223f389a2..877b6640f1 100644 --- a/Autoupdate/AppInstaller.m +++ b/Autoupdate/AppInstaller.m @@ -59,6 +59,7 @@ @interface AppInstaller () unarchiver = [SUUnarchiver unarchiverForPath:archivePath updatingHostBundlePath:self.host.bundlePath decryptionPassword:self.decryptionPassword expectingInstallationType:self.installationType]; + id unarchiver = [SUUnarchiver unarchiverForPath:archivePath extractionDirectory:self.extractionDirectory updatingHostBundlePath:self.host.bundlePath decryptionPassword:self.decryptionPassword expectingInstallationType:self.installationType]; NSError *unarchiverError = nil; BOOL success = NO; @@ -226,7 +228,7 @@ - (void)extractAndInstallUpdate [self.communicator handleMessageWithIdentifier:SPUValidationStarted data:[NSData data]]; NSError *validationError = nil; - BOOL validationSuccess = [self.updateValidator validateWithUpdateDirectory:self.updateDirectoryPath error:&validationError]; + BOOL validationSuccess = [self.updateValidator validateWithUpdateDirectory:self.extractionDirectory error:&validationError]; if (!validationSuccess) { [self cleanupAndExitWithStatus:EXIT_FAILURE error:[NSError errorWithDomain:SUSparkleErrorDomain code:SPUInstallerError userInfo:@{ NSLocalizedDescriptionKey: @"Update validation was a failure", NSUnderlyingErrorKey: validationError }]]; @@ -276,6 +278,7 @@ - (void)unarchiverDidFailWithError:(NSError *)error // but may as well set other fields to nil too [self clearUpdateDirectory]; self.downloadName = nil; + self.extractionDirectory = nil; self.decryptionPassword = nil; self.signatures = nil; self.relaunchPath = nil; @@ -407,6 +410,13 @@ - (void)handleMessageWithIdentifier:(int32_t)identifier data:(NSData *)data return; } + NSString *extractionDirectory = [SPULocalCacheDirectory createUniqueDirectoryInDirectory:cacheInstallationPath]; + if (extractionDirectory == nil) { + [self cleanupAndExitWithStatus:EXIT_FAILURE error:[NSError errorWithDomain:SUSparkleErrorDomain code:SPUInstallerError userInfo:@{ NSLocalizedDescriptionKey: [NSString stringWithFormat:@"Error: Failed to create installation extraction directory in %@", cacheInstallationPath] }]]; + + return; + } + // Carry these properities separately rather than using the SUInstallationInputData object // Some of our properties may slightly differ than our input and we don't want to make the mistake of using one of those self.installationType = installationType; @@ -414,6 +424,7 @@ - (void)handleMessageWithIdentifier:(int32_t)identifier data:(NSData *)data self.downloadName = installationData.downloadName; self.signatures = installationData.signatures; self.updateDirectoryPath = cacheInstallationPath; + self.extractionDirectory = extractionDirectory; self.host = [[SUHost alloc] initWithBundle:hostBundle]; [self extractAndInstallUpdate]; @@ -463,7 +474,7 @@ - (void)startInstallation dispatch_async(self.installerQueue, ^{ NSError *installerError = nil; - id installer = [SUInstaller installerForHost:self.host expectedInstallationType:self.installationType updateDirectory:self.updateDirectoryPath homeDirectory:self.homeDirectory userName:self.userName error:&installerError]; + id installer = [SUInstaller installerForHost:self.host expectedInstallationType:self.installationType updateDirectory:self.extractionDirectory homeDirectory:self.homeDirectory userName:self.userName error:&installerError]; if (installer == nil) { dispatch_async(dispatch_get_main_queue(), ^{ diff --git a/Autoupdate/SUBinaryDeltaUnarchiver.h b/Autoupdate/SUBinaryDeltaUnarchiver.h index ba79a0476e..9ffe721d31 100644 --- a/Autoupdate/SUBinaryDeltaUnarchiver.h +++ b/Autoupdate/SUBinaryDeltaUnarchiver.h @@ -16,7 +16,7 @@ NS_ASSUME_NONNULL_BEGIN @interface SUBinaryDeltaUnarchiver : NSObject -- (instancetype)initWithArchivePath:(NSString *)archivePath updateHostBundlePath:(NSString *)updateHostBundlePath; +- (instancetype)initWithArchivePath:(NSString *)archivePath extractionDirectory:(NSString *)extractionDirectory updateHostBundlePath:(NSString *)updateHostBundlePath; @end diff --git a/Autoupdate/SUBinaryDeltaUnarchiver.m b/Autoupdate/SUBinaryDeltaUnarchiver.m index 3aba85ad4f..b891475c21 100644 --- a/Autoupdate/SUBinaryDeltaUnarchiver.m +++ b/Autoupdate/SUBinaryDeltaUnarchiver.m @@ -20,6 +20,7 @@ @interface SUBinaryDeltaUnarchiver () @property (nonatomic, copy, readonly) NSString *archivePath; @property (nonatomic, copy, readonly) NSString *updateHostBundlePath; +@property (nonatomic, copy, readonly) NSString *extractionDirectory; @end @@ -27,6 +28,7 @@ @implementation SUBinaryDeltaUnarchiver @synthesize archivePath = _archivePath; @synthesize updateHostBundlePath = _updateHostBundlePath; +@synthesize extractionDirectory = _extractionDirectory; + (BOOL)canUnarchivePath:(NSString *)path { @@ -73,12 +75,13 @@ + (void)updateSpotlightImportersAtBundlePath:(NSString *)targetPath } } -- (instancetype)initWithArchivePath:(NSString *)archivePath updateHostBundlePath:(NSString *)updateHostBundlePath +- (instancetype)initWithArchivePath:(NSString *)archivePath extractionDirectory:(NSString *)extractionDirectory updateHostBundlePath:(NSString *)updateHostBundlePath { self = [super init]; if (self != nil) { _archivePath = [archivePath copy]; _updateHostBundlePath = [updateHostBundlePath copy]; + _extractionDirectory = [extractionDirectory copy]; } return self; } @@ -96,7 +99,7 @@ - (void)unarchiveWithCompletionBlock:(void (^)(NSError * _Nullable))completionBl - (void)extractDeltaWithNotifier:(SUUnarchiverNotifier *)notifier { NSString *sourcePath = self.updateHostBundlePath; - NSString *targetPath = [[self.archivePath stringByDeletingLastPathComponent] stringByAppendingPathComponent:[sourcePath lastPathComponent]]; + NSString *targetPath = [self.extractionDirectory stringByAppendingPathComponent:[sourcePath lastPathComponent]]; NSError *applyDiffError = nil; BOOL success = applyBinaryDelta(sourcePath, targetPath, self.archivePath, NO, ^(double progress){ diff --git a/Autoupdate/SUDiskImageUnarchiver.h b/Autoupdate/SUDiskImageUnarchiver.h index f0de0462ba..b60e898222 100644 --- a/Autoupdate/SUDiskImageUnarchiver.h +++ b/Autoupdate/SUDiskImageUnarchiver.h @@ -16,7 +16,7 @@ NS_ASSUME_NONNULL_BEGIN @interface SUDiskImageUnarchiver : NSObject -- (instancetype)initWithArchivePath:(NSString *)archivePath decryptionPassword:(nullable NSString *)decryptionPassword; +- (instancetype)initWithArchivePath:(NSString *)archivePath extractionDirectory:(NSString *)extractionDirectory decryptionPassword:(nullable NSString *)decryptionPassword; @end diff --git a/Autoupdate/SUDiskImageUnarchiver.m b/Autoupdate/SUDiskImageUnarchiver.m index cf909182ac..7915386719 100644 --- a/Autoupdate/SUDiskImageUnarchiver.m +++ b/Autoupdate/SUDiskImageUnarchiver.m @@ -17,6 +17,7 @@ @interface SUDiskImageUnarchiver () @property (nonatomic, copy, readonly) NSString *archivePath; @property (nullable, nonatomic, copy, readonly) NSString *decryptionPassword; +@property (nonatomic, copy, readonly) NSString *extractionDirectory; @end @@ -24,6 +25,7 @@ @implementation SUDiskImageUnarchiver @synthesize archivePath = _archivePath; @synthesize decryptionPassword = _decryptionPassword; +@synthesize extractionDirectory = _extractionDirectory; + (BOOL)canUnarchivePath:(NSString *)path { @@ -35,12 +37,13 @@ + (BOOL)mustValidateBeforeExtraction return NO; } -- (instancetype)initWithArchivePath:(NSString *)archivePath decryptionPassword:(nullable NSString *)decryptionPassword +- (instancetype)initWithArchivePath:(NSString *)archivePath extractionDirectory:(NSString *)extractionDirectory decryptionPassword:(nullable NSString *)decryptionPassword { self = [super init]; if (self != nil) { _archivePath = [archivePath copy]; _decryptionPassword = [decryptionPassword copy]; + _extractionDirectory = [extractionDirectory copy]; } return self; } @@ -189,7 +192,7 @@ - (void)extractDMGWithNotifier:(SUUnarchiverNotifier *)notifier for (NSString *item in contents) { NSString *fromPath = [mountPoint stringByAppendingPathComponent:item]; - NSString *toPath = [[self.archivePath stringByDeletingLastPathComponent] stringByAppendingPathComponent:item]; + NSString *toPath = [self.extractionDirectory stringByAppendingPathComponent:item]; itemsCopied += 1.0; [notifier notifyProgress:0.5 + itemsCopied/(totalItems*2.0)]; diff --git a/Autoupdate/SUFlatPackageUnarchiver.h b/Autoupdate/SUFlatPackageUnarchiver.h index 7a18f2634d..da8fc65214 100644 --- a/Autoupdate/SUFlatPackageUnarchiver.h +++ b/Autoupdate/SUFlatPackageUnarchiver.h @@ -14,7 +14,7 @@ NS_ASSUME_NONNULL_BEGIN // An unarchiver for flat packages that doesn't really do any unarchiving @interface SUFlatPackageUnarchiver : NSObject -- (instancetype)initWithFlatPackagePath:(NSString *)flatPackagePath expectingInstallationType:(NSString *)installationType; +- (instancetype)initWithFlatPackagePath:(NSString *)flatPackagePath extractionDirectory:(NSString *)extractionDirectory expectingInstallationType:(NSString *)installationType; @end diff --git a/Autoupdate/SUFlatPackageUnarchiver.m b/Autoupdate/SUFlatPackageUnarchiver.m index def788bd73..46a34993de 100644 --- a/Autoupdate/SUFlatPackageUnarchiver.m +++ b/Autoupdate/SUFlatPackageUnarchiver.m @@ -18,6 +18,7 @@ @interface SUFlatPackageUnarchiver () @property (nonatomic, readonly) NSString *flatPackagePath; @property (nonatomic, readonly) NSString *expectedInstallationType; +@property (nonatomic, readonly) NSString *extractionDirectory; @end @@ -25,6 +26,7 @@ @implementation SUFlatPackageUnarchiver @synthesize flatPackagePath = _flatPackagePath; @synthesize expectedInstallationType = _expectedInstallationType; +@synthesize extractionDirectory = _extractionDirectory; + (BOOL)canUnarchivePath:(NSString *)path { @@ -36,12 +38,13 @@ + (BOOL)mustValidateBeforeExtraction return YES; } -- (instancetype)initWithFlatPackagePath:(NSString *)flatPackagePath expectingInstallationType:(NSString *)installationType +- (instancetype)initWithFlatPackagePath:(NSString *)flatPackagePath extractionDirectory:(NSString *)extractionDirectory expectingInstallationType:(NSString *)installationType { self = [super init]; if (self != nil) { _flatPackagePath = [flatPackagePath copy]; _expectedInstallationType = [installationType copy]; + _extractionDirectory = [extractionDirectory copy]; } return self; } @@ -57,8 +60,20 @@ - (void)unarchiveWithCompletionBlock:(void (^)(NSError * _Nullable))completionBl } else if (![[NSFileManager defaultManager] fileExistsAtPath:self.flatPackagePath isDirectory:&isDirectory] || isDirectory) { [notifier notifyFailureWithError:[NSError errorWithDomain:SUSparkleErrorDomain code:SUUnarchivingError userInfo:@{ NSLocalizedDescriptionKey:[NSString stringWithFormat:@"Flat package does not exist at %@", self.flatPackagePath]}]]; } else { - [notifier notifyProgress:1.0]; - [notifier notifySuccess]; + // Copying the flat package should be very fast, especially on APFS + NSError *copyError = nil; + if (![[NSFileManager defaultManager] copyItemAtPath:self.flatPackagePath toPath:[self.extractionDirectory stringByAppendingPathComponent:self.flatPackagePath.lastPathComponent] error:©Error]) { + NSMutableDictionary *userInfoDictionary = [NSMutableDictionary dictionaryWithDictionary:@{ NSLocalizedDescriptionKey:[NSString stringWithFormat:@"Flat package (%@) cannot be copied to extraction directory (%@)", self.flatPackagePath, self.extractionDirectory]}]; + + if (copyError != nil) { + userInfoDictionary[NSUnderlyingErrorKey] = copyError; + } + + [notifier notifyFailureWithError:[NSError errorWithDomain:SUSparkleErrorDomain code:SUUnarchivingError userInfo:userInfoDictionary]]; + } else { + [notifier notifyProgress:1.0]; + [notifier notifySuccess]; + } } } diff --git a/Autoupdate/SUInstaller.m b/Autoupdate/SUInstaller.m index 9c68763283..9a541d15af 100644 --- a/Autoupdate/SUInstaller.m +++ b/Autoupdate/SUInstaller.m @@ -22,15 +22,6 @@ @implementation SUInstaller -+ (BOOL)isAliasFolderAtPath:(NSString *)path -{ - NSNumber *aliasFlag = nil; - [[NSURL fileURLWithPath:path] getResourceValue:&aliasFlag forKey:NSURLIsAliasFileKey error:nil]; - NSNumber *directoryFlag = nil; - [[NSURL fileURLWithPath:path] getResourceValue:&directoryFlag forKey:NSURLIsDirectoryKey error:nil]; - return aliasFlag.boolValue && directoryFlag.boolValue; -} - + (nullable NSString *)installSourcePathInUpdateFolder:(NSString *)inUpdateFolder forHost:(SUHost *)host isPackage:(BOOL *)isPackagePtr isGuided:(BOOL *)isGuidedPtr { NSParameterAssert(inUpdateFolder); @@ -49,6 +40,34 @@ + (nullable NSString *)installSourcePathInUpdateFolder:(NSString *)inUpdateFolde while ((currentFile = [dirEnum nextObject])) { NSString *currentPath = [inUpdateFolder stringByAppendingPathComponent:currentFile]; + + // Ignore all symbolic links and aliases + { + NSURL *currentPathURL = [NSURL fileURLWithPath:currentPath]; + + NSNumber *symbolicLinkFlag = nil; + [currentPathURL getResourceValue:&symbolicLinkFlag forKey:NSURLIsSymbolicLinkKey error:NULL]; + if (symbolicLinkFlag.boolValue) { + // NSDirectoryEnumerator won't recurse into symlinked directories + continue; + } + + NSNumber *aliasFlag = nil; + [currentPathURL getResourceValue:&aliasFlag forKey:NSURLIsAliasFileKey error:NULL]; + + if (aliasFlag.boolValue) { + NSNumber *directoryFlag = nil; + [currentPathURL getResourceValue:&directoryFlag forKey:NSURLIsDirectoryKey error:NULL]; + + // Some DMGs have symlinks into /Applications! That's no good! + if (directoryFlag.boolValue) { + [dirEnum skipDescendents]; + } + + continue; + } + } + NSString *currentFilename = [currentFile lastPathComponent]; NSString *currentExtension = [currentFile pathExtension]; NSString *currentFilenameNoExtension = [currentFilename stringByDeletingPathExtension]; @@ -78,10 +97,6 @@ + (nullable NSString *)installSourcePathInUpdateFolder:(NSString *)inUpdateFolde break; } } - - // Some DMGs have symlinks into /Applications! That's no good! - if ([self isAliasFolderAtPath:currentPath]) - [dirEnum skipDescendents]; } // We don't have a valid path. Try to use the fallback package. diff --git a/Autoupdate/SUPipedUnarchiver.h b/Autoupdate/SUPipedUnarchiver.h index 6e9c682f2e..904201a7c7 100644 --- a/Autoupdate/SUPipedUnarchiver.h +++ b/Autoupdate/SUPipedUnarchiver.h @@ -16,7 +16,7 @@ NS_ASSUME_NONNULL_BEGIN @interface SUPipedUnarchiver : NSObject -- (instancetype)initWithArchivePath:(NSString *)archivePath; +- (instancetype)initWithArchivePath:(NSString *)archivePath extractionDirectory:(NSString *)extractionDirectory; @end diff --git a/Autoupdate/SUPipedUnarchiver.m b/Autoupdate/SUPipedUnarchiver.m index 3033e82af8..16b2bd1048 100644 --- a/Autoupdate/SUPipedUnarchiver.m +++ b/Autoupdate/SUPipedUnarchiver.m @@ -17,12 +17,14 @@ @interface SUPipedUnarchiver () @property (nonatomic, copy, readonly) NSString *archivePath; +@property (nonatomic, copy, readonly) NSString *extractionDirectory; @end @implementation SUPipedUnarchiver @synthesize archivePath = _archivePath; +@synthesize extractionDirectory = _extractionDirectory; + (nullable NSArray *)commandAndArgumentsConformingToTypeOfPath:(NSString *)path { @@ -63,11 +65,12 @@ + (BOOL)mustValidateBeforeExtraction return NO; } -- (instancetype)initWithArchivePath:(NSString *)archivePath +- (instancetype)initWithArchivePath:(NSString *)archivePath extractionDirectory:(NSString *)extractionDirectory { self = [super init]; if (self != nil) { _archivePath = [archivePath copy]; + _extractionDirectory = [extractionDirectory copy]; } return self; } @@ -93,7 +96,7 @@ - (void)extractArchivePipingDataToCommand:(NSString *)command arguments:(NSArray { // *** GETS CALLED ON NON-MAIN THREAD!!! @autoreleasepool { - NSString *destination = [self.archivePath stringByDeletingLastPathComponent]; + NSString *destination = self.extractionDirectory; SULog(SULogLevelDefault, @"Extracting using '%@' '%@' < '%@' '%@'", command, [args componentsJoinedByString:@"' '"], self.archivePath, destination); diff --git a/Autoupdate/SUUnarchiver.h b/Autoupdate/SUUnarchiver.h index de440b9b29..f6749a9eb5 100644 --- a/Autoupdate/SUUnarchiver.h +++ b/Autoupdate/SUUnarchiver.h @@ -14,7 +14,7 @@ NS_ASSUME_NONNULL_BEGIN @interface SUUnarchiver : NSObject -+ (nullable id )unarchiverForPath:(NSString *)path updatingHostBundlePath:(nullable NSString *)hostPath decryptionPassword:(nullable NSString *)decryptionPassword expectingInstallationType:(NSString *)installationType; ++ (nullable id )unarchiverForPath:(NSString *)path extractionDirectory:(NSString *)extractionDirectory updatingHostBundlePath:(nullable NSString *)hostPath decryptionPassword:(nullable NSString *)decryptionPassword expectingInstallationType:(NSString *)installationType; @end diff --git a/Autoupdate/SUUnarchiver.m b/Autoupdate/SUUnarchiver.m index 5550589d4a..22f2d83716 100644 --- a/Autoupdate/SUUnarchiver.m +++ b/Autoupdate/SUUnarchiver.m @@ -18,21 +18,21 @@ @implementation SUUnarchiver -+ (nullable id )unarchiverForPath:(NSString *)path updatingHostBundlePath:(nullable NSString *)hostPath decryptionPassword:(nullable NSString *)decryptionPassword expectingInstallationType:(NSString *)installationType ++ (nullable id )unarchiverForPath:(NSString *)path extractionDirectory:(NSString *)extractionDirectory updatingHostBundlePath:(nullable NSString *)hostPath decryptionPassword:(nullable NSString *)decryptionPassword expectingInstallationType:(NSString *)installationType { if ([SUPipedUnarchiver canUnarchivePath:path]) { - return [[SUPipedUnarchiver alloc] initWithArchivePath:path]; + return [[SUPipedUnarchiver alloc] initWithArchivePath:path extractionDirectory:extractionDirectory]; } else if ([SUDiskImageUnarchiver canUnarchivePath:path]) { - return [[SUDiskImageUnarchiver alloc] initWithArchivePath:path decryptionPassword:decryptionPassword]; + return [[SUDiskImageUnarchiver alloc] initWithArchivePath:path extractionDirectory:extractionDirectory decryptionPassword:decryptionPassword]; } else if ([SUBinaryDeltaUnarchiver canUnarchivePath:path]) { assert(hostPath != nil); NSString *nonNullHostPath = hostPath; - return [[SUBinaryDeltaUnarchiver alloc] initWithArchivePath:path updateHostBundlePath:nonNullHostPath]; + return [[SUBinaryDeltaUnarchiver alloc] initWithArchivePath:path extractionDirectory:extractionDirectory updateHostBundlePath:nonNullHostPath]; } else if ([SUFlatPackageUnarchiver canUnarchivePath:path]) { // Flat packages are only supported for guided packaage installs - return [[SUFlatPackageUnarchiver alloc] initWithFlatPackagePath:path expectingInstallationType:installationType]; + return [[SUFlatPackageUnarchiver alloc] initWithFlatPackagePath:path extractionDirectory:extractionDirectory expectingInstallationType:installationType]; } return nil; } diff --git a/Tests/SUUnarchiverTest.swift b/Tests/SUUnarchiverTest.swift index 019b34f3ac..8ecb667e4f 100644 --- a/Tests/SUUnarchiverTest.swift +++ b/Tests/SUUnarchiverTest.swift @@ -24,10 +24,9 @@ class SUUnarchiverTest: XCTestCase let unarchivedSuccessExpectation = super.expectation(description: "Unarchived Success (format: \(archiveExtension))") let unarchivedFailureExpectation = super.expectation(description: "Unarchived Failure (format: \(archiveExtension))") - let tempArchiveURL = tempDirectoryURL.appendingPathComponent(archiveResourceURL.lastPathComponent) let extractedAppURL = tempDirectoryURL.appendingPathComponent(extractedAppName).appendingPathExtension("app") - self.unarchiveTestAppWithExtension(archiveExtension, appName: appName, tempDirectoryURL: tempDirectoryURL, tempArchiveURL: tempArchiveURL, archiveResourceURL: archiveResourceURL, password: password, expectingInstallationType: installationType, expectingSuccess: expectingSuccess, testExpectation: unarchivedSuccessExpectation) + self.unarchiveTestAppWithExtension(archiveExtension, appName: appName, tempDirectoryURL: tempDirectoryURL, archiveResourceURL: archiveResourceURL, password: password, expectingInstallationType: installationType, expectingSuccess: expectingSuccess, testExpectation: unarchivedSuccessExpectation) self.unarchiveNonExistentFileTestFailureAppWithExtension(archiveExtension, tempDirectoryURL: tempDirectoryURL, password: password, expectingInstallationType: installationType, testExpectation: unarchivedFailureExpectation) super.waitForExpectations(timeout: 30.0, handler: nil) @@ -40,7 +39,7 @@ class SUUnarchiverTest: XCTestCase func unarchiveNonExistentFileTestFailureAppWithExtension(_ archiveExtension: String, tempDirectoryURL: URL, password: String?, expectingInstallationType installationType: String, testExpectation: XCTestExpectation) { let tempArchiveURL = tempDirectoryURL.appendingPathComponent("error-invalid").appendingPathExtension(archiveExtension) - let unarchiver = SUUnarchiver.unarchiver(forPath: tempArchiveURL.path, updatingHostBundlePath: nil, decryptionPassword: password, expectingInstallationType: installationType)! + let unarchiver = SUUnarchiver.unarchiver(forPath: tempArchiveURL.path, extractionDirectory: tempDirectoryURL.path, updatingHostBundlePath: nil, decryptionPassword: password, expectingInstallationType: installationType)! unarchiver.unarchive(completionBlock: {(error: Error?) -> Void in XCTAssertNotNil(error) @@ -49,13 +48,9 @@ class SUUnarchiverTest: XCTestCase } // swiftlint:disable function_parameter_count - func unarchiveTestAppWithExtension(_ archiveExtension: String, appName: String, tempDirectoryURL: URL, tempArchiveURL: URL, archiveResourceURL: URL, password: String?, expectingInstallationType installationType: String, expectingSuccess: Bool, testExpectation: XCTestExpectation) { - - let fileManager = FileManager.default - - try! fileManager.copyItem(at: archiveResourceURL, to: tempArchiveURL) - - let unarchiver = SUUnarchiver.unarchiver(forPath: tempArchiveURL.path, updatingHostBundlePath: nil, decryptionPassword: password, expectingInstallationType: installationType)! + func unarchiveTestAppWithExtension(_ archiveExtension: String, appName: String, tempDirectoryURL: URL, archiveResourceURL: URL, password: String?, expectingInstallationType installationType: String, expectingSuccess: Bool, testExpectation: XCTestExpectation) { + + let unarchiver = SUUnarchiver.unarchiver(forPath: archiveResourceURL.path, extractionDirectory: tempDirectoryURL.path, updatingHostBundlePath: nil, decryptionPassword: password, expectingInstallationType: installationType)! unarchiver.unarchive(completionBlock: {(error: Error?) -> Void in if expectingSuccess { @@ -130,7 +125,7 @@ class SUUnarchiverTest: XCTestCase self.unarchiveTestAppWithExtension("dmg", resourceName: "SparkleTestCodeSign_apfs") } - func testUnarchivingFlatPackage() + func testUnarchivingBarePackage() { self.unarchiveTestAppWithExtension("pkg", resourceName: "test", expectingInstallationType: SPUInstallationTypeGuidedPackage) diff --git a/generate_appcast/Unarchive.swift b/generate_appcast/Unarchive.swift index c09669e97c..f501da6c70 100644 --- a/generate_appcast/Unarchive.swift +++ b/generate_appcast/Unarchive.swift @@ -8,38 +8,26 @@ import Foundation func unarchive(itemPath: URL, archiveDestDir: URL, callback: @escaping (Error?) -> Void) { let fileManager = FileManager.default let tempDir = archiveDestDir.appendingPathExtension("tmp") - let itemCopy = tempDir.appendingPathComponent(itemPath.lastPathComponent) + _ = try? fileManager.removeItem(at: tempDir) _ = try? fileManager.createDirectory(at: tempDir, withIntermediateDirectories: true, attributes: [:]) - do { - do { - try fileManager.linkItem(at: itemPath, to: itemCopy) - } catch { - try fileManager.copyItem(at: itemPath, to: itemCopy) - } - if let unarchiver = SUUnarchiver.unarchiver(forPath: itemCopy.path, updatingHostBundlePath: nil, decryptionPassword: nil, expectingInstallationType: SPUInstallationTypeApplication) { - unarchiver.unarchive(completionBlock: { (error: Error?) in - if error != nil { - callback(error) - return - } + if let unarchiver = SUUnarchiver.unarchiver(forPath: itemPath.path, extractionDirectory: tempDir.path, updatingHostBundlePath: nil, decryptionPassword: nil, expectingInstallationType: SPUInstallationTypeApplication) { + unarchiver.unarchive(completionBlock: { (error: Error?) in + if error != nil { + callback(error) + return + } - _ = try? fileManager.removeItem(at: itemCopy) - do { - try fileManager.moveItem(at: tempDir, to: archiveDestDir) - callback(nil) - } catch { - callback(error) - } - }, progressBlock: nil) - } else { - _ = try? fileManager.removeItem(at: itemCopy) - callback(makeError(code: .unarchivingError, "Not a supported archive format: \(itemCopy)")) - } - } catch { - _ = try? fileManager.removeItem(at: tempDir) - callback(error) + do { + try fileManager.moveItem(at: tempDir, to: archiveDestDir) + callback(nil) + } catch { + callback(error) + } + }, progressBlock: nil) + } else { + callback(makeError(code: .unarchivingError, "Not a supported archive format: \(itemPath.path)")) } }