// // OZZipFile.m // Objective-Zip v. 1.0.0 // // Created by Gianluca Bertani on 25/12/09. // Copyright 2009-2015 Gianluca Bertani. All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions // are met: // // * Redistributions of source code must retain the above copyright notice, // this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright notice, // this list of conditions and the following disclaimer in the documentation // and/or other materials provided with the distribution. // * Neither the name of Gianluca Bertani nor the names of its contributors // may be used to endorse or promote products derived from this software // without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. // #import "OZZipFile.h" #import "OZZipFile+Standard.h" #import "OZZipFile+NSError.h" #import "OZZipException.h" #import "OZZipException+Internals.h" #import "OZZipReadStream.h" #import "OZZipReadStream+Standard.h" #import "OZZipReadStream+NSError.h" #import "OZZipReadStream+Internals.h" #import "OZZipWriteStream.h" #import "OZZipWriteStream+Standard.h" #import "OZZipWriteStream+NSError.h" #import "OZZipWriteStream+Internals.h" #import "OZFileInZipInfo.h" #import "OZFileInZipInfo+Internals.h" #include "zip.h" #include "unzip.h" #define FILE_IN_ZIP_MAX_NAME_LENGTH (256) #pragma mark - #pragma mark OZZipFile extension @interface OZZipFile () { NSString *_fileName; OZZipFileMode _mode; BOOL _legacy32BitMode; @private zipFile _zipFile; unzFile _unzFile; } @end #pragma mark - #pragma mark OZZipFile implementation @implementation OZZipFile #pragma mark - #pragma mark Initialization - (instancetype) initWithFileName:(NSString *)fileName mode:(OZZipFileMode)mode { return [self initWithFileName:fileName mode:mode legacy32BitMode:NO]; } - (instancetype) initWithFileName:(NSString *)fileName mode:(OZZipFileMode)mode legacy32BitMode:(BOOL)legacy32BitMode { if (self= [super init]) { _fileName= fileName; _mode= mode; _legacy32BitMode= legacy32BitMode; const char *path= [_fileName cStringUsingEncoding:NSUTF8StringEncoding]; switch (mode) { case OZZipFileModeUnzip: // Support for legacy 32 bit mode: here we use 32 or 64 bit version // alternatively, as internal (common) version is not exposed _unzFile= (_legacy32BitMode ? unzOpen(path) : unzOpen64(path)); if (_unzFile == NULL) @throw [OZZipException zipExceptionWithError:ERROR_NO_SUCH_FILE reason:@"Can't open '%@'", _fileName]; break; case OZZipFileModeCreate: // Support for legacy 32 bit mode: here we use the common version _zipFile= zipOpen3(path, APPEND_STATUS_CREATE, 0, NULL, NULL); if (_zipFile == NULL) @throw [OZZipException zipExceptionWithError:ERROR_NO_SUCH_FILE reason:@"Can't open '%@'", _fileName]; break; case OZZipFileModeAppend: // Support for legacy 32 bit mode: here we use the common version _zipFile= zipOpen3(path, APPEND_STATUS_ADDINZIP, 0, NULL, NULL); if (_zipFile == NULL) @throw [OZZipException zipExceptionWithError:ERROR_NO_SUCH_FILE reason:@"Can't open '%@'", _fileName]; break; default: @throw [OZZipException zipExceptionWithReason:@"Unknown mode %d", _mode]; } } return self; } #pragma mark - #pragma mark Initialization (NSError variants) - (instancetype) initWithFileName:(NSString *)fileName mode:(OZZipFileMode)mode error:(NSError *__autoreleasing *)error { ERROR_WRAP_BEGIN { return [self initWithFileName:fileName mode:mode]; } ERROR_WRAP_END_AND_RETURN(error, nil); } - (instancetype) initWithFileName:(NSString *)fileName mode:(OZZipFileMode)mode legacy32BitMode:(BOOL)legacy32BitMode error:(NSError *__autoreleasing *)error { ERROR_WRAP_BEGIN { return [self initWithFileName:fileName mode:mode legacy32BitMode:legacy32BitMode]; } ERROR_WRAP_END_AND_RETURN(error, nil); } #pragma mark - #pragma mark File writing - (OZZipWriteStream *) writeFileInZipWithName:(NSString *)fileNameInZip compressionLevel:(OZZipCompressionLevel)compressionLevel { if (_mode == OZZipFileModeUnzip) @throw [OZZipException zipExceptionWithReason:@"Operation not permitted in Unzip mode"]; NSDate *now= [NSDate date]; NSCalendar *calendar= [NSCalendar currentCalendar]; NSDateComponents *date= [calendar components:(NSCalendarUnitSecond | NSCalendarUnitMinute | NSCalendarUnitHour | NSCalendarUnitDay | NSCalendarUnitMonth | NSCalendarUnitYear) fromDate:now]; zip_fileinfo zi; zi.tmz_date.tm_sec= (uInt) [date second]; zi.tmz_date.tm_min= (uInt) [date minute]; zi.tmz_date.tm_hour= (uInt) [date hour]; zi.tmz_date.tm_mday= (uInt) [date day]; zi.tmz_date.tm_mon= (uInt) [date month] -1; zi.tmz_date.tm_year= (uInt) [date year]; zi.internal_fa= 0; zi.external_fa= 0; zi.dosDate= 0; // Support for legacy 32 bit mode: here we use the common version, // passing a flag to tell if it is a 32 or 64 bit file int err= zipOpenNewFileInZip3_64(_zipFile, [fileNameInZip cStringUsingEncoding:NSUTF8StringEncoding], &zi, NULL, 0, NULL, 0, NULL, (compressionLevel != OZZipCompressionLevelNone) ? Z_DEFLATED : 0, compressionLevel, 0, -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY, NULL, 0, (_legacy32BitMode ? 0 : 1)); if (err != ZIP_OK) @throw [OZZipException zipExceptionWithError:err reason:@"Error opening '%@' in zipfile", fileNameInZip]; return [[OZZipWriteStream alloc] initWithZipFileStruct:_zipFile fileNameInZip:fileNameInZip]; } - (OZZipWriteStream *) writeFileInZipWithName:(NSString *)fileNameInZip fileDate:(NSDate *)fileDate compressionLevel:(OZZipCompressionLevel)compressionLevel { if (_mode == OZZipFileModeUnzip) @throw [OZZipException zipExceptionWithReason:@"Operation not permitted in Unzip mode"]; NSCalendar *calendar= [NSCalendar currentCalendar]; NSDateComponents *date= [calendar components:(NSCalendarUnitSecond | NSCalendarUnitMinute | NSCalendarUnitHour | NSCalendarUnitDay | NSCalendarUnitMonth | NSCalendarUnitYear) fromDate:fileDate]; zip_fileinfo zi; zi.tmz_date.tm_sec= (uInt) [date second]; zi.tmz_date.tm_min= (uInt) [date minute]; zi.tmz_date.tm_hour= (uInt) [date hour]; zi.tmz_date.tm_mday= (uInt) [date day]; zi.tmz_date.tm_mon= (uInt) [date month] -1; zi.tmz_date.tm_year= (uInt) [date year]; zi.internal_fa= 0; zi.external_fa= 0; zi.dosDate= 0; // Support for legacy 32 bit mode: here we use the common version, // passing a flag to tell if it is a 32 or 64 bit file int err= zipOpenNewFileInZip3_64(_zipFile, [fileNameInZip cStringUsingEncoding:NSUTF8StringEncoding], &zi, NULL, 0, NULL, 0, NULL, (compressionLevel != OZZipCompressionLevelNone) ? Z_DEFLATED : 0, compressionLevel, 0, -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY, NULL, 0, (_legacy32BitMode ? 0 : 1)); if (err != ZIP_OK) @throw [OZZipException zipExceptionWithError:err reason:@"Error opening '%@' in zipfile", fileNameInZip]; return [[OZZipWriteStream alloc] initWithZipFileStruct:_zipFile fileNameInZip:fileNameInZip]; } - (OZZipWriteStream *) writeFileInZipWithName:(NSString *)fileNameInZip fileDate:(NSDate *)fileDate compressionLevel:(OZZipCompressionLevel)compressionLevel password:(NSString *)password crc32:(NSUInteger)crc32 { if (_mode == OZZipFileModeUnzip) @throw [OZZipException zipExceptionWithReason:@"Operation not permitted in Unzip mode"]; NSCalendar *calendar= [NSCalendar currentCalendar]; NSDateComponents *date= [calendar components:(NSCalendarUnitSecond | NSCalendarUnitMinute | NSCalendarUnitHour | NSCalendarUnitDay | NSCalendarUnitMonth | NSCalendarUnitYear) fromDate:fileDate]; zip_fileinfo zi; zi.tmz_date.tm_sec= (uInt) [date second]; zi.tmz_date.tm_min= (uInt) [date minute]; zi.tmz_date.tm_hour= (uInt) [date hour]; zi.tmz_date.tm_mday= (uInt) [date day]; zi.tmz_date.tm_mon= (uInt) [date month] -1; zi.tmz_date.tm_year= (uInt) [date year]; zi.internal_fa= 0; zi.external_fa= 0; zi.dosDate= 0; // Support for legacy 32 bit mode: here we use the common version, // passing a flag to tell if it is a 32 or 64 bit file int err= zipOpenNewFileInZip3_64(_zipFile, [fileNameInZip cStringUsingEncoding:NSUTF8StringEncoding], &zi, NULL, 0, NULL, 0, NULL, (compressionLevel != OZZipCompressionLevelNone) ? Z_DEFLATED : 0, compressionLevel, 0, -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY, [password cStringUsingEncoding:NSUTF8StringEncoding], crc32, (_legacy32BitMode ? 0 : 1)); if (err != ZIP_OK) @throw [OZZipException zipExceptionWithError:err reason:@"Error opening '%@' in zipfile", fileNameInZip]; return [[OZZipWriteStream alloc] initWithZipFileStruct:_zipFile fileNameInZip:fileNameInZip]; } #pragma mark - #pragma mark File writing (NSError variants) - (OZZipWriteStream *) writeFileInZipWithName:(NSString *)fileNameInZip compressionLevel:(OZZipCompressionLevel)compressionLevel error:(NSError * __autoreleasing *)error { ERROR_WRAP_BEGIN { return [self writeFileInZipWithName:fileNameInZip compressionLevel:compressionLevel]; } ERROR_WRAP_END_AND_RETURN(error, nil); } - (OZZipWriteStream *) writeFileInZipWithName:(NSString *)fileNameInZip fileDate:(NSDate *)fileDate compressionLevel:(OZZipCompressionLevel)compressionLevel error:(NSError * __autoreleasing *)error { ERROR_WRAP_BEGIN { return [self writeFileInZipWithName:fileNameInZip fileDate:fileDate compressionLevel:compressionLevel]; } ERROR_WRAP_END_AND_RETURN(error, nil); } - (OZZipWriteStream *) writeFileInZipWithName:(NSString *)fileNameInZip fileDate:(NSDate *)fileDate compressionLevel:(OZZipCompressionLevel)compressionLevel password:(NSString *)password crc32:(NSUInteger)crc32 error:(NSError * __autoreleasing *)error { ERROR_WRAP_BEGIN { return [self writeFileInZipWithName:fileNameInZip fileDate:fileDate compressionLevel:compressionLevel password:password crc32:crc32]; } ERROR_WRAP_END_AND_RETURN(error, nil); } #pragma mark - #pragma mark File seeking and info - (void) goToFirstFileInZip { if (_mode != OZZipFileModeUnzip) @throw [OZZipException zipExceptionWithReason:@"Operation permitted only in Unzip mode"]; int err= unzGoToFirstFile(_unzFile); if (err != UNZ_OK) @throw [OZZipException zipExceptionWithError:err reason:@"Error going to first file in zip of '%@'", _fileName]; } - (BOOL) goToNextFileInZip { if (_mode != OZZipFileModeUnzip) @throw [OZZipException zipExceptionWithReason:@"Operation permitted only in Unzip mode"]; int err= unzGoToNextFile(_unzFile); if (err == UNZ_END_OF_LIST_OF_FILE) return NO; if (err != UNZ_OK) @throw [OZZipException zipExceptionWithError:err reason:@"Error going to next file in zip of '%@'", _fileName]; return YES; } - (BOOL) locateFileInZip:(NSString *)fileNameInZip { if (_mode != OZZipFileModeUnzip) @throw [OZZipException zipExceptionWithReason:@"Operation permitted only in Unzip mode"]; int err= unzLocateFile(_unzFile, [fileNameInZip cStringUsingEncoding:NSUTF8StringEncoding], NULL); if (err == UNZ_END_OF_LIST_OF_FILE) return NO; if (err != UNZ_OK) @throw [OZZipException zipExceptionWithError:err reason:@"Error localting file in zip of '%@'", _fileName]; return YES; } - (NSUInteger) numFilesInZip { if (_mode != OZZipFileModeUnzip) @throw [OZZipException zipExceptionWithReason:@"Operation permitted only in Unzip mode"]; // Support for legacy 32 bit mode: here we use the 32 or 64 bit // version alternatively, as there is not internal (common) version if (_legacy32BitMode) { unz_global_info gi; int err= unzGetGlobalInfo(_unzFile, &gi); if (err != UNZ_OK) @throw [OZZipException zipExceptionWithError:err reason:@"Error getting global info of '%@'", _fileName]; return gi.number_entry; } else { unz_global_info64 gi; int err= unzGetGlobalInfo64(_unzFile, &gi); if (err != UNZ_OK) @throw [OZZipException zipExceptionWithError:err reason:@"Error getting global info of '%@'", _fileName]; return (NSUInteger) gi.number_entry; } } - (NSArray *) listFileInZipInfos { NSUInteger num= [self numFilesInZip]; if (num < 1) return [NSArray array]; NSMutableArray *files= [[NSMutableArray alloc] initWithCapacity:num]; [self goToFirstFileInZip]; for (int i= 0; i < num; i++) { OZFileInZipInfo *info= [self getCurrentFileInZipInfo]; [files addObject:info]; if ((i +1) < num) [self goToNextFileInZip]; } return files; } - (OZFileInZipInfo *) getCurrentFileInZipInfo { if (_mode != OZZipFileModeUnzip) @throw [OZZipException zipExceptionWithReason:@"Operation permitted only in Unzip mode"]; char filename_inzip[FILE_IN_ZIP_MAX_NAME_LENGTH]; unz_file_info64 file_info; // Support for legacy 32 bit mode: here we use the 64 bit version, // as it also internally called from the 32 bit version int err= unzGetCurrentFileInfo64(_unzFile, &file_info, filename_inzip, sizeof(filename_inzip), NULL, 0, NULL, 0); if (err != UNZ_OK) @throw [OZZipException zipExceptionWithError:err reason:@"Error getting current file info of '%@'", _fileName]; NSString *name= [NSString stringWithCString:filename_inzip encoding:NSUTF8StringEncoding]; OZZipCompressionLevel level= OZZipCompressionLevelNone; if (file_info.compression_method != 0) { switch ((file_info.flag & 0x6) / 2) { case 0: level= OZZipCompressionLevelDefault; break; case 1: level= OZZipCompressionLevelBest; break; default: level= OZZipCompressionLevelFastest; break; } } BOOL crypted= ((file_info.flag & 1) != 0); NSDateComponents *components= [[NSDateComponents alloc] init]; [components setDay:file_info.tmu_date.tm_mday]; [components setMonth:file_info.tmu_date.tm_mon +1]; [components setYear:file_info.tmu_date.tm_year]; [components setHour:file_info.tmu_date.tm_hour]; [components setMinute:file_info.tmu_date.tm_min]; [components setSecond:file_info.tmu_date.tm_sec]; NSCalendar *calendar= [NSCalendar currentCalendar]; NSDate *date= [calendar dateFromComponents:components]; OZFileInZipInfo *info= [[OZFileInZipInfo alloc] initWithName:name length:file_info.uncompressed_size level:level crypted:crypted size:file_info.compressed_size date:date crc32:file_info.crc]; return info; } #pragma mark - #pragma mark File seeking and info (NSError variants) - (BOOL) goToFirstFileInZipWithError:(NSError * __autoreleasing *)error { ERROR_WRAP_BEGIN { [self goToFirstFileInZip]; return YES; } ERROR_WRAP_END_AND_RETURN(error, NO); } - (BOOL) goToNextFileInZipWithError:(NSError * __autoreleasing *)error { ERROR_WRAP_BEGIN { return [self goToNextFileInZip]; } ERROR_WRAP_END_AND_RETURN(error, NO); } - (BOOL) locateFileInZip:(NSString *)fileNameInZip error:(NSError * __autoreleasing *)error { ERROR_WRAP_BEGIN { return [self locateFileInZip:fileNameInZip]; } ERROR_WRAP_END_AND_RETURN(error, NO); } - (NSUInteger) numFilesInZipWithError:(NSError * __autoreleasing *)error { ERROR_WRAP_BEGIN { return [self numFilesInZip]; } ERROR_WRAP_END_AND_RETURN(error, 0); } - (NSArray *) listFileInZipInfosWithError:(NSError * __autoreleasing *)error { ERROR_WRAP_BEGIN { return [self listFileInZipInfos]; } ERROR_WRAP_END_AND_RETURN(error, nil); } - (OZFileInZipInfo *) getCurrentFileInZipInfoWithError:(NSError * __autoreleasing *)error { ERROR_WRAP_BEGIN { return [self getCurrentFileInZipInfo]; } ERROR_WRAP_END_AND_RETURN(error, nil); } #pragma mark - #pragma mark File reading - (OZZipReadStream *) readCurrentFileInZip { if (_mode != OZZipFileModeUnzip) @throw [OZZipException zipExceptionWithReason:@"Operation permitted only in Unzip mode"]; char filename_inzip[FILE_IN_ZIP_MAX_NAME_LENGTH]; unz_file_info64 file_info; // Support for legacy 32 bit mode: here we use the 64 bit version, // as it also internally called from the 32 bit version int err= unzGetCurrentFileInfo64(_unzFile, &file_info, filename_inzip, sizeof(filename_inzip), NULL, 0, NULL, 0); if (err != UNZ_OK) @throw [OZZipException zipExceptionWithError:err reason:@"Error getting current file info of '%@'", _fileName]; NSString *fileNameInZip= [NSString stringWithCString:filename_inzip encoding:NSUTF8StringEncoding]; err= unzOpenCurrentFilePassword(_unzFile, NULL); if (err != UNZ_OK) @throw [OZZipException zipExceptionWithError:err reason:@"Error opening current file of '%@'", _fileName]; return [[OZZipReadStream alloc] initWithUnzFileStruct:_unzFile fileNameInZip:fileNameInZip]; } - (OZZipReadStream *) readCurrentFileInZipWithPassword:(NSString *)password { if (_mode != OZZipFileModeUnzip) @throw [OZZipException zipExceptionWithReason:@"Operation permitted only in Unzip mode"]; char filename_inzip[FILE_IN_ZIP_MAX_NAME_LENGTH]; unz_file_info64 file_info; // Support for legacy 32 bit mode: here we use the 64 bit version, // as it also internally called from the 32 bit version int err= unzGetCurrentFileInfo64(_unzFile, &file_info, filename_inzip, sizeof(filename_inzip), NULL, 0, NULL, 0); if (err != UNZ_OK) @throw [OZZipException zipExceptionWithError:err reason:@"Error getting current file info of '%@'", _fileName]; NSString *fileNameInZip= [NSString stringWithCString:filename_inzip encoding:NSUTF8StringEncoding]; err= unzOpenCurrentFilePassword(_unzFile, [password cStringUsingEncoding:NSUTF8StringEncoding]); if (err != UNZ_OK) @throw [OZZipException zipExceptionWithError:err reason:@"Error opening current file of '%@'", _fileName]; return [[OZZipReadStream alloc] initWithUnzFileStruct:_unzFile fileNameInZip:fileNameInZip]; } #pragma mark - #pragma mark File reading (NSError variants) - (OZZipReadStream *) readCurrentFileInZipWithError:(NSError * __autoreleasing *)error { ERROR_WRAP_BEGIN { return [self readCurrentFileInZip]; } ERROR_WRAP_END_AND_RETURN(error, nil); } - (OZZipReadStream *) readCurrentFileInZipWithPassword:(NSString *)password error:(NSError * __autoreleasing *)error { ERROR_WRAP_BEGIN { return [self readCurrentFileInZipWithPassword:password]; } ERROR_WRAP_END_AND_RETURN(error, nil); } #pragma mark - #pragma mark Closing - (void) close { switch (_mode) { case OZZipFileModeUnzip: { int err= unzClose(_unzFile); if (err != UNZ_OK) @throw [OZZipException zipExceptionWithError:err reason:@"Error closing '%@'", _fileName]; break; } case OZZipFileModeCreate: { int err= zipClose(_zipFile, NULL); if (err != ZIP_OK) @throw [OZZipException zipExceptionWithError:err reason:@"Error closing '%@'", _fileName]; break; } case OZZipFileModeAppend: { int err= zipClose(_zipFile, NULL); if (err != ZIP_OK) @throw [OZZipException zipExceptionWithError:err reason:@"Error closing '%@'", _fileName]; break; } default: @throw [OZZipException zipExceptionWithReason:@"Unknown mode %d", _mode]; } } #pragma mark - #pragma mark Closing (NSError variants) - (BOOL) closeWithError:(NSError * __autoreleasing *)error { ERROR_WRAP_BEGIN { [self close]; return YES; } ERROR_WRAP_END_AND_RETURN(error, NO); } #pragma mark - #pragma mark Properties @synthesize fileName= _fileName; @synthesize mode= _mode; @synthesize legacy32BitMode= _legacy32BitMode; @end