diff --git a/Source/UZKArchive.h b/Source/UZKArchive.h index a77856f..ce99538 100644 --- a/Source/UZKArchive.h +++ b/Source/UZKArchive.h @@ -618,6 +618,34 @@ compressionMethod:(UZKCompressionMethod)method overwrite:(BOOL)overwrite error:(NSError **)error; +/** + * 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 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 + 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 @@ -649,6 +677,39 @@ compressionMethod:(UZKCompressionMethod)method 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. @@ -807,7 +868,7 @@ compressionMethod:(UZKCompressionMethod)method * the data into the archive without removing it first (legacy Objective-Zip * behavior) * @param preCRC The CRC-32 for the data being sent. Only necessary if encrypting the file. - Pass 0 otherwise + * Pass 0 otherwise * @param password Override the password associated with the archive (not recommended) * @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 @@ -831,6 +892,48 @@ compressionMethod:(UZKCompressionMethod)method 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, 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 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 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 preCRC The CRC-32 for the data being sent. Only necessary if encrypting the file. + * Pass 0 otherwise + * @param password Override the password associated with the archive (not recommended) + * @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:(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; + /** * Removes the given file from the archive * diff --git a/Source/UZKArchive.m b/Source/UZKArchive.m index 78f58ea..a8ac32a 100644 --- a/Source/UZKArchive.m +++ b/Source/UZKArchive.m @@ -626,7 +626,9 @@ - (BOOL)extractFilesTo:(NSString *)destinationDirectory UZKLogDebug("Closing file handle"); [deflatedFileHandle closeFile]; - NSDictionary* attribs = [NSDictionary dictionaryWithObjectsAndKeys:info.timestamp, NSFileModificationDate, nil]; + // Restore the timestamp and permission attributes of the file + NSDictionary* attribs = @{NSFileModificationDate: info.timestamp, + NSFilePosixPermissions: @(info.posixPermissions)}; [[NSFileManager defaultManager] setAttributes:attribs ofItemAtPath:path error:nil]; if (!extractSuccess) { @@ -1172,6 +1174,50 @@ - (BOOL)writeData:(NSData *)data overwrite:(BOOL)overwrite progress:(void (^)(CGFloat percentCompressed))progressBlock error:(NSError * __autoreleasing*)error +{ + return [self writeData:data + filePath:filePath + fileDate:fileDate + posixPermissions:0 + 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 + posixPermissions:(short)permissions +compressionMethod:(UZKCompressionMethod)method + password:(NSString *)password + overwrite:(BOOL)overwrite + progress:(void (^)(CGFloat percentCompressed))progressBlock + error:(NSError * __autoreleasing*)error { UZKCreateActivity("Writing Data"); @@ -1228,6 +1274,7 @@ - (BOOL)writeData:(NSData *)data } filePath:filePath fileDate:fileDate + posixPermissions:permissions compressionMethod:method password:password overwrite:overwrite @@ -1243,6 +1290,7 @@ - (BOOL)writeIntoBuffer:(NSString *)filePath { return [self writeIntoBuffer:filePath fileDate:nil + posixPermissions:0 compressionMethod:UZKCompressionMethodDefault overwrite:YES CRC:0 @@ -1258,6 +1306,7 @@ - (BOOL)writeIntoBuffer:(NSString *)filePath { return [self writeIntoBuffer:filePath fileDate:fileDate + posixPermissions:0 compressionMethod:UZKCompressionMethodDefault overwrite:YES CRC:0 @@ -1274,6 +1323,7 @@ - (BOOL)writeIntoBuffer:(NSString *)filePath { return [self writeIntoBuffer:filePath fileDate:fileDate + posixPermissions:0 compressionMethod:method overwrite:YES CRC:0 @@ -1291,6 +1341,7 @@ - (BOOL)writeIntoBuffer:(NSString *)filePath { return [self writeIntoBuffer:filePath fileDate:fileDate + posixPermissions:0 compressionMethod:method overwrite:overwrite CRC:0 @@ -1309,6 +1360,7 @@ - (BOOL)writeIntoBuffer:(NSString *)filePath { return [self writeIntoBuffer:filePath fileDate:fileDate + posixPermissions:0 compressionMethod:method overwrite:overwrite CRC:preCRC @@ -1325,6 +1377,27 @@ - (BOOL)writeIntoBuffer:(NSString *)filePath password:(NSString *)password 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]; +} + +- (BOOL)writeIntoBuffer:(NSString *)filePath + fileDate:(NSDate *)fileDate + posixPermissions:(short)permissions + compressionMethod:(UZKCompressionMethod)method + overwrite:(BOOL)overwrite + CRC:(uLong)preCRC + password:(NSString *)password + error:(NSError *__autoreleasing *)error + block:(BOOL (^)(BOOL (^)(const void *, unsigned int), NSError *__autoreleasing *))action { UZKCreateActivity("Writing Into Buffer"); @@ -1380,6 +1453,7 @@ - (BOOL)writeIntoBuffer:(NSString *)filePath } filePath:filePath fileDate:fileDate + posixPermissions:permissions compressionMethod:method password:password overwrite:overwrite @@ -1922,6 +1996,7 @@ - (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 @@ -1971,7 +2046,8 @@ - (BOOL)performWriteAction:(int(^)(uLong *crc, NSError * __autoreleasing*innerEr UZKCreateActivity("Performing Write Action"); UZKLogDebug("Making zip_fileinfo struct for date %{time_t}ld", lrint(fileDate.timeIntervalSince1970)); - zip_fileinfo zi = [UZKArchive zipFileInfoForDate:fileDate]; + zip_fileinfo zi = [UZKArchive zipFileInfoForDate:fileDate + posixPermissions:permissions]; const char *passwordStr = NULL; @@ -2623,6 +2699,7 @@ + (NSString *)errorNameForErrorCode:(NSInteger)errorCode } + (zip_fileinfo)zipFileInfoForDate:(NSDate *)fileDate + posixPermissions:(short)permissions { NSCalendar *calendar = [NSCalendar calendarWithIdentifier:NSCalendarIdentifierGregorian]; @@ -2655,6 +2732,11 @@ + (zip_fileinfo)zipFileInfoForDate:(NSDate *)fileDate zi.external_fa = 0; zi.dosDate = 0; + if (permissions > 0) { + unsigned long permissionsMask = (permissions & 0777) << 16; + zi.external_fa |= permissionsMask; + } + return zi; } diff --git a/Source/UZKFileInfo.h b/Source/UZKFileInfo.h index 0d7f0f6..e14248e 100644 --- a/Source/UZKFileInfo.h +++ b/Source/UZKFileInfo.h @@ -78,6 +78,13 @@ typedef NS_ENUM(NSInteger, UZKCompressionMethod) { */ @property (readonly) UZKCompressionMethod compressionMethod; +/** + * The POSIX permissions of the file, like you would get by retrieving the `NSFilePosixPermissions` + * key from the attributes NSFileManager returns. Assign in octal form, like 0777 in Objective-C or + * 0o777 in Swift + */ +@property (nonatomic, readonly) short posixPermissions; + @end diff --git a/Source/UZKFileInfo.m b/Source/UZKFileInfo.m index 1e070a7..050adf6 100644 --- a/Source/UZKFileInfo.m +++ b/Source/UZKFileInfo.m @@ -43,6 +43,9 @@ - (instancetype)initWithFileInfo:(unz_file_info64 *)fileInfo filename:(NSString _compressionMethod = [self readCompressionMethod:fileInfo->compression_method flag:fileInfo->flag]; + + uLong permissions = (fileInfo->external_fa >> 16) & 0777U; + _posixPermissions = permissions ? permissions : 0644U; } return self; } diff --git a/Tests/PermissionsTests.swift b/Tests/PermissionsTests.swift new file mode 100644 index 0000000..7910068 --- /dev/null +++ b/Tests/PermissionsTests.swift @@ -0,0 +1,183 @@ +// +// PermissionsTests.swift +// UnzipKitTests +// +// Created by Dov Frankel on 6/24/19. +// Copyright © 2019 Abbey Code. All rights reserved. +// + +import XCTest + +class PermissionsTests: UZKArchiveTestCase { + + #if os(OSX) + func testReadFileInfo() { + let permissionLevelsToTest: [Int16] = [ + 0o777, + 0o707, + 0o770, + 0o477, + 0o666, + 0o606, + 0o660, + 0o466, + ] + + let fileURLs: [URL] = permissionLevelsToTest.map { + let textFile = self.emptyTextFile(ofLength: 20)! + try! FileManager.default.setAttributes([.posixPermissions: $0], + ofItemAtPath: textFile.path) + return textFile + } + + let archiveURL = self.archive(withFiles: fileURLs)! + let archive = try! UZKArchive(url: archiveURL) + + let fileInfo = try! archive.listFileInfo() + + let expectedPermissions = zip( + fileURLs.map { $0.lastPathComponent }, + permissionLevelsToTest + ) + .reduce(into: [String:Int16]()) { result, pair in + result[pair.0] = pair.1 + } + let actualPermissions = fileInfo.reduce([String: Int16]()) { + var resultDict = $0 + resultDict[$1.filename] = $1.posixPermissions + return resultDict + } + + XCTAssertEqual(actualPermissions, expectedPermissions) + } + + func testExtraction() { + let permissionLevelsToTest: [Int16] = [ + 0o777, + 0o707, + 0o770, + 0o477, + 0o666, + 0o606, + 0o660, + 0o466, + ] + + let fileURLs: [URL] = permissionLevelsToTest.map { + let textFile = self.emptyTextFile(ofLength: 20)! + try! FileManager.default.setAttributes([.posixPermissions: $0], + ofItemAtPath: textFile.path) + return textFile + } + + let archiveURL = self.archive(withFiles: fileURLs)! + let archive = try! UZKArchive(url: archiveURL) + + let extractDirectory = self.randomDirectory(withPrefix: "PermissionsTest")! + let extractURL = self.tempDirectory.appendingPathComponent(extractDirectory) + + try! archive.extractFiles(to: extractURL.path, overwrite: false) + NSLog("Extracted to \(extractURL.path)") + + let expectedPermissions = zip( + fileURLs.map { extractURL.appendingPathComponent($0.lastPathComponent).path }, + permissionLevelsToTest + ) + + for (path, expectedPermissionLevel) in expectedPermissions { + let actualPermissions = try! FileManager.default.attributesOfItem(atPath: path)[.posixPermissions] as! NSNumber + XCTAssertEqual(actualPermissions.int16Value, expectedPermissionLevel, "Permissions mismatch for \(path)") + } + } + #endif + + func testWriteData_Default() { + let testArchiveURL = tempDirectory.appendingPathComponent("PermissionsTestWriteData.zip") + let testFilename = nonZipTestFilePaths.first as! String + let testFileURL = testFileURLs[testFilename] as! URL + let testFileData = try! Data(contentsOf: testFileURL) + + let writeArchive = try! UZKArchive(url: testArchiveURL) + + try! writeArchive.write(testFileData, filePath: testFilename) + + let readArchive = try! UZKArchive(url: testArchiveURL) + let fileList = try! readArchive.listFileInfo() + + let writtenFileInfo = fileList.first { $0.filename == testFilename } + let actualPermissions = writtenFileInfo!.posixPermissions + + XCTAssertEqual(actualPermissions, 0o644) + } + + func testWriteData_NonDefault() { + let testArchiveURL = tempDirectory.appendingPathComponent("PermissionsTestWriteData.zip") + let testFilename = nonZipTestFilePaths.first as! String + let testFileURL = testFileURLs[testFilename] as! URL + let testFileData = try! Data(contentsOf: testFileURL) + + let writeArchive = try! UZKArchive(url: testArchiveURL) + + let expectedPermissions: Int16 = 0o742 + + try! writeArchive.write(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 as! String + 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 readArchive = try! UZKArchive(url: testArchiveURL) + let fileList = try! readArchive.listFileInfo() + + let writtenFileInfo = fileList.first { $0.filename == testFilename } + let actualPermissions = writtenFileInfo!.posixPermissions + + XCTAssertEqual(actualPermissions, 0o644) + } + + func testWriteIntoBuffer_NonDefault() { + let testArchiveURL = tempDirectory.appendingPathComponent("PermissionsTestWriteBufferedData_CustomPermissions.zip") + let testFilename = nonZipTestFilePaths.first as! String + let testFileURL = testFileURLs[testFilename] as! URL + let testFileData = try! Data(contentsOf: testFileURL) + + 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) + { (writeDataHandler, error) in + testFileData.withUnsafeBytes({ buffer in + 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) + } + +} diff --git a/Tests/UZKArchiveTestCase.h b/Tests/UZKArchiveTestCase.h index e9c5c8c..1373471 100644 --- a/Tests/UZKArchiveTestCase.h +++ b/Tests/UZKArchiveTestCase.h @@ -42,8 +42,8 @@ #if !TARGET_OS_IPHONE - (NSInteger)numberOfOpenFileHandles; -- (NSURL *)archiveWithFiles:(NSArray *)fileURLs; -- (NSURL *)archiveWithFiles:(NSArray *)fileURLs password:(NSString *)password; +- (NSURL *)archiveWithFiles:(NSArray *)fileURLs; +- (NSURL *)archiveWithFiles:(NSArray *)fileURLs password:(NSString *)password; - (BOOL)extractArchive:(NSURL *)url password:(NSString *)password; - (NSURL *)largeArchive; #endif diff --git a/Tests/UZKArchiveTestCase.m b/Tests/UZKArchiveTestCase.m index ff90e54..2cd9fe8 100644 --- a/Tests/UZKArchiveTestCase.m +++ b/Tests/UZKArchiveTestCase.m @@ -50,7 +50,8 @@ - (void)setUp { NSString *uniqueName = [self randomDirectoryName]; NSError *error = nil; - NSArray *testFiles = @[@"Test Archive.zip", + NSArray *testFiles = @[ + @"Test Archive.zip", @"Test Archive (Password).zip", @"L'incertain.zip", @"Aces.zip", @@ -61,12 +62,15 @@ - (void)setUp { @"Test File B.jpg", @"Test File C.m4a", @"NotAZip-PK-ContentsUnknown", - @"Modified CRC Archive.zip"]; + @"Modified CRC Archive.zip", + ]; - NSArray *unicodeFiles = @[@"Ⓣest Ⓐrchive.zip", + NSArray *unicodeFiles = @[ + @"Ⓣest Ⓐrchive.zip", @"Test File Ⓐ.txt", @"Test File Ⓑ.jpg", - @"Test File Ⓒ.m4a"]; + @"Test File Ⓒ.m4a", + ]; NSString *tempDirSubtree = [@"UnzipKitTest" stringByAppendingPathComponent:uniqueName]; diff --git a/UnzipKit.xcodeproj/project.pbxproj b/UnzipKit.xcodeproj/project.pbxproj index 44111cb..2986ac9 100644 --- a/UnzipKit.xcodeproj/project.pbxproj +++ b/UnzipKit.xcodeproj/project.pbxproj @@ -19,6 +19,7 @@ 7A0029241F93DBF000618503 /* libminizip.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7A0029171F93DB5800618503 /* libminizip.a */; }; 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 */; }; 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 */; }; @@ -78,6 +79,7 @@ 7A0029171F93DB5800618503 /* libminizip.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libminizip.a; sourceTree = BUILT_PRODUCTS_DIR; }; 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 = ""; }; 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 = ""; }; @@ -259,6 +261,7 @@ 968C40D11B586310004C128E /* PasswordProtectionTests.m */, 968C40CD1B586277004C128E /* PerformOnDataTests.m */, 968C40CB1B586253004C128E /* PerformOnFilesTests.m */, + 7AA77FC722C16CF600121052 /* PermissionsTests.swift */, 968C40DF1B586490004C128E /* PropertyTests.m */, 968C40D31B586345004C128E /* WriteBufferedDataTests.m */, 961A9BB41B306881007C4C6B /* WriteDataTests.swift */, @@ -543,6 +546,7 @@ 968C40DC1B586401004C128E /* CommentsTests.m in Sources */, 968C40D21B586310004C128E /* PasswordProtectionTests.m in Sources */, 963386B91EE89A51006B16BF /* UtilityMethods.swift in Sources */, + 7AA77FC822C16CF600121052 /* PermissionsTests.swift in Sources */, 968C40C81B5861F4004C128E /* ExtractFilesTests.m in Sources */, 968C40D01B5862A0004C128E /* ExtractBufferedDataTests.m in Sources */, 968C40CE1B586277004C128E /* PerformOnDataTests.m in Sources */,