From 21ec5217ae63e5fbad66dbcc8e2ea20514171d5a Mon Sep 17 00:00:00 2001 From: Dov Frankel Date: Fri, 28 Jun 2019 08:31:59 -0400 Subject: [PATCH 01/56] Upgraded to recommended Xcode 10 settings --- UnzipKit.xcodeproj/project.pbxproj | 6 ++++-- UnzipKit.xcodeproj/xcshareddata/xcschemes/UnzipKit.xcscheme | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/UnzipKit.xcodeproj/project.pbxproj b/UnzipKit.xcodeproj/project.pbxproj index 2986ac9..fc6ae7d 100644 --- a/UnzipKit.xcodeproj/project.pbxproj +++ b/UnzipKit.xcodeproj/project.pbxproj @@ -425,7 +425,7 @@ attributes = { LastSwiftMigration = 0700; LastSwiftUpdateCheck = 0720; - LastUpgradeCheck = 0930; + LastUpgradeCheck = 1020; ORGANIZATIONNAME = "Abbey Code"; TargetAttributes = { 7A0029161F93DB5800618503 = { @@ -447,7 +447,7 @@ }; buildConfigurationList = 96EA65981A40AEAE00685B6D /* Build configuration list for PBXProject "UnzipKit" */; compatibilityVersion = "Xcode 8.0"; - developmentRegion = English; + developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, @@ -675,6 +675,7 @@ buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES = YES; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; @@ -735,6 +736,7 @@ buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES = YES; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; diff --git a/UnzipKit.xcodeproj/xcshareddata/xcschemes/UnzipKit.xcscheme b/UnzipKit.xcodeproj/xcshareddata/xcschemes/UnzipKit.xcscheme index 7ead498..0697e6d 100644 --- a/UnzipKit.xcodeproj/xcshareddata/xcschemes/UnzipKit.xcscheme +++ b/UnzipKit.xcodeproj/xcshareddata/xcschemes/UnzipKit.xcscheme @@ -1,6 +1,6 @@ Date: Fri, 28 Jun 2019 09:14:37 -0400 Subject: [PATCH 02/56] Migrated to Swift v5.0 (which the migrator said requires no code changes) --- UnzipKit.xcodeproj/project.pbxproj | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/UnzipKit.xcodeproj/project.pbxproj b/UnzipKit.xcodeproj/project.pbxproj index fc6ae7d..1e0f038 100644 --- a/UnzipKit.xcodeproj/project.pbxproj +++ b/UnzipKit.xcodeproj/project.pbxproj @@ -441,7 +441,7 @@ }; 96EA65A81A40AEAE00685B6D = { CreatedOnToolsVersion = 6.1.1; - LastSwiftMigration = 0900; + LastSwiftMigration = 1020; }; }; }; @@ -838,7 +838,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Tests/UnzipKitTests-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 5.0; WARNING_CFLAGS = ( "$(inherited)", "-Wno-gnu-statement-expression", @@ -862,7 +862,7 @@ PRODUCT_BUNDLE_IDENTIFIER = "com.abbey-code.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Tests/UnzipKitTests-Bridging-Header.h"; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 5.0; WARNING_CFLAGS = ( "$(inherited)", "-Wno-everything", From 895a9ad8194d92de64a6d199c69b425bf2eddc5b Mon Sep 17 00:00:00 2001 From: Dov Frankel Date: Fri, 28 Jun 2019 17:02:52 -0400 Subject: [PATCH 03/56] Fixed/silenced Xcode 10 warnings, that all exclusively affected the unit tests target --- Tests/DeleteFileTests.m | 6 +++--- Tests/PerformOnFilesTests.m | 2 +- Tests/PermissionsTests.swift | 22 ++++++++++++---------- Tests/ProgressReportingTests.m | 7 +++---- Tests/UZKArchiveTestCase.h | 6 +++--- Tests/WriteBufferedDataTests.m | 10 +++++----- Tests/WriteDataTests.swift | 16 ++++++++-------- UnzipKit.xcodeproj/project.pbxproj | 1 + 8 files changed, 36 insertions(+), 34 deletions(-) diff --git a/Tests/DeleteFileTests.m b/Tests/DeleteFileTests.m index 61a3d04..71db954 100644 --- a/Tests/DeleteFileTests.m +++ b/Tests/DeleteFileTests.m @@ -108,11 +108,11 @@ - (void)testDeleteFile_ThirdFile NSArray *testArchives = @[@"Test Archive.zip", @"Test Archive (Password).zip"]; - NSSet *expectedFileSet = self.nonZipTestFilePaths; - NSArray *expectedFiles = [expectedFileSet.allObjects sortedArrayUsingSelector:@selector(compare:)]; + NSSet *expectedFileSet = self.nonZipTestFilePaths; + NSArray *expectedFiles = [expectedFileSet.allObjects sortedArrayUsingSelector:@selector(compare:)]; NSString *fileToDelete = expectedFiles[2]; - NSMutableArray *newFileList = [NSMutableArray arrayWithArray:expectedFiles]; + NSMutableArray *newFileList = [NSMutableArray arrayWithArray:expectedFiles]; [newFileList removeObject:fileToDelete]; for (NSString *testArchiveName in testArchives) { diff --git a/Tests/PerformOnFilesTests.m b/Tests/PerformOnFilesTests.m index d07da3c..c0908f4 100644 --- a/Tests/PerformOnFilesTests.m +++ b/Tests/PerformOnFilesTests.m @@ -21,7 +21,7 @@ - (void)testPerformOnFiles @"Test Archive (Password).zip"]; NSSet *expectedFileSet = self.nonZipTestFilePaths; - NSArray *expectedFiles = [expectedFileSet.allObjects sortedArrayUsingSelector:@selector(compare:)]; + NSArray *expectedFiles = [expectedFileSet.allObjects sortedArrayUsingSelector:@selector(compare:)]; for (NSString *testArchiveName in testArchives) { NSURL *testArchiveURL = self.testFileURLs[testArchiveName]; diff --git a/Tests/PermissionsTests.swift b/Tests/PermissionsTests.swift index 7910068..265d83b 100644 --- a/Tests/PermissionsTests.swift +++ b/Tests/PermissionsTests.swift @@ -93,7 +93,7 @@ class PermissionsTests: UZKArchiveTestCase { func testWriteData_Default() { let testArchiveURL = tempDirectory.appendingPathComponent("PermissionsTestWriteData.zip") - let testFilename = nonZipTestFilePaths.first as! String + let testFilename = nonZipTestFilePaths.first! let testFileURL = testFileURLs[testFilename] as! URL let testFileData = try! Data(contentsOf: testFileURL) @@ -112,7 +112,7 @@ class PermissionsTests: UZKArchiveTestCase { func testWriteData_NonDefault() { let testArchiveURL = tempDirectory.appendingPathComponent("PermissionsTestWriteData.zip") - let testFilename = nonZipTestFilePaths.first as! String + let testFilename = nonZipTestFilePaths.first! let testFileURL = testFileURLs[testFilename] as! URL let testFileData = try! Data(contentsOf: testFileURL) @@ -134,15 +134,16 @@ class PermissionsTests: UZKArchiveTestCase { func testWriteIntoBuffer_Default() { let testArchiveURL = tempDirectory.appendingPathComponent("PermissionsTestWriteBufferedData.zip") - let testFilename = nonZipTestFilePaths.first as! String + let testFilename = nonZipTestFilePaths.first! let testFileURL = testFileURLs[testFilename] as! URL let testFileData = try! Data(contentsOf: testFileURL) let writeArchive = try! UZKArchive(url: testArchiveURL) try! writeArchive.write(intoBuffer: testFilename) { (writeDataHandler, error) in - testFileData.withUnsafeBytes({ buffer in - writeDataHandler(buffer, UInt32(testFileData.count)) - }) + let buffer = testFileData.withUnsafeBytes { + $0.baseAddress?.assumingMemoryBound(to: UInt32.self) + } + return writeDataHandler(buffer!, UInt32(testFileData.count)) } let readArchive = try! UZKArchive(url: testArchiveURL) @@ -156,7 +157,7 @@ class PermissionsTests: UZKArchiveTestCase { func testWriteIntoBuffer_NonDefault() { let testArchiveURL = tempDirectory.appendingPathComponent("PermissionsTestWriteBufferedData_CustomPermissions.zip") - let testFilename = nonZipTestFilePaths.first as! String + let testFilename = nonZipTestFilePaths.first! let testFileURL = testFileURLs[testFilename] as! URL let testFileData = try! Data(contentsOf: testFileURL) @@ -166,9 +167,10 @@ class PermissionsTests: UZKArchiveTestCase { try! writeArchive.write(intoBuffer: testFilename, fileDate: nil, posixPermissions: expectedPermissions, compressionMethod: .default, overwrite: false, crc: 0, password: nil) { (writeDataHandler, error) in - testFileData.withUnsafeBytes({ buffer in - writeDataHandler(buffer, UInt32(testFileData.count)) - }) + let buffer = testFileData.withUnsafeBytes { + $0.baseAddress?.assumingMemoryBound(to: UInt32.self) + } + return writeDataHandler(buffer!, UInt32(testFileData.count)) } let readArchive = try! UZKArchive(url: testArchiveURL) diff --git a/Tests/ProgressReportingTests.m b/Tests/ProgressReportingTests.m index 7db7a28..4523470 100644 --- a/Tests/ProgressReportingTests.m +++ b/Tests/ProgressReportingTests.m @@ -7,7 +7,7 @@ // #import "UZKArchiveTestCase.h" -#import "UnzipKit.h" +#import #import "UnzipKitMacros.h" @interface ProgressReportingTests : UZKArchiveTestCase @@ -568,10 +568,9 @@ - (void)observeValueForKeyPath:(NSString *)keyPath { observerCallCount++; - NSProgress *progress; + NSProgress *progress = object; - if ([object isKindOfClass:[NSProgress class]]) { - progress = object; + if ([progress isKindOfClass:[NSProgress class]]) { [self.fractionsCompletedReported addObject:@(progress.fractionCompleted)]; } else { return; diff --git a/Tests/UZKArchiveTestCase.h b/Tests/UZKArchiveTestCase.h index 1373471..6a58c1e 100644 --- a/Tests/UZKArchiveTestCase.h +++ b/Tests/UZKArchiveTestCase.h @@ -23,9 +23,9 @@ @property (retain) NSURL *tempDirectory; @property (retain) NSMutableDictionary *testFileURLs; -@property (retain) NSMutableDictionary *unicodeFileURLs; -@property (retain) NSSet *nonZipTestFilePaths; -@property (retain) NSSet *nonZipUnicodeFilePaths; +@property (retain) NSMutableDictionary *unicodeFileURLs; +@property (retain) NSSet *nonZipTestFilePaths; +@property (retain) NSSet *nonZipUnicodeFilePaths; @property (retain) NSURL *corruptArchive; diff --git a/Tests/WriteBufferedDataTests.m b/Tests/WriteBufferedDataTests.m index bb541b0..7245866 100644 --- a/Tests/WriteBufferedDataTests.m +++ b/Tests/WriteBufferedDataTests.m @@ -18,11 +18,11 @@ @implementation WriteBufferedDataTests - (void)testWriteInfoBuffer { - NSArray *testFiles = [self.nonZipTestFilePaths.allObjects sortedArrayUsingSelector:@selector(compare:)]; - NSArray *testDates = @[[[UZKArchiveTestCase dateFormatter] dateFromString:@"12/20/2014 9:35 AM"], - [[UZKArchiveTestCase dateFormatter] dateFromString:@"12/21/2014 10:00 AM"], - [[UZKArchiveTestCase dateFormatter] dateFromString:@"12/22/2014 11:54 PM"]]; - NSMutableArray *testFileData = [NSMutableArray arrayWithCapacity:testFiles.count]; + NSArray *testFiles = [self.nonZipTestFilePaths.allObjects sortedArrayUsingSelector:@selector(compare:)]; + NSArray *testDates = @[[[UZKArchiveTestCase dateFormatter] dateFromString:@"12/20/2014 9:35 AM"], + [[UZKArchiveTestCase dateFormatter] dateFromString:@"12/21/2014 10:00 AM"], + [[UZKArchiveTestCase dateFormatter] dateFromString:@"12/22/2014 11:54 PM"]]; + NSMutableArray *testFileData = [NSMutableArray arrayWithCapacity:testFiles.count]; NSURL *testArchiveURL = [self.tempDirectory URLByAppendingPathComponent:@"WriteIntoBufferTest.zip"]; diff --git a/Tests/WriteDataTests.swift b/Tests/WriteDataTests.swift index c509ffa..64f3ff8 100644 --- a/Tests/WriteDataTests.swift +++ b/Tests/WriteDataTests.swift @@ -17,7 +17,7 @@ import UnzipKit class WriteDataTests: UZKArchiveTestCase { func testWriteData() { - let testFilePaths = [String](nonZipTestFilePaths as! Set).sorted(by: <) + let testFilePaths = [String](nonZipTestFilePaths).sorted(by: <) let testDates = [ UZKArchiveTestCase.dateFormatter().date(from: "12/20/2014 9:35 AM"), UZKArchiveTestCase.dateFormatter().date(from: "12/21/2014 10:00 AM"), @@ -57,7 +57,7 @@ class WriteDataTests: UZKArchiveTestCase { } func testWriteData_Unicode() { - let testFilePaths = [String](nonZipUnicodeFilePaths as! Set).sorted(by: <) + let testFilePaths = [String](nonZipUnicodeFilePaths).sorted(by: <) let testDates = [ UZKArchiveTestCase.dateFormatter().date(from: "12/20/2014 9:35 AM"), UZKArchiveTestCase.dateFormatter().date(from: "12/21/2014 10:00 AM"), @@ -97,7 +97,7 @@ class WriteDataTests: UZKArchiveTestCase { } func testWriteData_Overwrite() { - let testFilePaths = [String](nonZipTestFilePaths as! Set).sorted(by: <) + let testFilePaths = [String](nonZipTestFilePaths).sorted(by: <) let testDates = [ UZKArchiveTestCase.dateFormatter().date(from: "12/20/2014 9:35 AM"), UZKArchiveTestCase.dateFormatter().date(from: "12/21/2014 10:00 AM"), @@ -171,7 +171,7 @@ class WriteDataTests: UZKArchiveTestCase { } func testWriteData_Overwrite_Unicode() { - let testFilePaths = [String](nonZipUnicodeFilePaths as! Set).sorted(by: <) + let testFilePaths = [String](nonZipUnicodeFilePaths).sorted(by: <) let testDates = [ UZKArchiveTestCase.dateFormatter().date(from: "12/20/2014 9:35 AM"), UZKArchiveTestCase.dateFormatter().date(from: "12/21/2014 10:00 AM"), @@ -245,7 +245,7 @@ class WriteDataTests: UZKArchiveTestCase { } func testWriteData_NoOverwrite() { - let testFilePaths = [String](nonZipTestFilePaths as! Set).sorted(by: <) + let testFilePaths = [String](nonZipTestFilePaths).sorted(by: <) let testDates = [ UZKArchiveTestCase.dateFormatter().date(from: "12/20/2014 9:35 AM"), UZKArchiveTestCase.dateFormatter().date(from: "12/21/2014 10:00 AM"), @@ -305,7 +305,7 @@ class WriteDataTests: UZKArchiveTestCase { func testWriteData_MultipleWrites() { let testArchiveURL = tempDirectory.appendingPathComponent("MultipleDataWriteTest.zip") - let testFilename = nonZipTestFilePaths.first as! String + let testFilename = nonZipTestFilePaths.first! let testFileURL = testFileURLs[testFilename] as! URL let testFileData = try! Data(contentsOf: testFileURL) @@ -353,7 +353,7 @@ class WriteDataTests: UZKArchiveTestCase { func testWriteData_DefaultDate() { let testArchiveURL = tempDirectory.appendingPathComponent("DefaultDateWriteTest.zip") - let testFilename = nonZipTestFilePaths.first as! String + let testFilename = nonZipTestFilePaths.first! let testFileURL = testFileURLs[testFilename] as! URL let testFileData = try! Data(contentsOf: testFileURL) @@ -377,7 +377,7 @@ class WriteDataTests: UZKArchiveTestCase { #if os(OSX) func testWriteData_PasswordProtected() { - let testFilePaths = [String](nonZipTestFilePaths as! Set).sorted(by: <) + let testFilePaths = [String](nonZipTestFilePaths).sorted(by: <) var testFileData = [Data]() let testArchiveURL = tempDirectory.appendingPathComponent("SwiftWriteDataTest.zip") diff --git a/UnzipKit.xcodeproj/project.pbxproj b/UnzipKit.xcodeproj/project.pbxproj index 1e0f038..b595028 100644 --- a/UnzipKit.xcodeproj/project.pbxproj +++ b/UnzipKit.xcodeproj/project.pbxproj @@ -848,6 +848,7 @@ "-Wno-pointer-arith", "-Wno-cast-qual", "-Wno-undef", + "-Wno-objc-messaging-id", ); }; name = Debug; From 138c738f4f6ed99892ff21fae008851aa273e428 Mon Sep 17 00:00:00 2001 From: Dov Frankel Date: Fri, 28 Jun 2019 17:08:10 -0400 Subject: [PATCH 04/56] Moved Swift 5 upgrade setting to Project from the Tests target --- UnzipKit.xcodeproj/project.pbxproj | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/UnzipKit.xcodeproj/project.pbxproj b/UnzipKit.xcodeproj/project.pbxproj index b595028..e248c66 100644 --- a/UnzipKit.xcodeproj/project.pbxproj +++ b/UnzipKit.xcodeproj/project.pbxproj @@ -722,7 +722,7 @@ MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SUPPORTED_PLATFORMS = "macosx iphonesimulator iphoneos"; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 5.0; WARNING_CFLAGS = ( "-Weverything", "-Wno-auto-import", @@ -778,7 +778,7 @@ MTL_ENABLE_DEBUG_INFO = NO; SUPPORTED_PLATFORMS = "macosx iphonesimulator iphoneos"; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 5.0; WARNING_CFLAGS = ( "-Weverything", "-Wno-auto-import", @@ -838,7 +838,6 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Tests/UnzipKitTests-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; WARNING_CFLAGS = ( "$(inherited)", "-Wno-gnu-statement-expression", @@ -863,7 +862,6 @@ PRODUCT_BUNDLE_IDENTIFIER = "com.abbey-code.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Tests/UnzipKitTests-Bridging-Header.h"; - SWIFT_VERSION = 5.0; WARNING_CFLAGS = ( "$(inherited)", "-Wno-everything", From 1417797b40db536171a63d0bb6b0e82d37adabda Mon Sep 17 00:00:00 2001 From: Dov Frankel Date: Fri, 28 Jun 2019 17:08:58 -0400 Subject: [PATCH 05/56] Updated Travis build to use Xcode 10 instead of 9 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 3434eee..b4181f0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,7 @@ language: - objective-c -osx_image: xcode9.3 +osx_image: xcode10.2 before_script: # Make log level less verbose. Temporarily undo if more info is needed From 0f1e68c3db40af2a8f38a8e2f8d4495237db8fb8 Mon Sep 17 00:00:00 2001 From: Dov Frankel Date: Fri, 28 Jun 2019 17:26:05 -0400 Subject: [PATCH 06/56] Upgraded demo project to Xcode 10 settings --- UnzipKitDemo/UnzipKitDemo.xcodeproj/project.pbxproj | 12 ++++++------ .../xcshareddata/xcschemes/UnzipKitDemo.xcscheme | 2 +- UnzipKitDemo/UnzipKitDemo/AppDelegate.swift | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/UnzipKitDemo/UnzipKitDemo.xcodeproj/project.pbxproj b/UnzipKitDemo/UnzipKitDemo.xcodeproj/project.pbxproj index 08fa86f..695d821 100644 --- a/UnzipKitDemo/UnzipKitDemo.xcodeproj/project.pbxproj +++ b/UnzipKitDemo/UnzipKitDemo.xcodeproj/project.pbxproj @@ -136,7 +136,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0700; - LastUpgradeCheck = 0930; + LastUpgradeCheck = 1020; ORGANIZATIONNAME = "Abbey Code"; TargetAttributes = { 969E2FDE1AD573F100E19F7A = { @@ -147,7 +147,7 @@ }; buildConfigurationList = 969E2FDA1AD573F100E19F7A /* Build configuration list for PBXProject "UnzipKitDemo" */; compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; + developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, @@ -252,6 +252,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; @@ -300,7 +301,7 @@ ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; @@ -309,6 +310,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; @@ -349,7 +351,7 @@ MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; @@ -368,7 +370,6 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "UnzipKitDemo-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 3.0; }; name = Debug; }; @@ -384,7 +385,6 @@ PRODUCT_BUNDLE_IDENTIFIER = "com.abbey-code.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "UnzipKitDemo-Bridging-Header.h"; - SWIFT_VERSION = 3.0; }; name = Release; }; diff --git a/UnzipKitDemo/UnzipKitDemo.xcodeproj/xcshareddata/xcschemes/UnzipKitDemo.xcscheme b/UnzipKitDemo/UnzipKitDemo.xcodeproj/xcshareddata/xcschemes/UnzipKitDemo.xcscheme index c4cbc20..53691fb 100644 --- a/UnzipKitDemo/UnzipKitDemo.xcodeproj/xcshareddata/xcschemes/UnzipKitDemo.xcscheme +++ b/UnzipKitDemo/UnzipKitDemo.xcodeproj/xcshareddata/xcschemes/UnzipKitDemo.xcscheme @@ -1,6 +1,6 @@ Bool { + internal func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. return true } From ec90cff55a6c0fbf4971a50c33ed0002ba42ee44 Mon Sep 17 00:00:00 2001 From: Dov Frankel Date: Tue, 2 Jul 2019 17:11:52 -0400 Subject: [PATCH 07/56] Implement Issue #90 (#92) * Removed deprecated methods (Issue #90) --- Source/UZKArchive.h | 217 -------------------------------------------- Source/UZKArchive.m | 188 +------------------------------------- 2 files changed, 2 insertions(+), 403 deletions(-) diff --git a/Source/UZKArchive.h b/Source/UZKArchive.h index ce99538..2f54582 100644 --- a/Source/UZKArchive.h +++ b/Source/UZKArchive.h @@ -178,45 +178,6 @@ extern NSString *UZKErrorDomain; @property(nullable, strong) NSProgress *progress; -/** - * DEPRECATED: Creates and returns an archive at the given path - * - * @param filePath A path to the archive file - * - * @return Returns a UZKArchive object, or nil if the path isn't reachable - */ -+ (nullable instancetype)zipArchiveAtPath:(NSString *)filePath __deprecated_msg("Use -initWithPath:error: instead"); - -/** - * DEPRECATED: Creates and returns an archive at the given URL - * - * @param fileURL The URL of the archive file - * - * @return Returns a UZKArchive object, or nil if the URL isn't reachable - */ -+ (nullable instancetype)zipArchiveAtURL:(NSURL *)fileURL __deprecated_msg("Use -initWithURL:error: instead"); - -/** - * DEPRECATED: Creates and returns an archive at the given path, with a given password - * - * @param filePath A path to the archive file - * @param password The password of the given archive - * - * @return Returns a UZKArchive object, or nil if the path isn't reachable - */ -+ (nullable instancetype)zipArchiveAtPath:(NSString *)filePath password:(nullable NSString *)password __deprecated_msg("Use -initWithPath:password:error: instead"); - -/** - * DEPRECATED: Creates and returns an archive at the given URL, with a given password - * - * @param fileURL The URL of the archive file - * @param password The password of the given archive - * - * @return Returns a UZKArchive object, or nil if the URL isn't reachable - */ -+ (nullable instancetype)zipArchiveAtURL:(NSURL *)fileURL password:(nullable NSString *)password __deprecated_msg("Use -initWithURL:password:error: instead");; - - /** * Creates and returns an archive at the given path * @@ -318,25 +279,6 @@ extern NSString *UZKErrorDomain; overwrite:(BOOL)overwrite error:(NSError **)error; -/** - * **DEPRECATED:** Writes all files in the archive to the given path - * - * @param destinationDirectory The destination path of the unarchived files - * @param overwrite YES to overwrite files in the destination directory, NO otherwise - * @param progress Called every so often to report the progress of the extraction - * - * - *currentFile* The info about the file that's being extracted - * - *percentArchiveDecompressed* The percentage of the archive that has been decompressed - * - * @param error Contains an NSError object when there was an error reading the archive - * - * @return YES on successful extraction, NO if an error was encountered - */ -- (BOOL)extractFilesTo:(NSString *)destinationDirectory - overwrite:(BOOL)overwrite - progress:(nullable void (^)(UZKFileInfo *currentFile, CGFloat percentArchiveDecompressed))progress - error:(NSError **)error __deprecated_msg("Use -extractFilesTo:overwrite:error: instead, and if using the progress block, replace with NSProgress as described in the README"); - /** * Unarchive a single file from the archive into memory. Supports NSProgress for progress reporting, which also * allows cancellation in the middle of extraction @@ -349,22 +291,6 @@ extern NSString *UZKErrorDomain; - (nullable NSData *)extractData:(UZKFileInfo *)fileInfo error:(NSError **)error; -/** - * **DEPRECATED:** Unarchive a single file from the archive into memory - * - * @param fileInfo The info of the file within the archive to be expanded. Only the filename property is used - * @param progress Called every so often to report the progress of the extraction - * - * - *percentDecompressed* The percentage of the archive that has been decompressed - * - * @param error Contains an NSError object when there was an error reading the archive - * - * @return An NSData object containing the bytes of the file, or nil if an error was encountered - */ -- (nullable NSData *)extractData:(UZKFileInfo *)fileInfo - progress:(nullable void (^)(CGFloat percentDecompressed))progress - error:(NSError **)error __deprecated_msg("Use -extractData:error: instead, and if using the progress block, replace with NSProgress as described in the README"); - /** * Unarchive a single file from the archive into memory. Supports NSProgress for progress reporting, which also * allows cancellation in the middle of extraction @@ -377,22 +303,6 @@ extern NSString *UZKErrorDomain; - (nullable NSData *)extractDataFromFile:(NSString *)filePath error:(NSError **)error; -/** - * **DEPRECATED:** Unarchive a single file from the archive into memory - * - * @param filePath The path of the file within the archive to be expanded - * @param progress Called every so often to report the progress of the extraction - * - * - *percentDecompressed* The percentage of the file that has been decompressed - * - * @param error Contains an NSError object when there was an error reading the archive - * - * @return An NSData object containing the bytes of the file, or nil if an error was encountered - */ -- (nullable NSData *)extractDataFromFile:(NSString *)filePath - progress:(nullable void (^)(CGFloat percentDecompressed))progress - error:(NSError **)error __deprecated_msg("Use -extractDataFromFile:error: instead, and if using the progress block, replace with NSProgress as described in the README"); - /** * Loops through each file in the archive into memory, allowing you to perform an action * using its info. Supports NSProgress for progress reporting, which also @@ -495,25 +405,6 @@ extern NSString *UZKErrorDomain; filePath:(NSString *)filePath error:(NSError **)error; -/** - * **DEPRECATED:** Writes the data to the zip file, overwriting it if a file of that name already exists in the - * archive - * - * @param data Data to write into the archive - * @param filePath The full path to the target file in the archive - * @param progress Called every so often to report the progress of the compression - * - * - *percentCompressed* The percentage of the file that has been compressed - * - * @param error Contains an NSError object when there was an error writing to the archive - * - * @return YES if successful, NO on error - */ -- (BOOL)writeData:(NSData *)data - filePath:(NSString *)filePath - progress:(nullable void (^)(CGFloat percentCompressed))progress - error:(NSError **)error __deprecated_msg("Use -writeData:filePath:error: instead, and if using the progress block, replace with NSProgress as described in the README"); - /** * Writes the data to the zip file, overwriting it if a file of that name already exists in the archive * @@ -529,26 +420,6 @@ extern NSString *UZKErrorDomain; fileDate:(nullable NSDate *)fileDate error:(NSError **)error; -/** - * **DEPRECATED:** Writes the data to the zip file, overwriting it if a file of that name already exists in the archive - * - * @param data Data to write into the archive - * @param filePath The full path to the target file in the archive - * @param fileDate The timestamp of the file in the archive. Uses the current time if nil - * @param progress Called every so often to report the progress of the compression - * - * - *percentCompressed* The percentage of the file that has been compressed - * - * @param error Contains an NSError object when there was an error writing to the archive - * - * @return YES if successful, NO on error - */ -- (BOOL)writeData:(NSData *)data - filePath:(NSString *)filePath - fileDate:(nullable NSDate *)fileDate - progress:(nullable void (^)(CGFloat percentCompressed))progress - error:(NSError **)error __deprecated_msg("Use -writeData:filePath:fileDate:error: instead, and if using the progress block, replace with NSProgress as described in the README"); - /** * Writes the data to the zip file, overwriting it if a file of that name already exists in the archive * @@ -568,30 +439,6 @@ compressionMethod:(UZKCompressionMethod)method password:(nullable NSString *)password error:(NSError **)error; -/** - * **DEPRECATED:** Writes the data to the zip file, overwriting it if a file of that name already exists in the archive - * - * @param data Data to write into the archive - * @param filePath The full path to the target file in the archive - * @param fileDate The timestamp of the file in the archive. Uses the current time if nil - * @param method The UZKCompressionMethod to use (Default, None, Fastest, Best) - * @param password Override the password associated with the archive (not recommended) - * @param progress Called every so often to report the progress of the compression - * - * - *percentCompressed* The percentage of the file that has been compressed - * - * @param error Contains an NSError object when there was an error writing to the archive - * - * @return YES if successful, NO on error - */ -- (BOOL)writeData:(NSData *)data - filePath:(NSString *)filePath - fileDate:(nullable NSDate *)fileDate -compressionMethod:(UZKCompressionMethod)method - password:(nullable NSString *)password - progress:(nullable void (^)(CGFloat percentCompressed))progress - error:(NSError **)error __deprecated_msg("Use -writeData:filePath:fileDate:compressionMethod:password:error: instead, and if using the progress block, replace with NSProgress as described in the README"); - /** * Writes the data to the zip file, overwriting only if specified with the overwrite flag. Overwriting * presents a tradeoff: the whole archive needs to be copied (minus the file to be overwritten) before @@ -646,70 +493,6 @@ compressionMethod:(UZKCompressionMethod)method overwrite:(BOOL)overwrite error:(NSError **)error; -/** - * **DEPRECATED:** Writes the data to the zip file, overwriting only if specified with the overwrite flag. Overwriting - * presents a tradeoff: the whole archive needs to be copied (minus the file to be overwritten) before - * the write begins. For a large archive, this can be slow. On the other hand, when not overwriting, - * the size of the archive will grow each time the file is written. - * - * @param data Data to write into the archive - * @param filePath The full path to the target file in the archive - * @param fileDate The timestamp of the file in the archive. Uses the current time if nil - * @param method The UZKCompressionMethod to use (Default, None, Fastest, Best) - * @param password Override the password associated with the archive (not recommended) - * @param overwrite If YES, and the file exists, delete it before writing. If NO, append - * the data into the archive without removing it first (legacy Objective-Zip - * behavior) - * @param progress Called every so often to report the progress of the compression - * - * - *percentCompressed* The percentage of the file that has been compressed - * - * @param error Contains an NSError object when there was an error writing to the archive - * - * @return YES if successful, NO on error - */ -- (BOOL)writeData:(NSData *)data - filePath:(NSString *)filePath - fileDate:(nullable NSDate *)fileDate -compressionMethod:(UZKCompressionMethod)method - password:(nullable NSString *)password - overwrite:(BOOL)overwrite - progress:(nullable void (^)(CGFloat percentCompressed))progress - error:(NSError **)error __deprecated_msg("Use -writeData:filePath:fileDate:compressionMethod:password:overwrite:error: instead, and if using the progress block, replace with NSProgress as described in the README"); - -/** - * **DEPRECATED:** Writes the data to the zip file, overwriting only if specified with the overwrite flag. Overwriting - * presents a tradeoff: the whole archive needs to be copied (minus the file to be overwritten) before - * the write begins. For a large archive, this can be slow. On the other hand, when not overwriting, - * the size of the archive will grow each time the file is written. - * - * @param data Data to write into the archive - * @param filePath The full path to the target file in the archive - * @param fileDate The timestamp of the file in the archive. Uses the current time if nil - * @param permissions The desired POSIX permissions of the file in the archive - * @param method The UZKCompressionMethod to use (Default, None, Fastest, Best) - * @param password Override the password associated with the archive (not recommended) - * @param overwrite If YES, and the file exists, delete it before writing. If NO, append - * the data into the archive without removing it first (legacy Objective-Zip - * behavior) - * @param progress Called every so often to report the progress of the compression - * - * - *percentCompressed* The percentage of the file that has been compressed - * - * @param error Contains an NSError object when there was an error writing to the archive - * - * @return YES if successful, NO on error - */ -- (BOOL)writeData:(NSData *)data - filePath:(NSString *)filePath - fileDate:(nullable NSDate *)fileDate - posixPermissions:(short)permissions -compressionMethod:(UZKCompressionMethod)method - password:(nullable NSString *)password - overwrite:(BOOL)overwrite - progress:(nullable void (^)(CGFloat percentCompressed))progress - error:(NSError **)error __deprecated_msg("Use -writeData:filePath:fileDate:permissions:compressionMethod:password:overwrite:error: instead, and if using the progress block, replace with NSProgress as described in the README"); - /** * Writes data to the zip file in pieces, allowing you to stream the write, so the entire contents * don't need to reside in memory at once. It overwrites an existing file with the same name. diff --git a/Source/UZKArchive.m b/Source/UZKArchive.m index a8ac32a..faf55c1 100644 --- a/Source/UZKArchive.m +++ b/Source/UZKArchive.m @@ -69,31 +69,6 @@ @implementation UZKArchive @synthesize comment = _comment; -#pragma mark - Deprecated Convenience Methods - - -+ (UZKArchive *)zipArchiveAtPath:(NSString *)filePath -{ - return [[UZKArchive alloc] initWithPath:filePath error:nil]; -} - -+ (UZKArchive *)zipArchiveAtURL:(NSURL *)fileURL -{ - return [[UZKArchive alloc] initWithURL:fileURL error:nil]; -} - -+ (UZKArchive *)zipArchiveAtPath:(NSString *)filePath password:(NSString *)password -{ - return [[UZKArchive alloc] initWithPath:filePath password:password error:nil]; -} - -+ (UZKArchive *)zipArchiveAtURL:(NSURL *)fileURL password:(NSString *)password -{ - return [[UZKArchive alloc] initWithURL:fileURL password:password error:nil]; -} - - - #pragma mark - Initializers + (void)initialize { @@ -468,20 +443,6 @@ + (BOOL)urlIsAZip:(NSURL *)fileURL - (BOOL)extractFilesTo:(NSString *)destinationDirectory overwrite:(BOOL)overwrite error:(NSError * __autoreleasing*)error -{ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - return [self extractFilesTo:destinationDirectory - overwrite:overwrite - progress:nil - error:error]; -#pragma clang diagnostic pop -} - -- (BOOL)extractFilesTo:(NSString *)destinationDirectory - overwrite:(BOOL)overwrite - progress:(void (^)(UZKFileInfo *currentFile, CGFloat percentArchiveDecompressed))progressBlock - error:(NSError * __autoreleasing*)error { UZKCreateActivity("Extracting Files to Directory"); @@ -530,10 +491,6 @@ - (BOOL)extractFilesTo:(NSString *)destinationDirectory } @autoreleasepool { - if (progressBlock) { - progressBlock(info, bytesDecompressed / totalSize.doubleValue); - } - if (![self locateFileInZip:info.filename error:&strongError]) { NSString *detail = [NSString localizedStringWithFormat:NSLocalizedStringFromTableInBundle(@"Error locating file '%@' in archive", @"UnzipKit", _resources, @"Detailed error string"), info.filename]; @@ -618,9 +575,6 @@ - (BOOL)extractFilesTo:(NSString *)destinationDirectory UZKLogDebug("Writing data chunk of size %lu (%lld total so far)", (unsigned long)dataChunk.length, bytesDecompressed); bytesDecompressed += dataChunk.length; [deflatedFileHandle writeData:dataChunk]; - if (progressBlock) { - progressBlock(info, (double)bytesDecompressed / totalSize.doubleValue); - } }]; UZKLogDebug("Closing file handle"); @@ -670,29 +624,8 @@ - (nullable NSData *)extractData:(UZKFileInfo *)fileInfo error:error]; } -- (nullable NSData *)extractData:(UZKFileInfo *)fileInfo - progress:(void (^)(CGFloat))progress - error:(NSError * __autoreleasing*)error -{ - return [self extractDataFromFile:fileInfo.filename - progress:progress - error:error]; -} - - (nullable NSData *)extractDataFromFile:(NSString *)filePath error:(NSError * __autoreleasing *)error -{ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - return [self extractDataFromFile:filePath - progress:nil - error:error]; -#pragma clang diagnostic pop -} - -- (nullable NSData *)extractDataFromFile:(NSString *)filePath - progress:(void (^)(CGFloat))progressBlock - error:(NSError * __autoreleasing*)error { UZKCreateActivity("Extracting Data from File"); @@ -705,19 +638,9 @@ - (nullable NSData *)extractDataFromFile:(NSString *)filePath error:&extractError action:^(NSData *dataChunk, CGFloat percentDecompressed) { UZKLogDebug("Appending data chunk of size %lu (%.3f%% complete)", (unsigned long)dataChunk.length, (double)percentDecompressed * 100); - - if (progressBlock) { - progressBlock(percentDecompressed); - } - [result appendData:dataChunk]; }]; - if (progressBlock) { - UZKLogDebug("Declaring extraction progress as completed"); - progressBlock(1.0); - } - if (success) { return [NSData dataWithData:result]; } @@ -1066,21 +989,6 @@ - (BOOL)writeData:(NSData *)data error:error]; } -- (BOOL)writeData:(NSData *)data - filePath:(NSString *)filePath - progress:(void (^)(CGFloat percentCompressed))progress - error:(NSError * __autoreleasing*)error -{ - return [self writeData:data - filePath:filePath - fileDate:nil - compressionMethod:UZKCompressionMethodDefault - password:nil - overwrite:YES - progress:progress - error:error]; -} - - (BOOL)writeData:(NSData *)data filePath:(NSString *)filePath fileDate:(NSDate *)fileDate @@ -1095,44 +1003,11 @@ - (BOOL)writeData:(NSData *)data error:error]; } -- (BOOL)writeData:(NSData *)data - filePath:(NSString *)filePath - fileDate:(NSDate *)fileDate - progress:(void (^)(CGFloat percentCompressed))progress - error:(NSError * __autoreleasing*)error -{ - return [self writeData:data - filePath:filePath - fileDate:fileDate - compressionMethod:UZKCompressionMethodDefault - password:nil - overwrite:YES - progress:progress - error:error]; -} - -- (BOOL)writeData:(NSData *)data - filePath:(NSString *)filePath - fileDate:(NSDate *)fileDate -compressionMethod:(UZKCompressionMethod)method - password:(NSString *)password - error:(NSError * __autoreleasing*)error -{ - return [self writeData:data - filePath:filePath - fileDate:fileDate - compressionMethod:method - password:password - overwrite:YES - error:error]; -} - - (BOOL)writeData:(NSData *)data filePath:(NSString *)filePath fileDate:(NSDate *)fileDate compressionMethod:(UZKCompressionMethod)method password:(NSString *)password - progress:(void (^)(CGFloat percentCompressed))progress error:(NSError * __autoreleasing*)error { return [self writeData:data @@ -1141,7 +1016,6 @@ - (BOOL)writeData:(NSData *)data compressionMethod:method password:password overwrite:YES - progress:progress error:error]; } @@ -1152,28 +1026,6 @@ - (BOOL)writeData:(NSData *)data password:(NSString *)password overwrite:(BOOL)overwrite error:(NSError * __autoreleasing*)error -{ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - return [self writeData:data - filePath:filePath - fileDate:fileDate - compressionMethod:method - password:password - overwrite:overwrite - progress:nil - error:error]; -#pragma clang diagnostic pop -} - -- (BOOL)writeData:(NSData *)data - filePath:(NSString *)filePath - fileDate:(NSDate *)fileDate -compressionMethod:(UZKCompressionMethod)method - password:(NSString *)password - overwrite:(BOOL)overwrite - progress:(void (^)(CGFloat percentCompressed))progressBlock - error:(NSError * __autoreleasing*)error { return [self writeData:data filePath:filePath @@ -1182,33 +1034,9 @@ - (BOOL)writeData:(NSData *)data compressionMethod:method password:password overwrite:overwrite - progress:progressBlock error:error]; } -- (BOOL)writeData:(NSData *)data - filePath:(NSString *)filePath - fileDate:(nullable NSDate *)fileDate - posixPermissions:(short)permissions -compressionMethod:(UZKCompressionMethod)method - password:(nullable NSString *)password - overwrite:(BOOL)overwrite - error:(NSError * __autoreleasing*)error -{ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - return [self writeData:data - filePath:filePath - fileDate:fileDate - posixPermissions:permissions - compressionMethod:method - password:password - overwrite:overwrite - progress:nil - error:error]; -#pragma clang diagnostic pop -} - - (BOOL)writeData:(NSData *)data filePath:(NSString *)filePath fileDate:(NSDate *)fileDate @@ -1216,15 +1044,14 @@ - (BOOL)writeData:(NSData *)data compressionMethod:(UZKCompressionMethod)method password:(NSString *)password overwrite:(BOOL)overwrite - progress:(void (^)(CGFloat percentCompressed))progressBlock error:(NSError * __autoreleasing*)error { UZKCreateActivity("Writing Data"); UZKLogInfo("Writing data to archive. filePath: %{public}@, fileDate: %{time_t}ld, compressionMethod: %ld, password: %{public}@, " - "overwrite: %{public}@, progress block specified: %{public}@, error pointer specified: %{public}@", + "overwrite: %{public}@, error pointer specified: %{public}@", filePath, lrint(fileDate.timeIntervalSince1970), (long)method, password != nil ? @"" : @"(null)", overwrite ? @"YES" : @"NO", - progressBlock ? @"YES" : @"NO", error ? @"YES" : @"NO"); + error ? @"YES" : @"NO"); const NSUInteger bufferSize = 4096; //Arbitrary const void *bytes = data.bytes; @@ -1232,11 +1059,6 @@ - (BOOL)writeData:(NSData *)data NSProgress *progress = [self beginProgressOperation:data.length]; progress.cancellable = NO; - if (progressBlock) { - UZKLogDebug("Calling progress block with zero"); - progressBlock(0); - } - __weak UZKArchive *welf = self; uLong calculatedCRC = crc32(0, data.bytes, (uInt)data.length); UZKLogDebug("Calculated CRC: %010lu", calculatedCRC); @@ -1262,12 +1084,6 @@ - (BOOL)writeData:(NSData *)data } progress.completedUnitCount += size; - - if (progressBlock) { - double percentComplete = i / (double)data.length; - UZKLogDebug("Calling progress block at %.3f%%", percentComplete * 100); - progressBlock(percentComplete); - } } return ZIP_OK; From 674d3b21239b135a7880b91881c426cabd177cdb Mon Sep 17 00:00:00 2001 From: Dov Frankel Date: Tue, 2 Jul 2019 17:15:53 -0400 Subject: [PATCH 08/56] Updated change notes --- CHANGELOG.md | 4 ++++ beta-notes.md | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 371e61c..67210bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # UnzipKit CHANGELOG +## 2.0 + +* Removed methods deprecated in v1.9 (Issue #90, PR #92) + ## 1.9 * Added support for `NSProgress` and `NSProgressReporting` in all extraction and iteration methods (Issue #32) diff --git a/beta-notes.md b/beta-notes.md index e908b45..dda93d3 100644 --- a/beta-notes.md +++ b/beta-notes.md @@ -1 +1 @@ -Added support for archiving and restoring files' POSIX permissions (PRs #84, #86, #87 - Thanks, [@MartinLau7](https://github.com/MartinLau7)!) \ No newline at end of file +* Removed methods deprecated in v1.9 (Issue #90, PR #92) \ No newline at end of file From f61a9f1acce25a43a5b534ce584d7c4bdafff17a Mon Sep 17 00:00:00 2001 From: Dov Frankel Date: Tue, 2 Jul 2019 22:15:08 -0400 Subject: [PATCH 09/56] Fix Issue #91 (#94) Fixed behavior that was causing progress within extraction of a single file to be lost (Issue #91) --- CHANGELOG.md | 1 + Source/UZKArchive.m | 5 +- Tests/ProgressReportingTests.m | 105 +++++++++++++++++++++++++++++++++ beta-notes.md | 3 +- 4 files changed, 112 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 67210bb..4a5e37e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## 2.0 * Removed methods deprecated in v1.9 (Issue #90, PR #92) +* Fixed behavior of `-extractFilesTo:overwrite:error:`, so it shows the progress of each individual file as they extract (Issue #91, PR #94) ## 1.9 diff --git a/Source/UZKArchive.m b/Source/UZKArchive.m index faf55c1..44208c4 100644 --- a/Source/UZKArchive.m +++ b/Source/UZKArchive.m @@ -567,6 +567,8 @@ - (BOOL)extractFilesTo:(NSString *)destinationDirectory return; } + [progress becomeCurrentWithPendingUnitCount:info.uncompressedSize]; + UZKLogDebug("Extracting buffered data"); BOOL extractSuccess = [welf extractBufferedDataFromFile:info.filename error:&strongError @@ -576,6 +578,8 @@ - (BOOL)extractFilesTo:(NSString *)destinationDirectory bytesDecompressed += dataChunk.length; [deflatedFileHandle writeData:dataChunk]; }]; + + [progress resignCurrent]; UZKLogDebug("Closing file handle"); [deflatedFileHandle closeFile]; @@ -599,7 +603,6 @@ - (BOOL)extractFilesTo:(NSString *)destinationDirectory forKey:NSProgressFileCompletedCountKey]; [progress setUserInfoObject:@(fileInfo.count) forKey:NSProgressFileTotalCountKey]; - progress.completedUnitCount = bytesDecompressed; } } } diff --git a/Tests/ProgressReportingTests.m b/Tests/ProgressReportingTests.m index 4523470..87a0ba0 100644 --- a/Tests/ProgressReportingTests.m +++ b/Tests/ProgressReportingTests.m @@ -87,6 +87,111 @@ - (void)testProgressReporting_ExtractFiles_FractionCompleted } } +#if !TARGET_OS_IPHONE +- (void)testProgressReporting_ExtractFiles_FractionCompleted_LargeFile +{ + NSURL *largeTextFile = [self emptyTextFileOfLength:1000000]; + NSURL *testArchiveURL = [self archiveWithFiles:@[largeTextFile]]; + + NSString *extractDirectory = [self randomDirectoryWithPrefix:@"LargeFileProgress"]; + NSURL *extractURL = [self.tempDirectory URLByAppendingPathComponent:extractDirectory]; + + UZKArchive *archive = [[UZKArchive alloc] initWithURL:testArchiveURL error:nil]; + + NSProgress *extractFilesProgress = [NSProgress progressWithTotalUnitCount:1]; + [extractFilesProgress becomeCurrentWithPendingUnitCount:1]; + + NSString *observedSelector = NSStringFromSelector(@selector(fractionCompleted)); + + [extractFilesProgress addObserver:self + forKeyPath:observedSelector + options:NSKeyValueObservingOptionInitial + context:ExtractFilesContext]; + + NSError *extractError = nil; + BOOL success = [archive extractFilesTo:extractURL.path + overwrite:NO + error:&extractError]; + + XCTAssertNil(extractError, @"Error returned by extractFilesTo:overwrite:progress:error:"); + XCTAssertTrue(success, @"Archive failed to extract large file to %@", extractURL); + + [extractFilesProgress resignCurrent]; + [extractFilesProgress removeObserver:self forKeyPath:observedSelector]; + + XCTAssertEqualWithAccuracy(extractFilesProgress.fractionCompleted, 1.00, .0000000001, @"Progress never reported as completed"); + + NSUInteger expectedProgressUpdates = 5; + NSArray *expectedProgresses = @[@0, + @0.262144, + @0.524287, + @0.786431, + @1.0]; + + XCTAssertEqual(self.fractionsCompletedReported.count, expectedProgressUpdates, @"Incorrect number of progress updates"); + for (NSUInteger i = 0; i < expectedProgressUpdates; i++) { + float expectedProgress = expectedProgresses[i].floatValue; + float actualProgress = self.fractionsCompletedReported[i].floatValue; + + XCTAssertEqualWithAccuracy(actualProgress, expectedProgress, 0.00001f, @"Incorrect progress reported at index %ld", (long)i); + } +} + +- (void)testProgressReporting_ExtractFiles_FractionCompleted_TwoLargeFiles +{ + NSArray *largeTextFiles = @[ + [self emptyTextFileOfLength:1000000], + [self emptyTextFileOfLength:500000], + ]; + NSURL *testArchiveURL = [self archiveWithFiles:largeTextFiles]; + + NSString *extractDirectory = [self randomDirectoryWithPrefix:@"TwoLargeFilesProgress"]; + NSURL *extractURL = [self.tempDirectory URLByAppendingPathComponent:extractDirectory]; + + UZKArchive *archive = [[UZKArchive alloc] initWithURL:testArchiveURL error:nil]; + + NSProgress *extractFilesProgress = [NSProgress progressWithTotalUnitCount:1]; + [extractFilesProgress becomeCurrentWithPendingUnitCount:1]; + + NSString *observedSelector = NSStringFromSelector(@selector(fractionCompleted)); + + [extractFilesProgress addObserver:self + forKeyPath:observedSelector + options:NSKeyValueObservingOptionInitial + context:ExtractFilesContext]; + + NSError *extractError = nil; + BOOL success = [archive extractFilesTo:extractURL.path + overwrite:NO + error:&extractError]; + + XCTAssertNil(extractError, @"Error returned by extractFilesTo:overwrite:progress:error:"); + XCTAssertTrue(success, @"Archive failed to extract large file to %@", extractURL); + + [extractFilesProgress resignCurrent]; + [extractFilesProgress removeObserver:self forKeyPath:observedSelector]; + + XCTAssertEqualWithAccuracy(extractFilesProgress.fractionCompleted, 1.00, .0000000001, @"Progress never reported as completed"); + + NSUInteger expectedProgressUpdates = 7; + NSArray *expectedProgresses = @[@0, + @0.174762, + @0.349525, + @0.524287, + @0.666667, + @0.841429, + @1.0]; + + XCTAssertEqual(self.fractionsCompletedReported.count, expectedProgressUpdates, @"Incorrect number of progress updates"); + for (NSUInteger i = 0; i < expectedProgressUpdates; i++) { + float expectedProgress = expectedProgresses[i].floatValue; + float actualProgress = self.fractionsCompletedReported[i].floatValue; + + XCTAssertEqualWithAccuracy(actualProgress, expectedProgress, 0.00001f, @"Incorrect progress reported at index %ld", (long)i); + } +} +#endif + - (void)testProgressReporting_ExtractFiles_Description { NSString *testArchiveName = @"Test Archive.zip"; diff --git a/beta-notes.md b/beta-notes.md index dda93d3..1825edb 100644 --- a/beta-notes.md +++ b/beta-notes.md @@ -1 +1,2 @@ -* Removed methods deprecated in v1.9 (Issue #90, PR #92) \ No newline at end of file +* Removed methods deprecated in v1.9 (Issue #90, PR #92) +* Fixed behavior of `-extractFilesTo:overwrite:error:`, so it shows the progress of each individual file as they extract (Issue #91, PR #94) \ No newline at end of file From c1af5bf542472b809592c3c024e6c6d972a8cdbb Mon Sep 17 00:00:00 2001 From: Dov Frankel Date: Wed, 3 Jul 2019 22:26:06 -0400 Subject: [PATCH 10/56] Deprecated file path initializers in favor of URL initializers (Issue #93) (#95) --- CHANGELOG.md | 1 + Source/UZKArchive.h | 8 ++++---- Tests/WriteDataTests.swift | 4 ++-- beta-notes.md | 3 ++- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a5e37e..a9bdf5f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ * Removed methods deprecated in v1.9 (Issue #90, PR #92) * Fixed behavior of `-extractFilesTo:overwrite:error:`, so it shows the progress of each individual file as they extract (Issue #91, PR #94) +* Deprecated the initializers that take a file path instead of an `NSURL` (Issue #90, PR #95) ## 1.9 diff --git a/Source/UZKArchive.h b/Source/UZKArchive.h index 2f54582..81c4619 100644 --- a/Source/UZKArchive.h +++ b/Source/UZKArchive.h @@ -179,14 +179,14 @@ extern NSString *UZKErrorDomain; /** - * Creates and returns an archive at the given path + * DEPRECATED: Creates and returns an archive at the given path * * @param filePath A path to the archive file * @param error Returns an error code if the object can't be initialized * * @return Returns a UZKArchive object, or nil if the path isn't reachable */ -- (nullable instancetype)initWithPath:(NSString *)filePath error:(NSError **)error; +- (nullable instancetype)initWithPath:(NSString *)filePath error:(NSError **)error __deprecated_msg("Use -initWithURL:error: instead"); /** * Creates and returns an archive at the given URL @@ -199,7 +199,7 @@ extern NSString *UZKErrorDomain; - (nullable instancetype)initWithURL:(NSURL *)fileURL error:(NSError **)error; /** - * Creates and returns an archive at the given path, with a given password + * DEPRECATED: Creates and returns an archive at the given path, with a given password * * @param filePath A path to the archive file * @param password The password of the given archive @@ -207,7 +207,7 @@ extern NSString *UZKErrorDomain; * * @return Returns a UZKArchive object, or nil if the path isn't reachable */ -- (nullable instancetype)initWithPath:(NSString *)filePath password:(nullable NSString *)password error:(NSError **)error; +- (nullable instancetype)initWithPath:(NSString *)filePath password:(nullable NSString *)password error:(NSError **)error __deprecated_msg("Use -initWithURL:password:error: instead"); /** * Creates and returns an archive at the given URL, with a given password diff --git a/Tests/WriteDataTests.swift b/Tests/WriteDataTests.swift index 64f3ff8..f265bf6 100644 --- a/Tests/WriteDataTests.swift +++ b/Tests/WriteDataTests.swift @@ -383,7 +383,7 @@ class WriteDataTests: UZKArchiveTestCase { let testArchiveURL = tempDirectory.appendingPathComponent("SwiftWriteDataTest.zip") let password = "111111" - let writeArchive = try! UZKArchive(path: testArchiveURL.path, password: password) + let writeArchive = try! UZKArchive(url: testArchiveURL, password: password) for testFilePath in testFilePaths { let fileData = try? Data(contentsOf: testFileURLs[testFilePath] as! URL) @@ -398,7 +398,7 @@ class WriteDataTests: UZKArchiveTestCase { // Read with UnzipKit - let readArchive = try! UZKArchive(path: testArchiveURL.path, password: password) + let readArchive = try! UZKArchive(url: testArchiveURL, password: password) XCTAssertTrue(readArchive.isPasswordProtected(), "Archive is not marked as password-protected") var index = 0 diff --git a/beta-notes.md b/beta-notes.md index 1825edb..5b6bbcd 100644 --- a/beta-notes.md +++ b/beta-notes.md @@ -1,2 +1,3 @@ * Removed methods deprecated in v1.9 (Issue #90, PR #92) -* Fixed behavior of `-extractFilesTo:overwrite:error:`, so it shows the progress of each individual file as they extract (Issue #91, PR #94) \ No newline at end of file +* Fixed behavior of `-extractFilesTo:overwrite:error:`, so it shows the progress of each individual file as they extract (Issue #91, PR #94) +* Deprecated the initializers that take a file path instead of an `NSURL` (Issue #90, PR #95) \ No newline at end of file From 0c394009423ce0ae091f961f1798db87151d0134 Mon Sep 17 00:00:00 2001 From: Dov Frankel Date: Tue, 30 Jul 2019 17:26:28 -0400 Subject: [PATCH 11/56] Applied CocoaPods fix until v1.8.0 beta 1 is released --- .travis.yml | 2 + Gemfile | 6 ++ Gemfile.lock | 105 +++++++++++++++++++++++++++++++++++ Scripts/cocoapod-validate.sh | 4 +- Scripts/install-demo-libs.sh | 4 +- 5 files changed, 117 insertions(+), 4 deletions(-) create mode 100644 Gemfile create mode 100644 Gemfile.lock diff --git a/.travis.yml b/.travis.yml index b4181f0..e4b3026 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,8 @@ language: osx_image: xcode10.2 before_script: + # Only until CocoaPods 1.8.0 beta 1 is released. Then remove this and the Gemfile + - bundle config github.https true && bundle install # Make log level less verbose. Temporarily undo if more info is needed - sudo log config --mode "level:default" diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..5d65347 --- /dev/null +++ b/Gemfile @@ -0,0 +1,6 @@ +source 'https://rubygems.org' + +gem 'cocoapods', :github => 'CocoaPods/CocoaPods' +gem 'cocoapods-core', :github => 'CocoaPods/Core' +gem 'cocoapods-trunk', :github => 'CocoaPods/cocoapods-trunk' +gem 'xcodeproj', :github => 'CocoaPods/Xcodeproj' \ No newline at end of file diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..fd69ae2 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,105 @@ +GIT + remote: https://github.com/CocoaPods/CocoaPods.git + revision: b93c72d3d68b5075435354de7cb2116579f02c8e + specs: + cocoapods (1.7.5) + activesupport (>= 4.0.2, < 5) + claide (>= 1.0.2, < 2.0) + cocoapods-core (= 1.7.5) + cocoapods-deintegrate (>= 1.0.3, < 2.0) + cocoapods-downloader (>= 1.2.2, < 2.0) + cocoapods-plugins (>= 1.0.0, < 2.0) + cocoapods-search (>= 1.0.0, < 2.0) + cocoapods-stats (>= 1.0.0, < 2.0) + cocoapods-trunk (>= 1.3.1, < 2.0) + cocoapods-try (>= 1.1.0, < 2.0) + colored2 (~> 3.1) + escape (~> 0.0.4) + fourflusher (>= 2.3.0, < 3.0) + gh_inspector (~> 1.0) + molinillo (~> 0.6.6) + nap (~> 1.0) + ruby-macho (~> 1.4) + xcodeproj (>= 1.10.0, < 2.0) + +GIT + remote: https://github.com/CocoaPods/Core.git + revision: 9ae4051d5fedf5869b603b1ac409a384537abdf4 + specs: + cocoapods-core (1.7.5) + activesupport (>= 4.0.2, < 6) + algoliasearch (~> 1.0) + fuzzy_match (~> 2.0.4) + nap (~> 1.0) + +GIT + remote: https://github.com/CocoaPods/Xcodeproj.git + revision: 5b4e8ef7bf54b5d5cbd8970b92cbb9d99f58a942 + specs: + xcodeproj (1.11.0) + CFPropertyList (>= 2.3.3, < 4.0) + atomos (~> 0.1.3) + claide (>= 1.0.2, < 2.0) + colored2 (~> 3.1) + nanaimo (~> 0.2.6) + +GIT + remote: https://github.com/CocoaPods/cocoapods-trunk.git + revision: 208396fca7eeecf4b7fb2810f1a1ab3b036bd1a3 + specs: + cocoapods-trunk (1.3.1) + nap (>= 0.8, < 2.0) + netrc (~> 0.11) + +GEM + remote: https://rubygems.org/ + specs: + CFPropertyList (3.0.0) + activesupport (4.2.11.1) + i18n (~> 0.7) + minitest (~> 5.1) + thread_safe (~> 0.3, >= 0.3.4) + tzinfo (~> 1.1) + algoliasearch (1.26.0) + httpclient (~> 2.8, >= 2.8.3) + json (>= 1.5.1) + atomos (0.1.3) + claide (1.0.2) + cocoapods-deintegrate (1.0.4) + cocoapods-downloader (1.2.2) + cocoapods-plugins (1.0.0) + nap + cocoapods-search (1.0.0) + cocoapods-stats (1.1.0) + cocoapods-try (1.1.0) + colored2 (3.1.2) + concurrent-ruby (1.1.5) + escape (0.0.4) + fourflusher (2.3.1) + fuzzy_match (2.0.4) + gh_inspector (1.1.3) + httpclient (2.8.3) + i18n (0.9.5) + concurrent-ruby (~> 1.0) + json (2.2.0) + minitest (5.11.3) + molinillo (0.6.6) + nanaimo (0.2.6) + nap (1.1.0) + netrc (0.11.0) + ruby-macho (1.4.0) + thread_safe (0.3.6) + tzinfo (1.2.5) + thread_safe (~> 0.1) + +PLATFORMS + ruby + +DEPENDENCIES + cocoapods! + cocoapods-core! + cocoapods-trunk! + xcodeproj! + +BUNDLED WITH + 2.0.2 diff --git a/Scripts/cocoapod-validate.sh b/Scripts/cocoapod-validate.sh index 57fcc73..5e82410 100755 --- a/Scripts/cocoapod-validate.sh +++ b/Scripts/cocoapod-validate.sh @@ -5,12 +5,12 @@ set -o pipefail . Scripts/set-travis-tag-to-latest.sh -pod env +bundle exec pod env # Lint the podspec to check for errors. Don't call `pod spec lint`, because we want it to evaluate locally # Using sed to remove logging from output until CocoaPods issue #7577 is implemented and I can use the # OS_ACTIVITY_MODE = disable environment variable from the test spec scheme -pod lib lint --verbose | sed -l '/xctest\[/d; /^$/d' +bundle exec pod lib lint --verbose | sed -l '/xctest\[/d; /^$/d' . Scripts/unset-travis-tag.sh \ No newline at end of file diff --git a/Scripts/install-demo-libs.sh b/Scripts/install-demo-libs.sh index 64982a0..8907a6b 100755 --- a/Scripts/install-demo-libs.sh +++ b/Scripts/install-demo-libs.sh @@ -5,8 +5,8 @@ set -ev . Scripts/set-travis-tag-to-latest.sh pushd UnzipKitDemo -pod --version -pod update +bundle exec pod --version +bundle exec pod update popd . Scripts/unset-travis-tag.sh \ No newline at end of file From 4188c8d8b7ec8a94de4bb99447c89ad76907d764 Mon Sep 17 00:00:00 2001 From: MartinLau Date: Thu, 1 Aug 2019 19:55:39 +0800 Subject: [PATCH 12/56] Add IsSymbolicLink property to UZKFileInfo (#88) --- Source/UZKFileInfo.h | 10 ++ Source/UZKFileInfo.m | 85 ++++++++++- Tests/FileInfoTests.swift | 143 ++++++++++++++++++ .../Test Archive (DOS Directories).zip | Bin 0 -> 897 bytes .../Test Data/Test Archive (Directories).zip | Bin 0 -> 843 bytes .../Test Archive (SymLink Directory).zip | Bin 0 -> 810 bytes Tests/UZKArchiveTestCase.h | 1 + Tests/UZKArchiveTestCase.m | 27 +++- UnzipKit.xcodeproj/project.pbxproj | 4 + 9 files changed, 261 insertions(+), 9 deletions(-) create mode 100644 Tests/FileInfoTests.swift create mode 100644 Tests/Test Data/Test Archive (DOS Directories).zip create mode 100644 Tests/Test Data/Test Archive (Directories).zip create mode 100644 Tests/Test Data/Test Archive (SymLink Directory).zip diff --git a/Source/UZKFileInfo.h b/Source/UZKFileInfo.h index e14248e..a5c0f99 100644 --- a/Source/UZKFileInfo.h +++ b/Source/UZKFileInfo.h @@ -73,6 +73,16 @@ typedef NS_ENUM(NSInteger, UZKCompressionMethod) { */ @property (readonly) BOOL isDirectory; +/** + * YES if the file is a symbolic link + */ +@property (readonly) BOOL isSymbolicLink; + +/** + * YES if the item is a macOS resource fork + */ +@property (readonly) BOOL isMacResourceFork; + /** * The type of compression */ diff --git a/Source/UZKFileInfo.m b/Source/UZKFileInfo.m index 050adf6..4503313 100644 --- a/Source/UZKFileInfo.m +++ b/Source/UZKFileInfo.m @@ -7,6 +7,50 @@ #import "UZKFileInfo.h" #import "unzip.h" +/** + * Define the file storage system in zip according to "version made by" + * These values are taken from the PKWARE zip zpec, section 4.4.2.1 + * + * https://www.pkware.com/documents/casestudies/APPNOTE.TXT) + */ +typedef NS_ENUM(NSUInteger, UZKZipOS) { + UZKZipOSMSDOS = 0, + UZKZipOSAmiga = 1, + UZKZipOSOpenVMS = 2, + UZKZipOSUnix = 3, + UZKZipOSVMCMS = 4, + UZKZipOSAtariST = 5, + UZKZipOSOS2 = 6, + UZKZipOSClassicMac = 7, + UZKZipOSZSystem = 8, + UZKZipOSCPM = 9, + UZKZipOSWindowsNT = 10, + UZKZipOSMVS = 11, + UZKZipOSVSE = 12, + UZKZipOSAcorn = 13, + UZKZipOSVFAT = 14, + UZKZipOSAlternateMVS = 15, + UZKZipOSBeOS = 16, + UZKZipOSTandem = 17, + UZKZipOSOS400 = 18, + UZKZipOSDarwinOSX = 19 +}; + +/** + * These are the UNIX file types, as defined in system headers. These values are reused by the ZIP + * format's external file attributes (see especially Directory, Regular, SymLink) + */ +typedef NS_ENUM(ushort, UZKFileType) { + UZKFileTypeNamedPipe = S_IFIFO, + UZKFileTypeCharacterSpecial = S_IFCHR, + UZKFileTypeDirectory = S_IFDIR, + UZKFileTypeBlockSpecial = S_IFBLK, + UZKFileTypeRegular = S_IFREG, + UZKFileTypeSymLink = S_IFLNK, + UZKFileTypeSocket = S_IFSOCK +}; + + @interface UZKFileInfo () @property (readwrite) tm_unz zipTMUDate; @@ -19,6 +63,7 @@ @implementation UZKFileInfo @synthesize timestamp = _timestamp; + #pragma mark - Initialization @@ -35,7 +80,8 @@ - (instancetype)initWithFileInfo:(unz_file_info64 *)fileInfo filename:(NSString _zipTMUDate = fileInfo->tmu_date; _CRC = fileInfo->crc; _isEncryptedWithPassword = (fileInfo->flag & 1) != 0; - _isDirectory = [filename hasSuffix:@"/"]; + _isDirectory = [UZKFileInfo itemIsDirectory:fileInfo]; + _isSymbolicLink = [UZKFileInfo itemIsSymbolicLink:fileInfo]; if (_isDirectory) { _filename = [_filename substringToIndex:_filename.length - 1]; @@ -44,7 +90,7 @@ - (instancetype)initWithFileInfo:(unz_file_info64 *)fileInfo filename:(NSString _compressionMethod = [self readCompressionMethod:fileInfo->compression_method flag:fileInfo->flag]; - uLong permissions = (fileInfo->external_fa >> 16) & 0777U; + uLong permissions = [UZKFileInfo itemPermissions:fileInfo]; _posixPermissions = permissions ? permissions : 0644U; } return self; @@ -65,6 +111,41 @@ - (NSDate *)timestamp { +#pragma mark - Private Class Methods + + ++ (UZKZipOS)itemOS:(unz_file_info64 *)fileInfo { + return fileInfo->version >> 8; +} + ++ (UZKFileType)itemFileType:(unz_file_info64 *)fileInfo { + return S_IFMT & (fileInfo->external_fa >> 16); +} + ++ (uLong)itemPermissions:(unz_file_info64 *)fileInfo { + uLong permissionsMask = S_IRWXU | S_IRWXG | S_IRWXO; + return permissionsMask & (fileInfo->external_fa >> 16); +} + ++ (BOOL)itemIsDOSDirectory:(unz_file_info64 *)fileInfo { + return 0x01 & (fileInfo->external_fa >> 4); +} + ++ (BOOL)itemIsDirectory:(unz_file_info64 *)fileInfo { + UZKZipOS itemOS = [UZKFileInfo itemOS:fileInfo]; + if (itemOS == UZKZipOSMSDOS || itemOS == UZKZipOSWindowsNT) { + return [UZKFileInfo itemIsDOSDirectory:fileInfo]; + } + + return [UZKFileInfo itemFileType:fileInfo] == UZKFileTypeDirectory; +} + ++ (BOOL)itemIsSymbolicLink:(unz_file_info64 *)fileInfo { + return [UZKFileInfo itemFileType:fileInfo] == UZKFileTypeSymLink; +} + + + #pragma mark - Private Methods diff --git a/Tests/FileInfoTests.swift b/Tests/FileInfoTests.swift new file mode 100644 index 0000000..2ff5324 --- /dev/null +++ b/Tests/FileInfoTests.swift @@ -0,0 +1,143 @@ +// +// FileInfoTests.swift +// UnzipKitTests +// +// Created by Dov Frankel on 7/25/19. +// Copyright © 2019 Abbey Code. All rights reserved. +// + +import XCTest + +class FileInfoTests: UZKArchiveTestCase { + + func testIsDirectory_NoDirectories() { + let testArchiveName = "Test Archive.zip" + let testFileURL = self.testFileURLs[testArchiveName] as! URL + let archive = try! UZKArchive(url: testFileURL) + + let fileInfo = try! archive.listFileInfo() + + let expected = [false, false, false] + let actual = fileInfo.map { $0.isDirectory } + + XCTAssertEqual(actual, expected) + } + + func testIsDirectory_ContainsDirectories() { + let testArchiveName = "Test Archive (Directories).zip" + let testFileURL = self.testFileURLs[testArchiveName] as! URL + let archive = try! UZKArchive(url: testFileURL) + + let fileInfo = try! archive.listFileInfo() + + let expected = [ + "Folder A": true, + "Folder A/Test File A.txt": false, + "Test File B.txt": false, + "X Folder": true, + "X Folder/Test File C.txt": false + ] + let actual = fileInfo.reduce(into: Dictionary()) { + $0[$1.filename] = $1.isDirectory + } + + XCTAssertEqual(actual, expected) + } + + func testIsDirectory_ContainsDirectories_DOS() { + let testArchiveName = "Test Archive (DOS Directories).zip" + let testFileURL = self.testFileURLs[testArchiveName] as! URL + let archive = try! UZKArchive(url: testFileURL) + + let fileInfo = try! archive.listFileInfo() + + let expected = [ + "FOLDERA": true, + "FOLDERA/TESTFILE.TXT": false, + "TESTFILE.TXT": false, + "XFOLDER": true, + "XFOLDER/TESTFILE.TXT": false + ] + let actual = fileInfo.reduce(into: Dictionary()) { + $0[$1.filename] = $1.isDirectory + } + + XCTAssertEqual(actual, expected) + } + + func testIsSymbolicLink_NoSymLinks() { + let testArchiveName = "Test Archive.zip" + let testFileURL = self.testFileURLs[testArchiveName] as! URL + let archive = try! UZKArchive(url: testFileURL) + + let fileInfo = try! archive.listFileInfo() + + let expected = [false, false, false] + let actual = fileInfo.map { $0.isSymbolicLink } + + XCTAssertEqual(actual, expected) + } + + #if os(OSX) + func testIsSymbolicLink_ContainsSymLinkFile() { + let textFileURL = self.emptyTextFile(ofLength: 20)! + let symLinkURL = textFileURL.deletingLastPathComponent() + .appendingPathComponent(textFileURL + .lastPathComponent + .replacingOccurrences(of: ".txt", with: "-Link.txt")) + + try! FileManager.default.createSymbolicLink(at: symLinkURL, withDestinationURL: textFileURL) + + let archiveURL = self.archive(withFiles: [textFileURL, symLinkURL], + zipOptions: ["--junk-paths", "--symlinks"])! + let archive = try! UZKArchive(url: archiveURL) + + let fileInfo = try! archive.listFileInfo() + + let expected = [ + textFileURL.lastPathComponent: false, + symLinkURL.lastPathComponent: true, + ] + let actual = fileInfo.reduce(into: Dictionary()) { + $0[$1.filename] = $1.isSymbolicLink + } + + XCTAssertEqual(actual, expected) + } + + func testIsDirectory_ContainsSymLinkFileAndDir() { + struct FileInfo: Equatable, CustomStringConvertible { + var description: String { + return "FileInfo(isLink: \(isLink), isDir: \(isDir))" + } + + let isLink: Bool + let isDir: Bool + + static func == (lhs: FileInfo, rhs: FileInfo) -> Bool { + return lhs.isLink == rhs.isLink && lhs.isLink == rhs.isLink + } + } + + let testArchiveName = "Test Archive (SymLink Directory).zip" + let testFileURL = self.testFileURLs[testArchiveName] as! URL + let archive = try! UZKArchive(url: testFileURL) + + let fileInfo = try! archive.listFileInfo() + + let expected: Dictionary = [ + "testDir": FileInfo(isLink: false, isDir: true), + "testDir/testFile2.md": FileInfo(isLink: false, isDir: false), + "testDirLink": FileInfo(isLink: true, isDir: false), + "testFile.md": FileInfo(isLink: false, isDir: false), + "testFileLink.md": FileInfo(isLink: true, isDir: false) + ] + let actual = fileInfo.reduce(into: Dictionary()) { + $0[$1.filename!] = FileInfo(isLink: $1.isSymbolicLink, isDir: $1.isDirectory) + } + + XCTAssertEqual(actual, expected) + } + #endif + +} diff --git a/Tests/Test Data/Test Archive (DOS Directories).zip b/Tests/Test Data/Test Archive (DOS Directories).zip new file mode 100644 index 0000000000000000000000000000000000000000..99efd914fb84c16c6a8bbff3ecaac3c731c9dc87 GIT binary patch literal 897 zcmWIWW@h1H0D&tT6}_dm=^7~l*&xirAj1&i8XV&0>Eo&w5)l#_!pXqQFS07q`L$ha zX$3a}Bg;2N1_l-ppxThs;t~b7%$!sOM+J@I)a0Vn5{2B<;^M^gRE3PxqEyWQxHX*K zKmC9@;D7^YjhnxZi))ahKG=$;wU)6UjAjMG1od5IF31iQK{5fu!B#`n<_0Kfu1Gz92QtkH&W iCuYPUyE6*OGz@owq7lQLtZbm*X9dDVK)oly(E$LgAEG1x literal 0 HcmV?d00001 diff --git a/Tests/Test Data/Test Archive (Directories).zip b/Tests/Test Data/Test Archive (Directories).zip new file mode 100644 index 0000000000000000000000000000000000000000..4ccadbc5a72dd8823270f6b11a2ffa8d8e3800e7 GIT binary patch literal 843 zcmWIWW@Zs#W&i?C@1K4k8U{Fl47dE8l++>xNBsbxGA^((^<8By(%W>66oJwpEP<>n zB(=Ci!7Vc8(D2p~M8iXti$MXz zXJ7<{WbS*XH%?a>;q`XK6iR1+ z7QiC|#c26!fuK-ofYPW&LqZAN?Gh+%|BP%j!V}m_gA7ASYH^86W|2>3UUp~*Cj)bIn0+h=msW5y zFtU7OWME(s0V;(m3xJ!I0yGOK4hI|vv-H6R%>f#+0Mj7cCWs)J0J6?4GbhzZFE<5l z8puR6XCX{;MRO0#Zy-}(h9m(E0AZYlgaZw+OFzua4fGZW^Fy5kG6WPtFoW8FhJY}d zK`>VXl?8Y+GRZOH3Uu)SRfVxmL>D|vBXq>H!B;^dInY?oDbAi3d9Ty02zXu-v9sr literal 0 HcmV?d00001 diff --git a/Tests/UZKArchiveTestCase.h b/Tests/UZKArchiveTestCase.h index 6a58c1e..752f0cc 100644 --- a/Tests/UZKArchiveTestCase.h +++ b/Tests/UZKArchiveTestCase.h @@ -43,6 +43,7 @@ #if !TARGET_OS_IPHONE - (NSInteger)numberOfOpenFileHandles; - (NSURL *)archiveWithFiles:(NSArray *)fileURLs; +- (NSURL *)archiveWithFiles:(NSArray *)fileURLs zipOptions:(NSArray *)zipOpts; - (NSURL *)archiveWithFiles:(NSArray *)fileURLs password:(NSString *)password; - (BOOL)extractArchive:(NSURL *)url password:(NSString *)password; - (NSURL *)largeArchive; diff --git a/Tests/UZKArchiveTestCase.m b/Tests/UZKArchiveTestCase.m index 2cd9fe8..89fdd84 100644 --- a/Tests/UZKArchiveTestCase.m +++ b/Tests/UZKArchiveTestCase.m @@ -52,7 +52,10 @@ - (void)setUp { NSArray *testFiles = @[ @"Test Archive.zip", + @"Test Archive (DOS Directories).zip", + @"Test Archive (Directories).zip", @"Test Archive (Password).zip", + @"Test Archive (SymLink Directory).zip", @"L'incertain.zip", @"Aces.zip", @"Comments Archive.zip", @@ -238,14 +241,22 @@ - (NSURL *)archiveWithFiles:(NSArray *)fileURLs return [self archiveWithFiles:fileURLs password:nil]; } +- (NSURL *)archiveWithFiles:(NSArray *)fileURLs zipOptions:(NSArray *)zipOpts +{ + return [self archiveWithFiles:fileURLs password:nil name:nil zipOptions:zipOpts]; +} + - (NSURL *)archiveWithFiles:(NSArray *)fileURLs password:(NSString *)password { - NSString *uniqueString = [[NSProcessInfo processInfo] globallyUniqueString]; - return [self archiveWithFiles:fileURLs password:password name:uniqueString]; + return [self archiveWithFiles:fileURLs password:password name:nil zipOptions:@[]]; } -- (NSURL *)archiveWithFiles:(NSArray *)fileURLs password:(NSString *)password name:(NSString *)name +- (NSURL *)archiveWithFiles:(NSArray *)fileURLs password:(NSString *)password name:(NSString *)name zipOptions:(NSArray *)zipOpts { + if (![name length]) { + name = [[NSProcessInfo processInfo] globallyUniqueString]; + } + NSURL *archiveURL = [[self.tempDirectory URLByAppendingPathComponent:name] URLByAppendingPathExtension:@"zip"]; NSFileHandle *consoleOutputHandle = nil; @@ -269,11 +280,11 @@ - (NSURL *)archiveWithFiles:(NSArray *)fileURLs password:(NSString *)password na while (startIndex < filePaths.count) { @autoreleasepool { - NSMutableArray *zipArgs = [NSMutableArray arrayWithArray: - @[@"-j", archiveURL.path]]; + NSMutableArray *zipArgs = [NSMutableArray arrayWithArray:zipOpts]; + [zipArgs addObjectsFromArray:@[@"--junk-paths", archiveURL.path]]; if (password) { - [zipArgs addObjectsFromArray:@[@"-P", password]]; + [zipArgs addObjectsFromArray:@[@"--password", password]]; } NSRange currentRange = NSMakeRange(startIndex, MIN(pathsRemaining, maxFilesPerCall)); @@ -288,6 +299,7 @@ - (NSURL *)archiveWithFiles:(NSArray *)fileURLs password:(NSString *)password na UZKLog("Compressing files %lu-%lu of %lu", startIndex + 1, startIndex + pathArrayChunk.count, filePaths.count); + UZKLogDebug("Zip command: %@ %@", task.launchPath, [task.arguments componentsJoinedByString:@" "]); [task launch]; [task waitUntilExit]; @@ -346,7 +358,8 @@ - (NSURL *)largeArchive static NSInteger archiveNumber = 1; NSURL *largeArchiveURL = [self archiveWithFiles:emptyFiles password:nil - name:[NSString stringWithFormat:@"Large Archive %ld", archiveNumber++]]; + name:[NSString stringWithFormat:@"Large Archive %ld", archiveNumber++] + zipOptions:@[]]; return largeArchiveURL; } diff --git a/UnzipKit.xcodeproj/project.pbxproj b/UnzipKit.xcodeproj/project.pbxproj index e248c66..f4168f7 100644 --- a/UnzipKit.xcodeproj/project.pbxproj +++ b/UnzipKit.xcodeproj/project.pbxproj @@ -17,6 +17,7 @@ 7A0029221F93DBC900618503 /* unzip.h in Headers */ = {isa = PBXBuildFile; fileRef = 96EA65C81A40C44300685B6D /* unzip.h */; }; 7A0029231F93DBC900618503 /* zip.h in Headers */ = {isa = PBXBuildFile; fileRef = 96EA65CA1A40C44300685B6D /* zip.h */; }; 7A0029241F93DBF000618503 /* libminizip.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7A0029171F93DB5800618503 /* libminizip.a */; }; + 7A27405422EAA3A500E23E75 /* FileInfoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A27405322EAA3A500E23E75 /* FileInfoTests.swift */; }; 7A5652241F90E01C006B782E /* CheckDataTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 7A5652231F90E01C006B782E /* CheckDataTests.m */; }; 7A5A97011F89808900BCA061 /* ProgressReportingTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 7A5A97001F89808900BCA061 /* ProgressReportingTests.m */; }; 7AA77FC822C16CF600121052 /* PermissionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AA77FC722C16CF600121052 /* PermissionsTests.swift */; }; @@ -77,6 +78,7 @@ /* Begin PBXFileReference section */ 7A0029171F93DB5800618503 /* libminizip.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libminizip.a; sourceTree = BUILT_PRODUCTS_DIR; }; + 7A27405322EAA3A500E23E75 /* FileInfoTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileInfoTests.swift; sourceTree = ""; }; 7A5652231F90E01C006B782E /* CheckDataTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CheckDataTests.m; sourceTree = ""; }; 7A5A97001F89808900BCA061 /* ProgressReportingTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ProgressReportingTests.m; sourceTree = ""; }; 7AA77FC722C16CF600121052 /* PermissionsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PermissionsTests.swift; sourceTree = ""; }; @@ -252,6 +254,7 @@ 968C40C91B586227004C128E /* ExtractDataTests.m */, 9630C0371C6D27A4000693EE /* ExtractDataTests_Swift.swift */, 968C40C71B5861F4004C128E /* ExtractFilesTests.m */, + 7A27405322EAA3A500E23E75 /* FileInfoTests.swift */, 7A5A97001F89808900BCA061 /* ProgressReportingTests.m */, 968C40D91B5863D9004C128E /* FileDescriptorUsageTests.m */, 968C40C31B58619C004C128E /* ListFilenamesTests.m */, @@ -545,6 +548,7 @@ 968C40E01B586490004C128E /* PropertyTests.m in Sources */, 968C40DC1B586401004C128E /* CommentsTests.m in Sources */, 968C40D21B586310004C128E /* PasswordProtectionTests.m in Sources */, + 7A27405422EAA3A500E23E75 /* FileInfoTests.swift in Sources */, 963386B91EE89A51006B16BF /* UtilityMethods.swift in Sources */, 7AA77FC822C16CF600121052 /* PermissionsTests.swift in Sources */, 968C40C81B5861F4004C128E /* ExtractFilesTests.m in Sources */, From 6add3e76933888dea5e16da2ef739dc39d1313de Mon Sep 17 00:00:00 2001 From: Dov Frankel Date: Thu, 1 Aug 2019 07:57:36 -0400 Subject: [PATCH 13/56] Removed isMacResourceFork property from UZKFileInfo, which has no implementation --- Source/UZKFileInfo.h | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Source/UZKFileInfo.h b/Source/UZKFileInfo.h index a5c0f99..d47200c 100644 --- a/Source/UZKFileInfo.h +++ b/Source/UZKFileInfo.h @@ -78,11 +78,6 @@ typedef NS_ENUM(NSInteger, UZKCompressionMethod) { */ @property (readonly) BOOL isSymbolicLink; -/** - * YES if the item is a macOS resource fork - */ -@property (readonly) BOOL isMacResourceFork; - /** * The type of compression */ From fdb8cf13e5df117bcd36181dfd961bd561617765 Mon Sep 17 00:00:00 2001 From: Dov Frankel Date: Thu, 1 Aug 2019 08:02:20 -0400 Subject: [PATCH 14/56] Fixed Xcode 11 warnings --- Source/UZKFileInfo.h | 2 +- Tests/ListFileInfoTests.m | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/UZKFileInfo.h b/Source/UZKFileInfo.h index d47200c..898b67d 100644 --- a/Source/UZKFileInfo.h +++ b/Source/UZKFileInfo.h @@ -13,7 +13,7 @@ /** * Defines the various compression levels that can be applied to a file */ -typedef NS_ENUM(NSInteger, UZKCompressionMethod) { +typedef NS_ENUM(int, UZKCompressionMethod) { /** * Default level */ diff --git a/Tests/ListFileInfoTests.m b/Tests/ListFileInfoTests.m index e3fcb7a..dd2f830 100644 --- a/Tests/ListFileInfoTests.m +++ b/Tests/ListFileInfoTests.m @@ -61,7 +61,7 @@ - (void)testListFileInfo { XCTAssertEqual(fileInfo.uncompressedSize, expectedFileSize, @"Incorrect uncompressed file size"); // Test Compression method - UZKCompressionMethod expectedCompressionMethod = ((NSNumber *)expectedCompressionMethods[fileInfo.filename]).integerValue; + UZKCompressionMethod expectedCompressionMethod = ((NSNumber *)expectedCompressionMethods[fileInfo.filename]).intValue; XCTAssertEqual(fileInfo.compressionMethod, expectedCompressionMethod, @"Incorrect compression method"); } } From 2b25efe170b9143820036e2e0afb609ff64c189c Mon Sep 17 00:00:00 2001 From: Dov Frankel Date: Thu, 1 Aug 2019 08:02:54 -0400 Subject: [PATCH 15/56] Updated overloads of test case archiveWithFiles methods --- Tests/UZKArchiveTestCase.h | 4 +++- Tests/UZKArchiveTestCase.m | 30 +++++++++++++++++------------- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/Tests/UZKArchiveTestCase.h b/Tests/UZKArchiveTestCase.h index 752f0cc..b85420f 100644 --- a/Tests/UZKArchiveTestCase.h +++ b/Tests/UZKArchiveTestCase.h @@ -43,8 +43,10 @@ #if !TARGET_OS_IPHONE - (NSInteger)numberOfOpenFileHandles; - (NSURL *)archiveWithFiles:(NSArray *)fileURLs; -- (NSURL *)archiveWithFiles:(NSArray *)fileURLs zipOptions:(NSArray *)zipOpts; - (NSURL *)archiveWithFiles:(NSArray *)fileURLs password:(NSString *)password; +- (NSURL *)archiveWithFiles:(NSArray *)fileURLs zipOptions:(NSArray *)zipOpts; +- (NSURL *)archiveWithFiles:(NSArray *)fileURLs password:(NSString *)password name:(NSString *)name; +- (NSURL *)archiveWithFiles:(NSArray *)fileURLs name:(NSString *)name zipOptions:(NSArray *)zipOpts; - (BOOL)extractArchive:(NSURL *)url password:(NSString *)password; - (NSURL *)largeArchive; #endif diff --git a/Tests/UZKArchiveTestCase.m b/Tests/UZKArchiveTestCase.m index 89fdd84..01c761b 100644 --- a/Tests/UZKArchiveTestCase.m +++ b/Tests/UZKArchiveTestCase.m @@ -236,27 +236,36 @@ - (NSInteger)numberOfOpenFileHandles { return result; } -- (NSURL *)archiveWithFiles:(NSArray *)fileURLs +- (NSURL *)archiveWithFiles:(NSArray *)fileURLs { - return [self archiveWithFiles:fileURLs password:nil]; + return [self archiveWithFiles:fileURLs name:nil zipOptions:nil]; +} + +- (NSURL *)archiveWithFiles:(NSArray *)fileURLs password:(NSString *)password +{ + return [self archiveWithFiles:fileURLs password:password name:nil]; } - (NSURL *)archiveWithFiles:(NSArray *)fileURLs zipOptions:(NSArray *)zipOpts { - return [self archiveWithFiles:fileURLs password:nil name:nil zipOptions:zipOpts]; + return [self archiveWithFiles:fileURLs name:nil zipOptions:zipOpts]; } -- (NSURL *)archiveWithFiles:(NSArray *)fileURLs password:(NSString *)password +- (NSURL *)archiveWithFiles:(NSArray *)fileURLs password:(NSString *)password name:(NSString *)name { - return [self archiveWithFiles:fileURLs password:password name:nil zipOptions:@[]]; + return [self archiveWithFiles:fileURLs name:name zipOptions:@[@"--junk-paths", @"--password", password]]; } -- (NSURL *)archiveWithFiles:(NSArray *)fileURLs password:(NSString *)password name:(NSString *)name zipOptions:(NSArray *)zipOpts +- (NSURL *)archiveWithFiles:(NSArray *)fileURLs name:(NSString *)name zipOptions:(NSArray *)zipOpts; { if (![name length]) { name = [[NSProcessInfo processInfo] globallyUniqueString]; } + if (!zipOpts) { + zipOpts = @[@"--junk-paths"]; + } + NSURL *archiveURL = [[self.tempDirectory URLByAppendingPathComponent:name] URLByAppendingPathExtension:@"zip"]; NSFileHandle *consoleOutputHandle = nil; @@ -281,11 +290,7 @@ - (NSURL *)archiveWithFiles:(NSArray *)fileURLs password:(NSString *)password na while (startIndex < filePaths.count) { @autoreleasepool { NSMutableArray *zipArgs = [NSMutableArray arrayWithArray:zipOpts]; - [zipArgs addObjectsFromArray:@[@"--junk-paths", archiveURL.path]]; - - if (password) { - [zipArgs addObjectsFromArray:@[@"--password", password]]; - } + [zipArgs addObject:archiveURL.path]; NSRange currentRange = NSMakeRange(startIndex, MIN(pathsRemaining, maxFilesPerCall)); NSArray *pathArrayChunk = [filePaths subarrayWithRange:currentRange]; @@ -357,9 +362,8 @@ - (NSURL *)largeArchive static NSInteger archiveNumber = 1; NSURL *largeArchiveURL = [self archiveWithFiles:emptyFiles - password:nil name:[NSString stringWithFormat:@"Large Archive %ld", archiveNumber++] - zipOptions:@[]]; + zipOptions:nil]; return largeArchiveURL; } From 24d82dd7811558350a8650e2ec7c487ab6e5670b Mon Sep 17 00:00:00 2001 From: Dov Frankel Date: Mon, 5 Aug 2019 16:52:20 -0400 Subject: [PATCH 16/56] Reverted bundler workaround for no CocoaPods beta being available for v1.8.0 yet, since it's out now --- .travis.yml | 4 +- Gemfile | 6 -- Gemfile.lock | 105 ----------------------------------- Scripts/cocoapod-validate.sh | 4 +- Scripts/install-demo-libs.sh | 4 +- 5 files changed, 6 insertions(+), 117 deletions(-) delete mode 100644 Gemfile delete mode 100644 Gemfile.lock diff --git a/.travis.yml b/.travis.yml index e4b3026..c71efd3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,8 +4,8 @@ language: osx_image: xcode10.2 before_script: - # Only until CocoaPods 1.8.0 beta 1 is released. Then remove this and the Gemfile - - bundle config github.https true && bundle install + # Validation fails on CocoaPods versions before 1.8.0 + - gem install cocoapods --pre # Make log level less verbose. Temporarily undo if more info is needed - sudo log config --mode "level:default" diff --git a/Gemfile b/Gemfile deleted file mode 100644 index 5d65347..0000000 --- a/Gemfile +++ /dev/null @@ -1,6 +0,0 @@ -source 'https://rubygems.org' - -gem 'cocoapods', :github => 'CocoaPods/CocoaPods' -gem 'cocoapods-core', :github => 'CocoaPods/Core' -gem 'cocoapods-trunk', :github => 'CocoaPods/cocoapods-trunk' -gem 'xcodeproj', :github => 'CocoaPods/Xcodeproj' \ No newline at end of file diff --git a/Gemfile.lock b/Gemfile.lock deleted file mode 100644 index fd69ae2..0000000 --- a/Gemfile.lock +++ /dev/null @@ -1,105 +0,0 @@ -GIT - remote: https://github.com/CocoaPods/CocoaPods.git - revision: b93c72d3d68b5075435354de7cb2116579f02c8e - specs: - cocoapods (1.7.5) - activesupport (>= 4.0.2, < 5) - claide (>= 1.0.2, < 2.0) - cocoapods-core (= 1.7.5) - cocoapods-deintegrate (>= 1.0.3, < 2.0) - cocoapods-downloader (>= 1.2.2, < 2.0) - cocoapods-plugins (>= 1.0.0, < 2.0) - cocoapods-search (>= 1.0.0, < 2.0) - cocoapods-stats (>= 1.0.0, < 2.0) - cocoapods-trunk (>= 1.3.1, < 2.0) - cocoapods-try (>= 1.1.0, < 2.0) - colored2 (~> 3.1) - escape (~> 0.0.4) - fourflusher (>= 2.3.0, < 3.0) - gh_inspector (~> 1.0) - molinillo (~> 0.6.6) - nap (~> 1.0) - ruby-macho (~> 1.4) - xcodeproj (>= 1.10.0, < 2.0) - -GIT - remote: https://github.com/CocoaPods/Core.git - revision: 9ae4051d5fedf5869b603b1ac409a384537abdf4 - specs: - cocoapods-core (1.7.5) - activesupport (>= 4.0.2, < 6) - algoliasearch (~> 1.0) - fuzzy_match (~> 2.0.4) - nap (~> 1.0) - -GIT - remote: https://github.com/CocoaPods/Xcodeproj.git - revision: 5b4e8ef7bf54b5d5cbd8970b92cbb9d99f58a942 - specs: - xcodeproj (1.11.0) - CFPropertyList (>= 2.3.3, < 4.0) - atomos (~> 0.1.3) - claide (>= 1.0.2, < 2.0) - colored2 (~> 3.1) - nanaimo (~> 0.2.6) - -GIT - remote: https://github.com/CocoaPods/cocoapods-trunk.git - revision: 208396fca7eeecf4b7fb2810f1a1ab3b036bd1a3 - specs: - cocoapods-trunk (1.3.1) - nap (>= 0.8, < 2.0) - netrc (~> 0.11) - -GEM - remote: https://rubygems.org/ - specs: - CFPropertyList (3.0.0) - activesupport (4.2.11.1) - i18n (~> 0.7) - minitest (~> 5.1) - thread_safe (~> 0.3, >= 0.3.4) - tzinfo (~> 1.1) - algoliasearch (1.26.0) - httpclient (~> 2.8, >= 2.8.3) - json (>= 1.5.1) - atomos (0.1.3) - claide (1.0.2) - cocoapods-deintegrate (1.0.4) - cocoapods-downloader (1.2.2) - cocoapods-plugins (1.0.0) - nap - cocoapods-search (1.0.0) - cocoapods-stats (1.1.0) - cocoapods-try (1.1.0) - colored2 (3.1.2) - concurrent-ruby (1.1.5) - escape (0.0.4) - fourflusher (2.3.1) - fuzzy_match (2.0.4) - gh_inspector (1.1.3) - httpclient (2.8.3) - i18n (0.9.5) - concurrent-ruby (~> 1.0) - json (2.2.0) - minitest (5.11.3) - molinillo (0.6.6) - nanaimo (0.2.6) - nap (1.1.0) - netrc (0.11.0) - ruby-macho (1.4.0) - thread_safe (0.3.6) - tzinfo (1.2.5) - thread_safe (~> 0.1) - -PLATFORMS - ruby - -DEPENDENCIES - cocoapods! - cocoapods-core! - cocoapods-trunk! - xcodeproj! - -BUNDLED WITH - 2.0.2 diff --git a/Scripts/cocoapod-validate.sh b/Scripts/cocoapod-validate.sh index 5e82410..57fcc73 100755 --- a/Scripts/cocoapod-validate.sh +++ b/Scripts/cocoapod-validate.sh @@ -5,12 +5,12 @@ set -o pipefail . Scripts/set-travis-tag-to-latest.sh -bundle exec pod env +pod env # Lint the podspec to check for errors. Don't call `pod spec lint`, because we want it to evaluate locally # Using sed to remove logging from output until CocoaPods issue #7577 is implemented and I can use the # OS_ACTIVITY_MODE = disable environment variable from the test spec scheme -bundle exec pod lib lint --verbose | sed -l '/xctest\[/d; /^$/d' +pod lib lint --verbose | sed -l '/xctest\[/d; /^$/d' . Scripts/unset-travis-tag.sh \ No newline at end of file diff --git a/Scripts/install-demo-libs.sh b/Scripts/install-demo-libs.sh index 8907a6b..64982a0 100755 --- a/Scripts/install-demo-libs.sh +++ b/Scripts/install-demo-libs.sh @@ -5,8 +5,8 @@ set -ev . Scripts/set-travis-tag-to-latest.sh pushd UnzipKitDemo -bundle exec pod --version -bundle exec pod update +pod --version +pod update popd . Scripts/unset-travis-tag.sh \ No newline at end of file From 04023da5f86345b9a8efa5041323efd44c387e0b Mon Sep 17 00:00:00 2001 From: Dov Frankel Date: Tue, 13 Aug 2019 17:10:23 -0400 Subject: [PATCH 17/56] Made changes to logging, turning off for test spec, and restricting reduction to UnzipKit for the other tests --- .travis.yml | 4 ++-- UnzipKit.podspec | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index c71efd3..c9a253d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,8 +6,8 @@ osx_image: xcode10.2 before_script: # Validation fails on CocoaPods versions before 1.8.0 - gem install cocoapods --pre - # Make log level less verbose. Temporarily undo if more info is needed - - sudo log config --mode "level:default" + # Essentially turn off logging. Temporarily undo if more info is needed + - sudo log config --mode "level:default" --subsystem com.abbey-code.UnzipKit matrix: include: diff --git a/UnzipKit.podspec b/UnzipKit.podspec index 7b54d19..32a73ea 100644 --- a/UnzipKit.podspec +++ b/UnzipKit.podspec @@ -24,6 +24,10 @@ Pod::Spec.new do |s| test_spec.exclude_files = 'Tests/ExtractFilesTests.m' test_spec.resources = ['Tests/Test Data'] test_spec.pod_target_xcconfig = { "OTHER_CFLAGS" => "$(inherited) -Wno-unguarded-availability" } + test_spec.scheme = { + # Disable logging. Comment this line if you need diagnostic info + :environment_variables => { "OS_ACTIVITY_MODE" => "disable" } + } end s.library = "z" From e50f71d83d86310341484ba998c80a5fff885c2e Mon Sep 17 00:00:00 2001 From: Dov Frankel Date: Tue, 20 Aug 2019 17:49:47 -0400 Subject: [PATCH 18/56] Reduce number of -writeData: and -writeIntoBuffer: overloads (PR #97) * Updated UZKArchive to use ZipFileProperties and deprecate the old methods that don't use it * Updated CRC fields and variables to use uLong (unsigned long) consistently across the board in Objective-C, and UInt in Swift, which (counterintuitively) is the equivalent type * Upgraded platform targets to latest iOS and macOS releases * Updated demo project and podspec * Updated Travis build to validate the CocoaPod lib as both a static lib and as a dynamic framework * Made copies of all test cases using now-deprecated methods to have copies that use the new methods, and to silence the warnings for using the deprecated ones * Replaced '#import' with '@import' in Obj-C test cases --- .gitignore | 3 + .travis.yml | 6 +- README.md | 12 +- Scripts/cocoapod-validate.sh | 2 +- Scripts/generate-swift-import-header.sh | 34 ++ Source/UZKArchive.h | 91 +++- Source/UZKArchive.m | 238 +++++----- Source/UnzipKit.h | 4 +- Source/ZipFileProperties.swift | 75 +++ Tests/CheckDataTests.m | 2 +- Tests/CommentsTests.m | 2 +- Tests/DeleteFileTests.m | 2 +- Tests/ErrorHandlingTests.m | 2 +- Tests/ExtractBufferedDataTests.m | 2 +- Tests/ExtractDataTests.m | 2 +- Tests/FileDescriptorUsageTests.m | 69 ++- Tests/ListFileInfoTests.m | 2 +- Tests/ListFilenamesTests.m | 2 +- Tests/ModesTests.m | 22 +- Tests/MultithreadingTests.m | 2 +- Tests/PasswordProtectionTests.m | 44 +- Tests/PerformOnDataTests.m | 2 +- Tests/PerformOnFilesTests.m | 2 +- Tests/PermissionsTests.swift | 63 ++- Tests/PropertyTests.m | 2 +- Tests/UZKArchive+Deprecated.h | 53 +++ Tests/UZKArchive+Deprecated.m | 97 ++++ Tests/UZKArchiveTestCase.m | 4 +- Tests/UnzipKitTests-Bridging-Header.h | 3 +- Tests/WriteBufferedDataTests.m | 210 ++++++++- Tests/WriteDataTests.swift | 440 +++++++++++++++++- Tests/ZipFileDetectionTests.m | 2 +- UnzipKit.podspec | 23 +- UnzipKit.xcodeproj/project.pbxproj | 45 +- UnzipKitDemo/Podfile | 2 +- .../UnzipKitDemo.xcodeproj/project.pbxproj | 20 +- .../xcschemes/UnzipKitDemo.xcscheme | 4 +- 37 files changed, 1350 insertions(+), 240 deletions(-) create mode 100755 Scripts/generate-swift-import-header.sh create mode 100644 Source/ZipFileProperties.swift create mode 100644 Tests/UZKArchive+Deprecated.h create mode 100644 Tests/UZKArchive+Deprecated.m diff --git a/.gitignore b/.gitignore index 3ee5ebb..04c30e7 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,9 @@ build **/xcuserdata/* analyzer-output/** +# Generated by a Run Script build phase +Source/GeneratedSwiftImport.h + UnzipKitDemo/Pods/* UnzipKitDemo/Podfile.lock UnzipKitDemo/Carthage/* diff --git a/.travis.yml b/.travis.yml index c9a253d..f1e42cf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,9 +27,13 @@ matrix: script: ./Scripts/install-demo-libs.sh && xcodebuild -workspace UnzipKitDemo/UnzipKitDemo.xcworkspace -scheme UnzipKitDemo -destination 'platform=iOS Simulator,name=iPhone 7,OS=latest' -configuration Release analyze test ENABLE_NS_ASSERTIONS=1 CLANG_ANALYZER_OUTPUT=html CLANG_ANALYZER_OUTPUT_DIR=analyzer-output && [[ -z `find analyzer-output -name "*.html"` ]] - stage: Validate - env: Name=CocoaPods + env: Name=CocoaPods-Framework script: ./Scripts/cocoapod-validate.sh + - stage: Validate + env: Name=CocoaPods-Static-Lib + script: ./Scripts/cocoapod-validate.sh --use-libraries + - stage: Validate env: Name=Carthage script: ./Scripts/carthage-validate.sh diff --git a/README.md b/README.md index 89de8d5..681f1b3 100644 --- a/README.md +++ b/README.md @@ -100,9 +100,9 @@ You can also modify Zip archives: * Write data as a stream to the archive (from disk or over the network), using a block: ```Objective-C - BOOL success = [archive writeIntoBuffer:@"dir/filename.png" - error:&error - block: + BOOL success = [archive writeIntoBufferAtPath:@"dir/filename.png" + error:&error + block: ^BOOL(BOOL(^writeData)(const void *bytes, unsigned int length), NSError**(actionError)) { for (NSUInteger i = 0; i <= someFile.length; i += bufferSize) { const void *bytes = // some data @@ -139,9 +139,7 @@ The following methods support `NSProgress` and `NSProgressReporting`: * `performOnDataInArchive:error:` * `extractBufferedDataFromFile:error:action:` * `writeData:filePath:error:`* -* `writeData:filePath:fileDate:error:`* -* `writeData:filePath:fileDate:compressionMethod:password:error:`* -* `writeData:filePath:fileDate:compressionMethod:password:overwrite:error:`* +* `writeData:props:error:`* _* the `writeData...` methods don't support cancellation like the read-only methods do @@ -203,7 +201,7 @@ If you don't have a hierarchy of `NSProgress` instances, or if you want to obser Using either method above, you can call `[progress cancel]` to stop the operation in progress. It will cause the operation to fail, returning `nil` or `NO` (depending on the return type, and give an error with error code `UZKErrorCodeUserCancelled`. -Note: Cancellation is only supported on extraction methods, not write methods. +_Note: Cancellation is only supported on extraction methods, not write methods.__ # Documentation diff --git a/Scripts/cocoapod-validate.sh b/Scripts/cocoapod-validate.sh index 57fcc73..03b1a73 100755 --- a/Scripts/cocoapod-validate.sh +++ b/Scripts/cocoapod-validate.sh @@ -11,6 +11,6 @@ pod env # Using sed to remove logging from output until CocoaPods issue #7577 is implemented and I can use the # OS_ACTIVITY_MODE = disable environment variable from the test spec scheme -pod lib lint --verbose | sed -l '/xctest\[/d; /^$/d' +pod lib lint --verbose "$@" | sed -l '/xctest\[/d; /^$/d' . Scripts/unset-travis-tag.sh \ No newline at end of file diff --git a/Scripts/generate-swift-import-header.sh b/Scripts/generate-swift-import-header.sh new file mode 100755 index 0000000..f97ad06 --- /dev/null +++ b/Scripts/generate-swift-import-header.sh @@ -0,0 +1,34 @@ +#!/bin/sh + +# Different styles of import statement need to be used for the Swift generated header, +# depending on the target type (static library or dynamic framework). This script reads +# the PACKAGE_TYPE environment variable Xcode sets to create the correct one at build +# time, allowing the library to be built as either type from CocoaPods + +# static library: com.apple.product-type.library.static +# dynamic framework: com.apple.package-type.wrapper.framework +[[ "${PACKAGE_TYPE}" = "com.apple.package-type.wrapper.framework" ]] \ + && SWIFTIMPORT="<${PRODUCT_MODULE_NAME}/${PRODUCT_MODULE_NAME}-Swift.h>" \ + || SWIFTIMPORT="\"${PRODUCT_MODULE_NAME}-Swift.h\"" + +if [ -z "$PODS_TARGET_SRCROOT" ]; then + PODS_TARGET_SRCROOT=${SOURCE_ROOT} + echo "Building in Xcode instead of CocoaPods. Overriding PODS_TARGET_SRCROOT with SOURCE_ROOT" +fi + +IMPORT_TEXT=\ +"/** +* +* This header was generated by a Run Script build phase. See +* Scripts/generate-swift-import-header.sh for more information +* +*/ + +#ifndef GeneratedSwiftImport_h +#define GeneratedSwiftImport_h + +#import ${SWIFTIMPORT} + +#endif /* GeneratedSwiftImport_h */ +" +echo "$IMPORT_TEXT" > ${PODS_TARGET_SRCROOT}/Source/GeneratedSwiftImport.h diff --git a/Source/UZKArchive.h b/Source/UZKArchive.h index 81c4619..657f8f0 100644 --- a/Source/UZKArchive.h +++ b/Source/UZKArchive.h @@ -9,6 +9,8 @@ #import "UZKFileInfo.h" +@class ZipFileProperties; + /** * Defines the various error codes that the listing and extraction methods return. * These are returned in NSError's [code]([NSError code]) field. @@ -406,7 +408,22 @@ extern NSString *UZKErrorDomain; error:(NSError **)error; /** - * Writes the data to the zip file, overwriting it if a file of that name already exists in the archive + * Writes the data to the zip file, overwriting it if a file of that name already exists + * in the archive. Supports NSProgress for progress reporting, which DOES NOT allow cancellation + * in the middle of writing + * + * @param data Data to write into the archive + * @param props Specifies the properties of the file being archived + * @param error Contains an NSError object when there was an error writing to the archive + * + * @return YES if successful, NO on error + */ +- (BOOL)writeData:(NSData *)data + props:(ZipFileProperties *)props + error:(NSError **)error; + +/** + * DEPRECATED: Writes the data to the zip file, overwriting it if a file of that name already exists in the archive * * @param data Data to write into the archive * @param filePath The full path to the target file in the archive @@ -418,10 +435,11 @@ extern NSString *UZKErrorDomain; - (BOOL)writeData:(NSData *)data filePath:(NSString *)filePath fileDate:(nullable NSDate *)fileDate - error:(NSError **)error; + error:(NSError **)error +__deprecated_msg("Use -writeData:props:error: instead"); /** - * Writes the data to the zip file, overwriting it if a file of that name already exists in the archive + * DEPRECATED: Writes the data to the zip file, overwriting it if a file of that name already exists in the archive * * @param data Data to write into the archive * @param filePath The full path to the target file in the archive @@ -437,10 +455,11 @@ extern NSString *UZKErrorDomain; fileDate:(nullable NSDate *)fileDate compressionMethod:(UZKCompressionMethod)method password:(nullable NSString *)password - error:(NSError **)error; + error:(NSError **)error +__deprecated_msg("Use -writeData:props:error: instead"); /** - * Writes the data to the zip file, overwriting only if specified with the overwrite flag. Overwriting + * DEPRECATED: Writes the data to the zip file, overwriting only if specified with the overwrite flag. Overwriting * presents a tradeoff: the whole archive needs to be copied (minus the file to be overwritten) before * the write begins. For a large archive, this can be slow. On the other hand, when not overwriting, * the size of the archive will grow each time the file is written. @@ -463,10 +482,11 @@ compressionMethod:(UZKCompressionMethod)method compressionMethod:(UZKCompressionMethod)method password:(nullable NSString *)password overwrite:(BOOL)overwrite - error:(NSError **)error; + error:(NSError **)error +__deprecated_msg("Use -writeData:props:error: instead"); /** - * Writes the data to the zip file, overwriting only if specified with the overwrite flag. Overwriting + * DEPRECATED: Writes the data to the zip file, overwriting only if specified with the overwrite flag. Overwriting * presents a tradeoff: the whole archive needs to be copied (minus the file to be overwritten) before * the write begins. For a large archive, this can be slow. On the other hand, when not overwriting, * the size of the archive will grow each time the file is written. @@ -491,7 +511,8 @@ compressionMethod:(UZKCompressionMethod)method compressionMethod:(UZKCompressionMethod)method password:(nullable NSString *)password overwrite:(BOOL)overwrite - error:(NSError **)error; + error:(NSError **)error +__deprecated_msg("Use -writeData:props:error: instead"); /** * Writes data to the zip file in pieces, allowing you to stream the write, so the entire contents @@ -511,12 +532,34 @@ compressionMethod:(UZKCompressionMethod)method * * @return YES if successful, NO on error */ -- (BOOL)writeIntoBuffer:(NSString *)filePath +- (BOOL)writeIntoBufferAtPath:(NSString *)filePath + error:(NSError **)error + block:(BOOL(^)(BOOL(^writeData)(const void *bytes, unsigned int length), NSError **actionError))action; + +/** + * Writes data to the zip file in pieces, allowing you to stream the write, so the entire contents + * don't need to reside in memory at once. It overwrites an existing file with the same name. + * + * @param props Specifies the properties of the file being archived + * @param error Contains an NSError object when there was an error writing to the archive + * @param action Contains your code to loop through the source bytes and write them to the + * archive. Each time a chunk of data is ready to be written, call writeData, + * passing in a pointer to the bytes and their length. Return YES if successful, + * or NO on error (in which case, you should assign the actionError parameter + * + * - *writeData* Call this block to write some bytes into the archive. It returns NO if the + * write failed. If this happens, you should return from the action block, and + * handle the NSError returned into the error reference + * - *actionError* Assign to an NSError instance before returning NO + * + * @return YES if successful, NO on error + */ +- (BOOL)writeIntoBuffer:(ZipFileProperties *)props error:(NSError **)error block:(BOOL(^)(BOOL(^writeData)(const void *bytes, unsigned int length), NSError **actionError))action; /** - * Writes data to the zip file in pieces, allowing you to stream the write, so the entire contents + * DEPRECATED: Writes data to the zip file in pieces, allowing you to stream the write, so the entire contents * don't need to reside in memory at once. It overwrites an existing file with the same name. * * @param filePath The full path to the target file in the archive @@ -537,10 +580,11 @@ compressionMethod:(UZKCompressionMethod)method - (BOOL)writeIntoBuffer:(NSString *)filePath fileDate:(nullable NSDate *)fileDate error:(NSError **)error - block:(BOOL(^)(BOOL(^writeData)(const void *bytes, unsigned int length), NSError **actionError))action; + block:(BOOL(^)(BOOL(^writeData)(const void *bytes, unsigned int length), NSError **actionError))action +__deprecated_msg("Use -writeIntoBuffer:props:error:block: instead"); /** - * Writes data to the zip file in pieces, allowing you to stream the write, so the entire contents + * DEPRECATED: Writes data to the zip file in pieces, allowing you to stream the write, so the entire contents * don't need to reside in memory at once. It overwrites an existing file with the same name. * * @param filePath The full path to the target file in the archive @@ -563,10 +607,11 @@ compressionMethod:(UZKCompressionMethod)method fileDate:(nullable NSDate *)fileDate compressionMethod:(UZKCompressionMethod)method error:(NSError **)error - block:(BOOL(^)(BOOL(^writeData)(const void *bytes, unsigned int length), NSError **actionError))action; + block:(BOOL(^)(BOOL(^writeData)(const void *bytes, unsigned int length), NSError **actionError))action +__deprecated_msg("Use -writeIntoBuffer:props:error:block: instead"); /** - * Writes data to the zip file in pieces, allowing you to stream the write, so the entire contents + * DEPRECATED: Writes data to the zip file in pieces, allowing you to stream the write, so the entire contents * don't need to reside in memory at once. It overwrites an existing file with the same name, only if * specified with the overwrite flag. Overwriting presents a tradeoff: the whole archive needs to be * copied (minus the file to be overwritten) before the write begins. For a large archive, this can @@ -597,10 +642,11 @@ compressionMethod:(UZKCompressionMethod)method compressionMethod:(UZKCompressionMethod)method overwrite:(BOOL)overwrite error:(NSError **)error - block:(BOOL(^)(BOOL(^writeData)(const void *bytes, unsigned int length), NSError **actionError))action; + block:(BOOL(^)(BOOL(^writeData)(const void *bytes, unsigned int length), NSError **actionError))action +__deprecated_msg("Use -writeIntoBuffer:props:error:block: instead"); /** - * Writes data to the zip file in pieces, allowing you to stream the write, so the entire contents + * DEPRECATED: Writes data to the zip file in pieces, allowing you to stream the write, so the entire contents * don't need to reside in memory at once. It overwrites an existing file with the same name, only if * specified with the overwrite flag. Overwriting presents a tradeoff: the whole archive needs to be * copied (minus the file to be overwritten) before the write begins. For a large archive, this can @@ -634,10 +680,11 @@ compressionMethod:(UZKCompressionMethod)method overwrite:(BOOL)overwrite CRC:(unsigned long)preCRC error:(NSError **)error - block:(BOOL(^)(BOOL(^writeData)(const void *bytes, unsigned int length), NSError **actionError))action; + block:(BOOL(^)(BOOL(^writeData)(const void *bytes, unsigned int length), NSError **actionError))action +__deprecated_msg("Use -writeIntoBuffer:props:error:block: instead"); /** - * Writes data to the zip file in pieces, allowing you to stream the write, so the entire contents + * DEPRECATED: Writes data to the zip file in pieces, allowing you to stream the write, so the entire contents * don't need to reside in memory at once. It overwrites an existing file with the same name, only if * specified with the overwrite flag. Overwriting presents a tradeoff: the whole archive needs to be * copied (minus the file to be overwritten) before the write begins. For a large archive, this can @@ -673,11 +720,12 @@ compressionMethod:(UZKCompressionMethod)method CRC:(unsigned long)preCRC password:(nullable NSString *)password error:(NSError **)error - block:(BOOL(^)(BOOL(^writeData)(const void *bytes, unsigned int length), NSError **actionError))action; + block:(BOOL(^)(BOOL(^writeData)(const void *bytes, unsigned int length), NSError **actionError))action +__deprecated_msg("Use -writeIntoBuffer:props:error:block: instead"); /** - * Writes data to the zip file in pieces, allowing you to stream the write, so the entire contents + * DEPRECATED: Writes data to the zip file in pieces, allowing you to stream the write, so the entire contents * don't need to reside in memory at once. It overwrites an existing file with the same name, only if * specified with the overwrite flag. Overwriting presents a tradeoff: the whole archive needs to be * copied (minus the file to be overwritten) before the write begins. For a large archive, this can @@ -715,7 +763,8 @@ compressionMethod:(UZKCompressionMethod)method CRC:(unsigned long)preCRC password:(nullable NSString *)password error:(NSError **)error - block:(BOOL(^)(BOOL(^writeData)(const void *bytes, unsigned int length), NSError **actionError))action; + block:(BOOL(^)(BOOL(^writeData)(const void *bytes, unsigned int length), NSError **actionError))action +__deprecated_msg("Use -writeIntoBuffer:props:error:block: instead"); /** * Removes the given file from the archive diff --git a/Source/UZKArchive.m b/Source/UZKArchive.m index 44208c4..cc687ab 100644 --- a/Source/UZKArchive.m +++ b/Source/UZKArchive.m @@ -6,9 +6,10 @@ #import "UZKArchive.h" +#import "GeneratedSwiftImport.h" + #import "zip.h" -#import "UZKFileInfo.h" #import "UZKFileInfo_Private.h" #import "UnzipKitMacros.h" #import "NSURL+UnzipKitExtensions.h" @@ -958,7 +959,7 @@ - (BOOL)checkDataIntegrityOfFile:(NSString *)filePath if (extractedCRC != fileInfo.CRC) { UZKLogError("CRC mismatch in '%{public}@': expected %010lu, found %010lu", - fileInfo.filename, (unsigned long)fileInfo.CRC, extractedCRC) + fileInfo.filename, (uLong)fileInfo.CRC, extractedCRC) dataIsValid = NO; } @@ -984,11 +985,7 @@ - (BOOL)writeData:(NSData *)data error:(NSError * __autoreleasing*)error { return [self writeData:data - filePath:filePath - fileDate:nil - compressionMethod:UZKCompressionMethodDefault - password:nil - overwrite:YES + props:[[ZipFileProperties alloc] init:filePath] error:error]; } @@ -1013,12 +1010,13 @@ - (BOOL)writeData:(NSData *)data password:(NSString *)password error:(NSError * __autoreleasing*)error { + ZipFileProperties *props = [[ZipFileProperties alloc] init:filePath]; + props.timestamp = fileDate; + props.compressionMethod = method; + props.password = password; + return [self writeData:data - filePath:filePath - fileDate:fileDate - compressionMethod:method - password:password - overwrite:YES + props:props error:error]; } @@ -1030,13 +1028,14 @@ - (BOOL)writeData:(NSData *)data overwrite:(BOOL)overwrite error:(NSError * __autoreleasing*)error { + ZipFileProperties *props = [[ZipFileProperties alloc] init:filePath]; + props.timestamp = fileDate; + props.compressionMethod = method; + props.password = password; + props.overwriteIfInArchive = overwrite; + return [self writeData:data - filePath:filePath - fileDate:fileDate - posixPermissions:0 - compressionMethod:method - password:password - overwrite:overwrite + props:props error:error]; } @@ -1048,13 +1047,27 @@ - (BOOL)writeData:(NSData *)data password:(NSString *)password overwrite:(BOOL)overwrite error:(NSError * __autoreleasing*)error +{ + ZipFileProperties *props = [[ZipFileProperties alloc] init:filePath]; + props.timestamp = fileDate; + props.permissions = permissions; + props.compressionMethod = method; + props.password = password; + props.overwriteIfInArchive = overwrite; + + return [self writeData:data + props:props + error:error]; +} + +- (BOOL)writeData:(NSData *)data + props:(ZipFileProperties *)props + error:(NSError * __autoreleasing*)error { UZKCreateActivity("Writing Data"); - UZKLogInfo("Writing data to archive. filePath: %{public}@, fileDate: %{time_t}ld, compressionMethod: %ld, password: %{public}@, " - "overwrite: %{public}@, error pointer specified: %{public}@", - filePath, lrint(fileDate.timeIntervalSince1970), (long)method, password != nil ? @"" : @"(null)", overwrite ? @"YES" : @"NO", - error ? @"YES" : @"NO"); + UZKLogInfo("Writing data to archive. Properties: %{public}@, error pointer specified: %{public}@", + props, error ? @"YES" : @"NO"); const NSUInteger bufferSize = 4096; //Arbitrary const void *bytes = data.bytes; @@ -1064,6 +1077,7 @@ - (BOOL)writeData:(NSData *)data __weak UZKArchive *welf = self; uLong calculatedCRC = crc32(0, data.bytes, (uInt)data.length); + props.crc = calculatedCRC; UZKLogDebug("Calculated CRC: %010lu", calculatedCRC); BOOL success = [self performWriteAction:^int(uLong *crc, NSError * __autoreleasing*innerError) { @@ -1091,29 +1105,17 @@ - (BOOL)writeData:(NSData *)data return ZIP_OK; } - filePath:filePath - fileDate:fileDate - posixPermissions:permissions - compressionMethod:method - password:password - overwrite:overwrite - CRC:calculatedCRC + props:props error:error]; return success; } -- (BOOL)writeIntoBuffer:(NSString *)filePath - error:(NSError * __autoreleasing*)error - block:(BOOL(^)(BOOL(^writeData)(const void *bytes, unsigned int length), NSError * __autoreleasing*actionError))action +- (BOOL)writeIntoBufferAtPath:(NSString *)filePath + error:(NSError * __autoreleasing*)error + block:(BOOL(^)(BOOL(^writeData)(const void *bytes, unsigned int length), NSError * __autoreleasing*actionError))action { - return [self writeIntoBuffer:filePath - fileDate:nil - posixPermissions:0 - compressionMethod:UZKCompressionMethodDefault - overwrite:YES - CRC:0 - password:nil + return [self writeIntoBuffer:[[ZipFileProperties alloc] init:filePath] error:error block:action]; } @@ -1123,15 +1125,10 @@ - (BOOL)writeIntoBuffer:(NSString *)filePath error:(NSError * __autoreleasing*)error block:(BOOL(^)(BOOL(^writeData)(const void *bytes, unsigned int length), NSError * __autoreleasing*actionError))action { - return [self writeIntoBuffer:filePath - fileDate:fileDate - posixPermissions:0 - compressionMethod:UZKCompressionMethodDefault - overwrite:YES - CRC:0 - password:nil - error:error - block:action]; + ZipFileProperties *props = [[ZipFileProperties alloc] init:filePath]; + props.timestamp = fileDate; + + return [self writeIntoBuffer:props error:error block:action]; } - (BOOL)writeIntoBuffer:(NSString *)filePath @@ -1140,15 +1137,11 @@ - (BOOL)writeIntoBuffer:(NSString *)filePath error:(NSError * __autoreleasing*)error block:(BOOL(^)(BOOL(^writeData)(const void *bytes, unsigned int length), NSError * __autoreleasing*actionError))action { - return [self writeIntoBuffer:filePath - fileDate:fileDate - posixPermissions:0 - compressionMethod:method - overwrite:YES - CRC:0 - password:nil - error:error - block:action]; + ZipFileProperties *props = [[ZipFileProperties alloc] init:filePath]; + props.timestamp = fileDate; + props.compressionMethod = method; + + return [self writeIntoBuffer:props error:error block:action]; } - (BOOL)writeIntoBuffer:(NSString *)filePath @@ -1158,15 +1151,12 @@ - (BOOL)writeIntoBuffer:(NSString *)filePath error:(NSError * __autoreleasing*)error block:(BOOL(^)(BOOL(^writeData)(const void *bytes, unsigned int length), NSError * __autoreleasing*actionError))action { - return [self writeIntoBuffer:filePath - fileDate:fileDate - posixPermissions:0 - compressionMethod:method - overwrite:overwrite - CRC:0 - password:nil - error:error - block:action]; + ZipFileProperties *props = [[ZipFileProperties alloc] init:filePath]; + props.timestamp = fileDate; + props.compressionMethod = method; + props.overwriteIfInArchive = overwrite; + + return [self writeIntoBuffer:props error:error block:action]; } - (BOOL)writeIntoBuffer:(NSString *)filePath @@ -1177,15 +1167,13 @@ - (BOOL)writeIntoBuffer:(NSString *)filePath error:(NSError *__autoreleasing *)error block:(BOOL (^)(BOOL (^)(const void *, unsigned int), NSError *__autoreleasing *))action { - return [self writeIntoBuffer:filePath - fileDate:fileDate - posixPermissions:0 - compressionMethod:method - overwrite:overwrite - CRC:preCRC - password:nil - error:error - block:action]; + ZipFileProperties *props = [[ZipFileProperties alloc] init:filePath]; + props.timestamp = fileDate; + props.compressionMethod = method; + props.overwriteIfInArchive = overwrite; + props.crc = preCRC; + + return [self writeIntoBuffer:props error:error block:action]; } - (BOOL)writeIntoBuffer:(NSString *)filePath @@ -1197,15 +1185,14 @@ - (BOOL)writeIntoBuffer:(NSString *)filePath error:(NSError *__autoreleasing *)error block:(BOOL (^)(BOOL (^)(const void *, unsigned int), NSError *__autoreleasing *))action { - return [self writeIntoBuffer:filePath - fileDate:fileDate - posixPermissions:0 - compressionMethod:method - overwrite:overwrite - CRC:preCRC - password:password - error:error - block:action]; + ZipFileProperties *props = [[ZipFileProperties alloc] init:filePath]; + props.timestamp = fileDate; + props.compressionMethod = method; + props.overwriteIfInArchive = overwrite; + props.crc = preCRC; + props.password = password; + + return [self writeIntoBuffer:props error:error block:action]; } - (BOOL)writeIntoBuffer:(NSString *)filePath @@ -1217,15 +1204,30 @@ - (BOOL)writeIntoBuffer:(NSString *)filePath password:(NSString *)password error:(NSError *__autoreleasing *)error block:(BOOL (^)(BOOL (^)(const void *, unsigned int), NSError *__autoreleasing *))action +{ + ZipFileProperties *props = [[ZipFileProperties alloc] init:filePath]; + props.timestamp = fileDate; + props.permissions = permissions; + props.compressionMethod = method; + props.overwriteIfInArchive = overwrite; + props.crc = preCRC; + props.password = password; + + return [self writeIntoBuffer:props error:error block:action]; +} + +- (BOOL)writeIntoBuffer:(ZipFileProperties *)props + error:(NSError *__autoreleasing *)error + block:(BOOL (^)(BOOL (^)(const void *, unsigned int), NSError *__autoreleasing *))action { UZKCreateActivity("Writing Into Buffer"); - UZKLogInfo("Writing data into buffer. filePath: %{public}@, fileDate: %{time_t}ld, compressionMethod: %ld, " - "overwrite: %{public}@, CRC: %010lu, password: %{public}@, error pointer specified: %{public}@", - filePath, lrint(fileDate.timeIntervalSince1970), (long)method, overwrite ? @"YES" : @"NO", preCRC, - password != nil ? @"" : @"(null)", error ? @"YES" : @"NO"); + UZKLogInfo("Writing data into buffer. Properties: %{public}@, error pointer specified: %{public}@", + props, error ? @"YES" : @"NO"); - NSAssert(preCRC != 0 || ([password length] == 0 && [self.password length] == 0), + uLong preCRC = props.crc; + + NSAssert(preCRC != 0 || ([props.password length] == 0 && [self.password length] == 0), @"Cannot provide a password when writing into a buffer, " "unless a CRC is provided up front for inclusion in the header", nil); @@ -1265,18 +1267,12 @@ - (BOOL)writeIntoBuffer:(NSString *)filePath preCRCStr, calculatedCRCStr]; UZKLogError("UZKErrorCodePreCRCMismatch: %{public}@", detail); return [welf assignError:innerError code:UZKErrorCodePreCRCMismatch - detail:detail]; + detail:detail]; } return result; } - filePath:filePath - fileDate:fileDate - posixPermissions:permissions - compressionMethod:method - password:password - overwrite:overwrite - CRC:preCRC + props:props error:error]; return success; @@ -1609,7 +1605,7 @@ - (BOOL)deleteFile:(NSString *)filePath error:(NSError * __autoreleasing*)error // Close destination archive UZKLogDebug("Closing file in destination archive"); - err = zipCloseFileInZipRaw64(dest_zip, unzipInfo.uncompressed_size, unzipInfo.crc); + err = zipCloseFileInZipRaw64(dest_zip, unzipInfo.uncompressed_size, (uLong)unzipInfo.crc); if (err != UNZ_OK) { NSString *detail = [NSString localizedStringWithFormat:NSLocalizedStringFromTableInBundle(@"Error closing %@ in destination zip while deleting %@ (%d)", @"UnzipKit", _resources, @"Detailed error string"), currentFileName, filePath, err]; @@ -1813,19 +1809,15 @@ - (BOOL)performActionWithArchiveOpen:(void(^)(NSError * __autoreleasing*innerErr } - (BOOL)performWriteAction:(int(^)(uLong *crc, NSError * __autoreleasing*innerError))write - filePath:(NSString *)filePath - fileDate:(NSDate *)fileDate - posixPermissions:(short)permissions - compressionMethod:(UZKCompressionMethod)method - password:(NSString *)password - overwrite:(BOOL)overwrite - CRC:(uLong)crc + props:(ZipFileProperties *)props error:(NSError * __autoreleasing*)error { UZKCreateActivity("Performing Write"); - if (overwrite) { - UZKLogInfo("Overwriting %{public}@ if it already exists. Will look for existing file to delete", filePath); + NSString *password = props.password; + + if (props.overwriteIfInArchive) { + UZKLogInfo("Overwriting %{public}@ if it already exists. Will look for existing file to delete", props.fullFilePath); NSError *listFilesError = nil; NSArray *existingFiles; @@ -1835,11 +1827,11 @@ - (BOOL)performWriteAction:(int(^)(uLong *crc, NSError * __autoreleasing*innerEr existingFiles = [self listFileInfo:&listFilesError]; } - if (existingFiles) { - UZKLogDebug("Existing files found. Looking for matches to filePath %{public}@", filePath); + if ([existingFiles count]) { + UZKLogDebug("Existing files found. Looking for matches to filePath %{public}@", props.fullFilePath); NSIndexSet *matchingFiles = [existingFiles indexesOfObjectsPassingTest: ^BOOL(UZKFileInfo *info, NSUInteger idx, BOOL *stop) { - if ([info.filename isEqualToString:filePath]) { + if ([info.filename isEqualToString:props.fullFilePath]) { *stop = YES; return YES; } @@ -1847,16 +1839,16 @@ - (BOOL)performWriteAction:(int(^)(uLong *crc, NSError * __autoreleasing*innerEr return NO; }]; - if (matchingFiles.count > 0 && ![self deleteFile:filePath error:error]) { - UZKLogError("Failed to delete %{public}@ before writing new data for it", filePath); + if (matchingFiles.count > 0 && ![self deleteFile:props.fullFilePath error:error]) { + UZKLogError("Failed to delete %{public}@ before writing new data for it", props.fullFilePath); return NO; } } } if (!password) { - UZKLogDebug("No password specified for file. Using archive's password: %{public}@", password != nil ? @"" : @"(null)"); password = self.password; + UZKLogDebug("No password specified for file. Using archive's password: %{public}@", password != nil ? @"" : @"(null)"); } __weak UZKArchive *welf = self; @@ -1864,9 +1856,9 @@ - (BOOL)performWriteAction:(int(^)(uLong *crc, NSError * __autoreleasing*innerEr BOOL success = [self performActionWithArchiveOpen:^(NSError * __autoreleasing*innerError) { UZKCreateActivity("Performing Write Action"); - UZKLogDebug("Making zip_fileinfo struct for date %{time_t}ld", lrint(fileDate.timeIntervalSince1970)); - zip_fileinfo zi = [UZKArchive zipFileInfoForDate:fileDate - posixPermissions:permissions]; + UZKLogDebug("Making zip_fileinfo struct for date %{time_t}ld", lrint(props.timestamp.timeIntervalSince1970)); + zip_fileinfo zi = [UZKArchive zipFileInfoForDate:props.timestamp + posixPermissions:props.permissions]; const char *passwordStr = NULL; @@ -1877,19 +1869,19 @@ - (BOOL)performWriteAction:(int(^)(uLong *crc, NSError * __autoreleasing*innerEr UZKLogDebug("Opening new file..."); int err = zipOpenNewFileInZip3(welf.zipFile, - filePath.UTF8String, + props.fullFilePath.UTF8String, &zi, NULL, 0, NULL, 0, NULL, - (method != UZKCompressionMethodNone) ? Z_DEFLATED : 0, - method, + (props.compressionMethod != UZKCompressionMethodNone) ? Z_DEFLATED : 0, + props.compressionMethod, 0, -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY, passwordStr, - crc); + props.crc); if (err != ZIP_OK) { NSString *detail = [NSString localizedStringWithFormat:NSLocalizedStringFromTableInBundle(@"Error opening file '%@' for write (%d)", @"UnzipKit", _resources, @"Detailed error string"), - filePath, err]; + props.fullFilePath, err]; UZKLogError("UZKErrorCodeFileOpenForWrite: %{public}@", detail); [welf assignError:innerError code:UZKErrorCodeFileOpenForWrite detail:detail]; @@ -1901,7 +1893,7 @@ - (BOOL)performWriteAction:(int(^)(uLong *crc, NSError * __autoreleasing*innerEr err = write(&outCRC, innerError); if (err < 0) { NSString *detail = [NSString localizedStringWithFormat:NSLocalizedStringFromTableInBundle(@"Error writing to file '%@' (%d)", @"UnzipKit", _resources, @"Detailed error string"), - filePath, err]; + props.fullFilePath, err]; UZKLogError("UZKErrorCodeFileOpenForWrite: %{public}@", detail); [welf assignError:innerError code:UZKErrorCodeFileWrite detail:detail]; @@ -1912,7 +1904,7 @@ - (BOOL)performWriteAction:(int(^)(uLong *crc, NSError * __autoreleasing*innerEr err = zipCloseFileInZipRaw(self.zipFile, 0, outCRC); if (err != ZIP_OK) { NSString *detail = [NSString localizedStringWithFormat:NSLocalizedStringFromTableInBundle(@"Error closing file '%@' for write (%d)", @"UnzipKit", _resources, @"Detailed error string"), - filePath, err]; + props.fullFilePath, err]; UZKLogError("UZKErrorCodeFileOpenForWrite: %{public}@", detail); [welf assignError:innerError code:UZKErrorCodeFileWrite detail:detail]; diff --git a/Source/UnzipKit.h b/Source/UnzipKit.h index d390a9d..8864a29 100644 --- a/Source/UnzipKit.h +++ b/Source/UnzipKit.h @@ -15,5 +15,5 @@ FOUNDATION_EXPORT double UnzipKitVersionNumber; FOUNDATION_EXPORT const unsigned char UnzipKitVersionString[]; -#import "UZKArchive.h" -#import "UZKFileInfo.h" +#import +#import diff --git a/Source/ZipFileProperties.swift b/Source/ZipFileProperties.swift new file mode 100644 index 0000000..e5244e3 --- /dev/null +++ b/Source/ZipFileProperties.swift @@ -0,0 +1,75 @@ +// +// ZipFileArgs.swift +// UnzipKit +// +// Created by Dov Frankel on 8/1/19. +// Copyright © 2019 Abbey Code. All rights reserved. +// + +import Foundation + +// Not a struct because of Objective-C compatibility +@objc public class ZipFileProperties: NSObject { + + + // MARK: - Required + + /// The full path and filename of the file to be written + @objc public var fullFilePath: String + + + // MARK: - Optional/Defaulted + + /// The UZKCompressionMethod to use (Default, None, Fastest, Best) + @objc public var compressionMethod = UZKCompressionMethod.default + + /** + The CRC-32 checksum for the data being written. Only required + if encrypting the file, otherwise it will be calculated automatically + */ + @objc public var crc: UInt = 0 + + /** + If true, and the file exists, delete it before writing. If false, append + the data into the archive without removing it first (legacy Objective-Zip + behavior). Turn this off to gain speed at the expense of creating bloated + archives. Defaults to true + */ + @objc public var overwriteIfInArchive = true + + /// Override the password associated with the rest of the archive (not a recommended practice) + @objc public var password: String? = nil + + /** + The desired POSIX permissions of the archived file (e.g. 0o644 in Swift + or 0644U in Objective-C). Defaults to 644 (Read/Write for owner, + read-only for group and others) + */ + @objc public var permissions: Int16 = 0o644 + + /// The timestamp of the file to be written + @objc public var timestamp: Date? + + + // MARK: - Initializer + + @objc public init(_ fullFilePath: String) { + self.fullFilePath = fullFilePath + } + + + // MARK: - Overrides + + public override var description: String { + let crcStr = String(crc, radix: 16).uppercased() + let password = self.password != nil ? "" : "none" + let permissionStr = String(permissions, radix: 8).uppercased() + + return """ + { fullFilePath: \(fullFilePath), compressionMethod: \(compressionMethod), crc: \(crcStr), + overwriteIfInArchive: \(overwriteIfInArchive), password: \(password), + permissions: \(permissionStr), timestamp: \(timestamp?.description ?? "none") } + """.replacingOccurrences(of: "\n", with: " "); + } + +} diff --git a/Tests/CheckDataTests.m b/Tests/CheckDataTests.m index fe1f748..fceeb75 100644 --- a/Tests/CheckDataTests.m +++ b/Tests/CheckDataTests.m @@ -7,7 +7,7 @@ // #import "UZKArchiveTestCase.h" -#import "UnzipKit.h" +@import UnzipKit; @interface CheckDataTests : UZKArchiveTestCase @end diff --git a/Tests/CommentsTests.m b/Tests/CommentsTests.m index 3a26b7e..90a7cd4 100644 --- a/Tests/CommentsTests.m +++ b/Tests/CommentsTests.m @@ -7,7 +7,7 @@ // #import "UZKArchiveTestCase.h" -#import "UnzipKit.h" +@import UnzipKit; @interface CommentsTests : UZKArchiveTestCase @end diff --git a/Tests/DeleteFileTests.m b/Tests/DeleteFileTests.m index 71db954..02715a8 100644 --- a/Tests/DeleteFileTests.m +++ b/Tests/DeleteFileTests.m @@ -7,7 +7,7 @@ // #import "UZKArchiveTestCase.h" -#import "UnzipKit.h" +@import UnzipKit; @interface DeleteFileTests : UZKArchiveTestCase @end diff --git a/Tests/ErrorHandlingTests.m b/Tests/ErrorHandlingTests.m index 8f4d783..eb48188 100644 --- a/Tests/ErrorHandlingTests.m +++ b/Tests/ErrorHandlingTests.m @@ -7,7 +7,7 @@ // #import "UZKArchiveTestCase.h" -#import "UnzipKit.h" +@import UnzipKit; @interface ErrorHandlingTests : UZKArchiveTestCase @end diff --git a/Tests/ExtractBufferedDataTests.m b/Tests/ExtractBufferedDataTests.m index d341e01..ff5d045 100644 --- a/Tests/ExtractBufferedDataTests.m +++ b/Tests/ExtractBufferedDataTests.m @@ -6,7 +6,7 @@ // Copyright (c) 2015 Abbey Code. All rights reserved. // -#import "UnzipKit.h" +@import UnzipKit; #import "UZKArchiveTestCase.h" #import "UnzipKitMacros.h" diff --git a/Tests/ExtractDataTests.m b/Tests/ExtractDataTests.m index 3c71d27..3b2a193 100644 --- a/Tests/ExtractDataTests.m +++ b/Tests/ExtractDataTests.m @@ -7,7 +7,7 @@ // #import "UZKArchiveTestCase.h" -#import "UnzipKit.h" +@import UnzipKit; #import "UnzipKitMacros.h" @interface ExtractDataTests : UZKArchiveTestCase diff --git a/Tests/FileDescriptorUsageTests.m b/Tests/FileDescriptorUsageTests.m index a824da7..d750105 100644 --- a/Tests/FileDescriptorUsageTests.m +++ b/Tests/FileDescriptorUsageTests.m @@ -7,7 +7,7 @@ // #import "UZKArchiveTestCase.h" -#import "UnzipKit.h" +@import UnzipKit; @interface FileDescriptorUsageTests : UZKArchiveTestCase @end @@ -143,6 +143,72 @@ - (void)testFileDescriptorUsage_WriteIntoArchive NSString *fileContents = [NSString stringWithFormat:@"This is a string %d", x]; NSData *newFileData = [fileContents dataUsingEncoding:NSUTF8StringEncoding]; NSString *fileName = fileList.lastObject; + BOOL writeResult = [archive writeData:newFileData + filePath:fileName + error:&writeError]; + XCTAssertTrue(writeResult, @"Failed to write to archive (attempt %d)", x); + XCTAssertNil(writeError, @"Error writing to archive (attempt %d)", x); + + NSError *extractError = nil; + NSData *extractedData = [archive extractDataFromFile:fileName + error:&extractError]; + XCTAssertEqualObjects(extractedData, newFileData, @"Incorrect data written to file (attempt %d)", x); + XCTAssertNil(extractError, @"Error extracting from archive (attempt %d)", x); + } + } + + NSInteger finalFileCount = [self numberOfOpenFileHandles]; + + XCTAssertEqualWithAccuracy(initialFileCount, finalFileCount, 5, @"File descriptors were left open"); +} + + +- (void)testFileDescriptorUsage_WriteIntoArchive_deprecatedOverload +{ + NSInteger initialFileCount = [self numberOfOpenFileHandles]; + + NSURL *testArchiveOriginalURL = [self largeArchive]; + NSString *testArchiveName = testArchiveOriginalURL.lastPathComponent; + NSFileManager *fm = [NSFileManager defaultManager]; + + for (NSInteger i = 0; i < 100; i++) { + // Keep this test from stalling out the build + printf("testFileDescriptorUsage_WriteIntoArchive: Iteration %ld/100\n", (long)i); + + NSString *tempDir = [self randomDirectoryName]; + NSURL *tempDirURL = [self.tempDirectory URLByAppendingPathComponent:tempDir]; + NSURL *testArchiveCopyURL = [tempDirURL URLByAppendingPathComponent:testArchiveName]; + + NSError *error = nil; + [fm createDirectoryAtURL:tempDirURL + withIntermediateDirectories:YES + attributes:nil + error:&error]; + + XCTAssertNil(error, @"Error creating temp directory: %@", tempDirURL); + + [fm copyItemAtURL:testArchiveOriginalURL toURL:testArchiveCopyURL error:&error]; + XCTAssertNil(error, @"Error copying test archive \n from: %@ \n\n to: %@", testArchiveOriginalURL, testArchiveCopyURL); + + UZKArchive *archive = [[UZKArchive alloc] initWithURL:testArchiveCopyURL error:nil]; + + NSArray *fileList = [archive listFilenames:&error]; + XCTAssertNotNil(fileList, @"No filenames listed"); + + for (NSString *fileName in fileList) { + NSData *fileData = [archive extractDataFromFile:fileName + error:&error]; + XCTAssertNotNil(fileData, @"No data extracted"); + XCTAssertNil(error, @"Error extracting data"); + } + + for (int x = 0; x < 50; x++) { + NSError *writeError = nil; + NSString *fileContents = [NSString stringWithFormat:@"This is a string %d", x]; + NSData *newFileData = [fileContents dataUsingEncoding:NSUTF8StringEncoding]; + NSString *fileName = fileList.lastObject; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" BOOL writeResult = [archive writeData:newFileData filePath:fileName fileDate:[NSDate date] @@ -150,6 +216,7 @@ - (void)testFileDescriptorUsage_WriteIntoArchive password:nil overwrite:YES error:&writeError]; +#pragma clang diagnostic pop XCTAssertTrue(writeResult, @"Failed to write to archive (attempt %d)", x); XCTAssertNil(writeError, @"Error writing to archive (attempt %d)", x); diff --git a/Tests/ListFileInfoTests.m b/Tests/ListFileInfoTests.m index dd2f830..4d66005 100644 --- a/Tests/ListFileInfoTests.m +++ b/Tests/ListFileInfoTests.m @@ -7,7 +7,7 @@ // #import "UZKArchiveTestCase.h" -#import "UnzipKit.h" +@import UnzipKit; @interface ListFileInfoTests : UZKArchiveTestCase @end diff --git a/Tests/ListFilenamesTests.m b/Tests/ListFilenamesTests.m index 77eca2b..42b2322 100644 --- a/Tests/ListFilenamesTests.m +++ b/Tests/ListFilenamesTests.m @@ -7,7 +7,7 @@ // #import "UZKArchiveTestCase.h" -#import "UnzipKit.h" +@import UnzipKit; @interface ListFilenamesTests : UZKArchiveTestCase @end diff --git a/Tests/ModesTests.m b/Tests/ModesTests.m index 9d739bc..06287c1 100644 --- a/Tests/ModesTests.m +++ b/Tests/ModesTests.m @@ -7,7 +7,7 @@ // #import "UZKArchiveTestCase.h" -#import "UnzipKit.h" +@import UnzipKit; @interface ModesTests : UZKArchiveTestCase @@ -72,9 +72,9 @@ - (void)testModes_ReadWhileWriting UZKArchive *archive = [[UZKArchive alloc] initWithURL:testArchiveURL error:nil]; NSError *writeError = nil; - [archive writeIntoBuffer:@"newFile.zip" - error:&writeError - block: + [archive writeIntoBufferAtPath:@"newFile.zip" + error:&writeError + block: ^BOOL(BOOL(^writeData)(const void *bytes, unsigned int length), NSError**(actionError)) { NSError *readError = nil; [archive listFilenames:&readError]; @@ -93,15 +93,15 @@ - (void)testModes_NestedWrites UZKArchive *archive = [[UZKArchive alloc] initWithURL:testArchiveURL error:nil]; NSError *outerWriteError = nil; - [archive writeIntoBuffer:@"newFile.zip" - error:&outerWriteError - block: + [archive writeIntoBufferAtPath:@"newFile.zip" + error:&outerWriteError + block: ^BOOL(BOOL(^writeData)(const void *bytes, unsigned int length), NSError**(actionError)) { NSError *innerWriteError = nil; - [archive writeIntoBuffer:@"newFile.zip" - error:&innerWriteError - block:^BOOL(BOOL(^writeData)(const void *bytes, unsigned int length), NSError**(actionError)) {return YES;}]; - XCTAssertNotNil(innerWriteError, @"Nested write operation succeeded"); + [archive writeIntoBufferAtPath:@"newFile.zip" + error:&innerWriteError + block:^BOOL(BOOL(^writeData)(const void *bytes, unsigned int length), NSError**(actionError)) {return YES;}]; + XCTAssertNotNil(innerWriteError, @"Nested write operation succeeded"); XCTAssertEqual(innerWriteError.code, UZKErrorCodeFileWrite, @"Wrong error code returned"); return YES; diff --git a/Tests/MultithreadingTests.m b/Tests/MultithreadingTests.m index 8e11988..bce0df5 100644 --- a/Tests/MultithreadingTests.m +++ b/Tests/MultithreadingTests.m @@ -7,7 +7,7 @@ // #import "UZKArchiveTestCase.h" -#import "UnzipKit.h" +@import UnzipKit; #import "UnzipKitMacros.h" @interface MultithreadingTests : UZKArchiveTestCase diff --git a/Tests/PasswordProtectionTests.m b/Tests/PasswordProtectionTests.m index e942d98..0221997 100644 --- a/Tests/PasswordProtectionTests.m +++ b/Tests/PasswordProtectionTests.m @@ -7,7 +7,8 @@ // #import "UZKArchiveTestCase.h" -#import "UnzipKit.h" +@import UnzipKit; + @interface PasswordProtectionTests : UZKArchiveTestCase @end @@ -49,6 +50,44 @@ - (void)testIsPasswordProtected_PasswordRequired_LastFileOnly password = @"111111"; } + ZipFileProperties *props = [[ZipFileProperties alloc] init:testFile]; + props.password = password; + BOOL result = [writeArchive writeData:fileData + props:props + error:&writeError]; + + XCTAssertTrue(result, @"Error writing archive data"); + XCTAssertNil(writeError, @"Error writing to file %@: %@", testFile, writeError); + }]; + + UZKArchive *readArchive = [[UZKArchive alloc] initWithURL:testArchiveURL error:nil]; + + XCTAssertTrue(readArchive.isPasswordProtected, @"isPasswordProtected = NO for password-protected archive"); +} + +- (void)testIsPasswordProtected_PasswordRequired_LastFileOnly_deprecatedOverload +{ + NSArray *testFiles = [self.nonZipTestFilePaths.allObjects sortedArrayUsingSelector:@selector(compare:)]; + NSMutableArray *testFileData = [NSMutableArray arrayWithCapacity:testFiles.count]; + + NSURL *testArchiveURL = [self.tempDirectory URLByAppendingPathComponent:@"testIsPasswordProtected_PasswordRequired_LastFileOnly.zip"]; + + UZKArchive *writeArchive = [[UZKArchive alloc] initWithURL:testArchiveURL error:nil]; + + __block NSError *writeError = nil; + + [testFiles enumerateObjectsUsingBlock:^(NSString *testFile, NSUInteger idx, BOOL *stop) { + NSData *fileData = [NSData dataWithContentsOfURL:self.testFileURLs[testFile]]; + [testFileData addObject:fileData]; + + NSString *password = nil; + + if (idx == testFiles.count - 1) { + password = @"111111"; + } + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" BOOL result = [writeArchive writeData:fileData filePath:testFile fileDate:nil @@ -59,7 +98,8 @@ - (void)testIsPasswordProtected_PasswordRequired_LastFileOnly XCTAssertTrue(result, @"Error writing archive data"); XCTAssertNil(writeError, @"Error writing to file %@: %@", testFile, writeError); }]; - +#pragma clang diagnostic pop + UZKArchive *readArchive = [[UZKArchive alloc] initWithURL:testArchiveURL error:nil]; XCTAssertTrue(readArchive.isPasswordProtected, @"isPasswordProtected = NO for password-protected archive"); diff --git a/Tests/PerformOnDataTests.m b/Tests/PerformOnDataTests.m index c631458..1b4f5e3 100644 --- a/Tests/PerformOnDataTests.m +++ b/Tests/PerformOnDataTests.m @@ -7,7 +7,7 @@ // #import "UZKArchiveTestCase.h" -#import "UnzipKit.h" +@import UnzipKit; @interface PerformOnDataTests : UZKArchiveTestCase @end diff --git a/Tests/PerformOnFilesTests.m b/Tests/PerformOnFilesTests.m index c0908f4..f0038f0 100644 --- a/Tests/PerformOnFilesTests.m +++ b/Tests/PerformOnFilesTests.m @@ -7,7 +7,7 @@ // #import "UZKArchiveTestCase.h" -#import "UnzipKit.h" +@import UnzipKit; @interface PerformOnFilesTests : UZKArchiveTestCase @end diff --git a/Tests/PermissionsTests.swift b/Tests/PermissionsTests.swift index 265d83b..e6cd55b 100644 --- a/Tests/PermissionsTests.swift +++ b/Tests/PermissionsTests.swift @@ -7,6 +7,7 @@ // import XCTest +import UnzipKit class PermissionsTests: UZKArchiveTestCase { @@ -120,8 +121,9 @@ class PermissionsTests: UZKArchiveTestCase { let expectedPermissions: Int16 = 0o742 - try! writeArchive.write(testFileData, filePath: testFilename, fileDate: nil, posixPermissions: expectedPermissions, - compressionMethod: .default, password: nil, overwrite: true) + let props = ZipFileProperties(testFilename) + props.permissions = expectedPermissions; + try! writeArchive.write(testFileData, props: props) let readArchive = try! UZKArchive(url: testArchiveURL) let fileList = try! readArchive.listFileInfo() @@ -131,7 +133,29 @@ class PermissionsTests: UZKArchiveTestCase { XCTAssertEqual(actualPermissions, expectedPermissions) } + + func testWriteData_NonDefault_deprecatedOverload() { + let testArchiveURL = tempDirectory.appendingPathComponent("PermissionsTestWriteData.zip") + let testFilename = nonZipTestFilePaths.first! + let testFileURL = testFileURLs[testFilename] as! URL + let testFileData = try! Data(contentsOf: testFileURL) + + let writeArchive = try! UZKArchive(url: testArchiveURL) + + let expectedPermissions: Int16 = 0o742 + + try! writeArchive.deprecatedWrite(testFileData, filePath: testFilename, fileDate: nil, posixPermissions: expectedPermissions, + compressionMethod: .default, password: nil, overwrite: true) + let readArchive = try! UZKArchive(url: testArchiveURL) + let fileList = try! readArchive.listFileInfo() + + let writtenFileInfo = fileList.first { $0.filename == testFilename } + let actualPermissions = writtenFileInfo!.posixPermissions + + XCTAssertEqual(actualPermissions, expectedPermissions) + } + func testWriteIntoBuffer_Default() { let testArchiveURL = tempDirectory.appendingPathComponent("PermissionsTestWriteBufferedData.zip") let testFilename = nonZipTestFilePaths.first! @@ -139,7 +163,7 @@ class PermissionsTests: UZKArchiveTestCase { let testFileData = try! Data(contentsOf: testFileURL) let writeArchive = try! UZKArchive(url: testArchiveURL) - try! writeArchive.write(intoBuffer: testFilename) { (writeDataHandler, error) in + try! writeArchive.writeIntoBuffer(atPath: testFilename) { (writeDataHandler, error) in let buffer = testFileData.withUnsafeBytes { $0.baseAddress?.assumingMemoryBound(to: UInt32.self) } @@ -164,8 +188,37 @@ class PermissionsTests: UZKArchiveTestCase { let expectedPermissions: Int16 = 0o764 let writeArchive = try! UZKArchive(url: testArchiveURL) - try! writeArchive.write(intoBuffer: testFilename, fileDate: nil, posixPermissions: expectedPermissions, - compressionMethod: .default, overwrite: false, crc: 0, password: nil) + let props = ZipFileProperties(testFilename) + props.permissions = expectedPermissions + props.overwriteIfInArchive = false + try! writeArchive.write(intoBuffer: props) + { (writeDataHandler, error) in + let buffer = testFileData.withUnsafeBytes { + $0.baseAddress?.assumingMemoryBound(to: UInt32.self) + } + return writeDataHandler(buffer!, UInt32(testFileData.count)) + } + + let readArchive = try! UZKArchive(url: testArchiveURL) + let fileList = try! readArchive.listFileInfo() + + let writtenFileInfo = fileList.first { $0.filename == testFilename } + let actualPermissions = writtenFileInfo!.posixPermissions + + XCTAssertEqual(actualPermissions, expectedPermissions) + } + + func testWriteIntoBuffer_NonDefault_deprecatedOverload() { + let testArchiveURL = tempDirectory.appendingPathComponent("PermissionsTestWriteBufferedData_CustomPermissions.zip") + let testFilename = nonZipTestFilePaths.first! + let testFileURL = testFileURLs[testFilename] as! URL + let testFileData = try! Data(contentsOf: testFileURL) + + let expectedPermissions: Int16 = 0o764 + + let writeArchive = try! UZKArchive(url: testArchiveURL) + try! writeArchive.deprecatedWrite(intoBuffer: testFilename, fileDate: nil, posixPermissions: expectedPermissions, + compressionMethod: .default, overwrite: false, crc: 0, password: nil) { (writeDataHandler, error) in let buffer = testFileData.withUnsafeBytes { $0.baseAddress?.assumingMemoryBound(to: UInt32.self) diff --git a/Tests/PropertyTests.m b/Tests/PropertyTests.m index 136545a..8d5de5a 100644 --- a/Tests/PropertyTests.m +++ b/Tests/PropertyTests.m @@ -7,7 +7,7 @@ // #import "UZKArchiveTestCase.h" -#import "UnzipKit.h" +@import UnzipKit; @interface PropertyTests : UZKArchiveTestCase @end diff --git a/Tests/UZKArchive+Deprecated.h b/Tests/UZKArchive+Deprecated.h new file mode 100644 index 0000000..72a41ee --- /dev/null +++ b/Tests/UZKArchive+Deprecated.h @@ -0,0 +1,53 @@ +// +// UZKArchive+Deprecated.h +// UnzipKit +// +// Created by Dov Frankel on 8/12/19. +// Copyright © 2019 Abbey Code. All rights reserved. +// + +#import + +#import "UZKArchive.h" + +@interface UZKArchive (Deprecated) + +NS_ASSUME_NONNULL_BEGIN + +- (BOOL)deprecatedWriteData:(NSData *)data + filePath:(NSString *)filePath + fileDate:(nullable NSDate *)fileDate + compressionMethod:(UZKCompressionMethod)method + password:(nullable NSString *)password + error:(NSError **)error; + +- (BOOL)deprecatedWriteData:(NSData *)data + filePath:(NSString *)filePath + fileDate:(nullable NSDate *)fileDate + compressionMethod:(UZKCompressionMethod)method + password:(nullable NSString *)password + overwrite:(BOOL)overwrite + error:(NSError **)error; + +- (BOOL)deprecatedWriteData:(NSData *)data + filePath:(NSString *)filePath + fileDate:(nullable NSDate *)fileDate + posixPermissions:(short)permissions + compressionMethod:(UZKCompressionMethod)method + password:(nullable NSString *)password + overwrite:(BOOL)overwrite + error:(NSError **)error; + +- (BOOL)deprecatedWriteIntoBuffer:(NSString *)filePath + fileDate:(nullable NSDate *)fileDate + posixPermissions:(short)permissions + compressionMethod:(UZKCompressionMethod)method + overwrite:(BOOL)overwrite + CRC:(unsigned long)preCRC + password:(nullable NSString *)password + error:(NSError **)error + block:(BOOL(^)(BOOL(^writeData)(const void *bytes, unsigned int length), NSError **actionError))action; + +NS_ASSUME_NONNULL_END + +@end diff --git a/Tests/UZKArchive+Deprecated.m b/Tests/UZKArchive+Deprecated.m new file mode 100644 index 0000000..cc0422d --- /dev/null +++ b/Tests/UZKArchive+Deprecated.m @@ -0,0 +1,97 @@ +// +// UZKArchive+Deprecated.m +// UnzipKit +// +// Created by Dov Frankel on 8/12/19. +// Copyright © 2019 Abbey Code. All rights reserved. +// + +#import "UZKArchive+Deprecated.h" +#import "UZKArchive.h" + +@implementation UZKArchive (Deprecated) + +- (BOOL)deprecatedWriteData:(NSData *)data + filePath:(NSString *)filePath + fileDate:(nullable NSDate *)fileDate + compressionMethod:(UZKCompressionMethod)method + password:(nullable NSString *)password + error:(NSError *__autoreleasing *)error +{ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + return [self writeData:data + filePath:filePath + fileDate:fileDate + compressionMethod:method + password:password + error:error]; +#pragma clang diagnostic pop +} + +- (BOOL)deprecatedWriteData:(NSData *)data + filePath:(NSString *)filePath + fileDate:(nullable NSDate *)fileDate + compressionMethod:(UZKCompressionMethod)method + password:(nullable NSString *)password + overwrite:(BOOL)overwrite + error:(NSError *__autoreleasing *)error +{ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + return [self writeData:data + filePath:filePath + fileDate:fileDate + compressionMethod:method + password:password + overwrite:overwrite + error:error]; +#pragma clang diagnostic pop +} + +- (BOOL)deprecatedWriteData:(NSData *)data + filePath:(NSString *)filePath + fileDate:(nullable NSDate *)fileDate + posixPermissions:(short)permissions + compressionMethod:(UZKCompressionMethod)method + password:(nullable NSString *)password + overwrite:(BOOL)overwrite + error:(NSError *__autoreleasing *)error +{ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + return [self writeData:data + filePath:filePath + fileDate:fileDate + posixPermissions:permissions + compressionMethod:method + password:password + overwrite:overwrite + error:error]; +#pragma clang diagnostic pop +} + +- (BOOL)deprecatedWriteIntoBuffer:(NSString *)filePath + fileDate:(nullable NSDate *)fileDate + posixPermissions:(short)permissions + compressionMethod:(UZKCompressionMethod)method + overwrite:(BOOL)overwrite + CRC:(unsigned long)preCRC + password:(nullable NSString *)password + error:(NSError *__autoreleasing *)error + block:(BOOL(^)(BOOL(^writeData)(const void *bytes, unsigned int length), NSError **actionError))action +{ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + return [self writeIntoBuffer:filePath + fileDate:fileDate + posixPermissions:permissions + compressionMethod:method + overwrite:overwrite + CRC:preCRC + password:password + error:error + block:action]; +#pragma clang diagnostic pop +} +@end diff --git a/Tests/UZKArchiveTestCase.m b/Tests/UZKArchiveTestCase.m index 01c761b..37fac77 100644 --- a/Tests/UZKArchiveTestCase.m +++ b/Tests/UZKArchiveTestCase.m @@ -15,7 +15,9 @@ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wundef" -#if UNIFIED_LOGGING_SUPPORTED +// UnzipKit compiles the library files into the test target directly, so these +// are defined inside UZKArchive.m there. Only necessary in Xcode project +#if defined(UNIFIED_LOGGING_SUPPORTED) && defined(CODE_IMPORTED_AS_LIB) os_log_t unzipkit_log; BOOL unzipkitIsAtLeast10_13SDK; #endif diff --git a/Tests/UnzipKitTests-Bridging-Header.h b/Tests/UnzipKitTests-Bridging-Header.h index 6ba7233..40fe7be 100644 --- a/Tests/UnzipKitTests-Bridging-Header.h +++ b/Tests/UnzipKitTests-Bridging-Header.h @@ -5,4 +5,5 @@ #import "UZKArchive.h" #import "zip.h" -#import "UZKArchiveTestCase.h" \ No newline at end of file +#import "UZKArchiveTestCase.h" +#import "UZKArchive+Deprecated.h" diff --git a/Tests/WriteBufferedDataTests.m b/Tests/WriteBufferedDataTests.m index 7245866..ceda811 100644 --- a/Tests/WriteBufferedDataTests.m +++ b/Tests/WriteBufferedDataTests.m @@ -8,7 +8,8 @@ #import "UZKArchiveTestCase.h" #import "zip.h" -#import "UnzipKit.h" +@import UnzipKit; + @interface WriteBufferedDataTests : UZKArchiveTestCase @end @@ -37,6 +38,65 @@ - (void)testWriteInfoBuffer NSError *writeError = nil; const void *bytes = fileData.bytes; + ZipFileProperties *props = [[ZipFileProperties alloc] init:testFile]; + props.timestamp = testDates[idx]; + + BOOL result = [archive writeIntoBuffer:props + error:&writeError + block: + ^BOOL(BOOL(^writeData)(const void *bytes, unsigned int length), NSError**(actionError)) { + for (NSUInteger i = 0; i <= fileData.length; i += bufferSize) { + unsigned int size = (unsigned int)MIN(fileData.length - i, bufferSize); + BOOL writeSuccess = writeData(&bytes[i], size); + XCTAssertTrue(writeSuccess, @"Failed to write buffered data"); + } + + return YES; + }]; + + XCTAssertTrue(result, @"Error writing archive data"); + XCTAssertNil(writeError, @"Error writing to file %@: %@", testFile, writeError); + }]; + + __block NSError *readError = nil; + __block NSUInteger idx = 0; + + [archive performOnDataInArchive:^(UZKFileInfo *fileInfo, NSData *fileData, BOOL *stop) { + NSData *expectedData = testFileData[idx]; + unsigned long expectedCRC = crc32(0, expectedData.bytes, (unsigned int)expectedData.length); + + XCTAssertEqualObjects(fileInfo.filename, testFiles[idx], @"Incorrect filename in archive"); + XCTAssertEqualObjects(fileInfo.timestamp, testDates[idx], @"Incorrect timestamp in archive"); + XCTAssertEqual(fileInfo.CRC, expectedCRC, @"CRC of extracted data doesn't match what was written"); + XCTAssertEqualObjects(fileData, expectedData, @"Data extracted doesn't match what was written"); + + idx++; + } error:&readError]; +} + +- (void)testWriteInfoBuffer_deprecatedOverload +{ + NSArray *testFiles = [self.nonZipTestFilePaths.allObjects sortedArrayUsingSelector:@selector(compare:)]; + NSArray *testDates = @[[[UZKArchiveTestCase dateFormatter] dateFromString:@"12/20/2014 9:35 AM"], + [[UZKArchiveTestCase dateFormatter] dateFromString:@"12/21/2014 10:00 AM"], + [[UZKArchiveTestCase dateFormatter] dateFromString:@"12/22/2014 11:54 PM"]]; + NSMutableArray *testFileData = [NSMutableArray arrayWithCapacity:testFiles.count]; + + NSURL *testArchiveURL = [self.tempDirectory URLByAppendingPathComponent:@"WriteIntoBufferTest.zip"]; + + UZKArchive *archive = [[UZKArchive alloc] initWithURL:testArchiveURL error:nil]; + + unsigned int bufferSize = 1024; //Arbitrary + + [testFiles enumerateObjectsUsingBlock:^(NSString *testFile, NSUInteger idx, BOOL *stop) { + NSData *fileData = [NSData dataWithContentsOfURL:self.testFileURLs[testFile]]; + [testFileData addObject:fileData]; + + NSError *writeError = nil; + const void *bytes = fileData.bytes; + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" BOOL result = [archive writeIntoBuffer:testFile fileDate:testDates[idx] compressionMethod:UZKCompressionMethodDefault @@ -56,6 +116,7 @@ - (void)testWriteInfoBuffer XCTAssertTrue(result, @"Error writing archive data"); XCTAssertNil(writeError, @"Error writing to file %@: %@", testFile, writeError); }]; +#pragma clang diagnostic pop __block NSError *readError = nil; __block NSUInteger idx = 0; @@ -83,6 +144,38 @@ - (void)testWriteInfoBuffer_Failure NSError *writeError = nil; + ZipFileProperties *props = [[ZipFileProperties alloc] init:@"Test File A.txt"]; + props.timestamp = [[UZKArchiveTestCase dateFormatter] dateFromString:@"12/20/2014 9:35 AM"]; + + BOOL result = [archive writeIntoBuffer:props + error:&writeError + block: + ^BOOL(BOOL(^writeData)(const void *bytes, unsigned int length), NSError**(actionError)) { + NSError *bufferError = [NSError errorWithDomain:@"UnzipKitUnitTest" + code:errorCode + userInfo:@{}]; + *actionError = bufferError; + + return NO; + }]; + + XCTAssertFalse(result, @"Success returned during failure writing archive data"); + XCTAssertNotNil(writeError, @"No error after failure writing to archive"); + XCTAssertEqual(writeError.code, errorCode, @"Wrong error code returned"); +} + +- (void)testWriteInfoBuffer_Failure_deprecatedOverload +{ + NSURL *testArchiveURL = [self.tempDirectory URLByAppendingPathComponent:@"WriteIntoBufferTest_Failure.zip"]; + + UZKArchive *archive = [[UZKArchive alloc] initWithURL:testArchiveURL error:nil]; + + NSInteger errorCode = 718; + + NSError *writeError = nil; + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" BOOL result = [archive writeIntoBuffer:@"Test File A.txt" fileDate:[[UZKArchiveTestCase dateFormatter] dateFromString:@"12/20/2014 9:35 AM"] compressionMethod:UZKCompressionMethodDefault @@ -97,7 +190,8 @@ - (void)testWriteInfoBuffer_Failure return NO; }]; - +#pragma clang diagnostic pop + XCTAssertFalse(result, @"Success returned during failure writing archive data"); XCTAssertNotNil(writeError, @"No error after failure writing to archive"); XCTAssertEqual(writeError.code, errorCode, @"Wrong error code returned"); @@ -120,6 +214,49 @@ - (void)testWriteInfoBuffer_PasswordGiven const void *bytes = fileData.bytes; + ZipFileProperties *props = [[ZipFileProperties alloc] init:testFile]; + props.crc = 841856539; + + BOOL result = [archive writeIntoBuffer:props + error:&writeError + block: + ^BOOL(BOOL(^writeData)(const void *bytes, unsigned int length), NSError**(actionError)) { + for (NSUInteger i = 0; i <= fileData.length; i += bufferSize) { + unsigned int size = (unsigned int)MIN(fileData.length - i, bufferSize); + BOOL writeSuccess = writeData(&bytes[i], size); + XCTAssertTrue(writeSuccess, @"Failed to write buffered data"); + } + + return YES; + }]; + + XCTAssertTrue(result, @"Error writing archive data"); + XCTAssertNil(writeError, @"Error writing to file %@: %@", testFile, writeError); + + BOOL extractSuccess = [self extractArchive:testArchiveURL + password:password]; + + XCTAssertTrue(extractSuccess, @"Failed to extract archive (encryption is incorrect)"); +} + +- (void)testWriteInfoBuffer_PasswordGiven_deprecatedOverload +{ + NSURL *testArchiveURL = [self.tempDirectory URLByAppendingPathComponent:@"testWriteInfoBuffer_PasswordGiven.zip"]; + + NSString *password = @"a password"; + UZKArchive *archive = [[UZKArchive alloc] initWithURL:testArchiveURL password:password error:nil]; + + NSError *writeError = nil; + + unsigned int bufferSize = 1024; //Arbitrary + + NSString *testFile = @"Test File A.txt"; + NSData *fileData = [NSData dataWithContentsOfURL:self.testFileURLs[testFile]]; + + const void *bytes = fileData.bytes; + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" BOOL result = [archive writeIntoBuffer:testFile fileDate:nil compressionMethod:UZKCompressionMethodDefault @@ -136,7 +273,8 @@ - (void)testWriteInfoBuffer_PasswordGiven return YES; }]; - +#pragma clang diagnostic pop + XCTAssertTrue(result, @"Error writing archive data"); XCTAssertNil(writeError, @"Error writing to file %@: %@", testFile, writeError); @@ -155,6 +293,28 @@ - (void)testWriteInfoBuffer_PasswordGiven_NoCRC NSError *writeError = nil; + ZipFileProperties *props = [[ZipFileProperties alloc] init:@"Test File A.txt"]; + props.timestamp = [[UZKArchiveTestCase dateFormatter] dateFromString:@"12/20/2014 9:35 AM"]; + + XCTAssertThrows([archive writeIntoBuffer:props + error:&writeError + block: + ^BOOL(BOOL(^writeData)(const void *bytes, unsigned int length), NSError**(actionError)) { + return YES; + }], + @"No assertion failed when streaming an encypted file with no CRC given"); +} + +- (void)testWriteInfoBuffer_PasswordGiven_NoCRC_deprecatedOverload +{ + NSURL *testArchiveURL = [self.tempDirectory URLByAppendingPathComponent:@"testWriteInfoBuffer_PasswordGiven_NoCRC.zip"]; + + UZKArchive *archive = [[UZKArchive alloc] initWithURL:testArchiveURL password:@"a password" error:nil]; + + NSError *writeError = nil; + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" XCTAssertThrows([archive writeIntoBuffer:@"Test File A.txt" fileDate:[[UZKArchiveTestCase dateFormatter] dateFromString:@"12/20/2014 9:35 AM"] compressionMethod:UZKCompressionMethodDefault @@ -165,6 +325,7 @@ - (void)testWriteInfoBuffer_PasswordGiven_NoCRC return YES; }], @"No assertion failed when streaming an encypted file with no CRC given"); +#pragma clang diagnostic pop } - (void)testWriteInfoBuffer_PasswordGiven_MismatchedCRC @@ -182,6 +343,46 @@ - (void)testWriteInfoBuffer_PasswordGiven_MismatchedCRC const void *bytes = fileData.bytes; + ZipFileProperties *props = [[ZipFileProperties alloc] init:@"Test File A.txt"]; + props.crc = 3; + + BOOL result = [archive writeIntoBuffer:props + error:&writeError + block: + ^BOOL(BOOL(^writeData)(const void *bytes, unsigned int length), NSError**(actionError)) { + for (NSUInteger i = 0; i <= fileData.length; i += bufferSize) { + unsigned int size = (unsigned int)MIN(fileData.length - i, bufferSize); + BOOL writeSuccess = writeData(&bytes[i], size); + XCTAssertTrue(writeSuccess, @"Failed to write buffered data"); + } + + return YES; + }]; + + XCTAssertFalse(result, @"No error writing archive data"); + XCTAssertNotNil(writeError, @"No error writing to file"); + XCTAssertEqual(writeError.code, UZKErrorCodePreCRCMismatch, @"Wrong error code returned for CRC mismatch"); + XCTAssertTrue([writeError.localizedRecoverySuggestion containsString:@"0000000003"], @"Bad CRC not included in message"); + XCTAssertTrue([writeError.localizedRecoverySuggestion containsString:@"0841856539"], @"Good CRC not included in message"); +} + +- (void)testWriteInfoBuffer_PasswordGiven_MismatchedCRC_deprecatedOverload +{ + NSURL *testArchiveURL = [self.tempDirectory URLByAppendingPathComponent:@"testWriteInfoBuffer_PasswordGiven_MismatchedCRC.zip"]; + + UZKArchive *archive = [[UZKArchive alloc] initWithURL:testArchiveURL password:@"a password" error:nil]; + + NSError *writeError = nil; + + unsigned int bufferSize = 1024; //Arbitrary + + NSString *testFile = @"Test File A.txt"; + NSData *fileData = [NSData dataWithContentsOfURL:self.testFileURLs[testFile]]; + + const void *bytes = fileData.bytes; + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" BOOL result = [archive writeIntoBuffer:testFile fileDate:nil compressionMethod:UZKCompressionMethodDefault @@ -198,7 +399,8 @@ - (void)testWriteInfoBuffer_PasswordGiven_MismatchedCRC return YES; }]; - +#pragma clang diagnostic pop + XCTAssertFalse(result, @"No error writing archive data"); XCTAssertNotNil(writeError, @"No error writing to file"); XCTAssertEqual(writeError.code, UZKErrorCodePreCRCMismatch, @"Wrong error code returned for CRC mismatch"); diff --git a/Tests/WriteDataTests.swift b/Tests/WriteDataTests.swift index f265bf6..84e0377 100644 --- a/Tests/WriteDataTests.swift +++ b/Tests/WriteDataTests.swift @@ -32,8 +32,49 @@ class WriteDataTests: UZKArchiveTestCase { testFileData.append(fileData!) do { - try archive.write(fileData!, filePath: testFilePath, fileDate: testDates[index], - compressionMethod: .default, password: nil) + let props = ZipFileProperties(testFilePath) + props.timestamp = testDates[index] + try archive.write(fileData!, props: props) + } catch let error as NSError { + XCTFail("Error writing to file \(testFilePath): \(error)") + } + } + + var index = 0 + + try! archive.performOnData(inArchive: { (fileInfo, fileData, stop) -> Void in + let expectedData = testFileData[index] + let expectedCRC = crc32(0, (expectedData as NSData).bytes.bindMemory(to: Bytef.self, capacity: expectedData.count), uInt(expectedData.count)) + + XCTAssertEqual(fileInfo.filename, testFilePaths[index], "Incorrect filename in archive") + XCTAssertEqual(fileInfo.timestamp, testDates[index]!, "Incorrect timestamp in archive") + XCTAssertEqual(fileInfo.crc, expectedCRC, "CRC of extracted data doesn't match what was written") + XCTAssertEqual(fileData, expectedData, "Data extracted doesn't match what was written") + + index += 1; + }) + + XCTAssert(index > 0, "No data iterated through") + } + + func testWriteData_deprecatedOverload() { + let testFilePaths = [String](nonZipTestFilePaths).sorted(by: <) + let testDates = [ + UZKArchiveTestCase.dateFormatter().date(from: "12/20/2014 9:35 AM"), + UZKArchiveTestCase.dateFormatter().date(from: "12/21/2014 10:00 AM"), + UZKArchiveTestCase.dateFormatter().date(from: "12/22/2014 11:54 PM")] + var testFileData = [Data]() + + let testArchiveURL = tempDirectory.appendingPathComponent("WriteDataTest.zip") + let archive = try! UZKArchive(url: testArchiveURL) + + for (index, testFilePath) in testFilePaths.enumerated() { + let fileData = try? Data(contentsOf: testFileURLs[testFilePath] as! URL) + testFileData.append(fileData!) + + do { + try archive.deprecatedWrite(fileData!, filePath: testFilePath, fileDate: testDates[index], + compressionMethod: .default, password: nil) } catch let error as NSError { XCTFail("Error writing to file \(testFilePath): \(error)") } @@ -72,8 +113,49 @@ class WriteDataTests: UZKArchiveTestCase { testFileData.append(fileData!) do { - try archive.write(fileData!, filePath: testFilePath, fileDate: testDates[index], - compressionMethod: .default, password: nil) + let props = ZipFileProperties(testFilePath) + props.timestamp = testDates[index] + try archive.write(fileData!, props: props) + } catch let error as NSError { + XCTFail("Error writing to file \(testFilePath): \(error)") + } + } + + var index = 0 + + try! archive.performOnData(inArchive: { (fileInfo, fileData, stop) -> Void in + let expectedData = testFileData[index] + let expectedCRC = crc32(0, (expectedData as NSData).bytes.bindMemory(to: Bytef.self, capacity: expectedData.count), uInt(expectedData.count)) + + XCTAssertEqual(fileInfo.filename, testFilePaths[index], "Incorrect filename in archive") + XCTAssertEqual(fileInfo.timestamp, testDates[index]!, "Incorrect timestamp in archive") + XCTAssertEqual(fileInfo.crc, expectedCRC, "CRC of extracted data doesn't match what was written") + XCTAssertEqual(fileData, expectedData, "Data extracted doesn't match what was written") + + index += 1; + }) + + XCTAssert(index > 0, "No data iterated through") + } + + func testWriteData_Unicode_deprecatedOverload() { + let testFilePaths = [String](nonZipUnicodeFilePaths).sorted(by: <) + let testDates = [ + UZKArchiveTestCase.dateFormatter().date(from: "12/20/2014 9:35 AM"), + UZKArchiveTestCase.dateFormatter().date(from: "12/21/2014 10:00 AM"), + UZKArchiveTestCase.dateFormatter().date(from: "12/22/2014 11:54 PM")] + var testFileData = [Data]() + + let testArchiveURL = tempDirectory.appendingPathComponent("UnicodeWriteDataTest.zip") + let archive = try! UZKArchive(url: testArchiveURL) + + for (index, testFilePath) in testFilePaths.enumerated() { + let fileData = try? Data(contentsOf: unicodeFileURLs[testFilePath] as! URL) + testFileData.append(fileData!) + + do { + try archive.deprecatedWrite(fileData!, filePath: testFilePath, fileDate: testDates[index], + compressionMethod: .default, password: nil) } catch let error as NSError { XCTFail("Error writing to file \(testFilePath): \(error)") } @@ -112,8 +194,9 @@ class WriteDataTests: UZKArchiveTestCase { testFileData.append(fileData!) do { - try archive.write(fileData!, filePath: testFilePath, fileDate: testDates[index], - compressionMethod: .default, password: nil) + let props = ZipFileProperties(testFilePath) + props.timestamp = testDates[index] + try archive.write(fileData!, props: props) } catch let error as NSError { XCTFail("Error writing to file \(testFilePath): \(error)") } @@ -142,8 +225,83 @@ class WriteDataTests: UZKArchiveTestCase { let x = testFilePaths.count - 1 - i do { - try archive.write(testFileData[x], filePath: testFilePaths[i], - fileDate: testDates[x], compressionMethod: .default, password: nil) + let props = ZipFileProperties(testFilePaths[i]) + props.timestamp = testDates[x] + try archive.write(testFileData[x], props: props) + } catch let error as NSError { + XCTFail("Error writing to file \(testFilePaths[x]) with data of " + + "file \(testFilePaths[i]): \(error)") + } + } + + var forwardIndex = 0 + + try! archive.performOnData(inArchive: { (fileInfo, fileData, stop) -> Void in + XCTAssertEqual(fileInfo.filename, testFilePaths[forwardIndex], "Incorrect filename in archive"); + + let reverseIndex = testFilePaths.count - 1 - forwardIndex + + let expectedData = testFileData[reverseIndex] + let expectedCRC = crc32(0, (expectedData as NSData).bytes.bindMemory(to: Bytef.self, capacity: expectedData.count), uInt(expectedData.count)) + + XCTAssertEqual(fileInfo.timestamp, testDates[reverseIndex]!, "Incorrect timestamp in archive") + XCTAssertEqual(fileInfo.crc, expectedCRC, "CRC of extracted data doesn't match what was written") + XCTAssertEqual(fileData, expectedData, "Data extracted doesn't match what was written") + + forwardIndex += 1; + }) + + XCTAssert(index > 0, "No data iterated through") + } + + func testWriteData_Overwrite_deprecatedOverload() { + let testFilePaths = [String](nonZipTestFilePaths).sorted(by: <) + let testDates = [ + UZKArchiveTestCase.dateFormatter().date(from: "12/20/2014 9:35 AM"), + UZKArchiveTestCase.dateFormatter().date(from: "12/21/2014 10:00 AM"), + UZKArchiveTestCase.dateFormatter().date(from: "12/22/2014 11:54 PM")] + var testFileData = [Data]() + + let testArchiveURL = tempDirectory.appendingPathComponent("RewriteDataTest.zip") + let archive = try! UZKArchive(url: testArchiveURL) + + for (index, testFilePath) in testFilePaths.enumerated() { + let fileData = try? Data(contentsOf: testFileURLs[testFilePath] as! URL) + testFileData.append(fileData!) + + do { + try archive.deprecatedWrite(fileData!, filePath: testFilePath, fileDate: testDates[index], + compressionMethod: .default, password: nil) + } catch let error as NSError { + XCTFail("Error writing to file \(testFilePath): \(error)") + } + } + + var index = 0 + + try! archive.performOnData(inArchive: { (fileInfo, fileData, stop) -> Void in + let expectedData = testFileData[index] + let expectedCRC = crc32(0, (expectedData as NSData).bytes.bindMemory(to: Bytef.self, capacity: expectedData.count), uInt(expectedData.count)) + + XCTAssertEqual(fileInfo.filename, testFilePaths[index], "Incorrect filename in archive") + XCTAssertEqual(fileInfo.timestamp, testDates[index]!, "Incorrect timestamp in archive") + XCTAssertEqual(fileInfo.crc, expectedCRC, "CRC of extracted data doesn't match what was written") + XCTAssertEqual(fileData, expectedData, "Data extracted doesn't match what was written") + + index += 1; + }) + + XCTAssert(index > 0, "No data iterated through") + + // Now write the files' contents to the zip in reverse + NSLog("Testing a second write, by reversing the contents and timestamps of the files from the first run") + + for i in 0.. Void in + let expectedData = testFileData[index] + let expectedCRC = crc32(0, (expectedData as NSData).bytes.bindMemory(to: Bytef.self, capacity: expectedData.count), uInt(expectedData.count)) + + XCTAssertEqual(fileInfo.filename, testFilePaths[index], "Incorrect filename in archive") + XCTAssertEqual(fileInfo.timestamp, testDates[index]!, "Incorrect timestamp in archive") + XCTAssertEqual(fileInfo.crc, expectedCRC, "CRC of extracted data doesn't match what was written") + XCTAssertEqual(fileData, expectedData, "Data extracted doesn't match what was written") + + index += 1; + }) + + XCTAssert(index > 0, "No data iterated through") + + // Now write the files' contents to the zip in reverse + NSLog("Testing a second write, by reversing the contents and timestamps of the files from the first run") + + for i in 0.. Void in + XCTAssertEqual(fileInfo.filename, testFilePaths[forwardIndex], "Incorrect filename in archive"); + + let reverseIndex = testFilePaths.count - 1 - forwardIndex + + let expectedData = testFileData[reverseIndex] + let expectedCRC = crc32(0, (expectedData as NSData).bytes.bindMemory(to: Bytef.self, capacity: expectedData.count), uInt(expectedData.count)) + + XCTAssertEqual(fileInfo.timestamp, testDates[reverseIndex]!, "Incorrect timestamp in archive") + XCTAssertEqual(fileInfo.crc, expectedCRC, "CRC of extracted data doesn't match what was written") + XCTAssertEqual(fileData, expectedData, "Data extracted doesn't match what was written") + + forwardIndex += 1; + }) + + XCTAssert(index > 0, "No data iterated through") + } + + func testWriteData_Overwrite_Unicode_deprecatedOverload() { + let testFilePaths = [String](nonZipUnicodeFilePaths).sorted(by: <) + let testDates = [ + UZKArchiveTestCase.dateFormatter().date(from: "12/20/2014 9:35 AM"), + UZKArchiveTestCase.dateFormatter().date(from: "12/21/2014 10:00 AM"), + UZKArchiveTestCase.dateFormatter().date(from: "12/22/2014 11:54 PM")] + var testFileData = [Data]() + + let testArchiveURL = tempDirectory.appendingPathComponent("RewriteDataTest.zip") + let archive = try! UZKArchive(url: testArchiveURL) + + for (index, testFilePath) in testFilePaths.enumerated() { + let fileData = try? Data(contentsOf: unicodeFileURLs[testFilePath] as! URL) + testFileData.append(fileData!) + + do { + try archive.deprecatedWrite(fileData!, filePath: testFilePath, fileDate: testDates[index], + compressionMethod: .default, password: nil) } catch let error as NSError { XCTFail("Error writing to file \(testFilePath): \(error)") } @@ -216,8 +450,8 @@ class WriteDataTests: UZKArchiveTestCase { let x = testFilePaths.count - 1 - i do { - try archive.write(testFileData[x], filePath: testFilePaths[i], - fileDate: testDates[x], compressionMethod: .default, password: nil) + try archive.deprecatedWrite(testFileData[x], filePath: testFilePaths[i], + fileDate: testDates[x], compressionMethod: .default, password: nil) } catch let error as NSError { XCTFail("Error writing to file \(testFilePaths[x]) with data of " + "file \(testFilePaths[i]): \(error)") @@ -260,8 +494,71 @@ class WriteDataTests: UZKArchiveTestCase { testFileData.append(fileData!) do { - try archive.write(fileData!, filePath: testFilePath, fileDate: testDates[index], - compressionMethod: .default, password: nil, overwrite: false) + let props = ZipFileProperties(testFilePath) + props.overwriteIfInArchive = false + props.timestamp = testDates[index] + try archive.write(fileData!, props: props) + } catch let error as NSError { + XCTFail("Error writing to file \(testFilePath): \(error)") + } + } + + var index = 0 + + try! archive.performOnData(inArchive: { (fileInfo, fileData, stop) -> Void in + let expectedData = testFileData[index] + let expectedCRC = crc32(0, (expectedData as NSData).bytes.bindMemory(to: Bytef.self, capacity: expectedData.count), uInt(expectedData.count)) + + XCTAssertEqual(fileInfo.filename, testFilePaths[index], "Incorrect filename in archive") + XCTAssertEqual(fileInfo.timestamp, testDates[index]!, "Incorrect timestamp in archive") + XCTAssertEqual(fileInfo.crc, expectedCRC, "CRC of extracted data doesn't match what was written") + XCTAssertEqual(fileData, expectedData, "Data extracted doesn't match what was written") + + index += 1; + }) + + XCTAssert(index > 0, "No data iterated through") + + // Now write the files' contents to the zip in reverse + + for i in 0.. 0 { + XCTAssertEqual(lastFileSize, fileSize.uint64Value, "File changed size between writes") + } + + lastFileSize = fileSize.uint64Value + } + } + + func testWriteData_MultipleWrites_deprecatedOverload() { + let testArchiveURL = tempDirectory.appendingPathComponent("MultipleDataWriteTest.zip") + let testFilename = nonZipTestFilePaths.first! + let testFileURL = testFileURLs[testFilename] as! URL + let testFileData = try! Data(contentsOf: testFileURL) + + let archive = try! UZKArchive(url: testArchiveURL) + + var lastFileSize: UInt64 = 0 + + for _ in 0..<100 { + do { + try archive.deprecatedWrite(testFileData, filePath: testFilename, fileDate: nil, + compressionMethod: .default, password: nil) + } catch let error as NSError { + XCTFail("Error writing to file \(testFileURL): \(error)") + } + + let fm = FileManager.default + let fileAttributes = try! fm.attributesOfItem(atPath: testArchiveURL.path) let fileSize = fileAttributes[FileAttributeKey.size] as! NSNumber if lastFileSize > 0 { @@ -360,8 +686,31 @@ class WriteDataTests: UZKArchiveTestCase { let archive = try! UZKArchive(url: testArchiveURL) do { - try archive.write(testFileData, filePath: testFilename, fileDate: nil, - compressionMethod: .default, password: nil) + try archive.write(testFileData, filePath: testFilename) + } catch let error as NSError { + XCTFail("Error writing to file \(testFileURL): \(error)") + } + + let fileList = try! archive.listFileInfo() + let writtenFileInfo = fileList.first! + + let expectedDate = Date().timeIntervalSinceReferenceDate + let actualDate = writtenFileInfo.timestamp.timeIntervalSinceReferenceDate + + XCTAssertEqual(actualDate, expectedDate, accuracy: 30, "Incorrect default date value written to file") + } + + func testWriteData_DefaultDate_deprecatedOverload() { + let testArchiveURL = tempDirectory.appendingPathComponent("DefaultDateWriteTest.zip") + let testFilename = nonZipTestFilePaths.first! + let testFileURL = testFileURLs[testFilename] as! URL + let testFileData = try! Data(contentsOf: testFileURL) + + let archive = try! UZKArchive(url: testArchiveURL) + + do { + try archive.deprecatedWrite(testFileData, filePath: testFilename, fileDate: nil, + compressionMethod: .default, password: nil) } catch let error as NSError { XCTFail("Error writing to file \(testFileURL): \(error)") } @@ -449,9 +798,52 @@ class WriteDataTests: UZKArchiveTestCase { let newTextData = newText.data(using: String.Encoding.utf8) var writeSuccessful = true do { - try archive.write(newTextData!, filePath: textFileName, fileDate: nil, - compressionMethod: UZKCompressionMethod.default, password: nil, - overwrite: true) + try archive.write(newTextData!, filePath: textFileName) + } catch let error { + NSLog("Error writing data to archive on external volume: \(error)") + writeSuccessful = false + } + + XCTAssertTrue(writeSuccessful, "Failed to update archive on external volume") + + let archivedFileData = try! archive.extractData(fromFile: textFileName) + XCTAssertNotNil(archivedFileData, "No data extracted from file in archive on external volume") + + let archivedText = NSString(data: archivedFileData, encoding: String.Encoding.utf8.rawValue)! + XCTAssertEqual(archivedText as String, newText, "Incorrect text extracted from archive on external volume") + } + + func testWriteData_ExternalVolume_deprecatedOverload() { + // Create a simple zip file + let tempDirURL = URL(fileURLWithPath: self.randomDirectoryName()) + let textFileName = "testWriteData_ExternalVolume.txt" + let textFileURL = tempDirURL.appendingPathComponent(textFileName) + try! FileManager.default.createDirectory(at: tempDirURL, withIntermediateDirectories: true, attributes: [:]) + try! "This is the original text".write(to: textFileURL, atomically: false, encoding: String.Encoding.utf8) + let tempZipFileURL = self.archive(withFiles: [textFileURL]) + NSLog("Original ZIP file: \(String(describing: tempZipFileURL?.path))") + + // Write that zip file to contents of a DMG and mount it + let dmgSourceFolderURL = tempDirURL.appendingPathComponent("DMGSource") + try! FileManager.default.createDirectory(at: dmgSourceFolderURL, withIntermediateDirectories: true, attributes: [:]) + try! FileManager.default.copyItem(at: tempZipFileURL!, to: dmgSourceFolderURL.appendingPathComponent(tempZipFileURL!.lastPathComponent)) + let dmgURL = tempDirURL.appendingPathComponent("testWriteData_ExternalVolume.dmg") + let mountPoint = createAndMountDMG(path: dmgURL, source: dmgSourceFolderURL, fileSystem: .HFS)! + NSLog("Disk image: \(dmgURL.path)") + defer { + unmountDMG(URL: mountPoint) + } + + // Update a file from the archive with overwrite=YES + let externalVolumeZipURL = mountPoint.appendingPathComponent(tempZipFileURL!.lastPathComponent) + let archive = try! UZKArchive(url: externalVolumeZipURL) + let newText = "This is the new text" + let newTextData = newText.data(using: String.Encoding.utf8) + var writeSuccessful = true + do { + try archive.deprecatedWrite(newTextData!, filePath: textFileName, fileDate: nil, + compressionMethod: UZKCompressionMethod.default, password: nil, + overwrite: true) } catch let error { NSLog("Error writing data to archive on external volume: \(error)") writeSuccessful = false diff --git a/Tests/ZipFileDetectionTests.m b/Tests/ZipFileDetectionTests.m index 34d5384..c35b1af 100644 --- a/Tests/ZipFileDetectionTests.m +++ b/Tests/ZipFileDetectionTests.m @@ -7,7 +7,7 @@ // #import "UZKArchiveTestCase.h" -#import "UnzipKit.h" +@import UnzipKit; @interface ZipFileDetectionTests : UZKArchiveTestCase @end diff --git a/UnzipKit.podspec b/UnzipKit.podspec index 32a73ea..ca3d3c4 100644 --- a/UnzipKit.podspec +++ b/UnzipKit.podspec @@ -7,29 +7,38 @@ Pod::Spec.new do |s| s.author = { "Dov Frankel" => "dov@abbey-code.com" } s.social_media_url = "https://twitter.com/dovfrankel" s.source = { :git => "https://github.com/abbeycode/UnzipKit.git", :tag => "#{s.version}" } - s.ios.deployment_target = "9.0" - s.osx.deployment_target = "10.9" + s.ios.deployment_target = "12.0" + s.osx.deployment_target = "10.14" + s.swift_version = "5.0" + s.library = "z" s.requires_arc = 'Source/**/*' s.public_header_files = "Source/UnzipKit.h", "Source/UZKArchive.h", "Source/UZKFileInfo.h" s.private_header_files = "Source/UZKFileInfo_Private.h" - s.source_files = "Source/**/*.{h,m}" + s.source_files = "Source/**/*.{h,m,swift}" s.exclude_files = 'Resources/**/Info.plist' s.resource_bundles = { 'UnzipKitResources' => ['Resources/**/*'] } + s.script_phases = { :name => "Generate UnzipKit Swift Header", + :script => "\"${PODS_TARGET_SRCROOT}\"/Scripts/generate-swift-import-header.sh", + :execution_position => :before_compile } s.test_spec 'Tests' do |test_spec| - test_spec.source_files = 'Tests/*.{h,m}' - test_spec.exclude_files = 'Tests/ExtractFilesTests.m' + test_spec.source_files = 'Tests/*.{h,m,swift}' + test_spec.exclude_files = 'Tests/ExtractFilesTests.m', + 'Tests/UnzipKitTests-Bridging-Header.h' + test_spec.preserve_paths = 'Tests/UnzipKitTests-Bridging-Header.h' test_spec.resources = ['Tests/Test Data'] - test_spec.pod_target_xcconfig = { "OTHER_CFLAGS" => "$(inherited) -Wno-unguarded-availability" } + test_spec.pod_target_xcconfig = { + "OTHER_CFLAGS" => "$(inherited) -Wno-unguarded-availability", + "SWIFT_OBJC_BRIDGING_HEADER" => "$(PODS_TARGET_SRCROOT)/Tests/UnzipKitTests-Bridging-Header.h" + } test_spec.scheme = { # Disable logging. Comment this line if you need diagnostic info :environment_variables => { "OS_ACTIVITY_MODE" => "disable" } } end - s.library = "z" s.subspec "minizip-lib" do |ss| ss.private_header_files = "Lib/MiniZip/*.h" diff --git a/UnzipKit.xcodeproj/project.pbxproj b/UnzipKit.xcodeproj/project.pbxproj index f4168f7..a715a15 100644 --- a/UnzipKit.xcodeproj/project.pbxproj +++ b/UnzipKit.xcodeproj/project.pbxproj @@ -21,6 +21,9 @@ 7A5652241F90E01C006B782E /* CheckDataTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 7A5652231F90E01C006B782E /* CheckDataTests.m */; }; 7A5A97011F89808900BCA061 /* ProgressReportingTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 7A5A97001F89808900BCA061 /* ProgressReportingTests.m */; }; 7AA77FC822C16CF600121052 /* PermissionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AA77FC722C16CF600121052 /* PermissionsTests.swift */; }; + 7AA9F194230199990076AD43 /* UZKArchive+Deprecated.h in Headers */ = {isa = PBXBuildFile; fileRef = 7AA9F192230199990076AD43 /* UZKArchive+Deprecated.h */; }; + 7AA9F195230199990076AD43 /* UZKArchive+Deprecated.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AA9F193230199990076AD43 /* UZKArchive+Deprecated.m */; }; + 7AB0E6AA22F3179F00AAE12F /* ZipFileProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB0E6A922F3179F00AAE12F /* ZipFileProperties.swift */; }; 961A9BB51B306881007C4C6B /* WriteDataTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 961A9BB41B306881007C4C6B /* WriteDataTests.swift */; }; 962F9DA61D5D286B00205BEC /* UnzipKit.strings in Resources */ = {isa = PBXBuildFile; fileRef = 962F9DA41D5D286B00205BEC /* UnzipKit.strings */; }; 962F9DA81D5D288B00205BEC /* UnzipKitResources.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 962F9D9E1D5D281E00205BEC /* UnzipKitResources.bundle */; }; @@ -82,6 +85,9 @@ 7A5652231F90E01C006B782E /* CheckDataTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CheckDataTests.m; sourceTree = ""; }; 7A5A97001F89808900BCA061 /* ProgressReportingTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ProgressReportingTests.m; sourceTree = ""; }; 7AA77FC722C16CF600121052 /* PermissionsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PermissionsTests.swift; sourceTree = ""; }; + 7AA9F192230199990076AD43 /* UZKArchive+Deprecated.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UZKArchive+Deprecated.h"; sourceTree = ""; }; + 7AA9F193230199990076AD43 /* UZKArchive+Deprecated.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UZKArchive+Deprecated.m"; sourceTree = ""; }; + 7AB0E6A922F3179F00AAE12F /* ZipFileProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZipFileProperties.swift; sourceTree = ""; }; 961A9BB31B306880007C4C6B /* UnzipKitTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = "UnzipKitTests-Bridging-Header.h"; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; 961A9BB41B306881007C4C6B /* WriteDataTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WriteDataTests.swift; sourceTree = ""; }; 961A9BB61B306902007C4C6B /* UZKArchiveTestCase.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = UZKArchiveTestCase.h; sourceTree = ""; }; @@ -225,6 +231,7 @@ 96EA65FF1A40E31900685B6D /* UZKFileInfo.h */, 963603521BFB7F6500BF0C4F /* UZKFileInfo_Private.h */, 96EA66001A40E31900685B6D /* UZKFileInfo.m */, + 7AB0E6A922F3179F00AAE12F /* ZipFileProperties.swift */, 96EA65A11A40AEAE00685B6D /* Supporting Files */, ); name = UnzipKit; @@ -245,6 +252,8 @@ 96EA65BF1A40BF1A00685B6D /* Test Data */, 961A9BB61B306902007C4C6B /* UZKArchiveTestCase.h */, 96FCC8401B306CDD00726AC7 /* UZKArchiveTestCase.m */, + 7AA9F192230199990076AD43 /* UZKArchive+Deprecated.h */, + 7AA9F193230199990076AD43 /* UZKArchive+Deprecated.m */, 963386B71EE89A49006B16BF /* UtilityMethods.swift */, 7A5652231F90E01C006B782E /* CheckDataTests.m */, 968C40DB1B586401004C128E /* CommentsTests.m */, @@ -338,6 +347,7 @@ buildActionMask = 2147483647; files = ( 96EA65BC1A40B2EC00685B6D /* UZKArchive.h in Headers */, + 7AA9F194230199990076AD43 /* UZKArchive+Deprecated.h in Headers */, 96EA66011A40E31900685B6D /* UZKFileInfo.h in Headers */, 96EA65A41A40AEAE00685B6D /* UnzipKit.h in Headers */, 963603531BFB815600BF0C4F /* UZKFileInfo_Private.h in Headers */, @@ -387,6 +397,7 @@ isa = PBXNativeTarget; buildConfigurationList = 96EA65B41A40AEAE00685B6D /* Build configuration list for PBXNativeTarget "UnzipKit" */; buildPhases = ( + 7A900754230AD74C004A95E5 /* ShellScript */, 96EA65991A40AEAE00685B6D /* Sources */, 96EA659A1A40AEAE00685B6D /* Frameworks */, 96EA659B1A40AEAE00685B6D /* Headers */, @@ -440,7 +451,7 @@ }; 96EA659D1A40AEAE00685B6D = { CreatedOnToolsVersion = 6.1.1; - LastSwiftMigration = 0830; + LastSwiftMigration = 1030; }; 96EA65A81A40AEAE00685B6D = { CreatedOnToolsVersion = 6.1.1; @@ -496,6 +507,26 @@ }; /* End PBXResourcesBuildPhase section */ +/* Begin PBXShellScriptBuildPhase section */ + 7A900754230AD74C004A95E5 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SOURCE_ROOT}\"/Scripts/generate-swift-import-header.sh\n"; + }; +/* End PBXShellScriptBuildPhase section */ + /* Begin PBXSourcesBuildPhase section */ 7A0029131F93DB5800618503 /* Sources */ = { isa = PBXSourcesBuildPhase; @@ -519,6 +550,8 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 7AB0E6AA22F3179F00AAE12F /* ZipFileProperties.swift in Sources */, + 7AA9F195230199990076AD43 /* UZKArchive+Deprecated.m in Sources */, 96EA65BD1A40B2EC00685B6D /* UZKArchive.m in Sources */, 96EA66021A40E31900685B6D /* UZKFileInfo.m in Sources */, 965CF00C1D241A8F00C80A88 /* NSURL+UnzipKitExtensions.m in Sources */, @@ -711,8 +744,8 @@ GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", "$(inherited)", + "DEBUG=1", ); GCC_SYMBOLS_PRIVATE_EXTERN = NO; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -808,6 +841,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; }; name = Debug; }; @@ -827,6 +861,7 @@ PRODUCT_BUNDLE_IDENTIFIER = "com.abbey-code.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; + SWIFT_VERSION = 5.0; }; name = Release; }; @@ -835,7 +870,10 @@ buildSettings = { CLANG_ENABLE_MODULES = YES; COMBINE_HIDPI_IMAGES = YES; - GCC_PREPROCESSOR_DEFINITIONS = "$(inherited)"; + GCC_PREPROCESSOR_DEFINITIONS = ( + "$(inherited)", + "CODE_IMPORTED_AS_LIB=1", + ); INFOPLIST_FILE = Tests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks $(DT_TOOLCHAIN_DIR)/usr/lib/swift/iphonesimulator"; PRODUCT_BUNDLE_IDENTIFIER = "com.abbey-code.$(PRODUCT_NAME:rfc1034identifier)"; @@ -861,6 +899,7 @@ buildSettings = { CLANG_ENABLE_MODULES = YES; COMBINE_HIDPI_IMAGES = YES; + GCC_PREPROCESSOR_DEFINITIONS = "CODE_IMPORTED_AS_LIB=1"; INFOPLIST_FILE = Tests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks $(DT_TOOLCHAIN_DIR)/usr/lib/swift/iphonesimulator"; PRODUCT_BUNDLE_IDENTIFIER = "com.abbey-code.$(PRODUCT_NAME:rfc1034identifier)"; diff --git a/UnzipKitDemo/Podfile b/UnzipKitDemo/Podfile index 4744369..01e588e 100644 --- a/UnzipKitDemo/Podfile +++ b/UnzipKitDemo/Podfile @@ -1,4 +1,4 @@ -platform :ios, '9.0' +platform :ios, '12.0' target 'UnzipKitDemo' do pod "UnzipKit", :path => "..", :testspecs => ['Tests'] diff --git a/UnzipKitDemo/UnzipKitDemo.xcodeproj/project.pbxproj b/UnzipKitDemo/UnzipKitDemo.xcodeproj/project.pbxproj index 695d821..598b422 100644 --- a/UnzipKitDemo/UnzipKitDemo.xcodeproj/project.pbxproj +++ b/UnzipKitDemo/UnzipKitDemo.xcodeproj/project.pbxproj @@ -14,12 +14,11 @@ 969E2FEC1AD573F100E19F7A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 969E2FEB1AD573F100E19F7A /* Images.xcassets */; }; 969E2FEF1AD573F100E19F7A /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 969E2FED1AD573F100E19F7A /* LaunchScreen.xib */; }; 969E30091AD57BD000E19F7A /* Test Data in Resources */ = {isa = PBXBuildFile; fileRef = 969E30081AD57BD000E19F7A /* Test Data */; }; - D058D1B1EDCAA17D37F1EEC5 /* libPods-UnzipKitDemo.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 836C8D3D2EBCC1B567DEBA7E /* libPods-UnzipKitDemo.a */; }; + E503D0FBEE04B7BC727B57B6 /* libPods-UnzipKitDemo.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D3795F70E736484A471E6F15 /* libPods-UnzipKitDemo.a */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 2F87D9A1D0DCA68C622CCE05 /* Pods-UnzipKitDemo.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-UnzipKitDemo.debug.xcconfig"; path = "Pods/Target Support Files/Pods-UnzipKitDemo/Pods-UnzipKitDemo.debug.xcconfig"; sourceTree = ""; }; - 836C8D3D2EBCC1B567DEBA7E /* libPods-UnzipKitDemo.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-UnzipKitDemo.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 969381281D209CF5001B2ED8 /* libPods-UnzipKitDemo.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = "libPods-UnzipKitDemo.a"; path = "../../../Library/Developer/Xcode/DerivedData/UnzipKitDemo-hdmwkglyskhqhncppjlrcqmehjld/Build/Products/Debug-iphonesimulator/libPods-UnzipKitDemo.a"; sourceTree = ""; }; 9693812A1D209CFA001B2ED8 /* libUnzipKit.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libUnzipKit.a; path = "../../../Library/Developer/Xcode/DerivedData/UnzipKitDemo-hdmwkglyskhqhncppjlrcqmehjld/Build/Products/Debug-iphonesimulator/UnzipKit/libUnzipKit.a"; sourceTree = ""; }; 969E2FDF1AD573F100E19F7A /* UnzipKitDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = UnzipKitDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -32,6 +31,7 @@ 969E30041AD5798D00E19F7A /* UnzipKitDemo-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UnzipKitDemo-Bridging-Header.h"; sourceTree = SOURCE_ROOT; }; 969E30081AD57BD000E19F7A /* Test Data */ = {isa = PBXFileReference; lastKnownFileType = folder; name = "Test Data"; path = "../../Tests/Test Data"; sourceTree = ""; }; D07031CAE5D79F16E6A159A8 /* Pods-UnzipKitDemo.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-UnzipKitDemo.release.xcconfig"; path = "Pods/Target Support Files/Pods-UnzipKitDemo/Pods-UnzipKitDemo.release.xcconfig"; sourceTree = ""; }; + D3795F70E736484A471E6F15 /* libPods-UnzipKitDemo.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-UnzipKitDemo.a"; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -40,7 +40,7 @@ buildActionMask = 2147483647; files = ( 9693812B1D209CFA001B2ED8 /* libUnzipKit.a in Frameworks */, - D058D1B1EDCAA17D37F1EEC5 /* libPods-UnzipKitDemo.a in Frameworks */, + E503D0FBEE04B7BC727B57B6 /* libPods-UnzipKitDemo.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -102,7 +102,7 @@ children = ( 9693812A1D209CFA001B2ED8 /* libUnzipKit.a */, 969381281D209CF5001B2ED8 /* libPods-UnzipKitDemo.a */, - 836C8D3D2EBCC1B567DEBA7E /* libPods-UnzipKitDemo.a */, + D3795F70E736484A471E6F15 /* libPods-UnzipKitDemo.a */, ); name = Frameworks; sourceTree = ""; @@ -118,7 +118,7 @@ 969E2FDB1AD573F100E19F7A /* Sources */, 969E2FDC1AD573F100E19F7A /* Frameworks */, 969E2FDD1AD573F100E19F7A /* Resources */, - FFBBDB5F70B02F25F7B1C9FA /* [CP] Copy Pods Resources */, + E71BD50F86C41C113D4362A9 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -196,13 +196,13 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - FFBBDB5F70B02F25F7B1C9FA /* [CP] Copy Pods Resources */ = { + E71BD50F86C41C113D4362A9 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( - "${SRCROOT}/Pods/Target Support Files/Pods-UnzipKitDemo/Pods-UnzipKitDemo-resources.sh", + "${PODS_ROOT}/Target Support Files/Pods-UnzipKitDemo/Pods-UnzipKitDemo-resources.sh", "${PODS_CONFIGURATION_BUILD_DIR}/UnzipKit/UnzipKitResources.bundle", ); name = "[CP] Copy Pods Resources"; @@ -211,7 +211,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-UnzipKitDemo/Pods-UnzipKitDemo-resources.sh\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-UnzipKitDemo/Pods-UnzipKitDemo-resources.sh\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ @@ -296,7 +296,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.2; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -347,7 +347,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.2; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; diff --git a/UnzipKitDemo/UnzipKitDemo.xcodeproj/xcshareddata/xcschemes/UnzipKitDemo.xcscheme b/UnzipKitDemo/UnzipKitDemo.xcodeproj/xcshareddata/xcschemes/UnzipKitDemo.xcscheme index 53691fb..fa4e044 100644 --- a/UnzipKitDemo/UnzipKitDemo.xcodeproj/xcshareddata/xcschemes/UnzipKitDemo.xcscheme +++ b/UnzipKitDemo/UnzipKitDemo.xcodeproj/xcshareddata/xcschemes/UnzipKitDemo.xcscheme @@ -1,6 +1,6 @@ From 4dde9ce0196ed266470016ca5a787c8005c157ee Mon Sep 17 00:00:00 2001 From: Dov Frankel Date: Thu, 7 Nov 2019 18:07:57 -0500 Subject: [PATCH 19/56] Fixed crasher in `+pathIsAZip:` (Issue #99) (#100) Catch the exception and log it, instead of letting it propagate --- CHANGELOG.md | 1 + Source/UZKArchive.m | 3 +++ 2 files changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a9bdf5f..4f51cf9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ * Removed methods deprecated in v1.9 (Issue #90, PR #92) * Fixed behavior of `-extractFilesTo:overwrite:error:`, so it shows the progress of each individual file as they extract (Issue #91, PR #94) * Deprecated the initializers that take a file path instead of an `NSURL` (Issue #90, PR #95) +* Fixed a crasher for unreadable files in `+pathIsAZip:` (Issue #99) ## 1.9 diff --git a/Source/UZKArchive.m b/Source/UZKArchive.m index cc687ab..e64f64f 100644 --- a/Source/UZKArchive.m +++ b/Source/UZKArchive.m @@ -311,6 +311,9 @@ + (BOOL)pathIsAZip:(NSString *)filePath UZKLogDebug("File is not a ZIP. Unknown contents in 3rd and 4th bytes (%02X %02X)", dataBytes[2], dataBytes[3]); } + @catch (NSException *e) { + UZKLogError("Error checking if %{public}@ is a Zip archive: %{public}@", filePath, e); + } @finally { [handle closeFile]; } From 9c53d8b4f7c16174a6953ca2ac6fcb0a56db4a9e Mon Sep 17 00:00:00 2001 From: Dov Frankel Date: Thu, 14 Nov 2019 07:52:42 -0500 Subject: [PATCH 20/56] Updated Travis build to use Xcode 11.2 (Issue #102) --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index f1e42cf..02857f9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,7 @@ language: - objective-c -osx_image: xcode10.2 +osx_image: xcode11.2 before_script: # Validation fails on CocoaPods versions before 1.8.0 From e9034a001c25135ca409164ad4591c3fb5b090a1 Mon Sep 17 00:00:00 2001 From: Dov Frankel Date: Thu, 14 Nov 2019 08:04:52 -0500 Subject: [PATCH 21/56] Updated iOS simulators to use iPhone 8 instead of iPHone 7 --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 02857f9..5fe6e78 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,12 +19,12 @@ matrix: - stage: Test env: Name=iOS # The CLANG arguments and find command fail the build on analyzer errors - script: xcodebuild -workspace UnzipKit.xcworkspace -scheme UnzipKit -destination 'platform=iOS Simulator,name=iPhone 7,OS=latest' -configuration Release analyze test ENABLE_NS_ASSERTIONS=1 CLANG_ANALYZER_OUTPUT=html CLANG_ANALYZER_OUTPUT_DIR=analyzer-output && [[ -z `find analyzer-output -name "*.html"` ]] + script: xcodebuild -workspace UnzipKit.xcworkspace -scheme UnzipKit -destination 'platform=iOS Simulator,name=iPhone 8,OS=latest' -configuration Release analyze test ENABLE_NS_ASSERTIONS=1 CLANG_ANALYZER_OUTPUT=html CLANG_ANALYZER_OUTPUT_DIR=analyzer-output && [[ -z `find analyzer-output -name "*.html"` ]] - stage: Test env: Name=DemoAppBuild # The CLANG arguments and find command fail the build on analyzer errors - script: ./Scripts/install-demo-libs.sh && xcodebuild -workspace UnzipKitDemo/UnzipKitDemo.xcworkspace -scheme UnzipKitDemo -destination 'platform=iOS Simulator,name=iPhone 7,OS=latest' -configuration Release analyze test ENABLE_NS_ASSERTIONS=1 CLANG_ANALYZER_OUTPUT=html CLANG_ANALYZER_OUTPUT_DIR=analyzer-output && [[ -z `find analyzer-output -name "*.html"` ]] + script: ./Scripts/install-demo-libs.sh && xcodebuild -workspace UnzipKitDemo/UnzipKitDemo.xcworkspace -scheme UnzipKitDemo -destination 'platform=iOS Simulator,name=iPhone 8,OS=latest' -configuration Release analyze test ENABLE_NS_ASSERTIONS=1 CLANG_ANALYZER_OUTPUT=html CLANG_ANALYZER_OUTPUT_DIR=analyzer-output && [[ -z `find analyzer-output -name "*.html"` ]] - stage: Validate env: Name=CocoaPods-Framework From fe723eb494c446d42300c3f18544dc4289aaf5d7 Mon Sep 17 00:00:00 2001 From: Dov Frankel Date: Thu, 14 Nov 2019 08:44:34 -0500 Subject: [PATCH 22/56] Removed CocoaPods upgrade, since the Xcode 11.2 image contains CocoaPods 1.8.1 --- .travis.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 5fe6e78..228cb9b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,8 +4,6 @@ language: osx_image: xcode11.2 before_script: - # Validation fails on CocoaPods versions before 1.8.0 - - gem install cocoapods --pre # Essentially turn off logging. Temporarily undo if more info is needed - sudo log config --mode "level:default" --subsystem com.abbey-code.UnzipKit From 51eede82f8e3bab3e10c76926fa921f26590e5a1 Mon Sep 17 00:00:00 2001 From: Dov Frankel Date: Thu, 13 Feb 2020 08:40:04 -0500 Subject: [PATCH 23/56] Updated changelog and readme for issue #89 --- CHANGELOG.md | 1 + README.md | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f51cf9..9819fd0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ * Fixed behavior of `-extractFilesTo:overwrite:error:`, so it shows the progress of each individual file as they extract (Issue #91, PR #94) * Deprecated the initializers that take a file path instead of an `NSURL` (Issue #90, PR #95) * Fixed a crasher for unreadable files in `+pathIsAZip:` (Issue #99) +* Deprecated all overloads of `-writeData:...` and `-writeIntoBuffer:...` that take any file properties other than the path, replacing them each with a single call that takes an instance of the new `ZipFileProperties`. This allows for all the default values to be defined in one place, so you can specify only where you want to deviate from the defaults (Issue #89, PR #97) ## 1.9 diff --git a/README.md b/README.md index 681f1b3..e8c0dbc 100644 --- a/README.md +++ b/README.md @@ -97,6 +97,30 @@ You can also modify Zip archives: filePath:@"dir/filename.jpg" error:&error]; ``` +* Write an in-memory `NSData` into the archive with custom properties + + **Objective-C** + + ```Objective-C + ZipFileProperties *props = [[ZipFileProperties alloc] init:someFile]; + props.password = @"secret"; + + BOOL success = [archive writeData:someFile + filePath:@"dir/filename.jpg" + error:&error]; + ``` + + **Swift** + + ```Swift + do { + let props = ZipFileProperties(filePath) + props.password = "secret" + try archive.write(someFile, props: props) + } catch let error as NSError { + NSLog("Error writing to file \(filePath): \(error)") + } + ``` * Write data as a stream to the archive (from disk or over the network), using a block: ```Objective-C From e766f115d9bcdbd1d5cff00eef1ff2e7d28758a2 Mon Sep 17 00:00:00 2001 From: Dov Frankel Date: Thu, 13 Feb 2020 08:40:47 -0500 Subject: [PATCH 24/56] Updated version to v2.0-beta --- Resources/UnzipKitResources-Info.plist | 4 ++-- Source/Info.plist | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Resources/UnzipKitResources-Info.plist b/Resources/UnzipKitResources-Info.plist index c04e70c..8fb6ef7 100644 --- a/Resources/UnzipKitResources-Info.plist +++ b/Resources/UnzipKitResources-Info.plist @@ -13,11 +13,11 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 1.9 + 2.0-beta CFBundleSignature ???? CFBundleVersion - 1.9 + 2.0-beta NSHumanReadableCopyright Copyright © 2019 Abbey Code. All rights reserved. NSPrincipalClass diff --git a/Source/Info.plist b/Source/Info.plist index 1e28140..efc90cd 100644 --- a/Source/Info.plist +++ b/Source/Info.plist @@ -15,11 +15,11 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 1.9 + 2.0-beta CFBundleSignature ???? CFBundleVersion - 1.9 + 2.0-beta NSHumanReadableCopyright © 2017 Abbey Code. All rights reserved. NSPrincipalClass From 6352eb64047fda69a0e5f36c3c8e869bd29afab8 Mon Sep 17 00:00:00 2001 From: Dov Frankel Date: Fri, 22 May 2020 00:54:47 -0400 Subject: [PATCH 25/56] Fixed a couple of Podspec errors --- UnzipKit.podspec | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/UnzipKit.podspec b/UnzipKit.podspec index ca3d3c4..0a33a0c 100644 --- a/UnzipKit.podspec +++ b/UnzipKit.podspec @@ -18,6 +18,7 @@ Pod::Spec.new do |s| s.private_header_files = "Source/UZKFileInfo_Private.h" s.source_files = "Source/**/*.{h,m,swift}" s.exclude_files = 'Resources/**/Info.plist' + s.preserve_paths = 'Scripts/generate-swift-import-header.sh' s.resource_bundles = { 'UnzipKitResources' => ['Resources/**/*'] } @@ -26,8 +27,7 @@ Pod::Spec.new do |s| :execution_position => :before_compile } s.test_spec 'Tests' do |test_spec| test_spec.source_files = 'Tests/*.{h,m,swift}' - test_spec.exclude_files = 'Tests/ExtractFilesTests.m', - 'Tests/UnzipKitTests-Bridging-Header.h' + test_spec.exclude_files = 'Tests/ExtractFilesTests.m' test_spec.preserve_paths = 'Tests/UnzipKitTests-Bridging-Header.h' test_spec.resources = ['Tests/Test Data'] test_spec.pod_target_xcconfig = { From 083bf389a4d03e444b17af77cc85ce19d824af4d Mon Sep 17 00:00:00 2001 From: Dov Frankel Date: Fri, 22 May 2020 10:54:24 -0400 Subject: [PATCH 26/56] Updated readme to include instructions on refreshing a CocoaPods token --- README.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/README.md b/README.md index e8c0dbc..d733c73 100644 --- a/README.md +++ b/README.md @@ -319,6 +319,28 @@ Before pushing a build, you must: Once that's done, you can call `git push --follow-tags` [1](#f1), and let [Travis CI](https://travis-ci.org/abbeycode/UnzipKit/builds) take care of the rest. +## Re-registering with CocoaPods + +If you see this message in the Release stage of the build, you need to get a new authentication token from CocaPods: + +> Authentication token is invalid or unverified. Either verify it with the email that was sent or register a new session + +1. Locally, call `pod trunk register _pod owner email address_ --description='_describe machine_'` +1. Click the verification link in the email that CocoaPods sends +1. Open ~/.netrc, and look for the section like this: + machine trunk.cocoapods.org + login user@example.com + password 0000000011111111 +1. Update the token in Travis CI: + 1. Go to the [plan settings](https://travis-ci.org/github/abbeycode/UnzipKit/settings) + 1. Delete `COCOAPODS_TRUNK_TOKEN` + 1. Create a new Environment Variable: + * Name: *COCOAPODS_TRUNK_TOKEN* + * Value: *0000000011111111* (substituting the value from you local machine found above) + * Branch: *All Branches* + * Display Value in Build Log: *Leave unchecked* +1. Re-run the Release stage + # License * UnzipKit: [See LICENSE (BSD)](LICENSE) From 62266afc476aeccce678aac0db0bc898fc2e7b20 Mon Sep 17 00:00:00 2001 From: Dov Frankel Date: Fri, 22 May 2020 10:56:56 -0400 Subject: [PATCH 27/56] Changed authentication with GitHub to use an Authorization header instead of an access_token query param, according to GitHub's instructions here: https://developer.github.com/changes/2020-02-10-deprecating-auth-through-query-param/ --- Scripts/add-github-release.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/Scripts/add-github-release.py b/Scripts/add-github-release.py index 0e0142f..617190c 100755 --- a/Scripts/add-github-release.py +++ b/Scripts/add-github-release.py @@ -26,7 +26,10 @@ def add_release(token, repo, tag, archive_path, notes): is_beta = tag_is_beta(tag) - url = 'https://api.github.com/repos/{}/releases?access_token={}'.format(repo, token) + url = 'https://api.github.com/repos/{}/releases'.format(repo) + header = { + 'Authorization': 'token {}'.format(token) + } values = { 'tag_name': tag, 'name': 'v{}'.format(tag), @@ -35,7 +38,7 @@ def add_release(token, repo, tag, archive_path, notes): } data = json.dumps(values) - request = urllib2.Request(url, data) + request = urllib2.Request(url, data, header) response = urllib2.urlopen(request) the_page = response.read() @@ -80,8 +83,11 @@ def upload_carthage_archive(token, upload_url, archive_path): ''' upload_url = upload_url.split('{')[0] - url = '{}?access_token={}&name={}'.format(upload_url, token, archive_path) - header = {'Content-Type': 'application/zip'} + url = '{}?name={}'.format(upload_url, archive_path) + header = { + 'Authorization': 'token {}'.format(token), + 'Content-Type': 'application/zip' + } with FileWithLen(archive_path, 'r') as f: request = urllib2.Request(url, f, header) From 5742af0ed608275ce29b1084c7a8991b8ba4e743 Mon Sep 17 00:00:00 2001 From: Dov Frankel Date: Tue, 26 May 2020 22:24:42 -0400 Subject: [PATCH 28/56] Made semicolon usage in macros consistent (all macros now lack a trailing semicolon) --- Source/UZKArchive.m | 8 ++++---- Source/UnzipKitMacros.h | 14 +++++++------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Source/UZKArchive.m b/Source/UZKArchive.m index e64f64f..ff6032a 100644 --- a/Source/UZKArchive.m +++ b/Source/UZKArchive.m @@ -124,7 +124,7 @@ - (instancetype)initWithFile:(NSURL *)fileURL password:(NSString*)password error UZKCreateActivity("Init Archive"); if (!fileURL) { - UZKLogError("Nil fileURL passed to UZKArchive initializer") + UZKLogError("Nil fileURL passed to UZKArchive initializer"); return nil; } @@ -142,7 +142,7 @@ - (instancetype)initWithFile:(NSURL *)fileURL password:(NSString*)password error return nil; } } else { - UZKLogInfo("URL %{public}@ doesn't yet exist", fileURL) + UZKLogInfo("URL %{public}@ doesn't yet exist", fileURL); } UZKLogDebug("Initializing private fields"); @@ -531,7 +531,7 @@ - (BOOL)extractFilesTo:(NSString *)destinationDirectory } if (info.isDirectory) { - UZKLogDebug("Created empty directory") + UZKLogDebug("Created empty directory"); continue; } @@ -962,7 +962,7 @@ - (BOOL)checkDataIntegrityOfFile:(NSString *)filePath if (extractedCRC != fileInfo.CRC) { UZKLogError("CRC mismatch in '%{public}@': expected %010lu, found %010lu", - fileInfo.filename, (uLong)fileInfo.CRC, extractedCRC) + fileInfo.filename, (uLong)fileInfo.CRC, extractedCRC); dataIsValid = NO; } diff --git a/Source/UnzipKitMacros.h b/Source/UnzipKitMacros.h index c68438d..1fa340e 100644 --- a/Source/UnzipKitMacros.h +++ b/Source/UnzipKitMacros.h @@ -48,25 +48,25 @@ extern BOOL unzipkitIsAtLeast10_13SDK; // Declared in UZKArchive.m minVersion.minorVersion = SDK_10_13_MINOR; \ minVersion.patchVersion = 0; \ unzipkitIsAtLeast10_13SDK = [[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:minVersion]; \ - UZKLogDebug("Is >= 10.13 (or iOS 11): %@", unzipkitIsAtLeast10_13SDK ? @"YES" : @"NO"); + UZKLogDebug("Is >= 10.13 (or iOS 11): %@", unzipkitIsAtLeast10_13SDK ? @"YES" : @"NO") -#define UZKLog(format, ...) os_log(unzipkit_log, format, ##__VA_ARGS__); -#define UZKLogInfo(format, ...) os_log_info(unzipkit_log, format, ##__VA_ARGS__); -#define UZKLogDebug(format, ...) os_log_debug(unzipkit_log, format, ##__VA_ARGS__); +#define UZKLog(format, ...) os_log(unzipkit_log, format, ##__VA_ARGS__) +#define UZKLogInfo(format, ...) os_log_info(unzipkit_log, format, ##__VA_ARGS__) +#define UZKLogDebug(format, ...) os_log_debug(unzipkit_log, format, ##__VA_ARGS__) #define UZKLogError(format, ...) \ if (unzipkitIsAtLeast10_13SDK) os_log_error(unzipkit_log, format, ##__VA_ARGS__); \ - else os_log_with_type(unzipkit_log, OS_LOG_TYPE_ERROR, format, ##__VA_ARGS__); + else os_log_with_type(unzipkit_log, OS_LOG_TYPE_ERROR, format, ##__VA_ARGS__) #define UZKLogFault(format, ...) \ if (unzipkitIsAtLeast10_13SDK) os_log_fault(unzipkit_log, format, ##__VA_ARGS__); \ - else os_log_with_type(unzipkit_log, OS_LOG_TYPE_FAULT, format, ##__VA_ARGS__); + else os_log_with_type(unzipkit_log, OS_LOG_TYPE_FAULT, format, ##__VA_ARGS__) #define UZKCreateActivity(name) \ os_activity_t activity = os_activity_create(name, OS_ACTIVITY_CURRENT, OS_ACTIVITY_FLAG_DEFAULT); \ - os_activity_scope(activity); + os_activity_scope(activity) #else // Fall back to regular NSLog From 8b25f559ef52b7d51d6302e0f55a986684473d9b Mon Sep 17 00:00:00 2001 From: Dov Frankel Date: Tue, 26 May 2020 22:25:31 -0400 Subject: [PATCH 29/56] Fixed Swift warning --- Source/ZipFileProperties.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/ZipFileProperties.swift b/Source/ZipFileProperties.swift index e5244e3..8e8c4a2 100644 --- a/Source/ZipFileProperties.swift +++ b/Source/ZipFileProperties.swift @@ -66,7 +66,7 @@ import Foundation let permissionStr = String(permissions, radix: 8).uppercased() return """ - { fullFilePath: \(fullFilePath), compressionMethod: \(compressionMethod), crc: \(crcStr), + { fullFilePath: \(fullFilePath), compressionMethod: \(compressionMethod.rawValue), crc: \(crcStr), overwriteIfInArchive: \(overwriteIfInArchive), password: \(password), permissions: \(permissionStr), timestamp: \(timestamp?.description ?? "none") } """.replacingOccurrences(of: "\n", with: " "); From 4b6e5fa511a090542a14bfc37df2ca89e42dcd40 Mon Sep 17 00:00:00 2001 From: Dov Frankel Date: Tue, 26 May 2020 22:28:25 -0400 Subject: [PATCH 30/56] Replaced all weak self-references inside of blocks with strong references --- Source/UZKArchive.m | 178 ++++++++++++++++++++++++++++---------------- 1 file changed, 112 insertions(+), 66 deletions(-) diff --git a/Source/UZKArchive.m b/Source/UZKArchive.m index ff6032a..e196159 100644 --- a/Source/UZKArchive.m +++ b/Source/UZKArchive.m @@ -374,17 +374,23 @@ + (BOOL)urlIsAZip:(NSURL *)fileURL BOOL success = [self performActionWithArchiveOpen:^(NSError * __autoreleasing*innerError) { UZKCreateActivity("Finding File Info Items"); + __strong UZKArchive *sself = welf; + if (!sself) { + UZKLogInfo("UZKArchive instance has been deallocated. Exiting listFileInfo:error: action"); + return; + } + UZKLogInfo("Getting global info..."); - unzGoToNextFile(welf.unzFile); + unzGoToNextFile(sself.unzFile); unz_global_info gi; - int err = unzGetGlobalInfo(welf.unzFile, &gi); + int err = unzGetGlobalInfo(sself.unzFile, &gi); if (err != UNZ_OK) { NSString *detail = [NSString localizedStringWithFormat:NSLocalizedStringFromTableInBundle(@"Error getting global info (%d)", @"UnzipKit", _resources, @"Detailed error string"), err]; UZKLogError("UZKErrorCodeArchiveNotFound: %{public}@", detail); - [welf assignError:innerError code:UZKErrorCodeArchiveNotFound - detail:detail]; + [sself assignError:innerError code:UZKErrorCodeArchiveNotFound + detail:detail]; return; } @@ -392,20 +398,20 @@ + (BOOL)urlIsAZip:(NSURL *)fileURL UZKLogDebug("fileCount: %lu", (unsigned long)fileCount); UZKLogInfo("Going to first file..."); - err = unzGoToFirstFile(welf.unzFile); + err = unzGoToFirstFile(sself.unzFile); if (err != UNZ_OK) { NSString *detail = [NSString localizedStringWithFormat:NSLocalizedStringFromTableInBundle(@"Error going to first file in archive (%d)", @"UnzipKit", _resources, @"Detailed error string"), err]; UZKLogError("UZKErrorCodeFileNavigationError: %{public}@", detail); - [welf assignError:innerError code:UZKErrorCodeFileNavigationError - detail:detail]; + [sself assignError:innerError code:UZKErrorCodeFileNavigationError + detail:detail]; return; } for (NSUInteger i = 0; i < fileCount; i++) { UZKLogDebug("Iterating through file info (iteration #%lu)", (unsigned long)i+1); - UZKFileInfo *info = [welf currentFileInZipInfo:innerError]; + UZKFileInfo *info = [sself currentFileInZipInfo:innerError]; if (info) { UZKLogDebug("Info found: %{public}@", info.filename); @@ -416,7 +422,7 @@ + (BOOL)urlIsAZip:(NSURL *)fileURL } UZKLogDebug("Going to next file..."); - err = unzGoToNextFile(welf.unzFile); + err = unzGoToNextFile(sself.unzFile); if (err == UNZ_END_OF_LIST_OF_FILE) { UZKLogInfo("End of file found"); return; @@ -426,8 +432,8 @@ + (BOOL)urlIsAZip:(NSURL *)fileURL NSString *detail = [NSString localizedStringWithFormat:NSLocalizedStringFromTableInBundle(@"Error navigating to next file (%d)", @"UnzipKit", _resources, @"Detailed error string"), err]; UZKLogError("UZKErrorCodeFileNavigationError: %{public}@", detail); - [welf assignError:innerError code:UZKErrorCodeFileNavigationError - detail:detail]; + [sself assignError:innerError code:UZKErrorCodeFileNavigationError + detail:detail]; return; } } @@ -480,7 +486,12 @@ - (BOOL)extractFilesTo:(NSString *)destinationDirectory UZKCreateActivity("Performing Extraction"); NSError *strongError = nil; - + __strong UZKArchive *sself = welf; + if (!sself) { + UZKLogInfo("UZKArchive instance has been deallocated. Exiting extractFilesTo:overwrite:error: action"); + return; + } + @try { for (UZKFileInfo *info in fileInfo) { UZKLogDebug("Extracting %{public}@ to disk", info.filename); @@ -489,8 +500,8 @@ - (BOOL)extractFilesTo:(NSString *)destinationDirectory NSString *detail = [NSString localizedStringWithFormat:NSLocalizedStringFromTableInBundle(@"Error locating file '%@' in archive", @"UnzipKit", _resources, @"Detailed error string"), info.filename]; UZKLogError("Halted file extraction due to user cancellation: %{public}@", detail); - [welf assignError:&strongError code:UZKErrorCodeUserCancelled - detail:detail]; + [sself assignError:&strongError code:UZKErrorCodeUserCancelled + detail:detail]; return; } @@ -499,8 +510,8 @@ - (BOOL)extractFilesTo:(NSString *)destinationDirectory NSString *detail = [NSString localizedStringWithFormat:NSLocalizedStringFromTableInBundle(@"Error locating file '%@' in archive", @"UnzipKit", _resources, @"Detailed error string"), info.filename]; UZKLogError("UZKErrorCodeFileNotFoundInArchive: %{public}@", detail); - [welf assignError:&strongError code:UZKErrorCodeFileNotFoundInArchive - detail:detail]; + [sself assignError:&strongError code:UZKErrorCodeFileNotFoundInArchive + detail:detail]; return; } @@ -524,8 +535,8 @@ - (BOOL)extractFilesTo:(NSString *)destinationDirectory NSString *detail = [NSString localizedStringWithFormat:NSLocalizedStringFromTableInBundle(@"Failed to create destination directory: %@", @"UnzipKit", _resources, @"Detailed error string"), extractDir]; UZKLogError("UZKErrorCodeOutputError: %{public}@", detail); - [welf assignError:&strongError code:UZKErrorCodeOutputError - detail:detail]; + [sself assignError:&strongError code:UZKErrorCodeOutputError + detail:detail]; return; } } @@ -552,8 +563,8 @@ - (BOOL)extractFilesTo:(NSString *)destinationDirectory NSString *detail = [NSString localizedStringWithFormat:NSLocalizedStringFromTableInBundle(@"Error creating current file (%d) '%@'", @"UnzipKit", _resources, @"Detailed error string"), strongError, info.filename]; UZKLogError("UZKErrorCodeOutputError: %{public}@", detail); - [welf assignError:&strongError code:UZKErrorCodeOutputError - detail:detail]; + [sself assignError:&strongError code:UZKErrorCodeOutputError + detail:detail]; return; } @@ -566,22 +577,22 @@ - (BOOL)extractFilesTo:(NSString *)destinationDirectory NSString *detail = [NSString localizedStringWithFormat:NSLocalizedStringFromTableInBundle(@"Error writing to file: %@", @"UnzipKit", _resources, @"Detailed error string"), deflatedFileURL]; UZKLogError("UZKErrorCodeOutputError: %{public}@", detail); - [welf assignError:&strongError code:UZKErrorCodeOutputError - detail:detail]; + [sself assignError:&strongError code:UZKErrorCodeOutputError + detail:detail]; return; } [progress becomeCurrentWithPendingUnitCount:info.uncompressedSize]; UZKLogDebug("Extracting buffered data"); - BOOL extractSuccess = [welf extractBufferedDataFromFile:info.filename - error:&strongError - action: - ^(NSData *dataChunk, CGFloat percentDecompressed) { - UZKLogDebug("Writing data chunk of size %lu (%lld total so far)", (unsigned long)dataChunk.length, bytesDecompressed); - bytesDecompressed += dataChunk.length; - [deflatedFileHandle writeData:dataChunk]; - }]; + BOOL extractSuccess = [sself extractBufferedDataFromFile:info.filename + error:&strongError + action: + ^(NSData *dataChunk, CGFloat percentDecompressed) { + UZKLogDebug("Writing data chunk of size %lu (%lld total so far)", (unsigned long)dataChunk.length, bytesDecompressed); + bytesDecompressed += dataChunk.length; + [deflatedFileHandle writeData:dataChunk]; + }]; [progress resignCurrent]; @@ -730,20 +741,26 @@ - (BOOL)performOnDataInArchive:(void (^)(UZKFileInfo *, NSData *, BOOL *))action return [self performOnFilesInArchive:^(UZKFileInfo *fileInfo, BOOL *stop) { UZKLogInfo("Locating file %{public}@", fileInfo.filename); - if (![welf locateFileInZip:fileInfo.filename error:error]) { + __strong UZKArchive *sself = welf; + if (!sself) { + UZKLogInfo("UZKArchive instance has been deallocated. Exiting performOnDataInArchive:error: action"); + return; + } + + if (![sself locateFileInZip:fileInfo.filename error:error]) { NSString *detail = [NSString localizedStringWithFormat:NSLocalizedStringFromTableInBundle(@"Failed to locate '%@' in archive during-perform on-data operation", @"UnzipKit", _resources, @"Detailed error string"), fileInfo.filename]; UZKLogError("UZKErrorCodeFileNotFoundInArchive: %{public}@", detail); - [welf assignError:error code:UZKErrorCodeFileNotFoundInArchive - detail:detail]; + [sself assignError:error code:UZKErrorCodeFileNotFoundInArchive + detail:detail]; return; } UZKLogInfo("Reading file from archive"); - NSData *fileData = [welf readFile:fileInfo.filename - length:fileInfo.uncompressedSize - error:error]; + NSData *fileData = [sself readFile:fileInfo.filename + length:fileInfo.uncompressedSize + error:error]; if (!fileData) { UZKLogError("Error reading file %{public}@ in archive", fileInfo.filename); @@ -767,17 +784,23 @@ - (BOOL)extractBufferedDataFromFile:(NSString *)filePath NSUInteger bufferSize = 1024 * 256; // 256 kb, arbitrary BOOL success = [self performActionWithArchiveOpen:^(NSError * __autoreleasing*innerError) { - if (![welf locateFileInZip:filePath error:innerError]) { + __strong UZKArchive *sself = welf; + if (!sself) { + UZKLogInfo("UZKArchive instance has been deallocated. Exiting extractBufferedDataFromFile:error:action: action"); + return; + } + + if (![sself locateFileInZip:filePath error:innerError]) { NSString *detail = [NSString localizedStringWithFormat:NSLocalizedStringFromTableInBundle(@"Failed to locate '%@' in archive during buffered read", @"UnzipKit", _resources, @"Detailed error string"), filePath]; UZKLogError("UZKErrorCodeFileNotFoundInArchive: %{public}@", detail); - [welf assignError:innerError code:UZKErrorCodeFileNotFoundInArchive - detail:detail]; + [sself assignError:innerError code:UZKErrorCodeFileNotFoundInArchive + detail:detail]; return; } UZKLogInfo("Getting file info"); - UZKFileInfo *info = [welf currentFileInZipInfo:innerError]; + UZKFileInfo *info = [sself currentFileInZipInfo:innerError]; if (!info) { UZKLogError("Failed to get info of file %{public}@ in archive", filePath); @@ -787,7 +810,7 @@ - (BOOL)extractBufferedDataFromFile:(NSString *)filePath progress.totalUnitCount = info.uncompressedSize; UZKLogInfo("Opening file"); - if (![welf openFile:innerError]) { + if (![sself openFile:innerError]) { UZKLogError("Failed to open file %{public}@ in archive", filePath); return; } @@ -806,14 +829,14 @@ - (BOOL)extractBufferedDataFromFile:(NSString *)filePath @autoreleasepool { UZKLogDebug("Reading file data"); NSMutableData *data = [NSMutableData dataWithLength:bufferSize]; - int bytesRead = unzReadCurrentFile(welf.unzFile, data.mutableBytes, (unsigned)bufferSize); + int bytesRead = unzReadCurrentFile(sself.unzFile, data.mutableBytes, (unsigned)bufferSize); if (bytesRead < 0) { NSString *detail = [NSString localizedStringWithFormat:NSLocalizedStringFromTableInBundle(@"Failed to read file %@ in zip", @"UnzipKit", _resources, @"Detailed error string"), info.filename]; UZKLogError("Error reading data (code %d): %{public}@", bytesRead, detail); - [welf assignError:&strongInnerError code:bytesRead - detail:detail]; + [sself assignError:&strongInnerError code:bytesRead + detail:detail]; break; } else if (bytesRead == 0) { @@ -841,7 +864,7 @@ - (BOOL)extractBufferedDataFromFile:(NSString *)filePath } UZKLogInfo("Closing file..."); - int err = unzCloseCurrentFile(welf.unzFile); + int err = unzCloseCurrentFile(sself.unzFile); if (err != UNZ_OK) { if (err == UZKErrorCodeCRCError) { err = UZKErrorCodeInvalidPassword; @@ -849,8 +872,8 @@ - (BOOL)extractBufferedDataFromFile:(NSString *)filePath NSString *detail = NSLocalizedStringFromTableInBundle(@"Error closing current file during buffered read", @"UnzipKit", _resources, @"Detailed error string"); UZKLogError("Error closing file (code %d): %{public}@", err, detail); - [welf assignError:innerError code:err - detail:detail]; + [sself assignError:innerError code:err + detail:detail]; return; } @@ -1086,6 +1109,12 @@ - (BOOL)writeData:(NSData *)data BOOL success = [self performWriteAction:^int(uLong *crc, NSError * __autoreleasing*innerError) { UZKCreateActivity("Performing File Write"); + __strong UZKArchive *sself = welf; + if (!sself) { + UZKLogInfo("UZKArchive instance has been deallocated. Exiting writeData:props:error: action"); + return ZIP_BADZIPFILE; + } + NSAssert(crc, @"No CRC reference passed", nil); *crc = calculatedCRC; @@ -1096,7 +1125,7 @@ - (BOOL)writeData:(NSData *)data unsigned int dataRemaining = (unsigned int)(data.length - i); unsigned int size = (unsigned int)(dataRemaining < bufferSize ? dataRemaining : bufferSize); - int err = zipWriteInFileInZip(welf.zipFile, (const char *)bytes + i, size); + int err = zipWriteInFileInZip(sself.zipFile, (const char *)bytes + i, size); if (err != ZIP_OK) { UZKLogError("Error writing data: %d", err); @@ -1239,6 +1268,12 @@ - (BOOL)writeIntoBuffer:(ZipFileProperties *)props BOOL success = [self performWriteAction:^int(uLong *crc, NSError * __autoreleasing*innerError) { UZKCreateActivity("Performing File Write"); + __strong UZKArchive *sself = welf; + if (!sself) { + UZKLogInfo("UZKArchive instance has been deallocated. Exiting writeIntoBuffer:error:block: action"); + return ZIP_BADZIPFILE; + } + NSAssert(crc, @"No CRC reference passed", nil); if (!action) { @@ -1269,8 +1304,8 @@ - (BOOL)writeIntoBuffer:(ZipFileProperties *)props NSLocalizedStringFromTableInBundle(@"Incorrect CRC provided\n%@ given\n%@ calculated", @"UnzipKit", _resources, @"CRC mismatch error detail"), preCRCStr, calculatedCRCStr]; UZKLogError("UZKErrorCodePreCRCMismatch: %{public}@", detail); - return [welf assignError:innerError code:UZKErrorCodePreCRCMismatch - detail:detail]; + return [sself assignError:innerError code:UZKErrorCodePreCRCMismatch + detail:detail]; } return result; @@ -1859,6 +1894,12 @@ - (BOOL)performWriteAction:(int(^)(uLong *crc, NSError * __autoreleasing*innerEr BOOL success = [self performActionWithArchiveOpen:^(NSError * __autoreleasing*innerError) { UZKCreateActivity("Performing Write Action"); + __strong UZKArchive *sself = welf; + if (!sself) { + UZKLogInfo("UZKArchive instance has been deallocated. Exiting performWriteAction:props:error: action"); + return; + } + UZKLogDebug("Making zip_fileinfo struct for date %{time_t}ld", lrint(props.timestamp.timeIntervalSince1970)); zip_fileinfo zi = [UZKArchive zipFileInfoForDate:props.timestamp posixPermissions:props.permissions]; @@ -1871,7 +1912,7 @@ - (BOOL)performWriteAction:(int(^)(uLong *crc, NSError * __autoreleasing*innerEr } UZKLogDebug("Opening new file..."); - int err = zipOpenNewFileInZip3(welf.zipFile, + int err = zipOpenNewFileInZip3(sself.zipFile, props.fullFilePath.UTF8String, &zi, NULL, 0, NULL, 0, NULL, @@ -1886,8 +1927,8 @@ - (BOOL)performWriteAction:(int(^)(uLong *crc, NSError * __autoreleasing*innerEr NSString *detail = [NSString localizedStringWithFormat:NSLocalizedStringFromTableInBundle(@"Error opening file '%@' for write (%d)", @"UnzipKit", _resources, @"Detailed error string"), props.fullFilePath, err]; UZKLogError("UZKErrorCodeFileOpenForWrite: %{public}@", detail); - [welf assignError:innerError code:UZKErrorCodeFileOpenForWrite - detail:detail]; + [sself assignError:innerError code:UZKErrorCodeFileOpenForWrite + detail:detail]; return; } @@ -1898,8 +1939,8 @@ - (BOOL)performWriteAction:(int(^)(uLong *crc, NSError * __autoreleasing*innerEr NSString *detail = [NSString localizedStringWithFormat:NSLocalizedStringFromTableInBundle(@"Error writing to file '%@' (%d)", @"UnzipKit", _resources, @"Detailed error string"), props.fullFilePath, err]; UZKLogError("UZKErrorCodeFileOpenForWrite: %{public}@", detail); - [welf assignError:innerError code:UZKErrorCodeFileWrite - detail:detail]; + [sself assignError:innerError code:UZKErrorCodeFileWrite + detail:detail]; return; } @@ -1909,8 +1950,8 @@ - (BOOL)performWriteAction:(int(^)(uLong *crc, NSError * __autoreleasing*innerEr NSString *detail = [NSString localizedStringWithFormat:NSLocalizedStringFromTableInBundle(@"Error closing file '%@' for write (%d)", @"UnzipKit", _resources, @"Detailed error string"), props.fullFilePath, err]; UZKLogError("UZKErrorCodeFileOpenForWrite: %{public}@", detail); - [welf assignError:innerError code:UZKErrorCodeFileWrite - detail:detail]; + [sself assignError:innerError code:UZKErrorCodeFileWrite + detail:detail]; return; } @@ -1925,7 +1966,6 @@ - (BOOL)openFile:(NSString *)zipFile error:(NSError * __autoreleasing*)error { UZKCreateActivity("Opening File"); - UZKLogDebug("Opening file in mode %lu", (unsigned long)mode); if (error) { @@ -2315,17 +2355,23 @@ - (NSString *)readGlobalComment { BOOL success = [self performActionWithArchiveOpen:^(NSError * __autoreleasing*innerError) { UZKCreateActivity("Perform Action"); + __strong UZKArchive *sself = welf; + if (!sself) { + UZKLogInfo("UZKArchive instance has been deallocated. Exiting readGlobalComment action"); + return; + } + UZKLogDebug("Getting global info..."); unz_global_info global_info; - int err = unzGetGlobalInfo(welf.unzFile, &global_info); + int err = unzGetGlobalInfo(sself.unzFile, &global_info); if (err != UNZ_OK) { NSString *detail = [NSString localizedStringWithFormat:NSLocalizedStringFromTableInBundle(@"Error getting global info of archive during comment read: %d", @"UnzipKit", _resources, @"Detailed error string"), err]; UZKLogError("UZKErrorCodeReadComment: %{public}@", detail); UZKLogDebug("Closing archive..."); - unzClose(welf.unzFile); + unzClose(sself.unzFile); - [welf assignError:innerError code:UZKErrorCodeReadComment detail:detail]; + [sself assignError:innerError code:UZKErrorCodeReadComment detail:detail]; return; } @@ -2339,21 +2385,21 @@ - (NSString *)readGlobalComment { NSString *detail = NSLocalizedStringFromTableInBundle(@"Error allocating the global comment during comment read", @"UnzipKit", _resources, @"Detailed error string"); UZKLogError("UZKErrorCodeReadComment: %{public}@", detail); UZKLogDebug("Closing archive..."); - unzClose(welf.unzFile); + unzClose(sself.unzFile); - [welf assignError:innerError code:UZKErrorCodeReadComment detail:detail]; + [sself assignError:innerError code:UZKErrorCodeReadComment detail:detail]; return; } UZKLogDebug("Reading global comment..."); - if ((unsigned int)unzGetGlobalComment(welf.unzFile, global_comment, global_info.size_comment + 1) != global_info.size_comment) { + if ((unsigned int)unzGetGlobalComment(sself.unzFile, global_comment, global_info.size_comment + 1) != global_info.size_comment) { NSString *detail = NSLocalizedStringFromTableInBundle(@"Error reading the comment (readGlobalComment)", @"UnzipKit", _resources, @"Detailed error string"); UZKLogError("UZKErrorCodeReadComment: %{public}@", detail); UZKLogDebug("Closing archive and freeing global_comment..."); - unzClose(welf.unzFile); + unzClose(sself.unzFile); free(global_comment); - [welf assignError:innerError code:UZKErrorCodeReadComment detail:@"Error reading global comment (unzGetGlobalComment)"]; + [sself assignError:innerError code:UZKErrorCodeReadComment detail:@"Error reading global comment (unzGetGlobalComment)"]; return; } From fecac350aa98b2f7d4059e43e49ea5202c978d27 Mon Sep 17 00:00:00 2001 From: Dov Frankel Date: Tue, 26 May 2020 22:30:52 -0400 Subject: [PATCH 31/56] Preemptively fixed some potential lurking bugs --- Source/UZKArchive.m | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Source/UZKArchive.m b/Source/UZKArchive.m index e196159..8f822e7 100644 --- a/Source/UZKArchive.m +++ b/Source/UZKArchive.m @@ -2023,7 +2023,7 @@ - (BOOL)openFile:(NSString *)zipFile } UZKLogDebug("Opening file for read..."); - self.unzFile = unzOpen(self.filename.UTF8String); + self.unzFile = unzOpen64(self.filename.UTF8String); if (self.unzFile == NULL) { NSString *detail = [NSString localizedStringWithFormat:NSLocalizedStringFromTableInBundle(@"Error opening zip file %@", @"UnzipKit", _resources, @"Detailed error string"), zipFile]; @@ -2342,8 +2342,9 @@ - (NSString *)readGlobalComment { UZKLogDebug("Checking archive exists"); + NSURL *fileURL = self.fileURL; NSError *checkExistsError = nil; - if (![self.fileURL checkResourceIsReachableAndReturnError:&checkExistsError]) { + if (!fileURL || ![fileURL checkResourceIsReachableAndReturnError:&checkExistsError]) { UZKLogDebug("Archive not found"); return nil; } @@ -2434,15 +2435,15 @@ - (BOOL)storeFileBookmark:(NSURL *)fileURL error:(NSError * __autoreleasing*)err relativeToURL:nil error:&bookmarkError]; - if (bookmarkError) { + if (!self.fileBookmark) { UZKLogFault("Error creating bookmark for URL %{public}@: %{public}@", fileURL, bookmarkError); } if (error) { - *error = bookmarkError ? bookmarkError : nil; + *error = bookmarkError; } - return bookmarkError == nil; + return self.fileBookmark != nil; } + (NSString *)figureOutCString:(const char *)filenameBytes From b87117474e2e35ae37da1ac1477fce60a56779c4 Mon Sep 17 00:00:00 2001 From: Dov Frankel Date: Tue, 26 May 2020 23:38:35 -0400 Subject: [PATCH 32/56] Updated version to v2.0-beta2 --- Resources/UnzipKitResources-Info.plist | 4 ++-- Source/Info.plist | 4 ++-- beta-notes.md | 6 +++++- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/Resources/UnzipKitResources-Info.plist b/Resources/UnzipKitResources-Info.plist index 8fb6ef7..1ca6dc9 100644 --- a/Resources/UnzipKitResources-Info.plist +++ b/Resources/UnzipKitResources-Info.plist @@ -13,11 +13,11 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 2.0-beta + 2.0-beta2 CFBundleSignature ???? CFBundleVersion - 2.0-beta + 2.0-beta2 NSHumanReadableCopyright Copyright © 2019 Abbey Code. All rights reserved. NSPrincipalClass diff --git a/Source/Info.plist b/Source/Info.plist index efc90cd..861f1d9 100644 --- a/Source/Info.plist +++ b/Source/Info.plist @@ -15,11 +15,11 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.0-beta + 2.0-beta2 CFBundleSignature ???? CFBundleVersion - 2.0-beta + 2.0-beta2 NSHumanReadableCopyright © 2017 Abbey Code. All rights reserved. NSPrincipalClass diff --git a/beta-notes.md b/beta-notes.md index 5b6bbcd..9e18975 100644 --- a/beta-notes.md +++ b/beta-notes.md @@ -1,3 +1,7 @@ * Removed methods deprecated in v1.9 (Issue #90, PR #92) * Fixed behavior of `-extractFilesTo:overwrite:error:`, so it shows the progress of each individual file as they extract (Issue #91, PR #94) -* Deprecated the initializers that take a file path instead of an `NSURL` (Issue #90, PR #95) \ No newline at end of file +* Deprecated the initializers that take a file path instead of an `NSURL` (Issue #90, PR #95) + +In this release: +* Fixed some potential crashing bugs +* Fixed some newer Xcode/Swift warnings \ No newline at end of file From b60747b5446b18b11c3b3ad91138b770a85d1aed Mon Sep 17 00:00:00 2001 From: Dov Frankel Date: Thu, 1 Oct 2020 23:16:12 -0400 Subject: [PATCH 33/56] Updated issue template, and added contribution guidelines --- .github/CONTRIBUTING.md | 25 +++++++++++++++++++++++++ .github/ISSUE_TEMPLATE.MD | 2 +- 2 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 .github/CONTRIBUTING.md diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 0000000..6d0b34d --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1,25 @@ +# Contributing to UnzipKit + +First of all, if you're reading this, thanks! I love getting feedback from other developers who use this library and welcome any and all feedback. Issues and Pull requests are welcome, with a few guidelines, laid out below. + +# Issues + +I need the following, at a minimum: + +1. The steps to reproduce your issue, detailed enough for me to follow along and see what you're seeing (bonus points for giving me a sample archive that demonstrates the issue) +2. What you expect to happen +3. What actually happened + +If what you're reporting is a crash, a crash report is key (or a stack trace, if you don't have a full crash report). + +Beyond that, the **quickest way** to get me to address what you're asking for is to provide a unit test (or tests) to demonstrate what you'd like to see (they should probably fail). I have a pretty complete set of tests already in the library that you can use as an example if you'd like. + +# Pull Requests + +Pull Requests are always greatly appreciated. The general rule of thumb for how quickly something will make it into a future release is the inverse of how much work I'll need to do on it. Creating a PR instead of an issue report takes a huge burden off of me. If you do create a PR, I'll require these things before I merge it: + +1. Style needs to mesh with the rest of the repo. I'd love to have some style enforcement checks at some point, but don't yet. Do your best according to what you see in the rest of the source and I'll point out anything you missed. No big deal +2. If you're fixing a bug, I need to see a unit test that reproduces the issue(s) by failing if I comment out your fix. I try to maintain code coverage that's as complete as possible, so more unit tests are always welcome +3. Don't touch the `Lib/MiniZip` directory. This is the MiniZip source code, downloaded from [the zlib project](https://github.com/madler/zlib/tree/master/contrib/minizip), and updated occasionally. You can usually look at the revision history of that folder to see what version it's currently on. This library does cause some compiler warnings, but I ignore them in Xcode and CocoaPods, relying on unit tests to tell me if anything is truly broken, since I don't maintain patches - I expect to be able to drop in newer versions as needed +4. Be patient with the process - I maintain a high attention to detail, and will take however much time is necessary to get the change to where I'd like it. If you'd rather have a more brief back-and-forth, I can always pull into a separate branch to make the changes myself before merging, but enjoy the interaction of collaborating on PRs whenever possible + diff --git a/.github/ISSUE_TEMPLATE.MD b/.github/ISSUE_TEMPLATE.MD index 3453922..d1d3b61 100644 --- a/.github/ISSUE_TEMPLATE.MD +++ b/.github/ISSUE_TEMPLATE.MD @@ -9,7 +9,7 @@ _Delete from this line up_ ## Option A -### Steps to reproduce issue (Detailed enough for me to reproduce): +### Steps to reproduce issue (Detailed enough for me to reproduce - attaching a sample archive can be very helpful): ### What you expect to happen: From 86dd120e139def8ad6a06310b1a4015aeda89c14 Mon Sep 17 00:00:00 2001 From: Dov Frankel Date: Sat, 13 Mar 2021 22:31:28 -0500 Subject: [PATCH 34/56] Fixed bug in -deleteFile:error: that causes a buffer overrun vulnerability (Issue #106) --- Source/UZKArchive.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/UZKArchive.m b/Source/UZKArchive.m index 8f822e7..a25dc37 100644 --- a/Source/UZKArchive.m +++ b/Source/UZKArchive.m @@ -1461,7 +1461,7 @@ - (BOOL)deleteFile:(NSString *)filePath error:(NSError * __autoreleasing*)error } UZKLogDebug("Allocating commentary"); - char *commentary = (char*)malloc(unzipInfo.size_file_comment); + char *commentary = unzipInfo.size_file_comment > 0 ? (char*)malloc(unzipInfo.size_file_comment) : NULL; if ((commentary == NULL) && (unzipInfo.size_file_comment != 0)) { NSString *detail = [NSString localizedStringWithFormat:NSLocalizedStringFromTableInBundle(@"Error allocating commentary info of %@ while deleting %@", @"UnzipKit", _resources, @"Detailed error string"), currentFileName, filePath]; From 741b561b454d931a94178063dc3527bcfce3aa7f Mon Sep 17 00:00:00 2001 From: Dov Frankel Date: Sat, 13 Mar 2021 22:35:10 -0500 Subject: [PATCH 35/56] Renamed variable in -deleteFile:error: to make it clearer what it's for --- Source/UZKArchive.m | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/Source/UZKArchive.m b/Source/UZKArchive.m index a25dc37..dd1a4ae 100644 --- a/Source/UZKArchive.m +++ b/Source/UZKArchive.m @@ -1460,9 +1460,9 @@ - (BOOL)deleteFile:(NSString *)filePath error:(NSError * __autoreleasing*)error detail:detail]; } - UZKLogDebug("Allocating commentary"); - char *commentary = unzipInfo.size_file_comment > 0 ? (char*)malloc(unzipInfo.size_file_comment) : NULL; - if ((commentary == NULL) && (unzipInfo.size_file_comment != 0)) { + UZKLogDebug("Allocating file comment buffer"); + char *fileCommentBuffer = unzipInfo.size_file_comment > 0 ? (char*)malloc(unzipInfo.size_file_comment) : NULL; + if ((fileCommentBuffer == NULL) && (unzipInfo.size_file_comment != 0)) { NSString *detail = [NSString localizedStringWithFormat:NSLocalizedStringFromTableInBundle(@"Error allocating commentary info of %@ while deleting %@", @"UnzipKit", _resources, @"Detailed error string"), currentFileName, filePath]; UZKLogError("UZKErrorCodeDeleteFile: %{public}@", detail); @@ -1476,14 +1476,14 @@ - (BOOL)deleteFile:(NSString *)filePath error:(NSError * __autoreleasing*)error } UZKLogDebug("Getting file info"); - err = unzGetCurrentFileInfo64(source_zip, &unzipInfo, filename_inzip, FILE_IN_ZIP_MAX_NAME_LENGTH, extra_field, unzipInfo.size_file_extra, commentary, unzipInfo.size_file_comment); + err = unzGetCurrentFileInfo64(source_zip, &unzipInfo, filename_inzip, FILE_IN_ZIP_MAX_NAME_LENGTH, extra_field, unzipInfo.size_file_extra, fileCommentBuffer, unzipInfo.size_file_comment); if (err != UNZ_OK) { NSString *detail = [NSString localizedStringWithFormat:NSLocalizedStringFromTableInBundle(@"Error reading extra_field and commentary info of %@ while deleting %@ (%d)", @"UnzipKit", _resources, @"Detailed error string"), currentFileName, filePath, err]; UZKLogError("UZKErrorCodeDeleteFile: %{public}@", detail); UZKLogDebug("Closing source_zip, dest_zip, freeing global_comment, extra_field, commentary"); free(extra_field); - free(commentary); + free(fileCommentBuffer); return [self assignError:error code:UZKErrorCodeDeleteFile detail:detail]; } @@ -1503,7 +1503,7 @@ - (BOOL)deleteFile:(NSString *)filePath error:(NSError * __autoreleasing*)error unzClose(source_zip); free(global_comment); free(extra_field); - free(commentary); + free(fileCommentBuffer); return [self assignError:error code:UZKErrorCodeDeleteFile detail:detail]; } @@ -1519,7 +1519,7 @@ - (BOOL)deleteFile:(NSString *)filePath error:(NSError * __autoreleasing*)error unzClose(source_zip); free(global_comment); free(extra_field); - free(commentary); + free(fileCommentBuffer); return [self assignError:error code:UZKErrorCodeDeleteFile detail:detail]; } @@ -1535,7 +1535,7 @@ - (BOOL)deleteFile:(NSString *)filePath error:(NSError * __autoreleasing*)error unzClose(source_zip); free(global_comment); free(extra_field); - free(commentary); + free(fileCommentBuffer); return [self assignError:error code:UZKErrorCodeDeleteFile detail:detail]; } @@ -1550,7 +1550,7 @@ - (BOOL)deleteFile:(NSString *)filePath error:(NSError * __autoreleasing*)error unzClose(source_zip); free(global_comment); free(extra_field); - free(commentary); + free(fileCommentBuffer); free(local_extra); return [self assignError:error code:UZKErrorCodeDeleteFile detail:detail]; @@ -1568,7 +1568,7 @@ - (BOOL)deleteFile:(NSString *)filePath error:(NSError * __autoreleasing*)error unzClose(source_zip); free(global_comment); free(extra_field); - free(commentary); + free(fileCommentBuffer); free(local_extra); return [self assignError:error code:UZKErrorCodeDeleteFile detail:detail]; @@ -1586,7 +1586,7 @@ - (BOOL)deleteFile:(NSString *)filePath error:(NSError * __autoreleasing*)error unzClose(source_zip); free(global_comment); free(extra_field); - free(commentary); + free(fileCommentBuffer); free(local_extra); free(buf); return [self assignError:error code:UZKErrorCodeDeleteFile @@ -1604,7 +1604,7 @@ - (BOOL)deleteFile:(NSString *)filePath error:(NSError * __autoreleasing*)error UZKLogDebug("Opening file in destination archive"); err = zipOpenNewFileInZip2(dest_zip, filename_inzip, &zipInfo, - local_extra, size_local_extra, extra_field, (uInt)unzipInfo.size_file_extra, commentary, + local_extra, size_local_extra, extra_field, (uInt)unzipInfo.size_file_extra, fileCommentBuffer, method, level, 1); if (err != UNZ_OK) { NSString *detail = [NSString localizedStringWithFormat:NSLocalizedStringFromTableInBundle(@"Error opening %@ in destination zip while deleting %@ (%d)", @"UnzipKit", _resources, @"Detailed error string"), @@ -1615,7 +1615,7 @@ - (BOOL)deleteFile:(NSString *)filePath error:(NSError * __autoreleasing*)error unzClose(source_zip); free(global_comment); free(extra_field); - free(commentary); + free(fileCommentBuffer); free(local_extra); free(buf); return [self assignError:error code:UZKErrorCodeDeleteFile @@ -1634,7 +1634,7 @@ - (BOOL)deleteFile:(NSString *)filePath error:(NSError * __autoreleasing*)error unzClose(source_zip); free(global_comment); free(extra_field); - free(commentary); + free(fileCommentBuffer); free(local_extra); free(buf); return [self assignError:error code:UZKErrorCodeDeleteFile @@ -1653,7 +1653,7 @@ - (BOOL)deleteFile:(NSString *)filePath error:(NSError * __autoreleasing*)error unzClose(source_zip); free(global_comment); free(extra_field); - free(commentary); + free(fileCommentBuffer); free(local_extra); free(buf); return [self assignError:error code:UZKErrorCodeDeleteFile @@ -1672,7 +1672,7 @@ - (BOOL)deleteFile:(NSString *)filePath error:(NSError * __autoreleasing*)error unzClose(source_zip); free(global_comment); free(extra_field); - free(commentary); + free(fileCommentBuffer); free(local_extra); free(buf); return [self assignError:error code:UZKErrorCodeDeleteFile @@ -1681,7 +1681,7 @@ - (BOOL)deleteFile:(NSString *)filePath error:(NSError * __autoreleasing*)error UZKLogDebug("Freeing extra_field, commentary, local_extra, buf"); free(extra_field); - free(commentary); + free(fileCommentBuffer); free(local_extra); free(buf); From 84728f3020aac4ba8db0f03b760ee1bfec57c303 Mon Sep 17 00:00:00 2001 From: Dov Frankel Date: Sat, 13 Mar 2021 23:09:08 -0500 Subject: [PATCH 36/56] Updated changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9819fd0..5a1d139 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ * Deprecated the initializers that take a file path instead of an `NSURL` (Issue #90, PR #95) * Fixed a crasher for unreadable files in `+pathIsAZip:` (Issue #99) * Deprecated all overloads of `-writeData:...` and `-writeIntoBuffer:...` that take any file properties other than the path, replacing them each with a single call that takes an instance of the new `ZipFileProperties`. This allows for all the default values to be defined in one place, so you can specify only where you want to deviate from the defaults (Issue #89, PR #97) +* Fixed buffer overrun vulnerability when deleting a file in an archive where not every file has a file comment (Issue #106) ## 1.9 From 2f97a52cf128007585fb745c51259205bd46b768 Mon Sep 17 00:00:00 2001 From: Dov Frankel Date: Sat, 13 Mar 2021 23:12:59 -0500 Subject: [PATCH 37/56] Fixed bug that could cause a deallocated pointer to be used (Issue #107) --- Source/UZKArchive.m | 21 +++++++++++++-------- Tests/ModesTests.m | 4 ++-- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/Source/UZKArchive.m b/Source/UZKArchive.m index dd1a4ae..28c7ceb 100644 --- a/Source/UZKArchive.m +++ b/Source/UZKArchive.m @@ -1805,6 +1805,8 @@ - (BOOL)performActionWithArchiveOpen:(void(^)(NSError * __autoreleasing*innerErr NSError *openError = nil; NSError *actionError = nil; + BOOL shouldCloseFile = YES; + @try { if (![self openFile:self.filename inMode:mode @@ -1817,6 +1819,7 @@ - (BOOL)performActionWithArchiveOpen:(void(^)(NSError * __autoreleasing*innerErr *error = openError; } + shouldCloseFile = NO; return NO; } @@ -1826,15 +1829,17 @@ - (BOOL)performActionWithArchiveOpen:(void(^)(NSError * __autoreleasing*innerErr } } @finally { - NSError *closeError = nil; - if (![self closeFile:&closeError inMode:mode]) { - UZKLogDebug("Archive failed to close"); - - if (error && !actionError && !openError) { - *error = closeError; + if (shouldCloseFile) { + NSError *closeError = nil; + if (![self closeFile:&closeError inMode:mode]) { + UZKLogDebug("Archive failed to close"); + + if (error && !actionError && !openError) { + *error = closeError; + } + + return NO; } - - return NO; } } diff --git a/Tests/ModesTests.m b/Tests/ModesTests.m index 06287c1..098f35f 100644 --- a/Tests/ModesTests.m +++ b/Tests/ModesTests.m @@ -93,12 +93,12 @@ - (void)testModes_NestedWrites UZKArchive *archive = [[UZKArchive alloc] initWithURL:testArchiveURL error:nil]; NSError *outerWriteError = nil; - [archive writeIntoBufferAtPath:@"newFile.zip" + [archive writeIntoBufferAtPath:@"outer file.txt" error:&outerWriteError block: ^BOOL(BOOL(^writeData)(const void *bytes, unsigned int length), NSError**(actionError)) { NSError *innerWriteError = nil; - [archive writeIntoBufferAtPath:@"newFile.zip" + [archive writeIntoBufferAtPath:@"inner file.txt" error:&innerWriteError block:^BOOL(BOOL(^writeData)(const void *bytes, unsigned int length), NSError**(actionError)) {return YES;}]; XCTAssertNotNil(innerWriteError, @"Nested write operation succeeded"); From 2b491405c11aa266ddcede7426c97c6639aa88c6 Mon Sep 17 00:00:00 2001 From: Dov Frankel Date: Sat, 13 Mar 2021 23:14:55 -0500 Subject: [PATCH 38/56] Updated changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a1d139..2298231 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ * Fixed a crasher for unreadable files in `+pathIsAZip:` (Issue #99) * Deprecated all overloads of `-writeData:...` and `-writeIntoBuffer:...` that take any file properties other than the path, replacing them each with a single call that takes an instance of the new `ZipFileProperties`. This allows for all the default values to be defined in one place, so you can specify only where you want to deviate from the defaults (Issue #89, PR #97) * Fixed buffer overrun vulnerability when deleting a file in an archive where not every file has a file comment (Issue #106) +* Fixed deallocated pointer use when a file write occurs inside the block of a file write operation, already an error condition (Issue #107) ## 1.9 From 43d15250b82f2169f55572a722f862958bd94c7a Mon Sep 17 00:00:00 2001 From: Dov Frankel Date: Sun, 14 Mar 2021 00:13:48 -0500 Subject: [PATCH 39/56] Fixed solution to #107 by limiting its scope --- Source/UZKArchive.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/UZKArchive.m b/Source/UZKArchive.m index 28c7ceb..9821c08 100644 --- a/Source/UZKArchive.m +++ b/Source/UZKArchive.m @@ -1819,7 +1819,7 @@ - (BOOL)performActionWithArchiveOpen:(void(^)(NSError * __autoreleasing*innerErr *error = openError; } - shouldCloseFile = NO; + shouldCloseFile = openError.code != UZKErrorCodeFileWrite; return NO; } From f3313302d0f0cc6aa864c0abfc4aa96e4494fa89 Mon Sep 17 00:00:00 2001 From: Dov Frankel Date: Tue, 16 Mar 2021 00:24:30 -0400 Subject: [PATCH 40/56] Updated version to v2.0-beta3 --- Resources/UnzipKitResources-Info.plist | 4 ++-- Source/Info.plist | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Resources/UnzipKitResources-Info.plist b/Resources/UnzipKitResources-Info.plist index 1ca6dc9..d26c6e6 100644 --- a/Resources/UnzipKitResources-Info.plist +++ b/Resources/UnzipKitResources-Info.plist @@ -13,11 +13,11 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 2.0-beta2 + 2.0-beta3 CFBundleSignature ???? CFBundleVersion - 2.0-beta2 + 2.0-beta3 NSHumanReadableCopyright Copyright © 2019 Abbey Code. All rights reserved. NSPrincipalClass diff --git a/Source/Info.plist b/Source/Info.plist index 861f1d9..9183b8d 100644 --- a/Source/Info.plist +++ b/Source/Info.plist @@ -15,11 +15,11 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.0-beta2 + 2.0-beta3 CFBundleSignature ???? CFBundleVersion - 2.0-beta2 + 2.0-beta3 NSHumanReadableCopyright © 2017 Abbey Code. All rights reserved. NSPrincipalClass From 9fc16048af4a9f1306f045d0ad1990c62893d1d2 Mon Sep 17 00:00:00 2001 From: Dov Frankel Date: Tue, 16 Mar 2021 00:26:30 -0400 Subject: [PATCH 41/56] Updated beta notes --- beta-notes.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/beta-notes.md b/beta-notes.md index 9e18975..83152db 100644 --- a/beta-notes.md +++ b/beta-notes.md @@ -1,7 +1,10 @@ * Removed methods deprecated in v1.9 (Issue #90, PR #92) * Fixed behavior of `-extractFilesTo:overwrite:error:`, so it shows the progress of each individual file as they extract (Issue #91, PR #94) * Deprecated the initializers that take a file path instead of an `NSURL` (Issue #90, PR #95) +* Fixed a crasher for unreadable files in `+pathIsAZip:` (Issue #99) +* Deprecated all overloads of `-writeData:...` and `-writeIntoBuffer:...` that take any file properties other than the path, replacing them each with a single call that takes an instance of the new `ZipFileProperties`. This allows for all the default values to be defined in one place, so you can specify only where you want to deviate from the defaults (Issue #89, PR #97) +* Fixed buffer overrun vulnerability when deleting a file in an archive where not every file has a file comment (Issue #106) +* Fixed deallocated pointer use when a file write occurs inside the block of a file write operation, already an error condition (Issue #107) In this release: -* Fixed some potential crashing bugs -* Fixed some newer Xcode/Swift warnings \ No newline at end of file +* Fixed some issues caught by running tests with sanitizers (Issues #106 and #107) \ No newline at end of file From be2cc2a80803959cc53ddfa146e0f1ac6c983609 Mon Sep 17 00:00:00 2001 From: Dov Frankel Date: Thu, 23 Jun 2022 23:30:55 -0400 Subject: [PATCH 42/56] Added test case reproducing Issue #110 --- Tests/ExtractDataTests.m | 16 ++++++++++++++++ Tests/UZKArchiveTestCase.m | 1 + 2 files changed, 17 insertions(+) diff --git a/Tests/ExtractDataTests.m b/Tests/ExtractDataTests.m index 3b2a193..a6b952b 100644 --- a/Tests/ExtractDataTests.m +++ b/Tests/ExtractDataTests.m @@ -59,6 +59,22 @@ - (void)testExtractData } } +- (void)testExtractData_Issue110 +{ + NSURL *testArchiveURL = self.testFileURLs[@"complete_49467_mptest_115761522.zip"]; + + NSString *password = @"49467"; + UZKArchive *archive = [[UZKArchive alloc] initWithURL:testArchiveURL password:password error:nil]; + BOOL passwordIsValid = [archive validatePassword]; + XCTAssertTrue(passwordIsValid, @"Invalid password given"); + + NSError *error = nil; + NSData *extractedData = [archive extractDataFromFile:@"complete_49467_mptest_115761522/49467.json" error:&error]; + + XCTAssertNotNil(extractedData, @""); + XCTAssertNil(error, @""); +} + - (void)testExtractData_Unicode { NSSet *expectedFileSet = self.nonZipUnicodeFilePaths; diff --git a/Tests/UZKArchiveTestCase.m b/Tests/UZKArchiveTestCase.m index 37fac77..ad9e8d1 100644 --- a/Tests/UZKArchiveTestCase.m +++ b/Tests/UZKArchiveTestCase.m @@ -68,6 +68,7 @@ - (void)setUp { @"Test File C.m4a", @"NotAZip-PK-ContentsUnknown", @"Modified CRC Archive.zip", + @"complete_49467_mptest_115761522.zip" ]; NSArray *unicodeFiles = @[ From 29a1a1742eb6b18c4fcd95d2905fe8247c3077e0 Mon Sep 17 00:00:00 2001 From: Dov Frankel Date: Thu, 23 Jun 2022 23:51:15 -0400 Subject: [PATCH 43/56] Replaced prior test case with one based on a file I created myself using AES encryption, and updated it to use a new error code reporting what went wrong (Issue #110) --- Resources/en.lproj/UnzipKit.strings | 39 ++++++------------ Source/UZKArchive.h | 5 +++ Source/UZKArchive.m | 19 +++++++++ Tests/ExtractDataTests.m | 16 +++---- .../Test Archive (Password, AES).zip | Bin 0 -> 90277 bytes Tests/UZKArchiveTestCase.m | 2 +- 6 files changed, 43 insertions(+), 38 deletions(-) create mode 100644 Tests/Test Data/Test Archive (Password, AES).zip diff --git a/Resources/en.lproj/UnzipKit.strings b/Resources/en.lproj/UnzipKit.strings index ce47516..9e7d2f8 100644 --- a/Resources/en.lproj/UnzipKit.strings +++ b/Resources/en.lproj/UnzipKit.strings @@ -17,10 +17,10 @@ "Can't open archive" = "Can't open archive"; /* Error message */ -"Cannot open archive, since it was compressed using the Deflate64 algorithm (method ID 9)" = "Cannot open archive, since it was compressed using the Deflate64 algorithm (method ID 9)"; +"Cannot open archive, since it was compressed using the AES algorithm (method ID 99)" = "Cannot open archive, since it was compressed using the AES algorithm (method ID 99)"; -/* Detailed error string */ -"Error allocating buffer for file %@ while deleting %@" = "Error allocating buffer for file %1$@ while deleting %2$@"; +/* Error message */ +"Cannot open archive, since it was compressed using the Deflate64 algorithm (method ID 9)" = "Cannot open archive, since it was compressed using the Deflate64 algorithm (method ID 9)"; /* Detailed error string */ "Error allocating buffer for file %@ while deleting %@. Is it too large?" = "Error allocating buffer for file %1$@ while deleting %2$@. Is it too large?"; @@ -31,9 +31,6 @@ /* Detailed error string */ "Error allocating extra_field info of %@ while deleting %@" = "Error allocating extra_field info of %1$@ while deleting %2$@"; -/* Detailed error string */ -"Error allocating extrafield info of %@ while deleting %@" = "Error allocating extrafield info of %1$@ while deleting %2$@"; - /* Detailed error string */ "Error allocating local_extra for file %@ while deleting %@" = "Error allocating local_extra for file %1$@ while deleting %2$@"; @@ -55,12 +52,6 @@ /* Detailed error string */ "Error closing file '%@' for write (%d)" = "Error closing file '%1$@' for write (%2$d)"; -/* Detailed error string */ -"Error closing file in archive after append (%d)" = "Error closing file in archive after append (%d)"; - -/* Detailed error string */ -"Error closing file in archive after create (%d)" = "Error closing file in archive after create (%d)"; - /* Detailed error string */ "Error closing file in archive after read (%d)" = "Error closing file in archive after read (%d)"; @@ -154,9 +145,6 @@ /* Detailed error string */ "Error reading extra_field and commentary info of %@ while deleting %@ (%d)" = "Error reading extra_field and commentary info of %1$@ while deleting %2$@ (%3$d)"; -/* Detailed error string */ -"Error reading extrafield and commentary info of %@ while deleting %@ (%d)" = "Error reading extrafield and commentary info of %1$@ while deleting %2$@ (%3$d)"; - /* Detailed error string */ "Error reading the comment (readGlobalComment)" = "Error reading the comment (readGlobalComment)"; @@ -184,12 +172,6 @@ /* Detailed error string */ "Error writing to file: %@" = "Error writing to file: %@"; -/* Detailed error string */ -"Extract path exists, but is not a directory: %@" = "Extract path exists, but is not a directory: %@"; - -/* Detailed error string */ -"Failed to copy archive to external volume '%@', after deleting '%@' from it" = "Failed to copy archive to external volume '%1$@', after deleting '%2$@' from it"; - /* Detailed error string */ "Failed to copy archive to external volume '%@', after deleting '%@' from it (%@)" = "Failed to copy archive to external volume '%1$@', after deleting '%2$@' from it (%3$@)"; @@ -208,15 +190,9 @@ /* Detailed error string */ "Failed to read file %@ in zip" = "Failed to read file %@ in zip"; -/* Detailed error string */ -"Failed to remove archive from external volume '%@', after deleting '%@' from a new version to replace it" = "Failed to remove archive from external volume '%1$@', after deleting '%2$@' from a new version to replace it"; - /* Detailed error string */ "Failed to remove original archive from external volume '%@', after deleting '%@' from a new version to replace it (%@)" = "Failed to remove original archive from external volume '%1$@', after deleting '%2$@' from a new version to replace it (%3$@)"; -/* Detailed error string */ -"Failed to replace the old archive with the new one, after deleting '%@' from it" = "Failed to replace the old archive with the new one, after deleting '%@' from it"; - /* Detailed error string */ "Failed to replace the old archive with the new one, after deleting '%@' from it (%@)" = "Failed to replace the old archive with the new one, after deleting '%1$@' from it (%2$@)"; @@ -247,6 +223,9 @@ /* UZKErrorCodeParameterError */ "Parameter error" = "Parameter error"; +/* UZKErrorCodeAES */ +"The archive was compressed with the AES method, which isn't supported" = "The archive was compressed with the AES method, which isn't supported"; + /* UZKErrorCodeDeflate64 */ "The archive was compressed with the Deflate64 method, which isn't supported" = "The archive was compressed with the Deflate64 method, which isn't supported"; @@ -265,3 +244,9 @@ /* UnknownErrorCode */ "Unknown error code: %ld" = "Unknown error code: %ld"; +/* Detailed error string */ +"User cancelled data read" = "User cancelled data read"; + +/* Detailed error string */ +"User cancelled operation" = "User cancelled operation"; + diff --git a/Source/UZKArchive.h b/Source/UZKArchive.h index 657f8f0..bb63ea8 100644 --- a/Source/UZKArchive.h +++ b/Source/UZKArchive.h @@ -121,6 +121,11 @@ typedef NS_ENUM(NSInteger, UZKErrorCode) { * User cancelled the operation */ UZKErrorCodeUserCancelled = 116, + + /** + * User cancelled the operation + */ + UZKErrorCodeAES = 117, }; diff --git a/Source/UZKArchive.m b/Source/UZKArchive.m index 9821c08..14ffb03 100644 --- a/Source/UZKArchive.m +++ b/Source/UZKArchive.m @@ -2302,6 +2302,13 @@ - (BOOL)openFile:(NSError * __autoreleasing*)error detail:detail]; } + if ([self isAES:file_info]) { + NSString *detail = NSLocalizedStringFromTableInBundle(@"Cannot open archive, since it was compressed using the AES algorithm (method ID 99)", @"UnzipKit", _resources, @"Error message"); + UZKLogError("UZKErrorCodeAES: %{public}@", detail); + return [self assignError:error code:UZKErrorCodeAES + detail:detail]; + } + UZKLogDebug("Opening file..."); err = unzOpenCurrentFilePassword(self.unzFile, passwordStr); if (err != UNZ_OK) { @@ -2555,6 +2562,10 @@ + (NSString *)errorNameForErrorCode:(NSInteger)errorCode errorName = NSLocalizedStringFromTableInBundle(@"The archive was compressed with the Deflate64 method, which isn't supported", @"UnzipKit", _resources, @"UZKErrorCodeDeflate64"); break; + case UZKErrorCodeAES: + errorName = NSLocalizedStringFromTableInBundle(@"The archive was compressed with the AES method, which isn't supported", @"UnzipKit", _resources, @"UZKErrorCodeAES"); + break; + default: errorName = [NSString localizedStringWithFormat: NSLocalizedStringFromTableInBundle(@"Unknown error code: %ld", @"UnzipKit", _resources, @"UnknownErrorCode"), errorCode]; @@ -2664,6 +2675,14 @@ - (BOOL)isDeflate64:(unz_file_info64)file_info return file_info.compression_method == 9; } +- (BOOL)isAES:(unz_file_info64)file_info +{ + UZKCreateActivity("isAES"); + + UZKLogDebug("Compression method: %lu", file_info.compression_method); + return file_info.compression_method == 99; +} + - (NSProgress *)beginProgressOperation:(unsigned long long)totalUnitCount { UZKCreateActivity("-beginProgressOperation:"); diff --git a/Tests/ExtractDataTests.m b/Tests/ExtractDataTests.m index a6b952b..cfed34f 100644 --- a/Tests/ExtractDataTests.m +++ b/Tests/ExtractDataTests.m @@ -59,20 +59,16 @@ - (void)testExtractData } } -- (void)testExtractData_Issue110 +- (void)testExtractData_AES { - NSURL *testArchiveURL = self.testFileURLs[@"complete_49467_mptest_115761522.zip"]; - - NSString *password = @"49467"; - UZKArchive *archive = [[UZKArchive alloc] initWithURL:testArchiveURL password:password error:nil]; - BOOL passwordIsValid = [archive validatePassword]; - XCTAssertTrue(passwordIsValid, @"Invalid password given"); + NSURL *testArchiveURL = self.testFileURLs[@"Test Archive (Password, AES).zip"]; + UZKArchive *archive = [[UZKArchive alloc] initWithURL:testArchiveURL password:@"password" error:nil]; NSError *error = nil; - NSData *extractedData = [archive extractDataFromFile:@"complete_49467_mptest_115761522/49467.json" error:&error]; + NSData *extractedData = [archive extractDataFromFile:@"Test File A.txt" error:&error]; - XCTAssertNotNil(extractedData, @""); - XCTAssertNil(error, @""); + XCTAssertNil(extractedData, @"No data should be returned for an AES-encrypted archive"); + XCTAssertEqual(error.code, UZKErrorCodeAES, @"Unexpected error code returned"); } - (void)testExtractData_Unicode diff --git a/Tests/Test Data/Test Archive (Password, AES).zip b/Tests/Test Data/Test Archive (Password, AES).zip new file mode 100644 index 0000000000000000000000000000000000000000..9978be1526d74731a46a0ebf051cca88f73617b2 GIT binary patch literal 90277 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�YKh7-n>be&!fc0HsipL;NzrIVEHHN0&TClDD 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 XhXn=wpJ#yhH^jgIfFB0`Ccys#ZW4l< literal 0 HcmV?d00001 diff --git a/Tests/UZKArchiveTestCase.m b/Tests/UZKArchiveTestCase.m index ad9e8d1..1f2b7a4 100644 --- a/Tests/UZKArchiveTestCase.m +++ b/Tests/UZKArchiveTestCase.m @@ -58,6 +58,7 @@ - (void)setUp { @"Test Archive (Directories).zip", @"Test Archive (Password).zip", @"Test Archive (SymLink Directory).zip", + @"Test Archive (Password, AES).zip", @"L'incertain.zip", @"Aces.zip", @"Comments Archive.zip", @@ -68,7 +69,6 @@ - (void)setUp { @"Test File C.m4a", @"NotAZip-PK-ContentsUnknown", @"Modified CRC Archive.zip", - @"complete_49467_mptest_115761522.zip" ]; NSArray *unicodeFiles = @[ From b465b474696fb97e012979b76e2d05cf2a75b244 Mon Sep 17 00:00:00 2001 From: Dov Frankel Date: Fri, 24 Jun 2022 00:11:45 -0400 Subject: [PATCH 44/56] Updated changelog [ci skip] --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2298231..8b8a135 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ * Deprecated all overloads of `-writeData:...` and `-writeIntoBuffer:...` that take any file properties other than the path, replacing them each with a single call that takes an instance of the new `ZipFileProperties`. This allows for all the default values to be defined in one place, so you can specify only where you want to deviate from the defaults (Issue #89, PR #97) * Fixed buffer overrun vulnerability when deleting a file in an archive where not every file has a file comment (Issue #106) * Fixed deallocated pointer use when a file write occurs inside the block of a file write operation, already an error condition (Issue #107) +* Added error code to report when an archive was compressed using AES, which is not yet supported (Issue #110, PR #113) ## 1.9 From 7ae2626553e1370c918ac1559d4d5d930f223162 Mon Sep 17 00:00:00 2001 From: Dov Frankel Date: Fri, 24 Jun 2022 00:38:01 -0400 Subject: [PATCH 45/56] Fixed Xcode 13 warnings --- Source/UZKArchive.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/UZKArchive.m b/Source/UZKArchive.m index 14ffb03..dc98242 100644 --- a/Source/UZKArchive.m +++ b/Source/UZKArchive.m @@ -560,7 +560,7 @@ - (BOOL)extractFilesTo:(NSString *)destinationDirectory attributes:nil]; if (!createSuccess) { - NSString *detail = [NSString localizedStringWithFormat:NSLocalizedStringFromTableInBundle(@"Error creating current file (%d) '%@'", @"UnzipKit", _resources, @"Detailed error string"), + NSString *detail = [NSString localizedStringWithFormat:NSLocalizedStringFromTableInBundle(@"Error creating current file (%@) '%@'", @"UnzipKit", _resources, @"Detailed error string"), strongError, info.filename]; UZKLogError("UZKErrorCodeOutputError: %{public}@", detail); [sself assignError:&strongError code:UZKErrorCodeOutputError @@ -1511,7 +1511,7 @@ - (BOOL)deleteFile:(NSString *)filePath error:(NSError * __autoreleasing*)error UZKLogDebug("Getting local extra field size"); int size_local_extra = unzGetLocalExtrafield(source_zip, NULL, 0); if (size_local_extra < 0) { - NSString *detail = [NSString localizedStringWithFormat:NSLocalizedStringFromTableInBundle(@"Error getting size_local_extra for file while deleting %@", @"UnzipKit", _resources, @"Detailed error string"), + NSString *detail = [NSString localizedStringWithFormat:NSLocalizedStringFromTableInBundle(@"Error getting size_local_extra for file while deleting %@ (current file %@)", @"UnzipKit", _resources, @"Detailed error string"), currentFileName, filePath]; UZKLogError("UZKErrorCodeDeleteFile: %{public}@", detail); UZKLogDebug("Closing source_zip, dest_zip, freeing global_comment, extra_field, commentary"); From 44613a6d7a7509478c3562a651c4ecc242b5087f Mon Sep 17 00:00:00 2001 From: Dov Frankel Date: Fri, 24 Jun 2022 00:39:33 -0400 Subject: [PATCH 46/56] Updated to recommended Xcode 13 project settings --- UnzipKit.xcodeproj/project.pbxproj | 4 +++- .../xcshareddata/xcschemes/UnzipKit.xcscheme | 24 ++++++++----------- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/UnzipKit.xcodeproj/project.pbxproj b/UnzipKit.xcodeproj/project.pbxproj index a715a15..b0a4511 100644 --- a/UnzipKit.xcodeproj/project.pbxproj +++ b/UnzipKit.xcodeproj/project.pbxproj @@ -439,7 +439,7 @@ attributes = { LastSwiftMigration = 0700; LastSwiftUpdateCheck = 0720; - LastUpgradeCheck = 1020; + LastUpgradeCheck = 1340; ORGANIZATIONNAME = "Abbey Code"; TargetAttributes = { 7A0029161F93DB5800618503 = { @@ -731,6 +731,7 @@ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; @@ -792,6 +793,7 @@ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; diff --git a/UnzipKit.xcodeproj/xcshareddata/xcschemes/UnzipKit.xcscheme b/UnzipKit.xcodeproj/xcshareddata/xcschemes/UnzipKit.xcscheme index 0697e6d..1a05e82 100644 --- a/UnzipKit.xcodeproj/xcshareddata/xcschemes/UnzipKit.xcscheme +++ b/UnzipKit.xcodeproj/xcshareddata/xcschemes/UnzipKit.xcscheme @@ -1,6 +1,6 @@ + + + + @@ -53,17 +62,6 @@ - - - - - - - - Date: Fri, 24 Jun 2022 00:40:45 -0400 Subject: [PATCH 47/56] Upgraded Travis build environment from Xcode 11 to Xcode 13.4 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 228cb9b..1089bf1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,7 @@ language: - objective-c -osx_image: xcode11.2 +osx_image: xcode13.4 before_script: # Essentially turn off logging. Temporarily undo if more info is needed From 685727457b2732b6c6323595ed830b5faa87843d Mon Sep 17 00:00:00 2001 From: Dov Frankel Date: Fri, 24 Jun 2022 00:47:54 -0400 Subject: [PATCH 48/56] Fixed additional Xcode 13 warning --- Source/UZKArchive.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/UZKArchive.h b/Source/UZKArchive.h index bb63ea8..18bbdb4 100644 --- a/Source/UZKArchive.h +++ b/Source/UZKArchive.h @@ -7,7 +7,7 @@ #import #import -#import "UZKFileInfo.h" +#import @class ZipFileProperties; From fdb19832cd70afebb5d10009bc4a44f6906168d2 Mon Sep 17 00:00:00 2001 From: Dov Frankel Date: Fri, 24 Jun 2022 01:18:05 -0400 Subject: [PATCH 49/56] Fixed angle-bracket import --- Source/UZKArchive.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/UZKArchive.h b/Source/UZKArchive.h index 18bbdb4..67b9517 100644 --- a/Source/UZKArchive.h +++ b/Source/UZKArchive.h @@ -7,7 +7,7 @@ #import #import -#import +#import @class ZipFileProperties; From 3c8317cb2d3e45109555176ab1995121e6fec242 Mon Sep 17 00:00:00 2001 From: Dov Frankel Date: Fri, 24 Jun 2022 01:18:41 -0400 Subject: [PATCH 50/56] Fixed analyzer warnings and removed an unnecessary import in the unit test Swift bridging header --- Source/UZKArchive.m | 24 +++++++++++++++++------- Tests/UnzipKitTests-Bridging-Header.h | 2 -- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/Source/UZKArchive.m b/Source/UZKArchive.m index dc98242..90d98eb 100644 --- a/Source/UZKArchive.m +++ b/Source/UZKArchive.m @@ -527,13 +527,14 @@ - (BOOL)extractFilesTo:(NSString *)destinationDirectory : extractPath.stringByDeletingLastPathComponent); if (![fm fileExistsAtPath:extractDir]) { UZKLogDebug("Creating directories for path %{public}@", extractDir); + NSError *createDirError = nil; BOOL directoriesCreated = [fm createDirectoryAtPath:extractDir withIntermediateDirectories:YES attributes:nil - error:error]; + error:&createDirError]; if (!directoriesCreated) { - NSString *detail = [NSString localizedStringWithFormat:NSLocalizedStringFromTableInBundle(@"Failed to create destination directory: %@", @"UnzipKit", _resources, @"Detailed error string"), - extractDir]; + NSString *detail = [NSString localizedStringWithFormat:NSLocalizedStringFromTableInBundle(@"Failed to create destination directory %@: %@", @"UnzipKit", _resources, @"Detailed error string"), + extractDir, createDirError.localizedDescription]; UZKLogError("UZKErrorCodeOutputError: %{public}@", detail); [sself assignError:&strongError code:UZKErrorCodeOutputError detail:detail]; @@ -858,7 +859,7 @@ - (BOOL)extractBufferedDataFromFile:(NSString *)filePath } } - if (strongInnerError) { + if (innerError && strongInnerError) { *innerError = strongInnerError; return; } @@ -2052,15 +2053,19 @@ - (BOOL)openFile:(NSString *)zipFile NSMutableDictionary *dic = [NSMutableDictionary dictionary]; UZKLogInfo("Reading file info to cache file positions"); - + + NSError *infoError = nil; + BOOL infoRetrieveFailed = NO; + do { @autoreleasepool { UZKLogDebug("Reading file info for current file in zip"); - UZKFileInfo *info = [self currentFileInZipInfo:error]; + UZKFileInfo *info = [self currentFileInZipInfo:&infoError]; if (!info) { UZKLogDebug("No info returned. Exiting loop"); - return NO; + infoRetrieveFailed = YES; + break; // while loop } UZKLogDebug("Got info for %{public}@", info.filename); @@ -2075,6 +2080,11 @@ - (BOOL)openFile:(NSString *)zipFile } } while (unzGoToNextFile (self.unzFile) != UNZ_END_OF_LIST_OF_FILE); + if (infoRetrieveFailed) { + if (error) *error = infoError; + return NO; + } + self.archiveContents = [dic copy]; break; } diff --git a/Tests/UnzipKitTests-Bridging-Header.h b/Tests/UnzipKitTests-Bridging-Header.h index 40fe7be..bfdb644 100644 --- a/Tests/UnzipKitTests-Bridging-Header.h +++ b/Tests/UnzipKitTests-Bridging-Header.h @@ -2,8 +2,6 @@ // Use this file to import your target's public headers that you would like to expose to Swift. // -#import "UZKArchive.h" - #import "zip.h" #import "UZKArchiveTestCase.h" #import "UZKArchive+Deprecated.h" From 79308ce5b62f0c2c0650fd584aba5d6bb36a1f53 Mon Sep 17 00:00:00 2001 From: Dov Frankel Date: Fri, 24 Jun 2022 13:37:49 -0400 Subject: [PATCH 51/56] Updated progress reporting for -extractFilesTo:overwrite:error: so it doesn't get stuck reporting the first file --- Source/UZKArchive.m | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/Source/UZKArchive.m b/Source/UZKArchive.m index 90d98eb..b709090 100644 --- a/Source/UZKArchive.m +++ b/Source/UZKArchive.m @@ -474,7 +474,6 @@ - (BOOL)extractFilesTo:(NSString *)destinationDirectory NSNumber *totalSize = [fileInfo valueForKeyPath:@"@sum.uncompressedSize"]; UZKLogDebug("totalSize: %lld", totalSize.longLongValue); __block long long bytesDecompressed = 0; - __block NSInteger filesExtracted = 0; NSProgress *progress = [self beginProgressOperation:totalSize.longLongValue]; progress.kind = NSProgressKindFile; @@ -496,6 +495,13 @@ - (BOOL)extractFilesTo:(NSString *)destinationDirectory for (UZKFileInfo *info in fileInfo) { UZKLogDebug("Extracting %{public}@ to disk", info.filename); + NSURL *deflatedDirectoryURL = [NSURL fileURLWithPath:destinationDirectory]; + NSURL *deflatedFileURL = [deflatedDirectoryURL URLByAppendingPathComponent:info.filename]; + [progress setUserInfoObject:deflatedFileURL + forKey:NSProgressFileURLKey]; + [progress setUserInfoObject:info + forKey:UZKProgressInfoKeyFileInfoExtracting]; + if (progress.isCancelled) { NSString *detail = [NSString localizedStringWithFormat:NSLocalizedStringFromTableInBundle(@"Error locating file '%@' in archive", @"UnzipKit", _resources, @"Detailed error string"), info.filename]; @@ -547,12 +553,6 @@ - (BOOL)extractFilesTo:(NSString *)destinationDirectory continue; } - NSURL *deflatedDirectoryURL = [NSURL fileURLWithPath:destinationDirectory]; - NSURL *deflatedFileURL = [deflatedDirectoryURL URLByAppendingPathComponent:info.filename]; - [progress setUserInfoObject:deflatedFileURL - forKey:NSProgressFileURLKey]; - [progress setUserInfoObject:info - forKey:UZKProgressInfoKeyFileInfoExtracting]; NSString *path = deflatedFileURL.path; UZKLogDebug("Creating empty file at path %{public}@", path); @@ -614,11 +614,6 @@ - (BOOL)extractFilesTo:(NSString *)destinationDirectory error:nil]; return; } - - [progress setUserInfoObject:@(++filesExtracted) - forKey:NSProgressFileCompletedCountKey]; - [progress setUserInfoObject:@(fileInfo.count) - forKey:NSProgressFileTotalCountKey]; } } } From b124e55d2f0de882fa019381da591d937a39f239 Mon Sep 17 00:00:00 2001 From: Dov Frankel Date: Fri, 24 Jun 2022 13:38:23 -0400 Subject: [PATCH 52/56] Updated strings file to reflect corrected error format strings --- Resources/en.lproj/UnzipKit.strings | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Resources/en.lproj/UnzipKit.strings b/Resources/en.lproj/UnzipKit.strings index 9e7d2f8..41a7539 100644 --- a/Resources/en.lproj/UnzipKit.strings +++ b/Resources/en.lproj/UnzipKit.strings @@ -62,7 +62,7 @@ "Error creating bookmark to new archive file: %@" = "Error creating bookmark to new archive file: %@"; /* Detailed error string */ -"Error creating current file (%d) '%@'" = "Error creating current file (%1$d) '%2$@'"; +"Error creating current file (%@) '%@'" = "Error creating current file (%1$@) '%2$@'"; /* UZKErrorCodeDeleteFile */ "Error deleting a file in the archive" = "Error deleting a file in the archive"; @@ -89,7 +89,7 @@ "Error getting local_extra for file %@ while deleting %@" = "Error getting local_extra for file %1$@ while deleting %2$@"; /* Detailed error string */ -"Error getting size_local_extra for file while deleting %@" = "Error getting size_local_extra for file while deleting %@"; +"Error getting size_local_extra for file while deleting %@ (current file %@)" = "Error getting size_local_extra for file while deleting %1$@ (current file %2$@)"; /* Detailed error string */ "Error getting the global info of the source file while deleting %@ (%d)" = "Error getting the global info of the source file while deleting %1$@ (%2$d)"; @@ -176,7 +176,7 @@ "Failed to copy archive to external volume '%@', after deleting '%@' from it (%@)" = "Failed to copy archive to external volume '%1$@', after deleting '%2$@' from it (%3$@)"; /* Detailed error string */ -"Failed to create destination directory: %@" = "Failed to create destination directory: %@"; +"Failed to create destination directory %@: %@" = "Failed to create destination directory %1$@: %2$@"; /* Detailed error string */ "Failed to create new file for archive: %@" = "Failed to create new file for archive: %@"; From 95b576f681bc34bc4b2cc6289a5a0e0061141cac Mon Sep 17 00:00:00 2001 From: Dov Frankel Date: Fri, 24 Jun 2022 14:14:33 -0400 Subject: [PATCH 53/56] Fixed iOS-only analyzer warnings --- Source/UZKArchive.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/UZKArchive.m b/Source/UZKArchive.m index b709090..ced1f2b 100644 --- a/Source/UZKArchive.m +++ b/Source/UZKArchive.m @@ -2186,7 +2186,7 @@ - (BOOL)closeFile:(NSError * __autoreleasing*)error err = zipClose(self.zipFile, cmt); if (err != ZIP_OK) { NSString *detail = [NSString localizedStringWithFormat:NSLocalizedStringFromTableInBundle(@"Error closing file in archive in write mode %lu (%d)", @"UnzipKit", _resources, @"Detailed error string"), - self.mode, err]; + (unsigned long)self.mode, err]; UZKLogError("UZKErrorCodeZLibError: %{public}@", detail); [self assignError:error code:UZKErrorCodeZLibError detail:detail]; @@ -2573,7 +2573,7 @@ + (NSString *)errorNameForErrorCode:(NSInteger)errorCode default: errorName = [NSString localizedStringWithFormat: - NSLocalizedStringFromTableInBundle(@"Unknown error code: %ld", @"UnzipKit", _resources, @"UnknownErrorCode"), errorCode]; + NSLocalizedStringFromTableInBundle(@"Unknown error code: %ld", @"UnzipKit", _resources, @"UnknownErrorCode"), (long)errorCode]; break; } From e5e8e18af5e7097e37b343c6169ff069a6ab7962 Mon Sep 17 00:00:00 2001 From: Dov Frankel Date: Fri, 24 Jun 2022 15:00:09 -0400 Subject: [PATCH 54/56] Moved Headers build phase before Compile Sources to resolve a cycle within the build --- UnzipKit.xcodeproj/project.pbxproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UnzipKit.xcodeproj/project.pbxproj b/UnzipKit.xcodeproj/project.pbxproj index b0a4511..1dfa7ac 100644 --- a/UnzipKit.xcodeproj/project.pbxproj +++ b/UnzipKit.xcodeproj/project.pbxproj @@ -398,9 +398,9 @@ buildConfigurationList = 96EA65B41A40AEAE00685B6D /* Build configuration list for PBXNativeTarget "UnzipKit" */; buildPhases = ( 7A900754230AD74C004A95E5 /* ShellScript */, + 96EA659B1A40AEAE00685B6D /* Headers */, 96EA65991A40AEAE00685B6D /* Sources */, 96EA659A1A40AEAE00685B6D /* Frameworks */, - 96EA659B1A40AEAE00685B6D /* Headers */, 96EA659C1A40AEAE00685B6D /* Resources */, ); buildRules = ( From 32f21655d9a8bd4af1e3ee7e9d17f5e974fbd772 Mon Sep 17 00:00:00 2001 From: Dov Frankel Date: Fri, 24 Jun 2022 20:03:21 -0400 Subject: [PATCH 55/56] Added --use-xcframeworks flag to Carthage calls in scripts --- Scripts/archive-carthage.sh | 14 +++++++++++--- Scripts/carthage-validate.sh | 11 +++-------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/Scripts/archive-carthage.sh b/Scripts/archive-carthage.sh index de22a0c..5d7ec18 100755 --- a/Scripts/archive-carthage.sh +++ b/Scripts/archive-carthage.sh @@ -4,7 +4,15 @@ set -ev # Archives the Carthage packages, and prints the name of the archive -carthage build --no-skip-current -carthage archive +carthage build --no-skip-current --use-xcframeworks -export ARCHIVE_PATH="UnzipKit.framework.zip" \ No newline at end of file +# This doesn't work with --use-xcframeworks above (https://github.com/Carthage/Carthage/issues/3130) +# carthage archive + +# When the above starts working again, this won't be necessary +pushd Carthage/Build/UnzipKit.xcframework +zip -r "../../../UnzipKit" . +popd +mv "UnzipKit.zip" "UnzipKit.xcframework" + +export ARCHIVE_PATH="UnzipKit.xcframework" \ No newline at end of file diff --git a/Scripts/carthage-validate.sh b/Scripts/carthage-validate.sh index cce38af..ca3e82b 100755 --- a/Scripts/carthage-validate.sh +++ b/Scripts/carthage-validate.sh @@ -30,18 +30,13 @@ echo "$REPO \"$COMMIT\"" > UnzipKitDemo/Cartfile pushd UnzipKitDemo > /dev/null -carthage bootstrap --configuration Debug --verbose +carthage bootstrap --use-xcframeworks --configuration Debug --verbose EXIT_CODE=$? echo "Checking for build products..." -if [ ! -d "Carthage/Build/Mac/UnzipKit.framework" ]; then - echo "No Mac library built" - EXIT_CODE=1 -fi - -if [ ! -d "Carthage/Build/iOS/UnzipKit.framework" ]; then - echo "No iOS library built" +if [ ! -d "Carthage/Build/UnzipKit.xcframework" ]; then + echo "No library built" EXIT_CODE=1 fi From 3af68c686adfee15edbd0fd271035b637539c96d Mon Sep 17 00:00:00 2001 From: Dov Frankel Date: Fri, 24 Jun 2022 20:17:57 -0400 Subject: [PATCH 56/56] Fixed Travis config warnings and hints --- .travis.yml | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1089bf1..6bb943a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,13 +1,12 @@ -language: - - objective-c - osx_image: xcode13.4 +language: objective-c +os: osx before_script: # Essentially turn off logging. Temporarily undo if more info is needed - sudo log config --mode "level:default" --subsystem com.abbey-code.UnzipKit -matrix: +jobs: include: - stage: Test env: Name=Mac @@ -40,6 +39,3 @@ matrix: if: tag IS present before_install: brew upgrade python # Needs Python 3 script: ./Scripts/push-output.sh - -# Turn on Docker, container-based infrastructure -sudo: false