diff --git a/Source/UZKArchive.h b/Source/UZKArchive.h index a77856f..3b77d96 100644 --- a/Source/UZKArchive.h +++ b/Source/UZKArchive.h @@ -593,26 +593,26 @@ compressionMethod:(UZKCompressionMethod)method 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 - * 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 error Contains an NSError object when there was an error writing to the archive - * - * @return YES if successful, NO on 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 posixPermissions the source 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:(unsigned long)posixPermissions compressionMethod:(UZKCompressionMethod)method password:(nullable NSString *)password overwrite:(BOOL)overwrite @@ -800,20 +800,21 @@ compressionMethod:(UZKCompressionMethod)method * 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 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 + * @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 posixPermissions the source 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 @@ -824,6 +825,7 @@ compressionMethod:(UZKCompressionMethod)method */ - (BOOL)writeIntoBuffer:(NSString *)filePath fileDate:(nullable NSDate *)fileDate + posixPermissions:(unsigned long)posixPermissions compressionMethod:(UZKCompressionMethod)method overwrite:(BOOL)overwrite CRC:(unsigned long)preCRC diff --git a/Source/UZKArchive.m b/Source/UZKArchive.m index 78f58ea..22660bc 100644 --- a/Source/UZKArchive.m +++ b/Source/UZKArchive.m @@ -626,7 +626,10 @@ - (BOOL)extractFilesTo:(NSString *)destinationDirectory UZKLogDebug("Closing file handle"); [deflatedFileHandle closeFile]; - NSDictionary* attribs = [NSDictionary dictionaryWithObjectsAndKeys:info.timestamp, NSFileModificationDate, nil]; + // Retain the permission attribute of a file + NSDictionary* attribs = @{NSFileModificationDate: info.timestamp, + NSFilePosixPermissions: info.posixPermissions}; + [[NSFileManager defaultManager] setAttributes:attribs ofItemAtPath:path error:nil]; if (!extractSuccess) { @@ -1058,6 +1061,7 @@ - (BOOL)writeData:(NSData *)data return [self writeData:data filePath:filePath fileDate:nil + posixPermissions:0 compressionMethod:UZKCompressionMethodDefault password:nil overwrite:YES @@ -1072,6 +1076,7 @@ - (BOOL)writeData:(NSData *)data return [self writeData:data filePath:filePath fileDate:nil + posixPermissions:0 compressionMethod:UZKCompressionMethodDefault password:nil overwrite:YES @@ -1087,6 +1092,7 @@ - (BOOL)writeData:(NSData *)data return [self writeData:data filePath:filePath fileDate:fileDate + posixPermissions:0 compressionMethod:UZKCompressionMethodDefault password:nil overwrite:YES @@ -1119,6 +1125,7 @@ - (BOOL)writeData:(NSData *)data return [self writeData:data filePath:filePath fileDate:fileDate + posixPermissions:0 compressionMethod:method password:password overwrite:YES @@ -1145,18 +1152,20 @@ - (BOOL)writeData:(NSData *)data - (BOOL)writeData:(NSData *)data filePath:(NSString *)filePath - fileDate:(NSDate *)fileDate + fileDate:(nullable NSDate *)fileDate + posixPermissions:(unsigned long)posixPermissions compressionMethod:(UZKCompressionMethod)method - password:(NSString *)password + password:(nullable NSString *)password overwrite:(BOOL)overwrite - error:(NSError * __autoreleasing*)error + error:(NSError *__autoreleasing*)error { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" return [self writeData:data filePath:filePath fileDate:fileDate - compressionMethod:method + posixPermissions:posixPermissions + compressionMethod:UZKCompressionMethodDefault password:password overwrite:overwrite progress:nil @@ -1172,6 +1181,27 @@ - (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 + error:error]; +} + + +- (BOOL)writeData:(NSData *)data + filePath:(NSString *)filePath + fileDate:(NSDate *)fileDate + posixPermissions:(unsigned long)posixPermissions +compressionMethod:(UZKCompressionMethod)method + password:(NSString *)password + overwrite:(BOOL)overwrite + progress:(void (^)(CGFloat percentCompressed))progressBlock + error:(NSError * __autoreleasing*)error { UZKCreateActivity("Writing Data"); @@ -1228,6 +1258,7 @@ - (BOOL)writeData:(NSData *)data } filePath:filePath fileDate:fileDate + posixPermissions:posixPermissions compressionMethod:method password:password overwrite:overwrite @@ -1243,6 +1274,7 @@ - (BOOL)writeIntoBuffer:(NSString *)filePath { return [self writeIntoBuffer:filePath fileDate:nil + posixPermissions:0 compressionMethod:UZKCompressionMethodDefault overwrite:YES CRC:0 @@ -1258,6 +1290,7 @@ - (BOOL)writeIntoBuffer:(NSString *)filePath { return [self writeIntoBuffer:filePath fileDate:fileDate + posixPermissions:0 compressionMethod:UZKCompressionMethodDefault overwrite:YES CRC:0 @@ -1274,6 +1307,7 @@ - (BOOL)writeIntoBuffer:(NSString *)filePath { return [self writeIntoBuffer:filePath fileDate:fileDate + posixPermissions:0 compressionMethod:method overwrite:YES CRC:0 @@ -1291,6 +1325,7 @@ - (BOOL)writeIntoBuffer:(NSString *)filePath { return [self writeIntoBuffer:filePath fileDate:fileDate + posixPermissions:0 compressionMethod:method overwrite:overwrite CRC:0 @@ -1309,6 +1344,7 @@ - (BOOL)writeIntoBuffer:(NSString *)filePath { return [self writeIntoBuffer:filePath fileDate:fileDate + posixPermissions:0 compressionMethod:method overwrite:overwrite CRC:preCRC @@ -1319,6 +1355,7 @@ - (BOOL)writeIntoBuffer:(NSString *)filePath - (BOOL)writeIntoBuffer:(NSString *)filePath fileDate:(NSDate *)fileDate + posixPermissions:(unsigned long)posixPermissions compressionMethod:(UZKCompressionMethod)method overwrite:(BOOL)overwrite CRC:(uLong)preCRC @@ -1380,6 +1417,7 @@ - (BOOL)writeIntoBuffer:(NSString *)filePath } filePath:filePath fileDate:fileDate + posixPermissions:posixPermissions compressionMethod:method password:password overwrite:overwrite @@ -1922,6 +1960,7 @@ - (BOOL)performActionWithArchiveOpen:(void(^)(NSError * __autoreleasing*innerErr - (BOOL)performWriteAction:(int(^)(uLong *crc, NSError * __autoreleasing*innerError))write filePath:(NSString *)filePath fileDate:(NSDate *)fileDate + posixPermissions:(unsigned long)posixPermissions compressionMethod:(UZKCompressionMethod)method password:(NSString *)password overwrite:(BOOL)overwrite @@ -1973,6 +2012,12 @@ - (BOOL)performWriteAction:(int(^)(uLong *crc, NSError * __autoreleasing*innerEr UZKLogDebug("Making zip_fileinfo struct for date %{time_t}ld", lrint(fileDate.timeIntervalSince1970)); zip_fileinfo zi = [UZKArchive zipFileInfoForDate:fileDate]; + if (posixPermissions > 0) { + + // Revert the value of NSFilePosixPermissions to zip external_fa raw data + zi.external_fa = (posixPermissions + 32768) << 16; + } + const char *passwordStr = NULL; if (password) { @@ -2653,6 +2698,7 @@ + (zip_fileinfo)zipFileInfoForDate:(NSDate *)fileDate zi.tmz_date.tm_year = (uInt)date.year; zi.internal_fa = 0; zi.external_fa = 0; +// zi.external_fa = 2175008768; // default posixPermissions value on zip: 2175008768 (0644U) zi.dosDate = 0; return zi; diff --git a/Source/UZKFileInfo.h b/Source/UZKFileInfo.h index 0d7f0f6..7560e1e 100644 --- a/Source/UZKFileInfo.h +++ b/Source/UZKFileInfo.h @@ -73,11 +73,24 @@ typedef NS_ENUM(NSInteger, UZKCompressionMethod) { */ @property (readonly) BOOL isDirectory; +/** + * YES if the item is a symLink + */ +@property (readonly) BOOL isSymLink; + +/** + * YES if the item is a resource fork + */ +@property (readonly) BOOL isResourceFork; + /** * The type of compression */ @property (readonly) UZKCompressionMethod compressionMethod; - +/** + @brief posixPermissions (posixPermissions of the file,The value from the file attributes - NSFilePosixPermissions) + */ +@property (nonatomic, readonly) NSNumber *posixPermissions; @end diff --git a/Source/UZKFileInfo.m b/Source/UZKFileInfo.m index 1e070a7..ebfef7e 100644 --- a/Source/UZKFileInfo.m +++ b/Source/UZKFileInfo.m @@ -35,14 +35,18 @@ - (instancetype)initWithFileInfo:(unz_file_info64 *)fileInfo filename:(NSString _zipTMUDate = fileInfo->tmu_date; _CRC = fileInfo->crc; _isEncryptedWithPassword = (fileInfo->flag & 1) != 0; - _isDirectory = [filename hasSuffix:@"/"]; +// _isDirectory = [filename hasSuffix:@"/"]; + _isDirectory = [self isDirectoryWith:fileInfo]; + _isSymLink = [self isSymLinkWith:fileInfo->external_fa]; if (_isDirectory) { _filename = [_filename substringToIndex:_filename.length - 1]; } + _isResourceFork = [[filename pathComponents].firstObject isEqualToString:@"__MACOSX"]; _compressionMethod = [self readCompressionMethod:fileInfo->compression_method flag:fileInfo->flag]; + _posixPermissions = @((fileInfo->external_fa >> 16) ? (fileInfo->external_fa >> 16) : 0755U); } return self; } @@ -102,4 +106,20 @@ - (NSDate *)readDate:(tm_unz)date return [calendar dateFromComponents:components]; } +- (BOOL)isDirectoryWith:(unz_file_info64 *)fileInfo { +// NSLog(@"ISDIR__ %@", S_ISDIR(fileInfo->external_fa)?@"YES":@"NO"); + uLong type = fileInfo->external_fa >> 0x1D & 0x1F; + if (0 == (fileInfo->version >> 8)) { //is DOS-archive + type = fileInfo->external_fa >> 4; + return (type == 0x01) && ![self isSymLinkWith:fileInfo->external_fa]; + } + return (0x02 == type) && ![self isSymLinkWith:fileInfo->external_fa]; +} + +- (BOOL)isSymLinkWith:(uLong)externalFileAttributes { + + uLong type = externalFileAttributes >> 0x1D & 0x1F; + return 0x05 == type; +} + @end diff --git a/Tests/FileDescriptorUsageTests.m b/Tests/FileDescriptorUsageTests.m index a824da7..481a69e 100644 --- a/Tests/FileDescriptorUsageTests.m +++ b/Tests/FileDescriptorUsageTests.m @@ -146,6 +146,7 @@ - (void)testFileDescriptorUsage_WriteIntoArchive BOOL writeResult = [archive writeData:newFileData filePath:fileName fileDate:[NSDate date] + posixPermissions:0 compressionMethod:UZKCompressionMethodDefault password:nil overwrite:YES diff --git a/Tests/PrmissionsTests.m b/Tests/PrmissionsTests.m new file mode 100644 index 0000000..69defe0 --- /dev/null +++ b/Tests/PrmissionsTests.m @@ -0,0 +1,64 @@ +// +// PrmissionsTests.m +// UnzipKitTests +// +// Created by MartinLau on 2019/6/24. +// Copyright © 2019 Abbey Code. All rights reserved. +// + +#import "UnzipKit.h" +#import "UZKArchiveTestCase.h" + +@interface PrmissionsTests : UZKArchiveTestCase + +@end + +@implementation PrmissionsTests + +- (void)setUp { + // Put setup code here. This method is called before the invocation of each test method in the class. + [super setUp]; +} + +- (void)tearDown { + // Put teardown code here. This method is called after the invocation of each test method in the class. + +} + +- (void)testExtract { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct results. + + UZKArchive *archive = [[UZKArchive alloc] initWithURL:self.testFileURLs[@"Test Permissions Archive.zip"] error:nil]; + NSString *extractDirectory = [self randomDirectoryWithPrefix:archive.filename.stringByDeletingPathExtension]; + + // show zip file posixPermissions value + NSArray *archiveItems = [archive listFileInfo:nil]; + for (UZKFileInfo *item in archiveItems) { + + if (![item isDirectory] && ![item isResourceFork]) { + + NSLog(@"zip file %@ : posixPermissions_%ld", item.filename, [item.posixPermissions unsignedLongValue] - 32768); + } + } + + printf("====================="); + + NSError *extractError = nil; + BOOL success = [archive extractFilesTo:extractDirectory overwrite:NO error:&extractError]; + XCTAssert(success, @"extract error %@", extractError); + + // show local file posixPermissions value + NSArray *localItems = [[NSFileManager defaultManager] subpathsAtPath:extractDirectory]; + [localItems enumerateObjectsUsingBlock:^(NSString *_Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { + + BOOL isDir = NO; + if ([[NSFileManager defaultManager] fileExistsAtPath:[extractDirectory stringByAppendingPathComponent:obj] isDirectory:&isDir] && !isDir) { + + NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:[extractDirectory stringByAppendingPathComponent:obj] error:nil]; + NSLog(@"local file %@: posixPermissions_ %lu", obj , [attributes filePosixPermissions]); + } + }]; +} + +@end diff --git a/Tests/Test Data/Test Permissions Archive.zip b/Tests/Test Data/Test Permissions Archive.zip new file mode 100644 index 0000000..158a90e Binary files /dev/null and b/Tests/Test Data/Test Permissions Archive.zip differ diff --git a/Tests/UZKArchiveTestCase.m b/Tests/UZKArchiveTestCase.m index ff90e54..f7dfcf4 100644 --- a/Tests/UZKArchiveTestCase.m +++ b/Tests/UZKArchiveTestCase.m @@ -61,7 +61,8 @@ - (void)setUp { @"Test File B.jpg", @"Test File C.m4a", @"NotAZip-PK-ContentsUnknown", - @"Modified CRC Archive.zip"]; + @"Modified CRC Archive.zip", + @"Test Permissions Archive.zip",]; NSArray *unicodeFiles = @[@"Ⓣest Ⓐrchive.zip", @"Test File Ⓐ.txt", diff --git a/UnzipKit.xcodeproj/project.pbxproj b/UnzipKit.xcodeproj/project.pbxproj index 44111cb..7496d91 100644 --- a/UnzipKit.xcodeproj/project.pbxproj +++ b/UnzipKit.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 5B5A9B9522C0B0CB00CD64D1 /* PrmissionsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 5B5A9B9422C0B0CB00CD64D1 /* PrmissionsTests.m */; }; 7A00291B1F93DB9200618503 /* ioapi.c in Sources */ = {isa = PBXBuildFile; fileRef = 96EA65C31A40C44300685B6D /* ioapi.c */; }; 7A00291C1F93DB9200618503 /* mztools.c in Sources */ = {isa = PBXBuildFile; fileRef = 96EA65C51A40C44300685B6D /* mztools.c */; }; 7A00291D1F93DB9200618503 /* unzip.c in Sources */ = {isa = PBXBuildFile; fileRef = 96EA65C71A40C44300685B6D /* unzip.c */; }; @@ -75,6 +76,7 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 5B5A9B9422C0B0CB00CD64D1 /* PrmissionsTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PrmissionsTests.m; sourceTree = ""; }; 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 = ""; }; @@ -240,29 +242,30 @@ children = ( 96EA65BF1A40BF1A00685B6D /* Test Data */, 961A9BB61B306902007C4C6B /* UZKArchiveTestCase.h */, - 96FCC8401B306CDD00726AC7 /* UZKArchiveTestCase.m */, - 963386B71EE89A49006B16BF /* UtilityMethods.swift */, 7A5652231F90E01C006B782E /* CheckDataTests.m */, 968C40DB1B586401004C128E /* CommentsTests.m */, 968C40D51B586380004C128E /* DeleteFileTests.m */, 968C40D71B5863A9004C128E /* ErrorHandlingTests.m */, 968C40CF1B5862A0004C128E /* ExtractBufferedDataTests.m */, 968C40C91B586227004C128E /* ExtractDataTests.m */, - 9630C0371C6D27A4000693EE /* ExtractDataTests_Swift.swift */, 968C40C71B5861F4004C128E /* ExtractFilesTests.m */, - 7A5A97001F89808900BCA061 /* ProgressReportingTests.m */, 968C40D91B5863D9004C128E /* FileDescriptorUsageTests.m */, - 968C40C31B58619C004C128E /* ListFilenamesTests.m */, 968C40C51B5861C3004C128E /* ListFileInfoTests.m */, + 968C40C31B58619C004C128E /* ListFilenamesTests.m */, 968C40BF1B585FDE004C128E /* ModesTests.m */, 968C40DD1B58642C004C128E /* MultithreadingTests.m */, 968C40D11B586310004C128E /* PasswordProtectionTests.m */, 968C40CD1B586277004C128E /* PerformOnDataTests.m */, 968C40CB1B586253004C128E /* PerformOnFilesTests.m */, + 5B5A9B9422C0B0CB00CD64D1 /* PrmissionsTests.m */, + 7A5A97001F89808900BCA061 /* ProgressReportingTests.m */, 968C40DF1B586490004C128E /* PropertyTests.m */, + 96FCC8401B306CDD00726AC7 /* UZKArchiveTestCase.m */, 968C40D31B586345004C128E /* WriteBufferedDataTests.m */, - 961A9BB41B306881007C4C6B /* WriteDataTests.swift */, 968C40C11B586132004C128E /* ZipFileDetectionTests.m */, + 9630C0371C6D27A4000693EE /* ExtractDataTests_Swift.swift */, + 963386B71EE89A49006B16BF /* UtilityMethods.swift */, + 961A9BB41B306881007C4C6B /* WriteDataTests.swift */, 96EA65AE1A40AEAE00685B6D /* Supporting Files */, ); name = UnzipKitTests; @@ -515,6 +518,7 @@ files = ( 96EA65BD1A40B2EC00685B6D /* UZKArchive.m in Sources */, 96EA66021A40E31900685B6D /* UZKFileInfo.m in Sources */, + 5B5A9B9522C0B0CB00CD64D1 /* PrmissionsTests.m in Sources */, 965CF00C1D241A8F00C80A88 /* NSURL+UnzipKitExtensions.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0;