/** // ZipArchive.m // // // Created by aish on 08-9-11. // acsolu@gmail.com // Copyright 2008 Inc. All rights reserved. // */ #import "ZipArchive.h" #import "zlib.h" #import "zconf.h" #include "minizip/zip.h" #include "minizip/unzip.h" @interface NSFileManager(ZipArchive) - (NSDictionary *)_attributesOfItemAtPath:(NSString *)path followingSymLinks:(BOOL)followingSymLinks error:(NSError **)error; @end @interface ZipArchive () -(void) OutputErrorMessage:(NSString*) msg; -(BOOL) OverWrite:(NSString*) file; -(NSDate*) Date1980; @property (nonatomic,copy) NSString* password; @end @implementation ZipArchive @synthesize delegate = _delegate; @synthesize numFiles = _numFiles; @synthesize password = _password; @synthesize unzippedFiles = _unzippedFiles; @synthesize progressBlock = _progressBlock; @synthesize stringEncoding = _stringEncoding; -(id) init { return [self initWithFileManager:[NSFileManager defaultManager]]; } -(id) initWithFileManager:(NSFileManager*) fileManager { if( self=[super init] ) { _zipFile = NULL; _fileManager = fileManager; self.stringEncoding = NSUTF8StringEncoding; self.compression = ZipArchiveCompressionDefault; } return self; } -(void) dealloc { // close any open file operations [self CloseZipFile2]; [self UnzipCloseFile]; // release retained/copied properties. [_password release]; [_delegate release]; [_unzippedFiles release]; [super dealloc]; } /** * Create a new zip file at the specified path, ready for new files to be added. * * @param zipFile the path of the zip file to create * @returns BOOL YES on success */ -(BOOL) CreateZipFile2:(NSString*) zipFile { return [self CreateZipFile2:zipFile append:NO]; } -(BOOL) CreateZipFile2:(NSString*) zipFile append:(BOOL)isAppend { _zipFile = zipOpen( (const char*)[zipFile UTF8String], (isAppend ? APPEND_STATUS_ADDINZIP : APPEND_STATUS_CREATE) ); if( !_zipFile ) return NO; return YES; } /** * Create a new zip file at the specified path, ready for new files to be added. * * @param zipFile the path of the zip file to create * @param password a password used to encrypt the zip file * @returns BOOL YES on success */ -(BOOL) CreateZipFile2:(NSString*) zipFile Password:(NSString*) password { self.password = password; return [self CreateZipFile2:zipFile]; } -(BOOL) CreateZipFile2:(NSString*) zipFile Password:(NSString*) password append:(BOOL)isAppend { self.password = password; return [self CreateZipFile2:zipFile append:isAppend]; } /** * add an existing file on disk to the zip archive, compressing it. * * @param file the path to the file to compress * @param newname the name of the file in the zip archive, ie: path relative to the zip archive root. * @returns BOOL YES on success */ -(BOOL) addFileToZip:(NSString*) file newname:(NSString*) newname; { NSData *data = [NSData dataWithContentsOfFile:file]; NSError* error = nil; NSDictionary* attr = [_fileManager _attributesOfItemAtPath:file followingSymLinks:YES error:&error]; BOOL result = [self addDataToZip:data fileAttributes:attr newname:newname]; return result; } /** * add an existing file on disk to the zip archive, compressing it. * * @param data the data to compress * @param attr the file attribute for data to add as file * @param newname the name of the file in the zip archive, ie: path relative to the zip archive root. * @returns BOOL YES on success */ -(BOOL) addDataToZip:(NSData*) data fileAttributes:(NSDictionary *)attr newname:(NSString*) newname { if (!data) { return NO; } if( !_zipFile ) return NO; // tm_zip filetime; zip_fileinfo zipInfo = {{0}}; NSDate* fileDate = nil; if( attr ) fileDate = (NSDate*)[attr objectForKey:NSFileModificationDate]; if( fileDate == nil ) fileDate = [NSDate date]; NSCalendar *gregorianCalendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian]; NSDateComponents* components = [gregorianCalendar components:NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay | NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond fromDate:fileDate]; [gregorianCalendar release]; zipInfo.tmz_date.tm_sec = (uInt)components.second; zipInfo.tmz_date.tm_min = (uInt)components.minute; zipInfo.tmz_date.tm_hour = (uInt)components.hour; zipInfo.tmz_date.tm_mday = (uInt)components.day; zipInfo.tmz_date.tm_mon = (uInt)components.month; zipInfo.tmz_date.tm_year = (uInt)components.year; int ret ; if( [_password length] == 0 ) { ret = zipOpenNewFileInZip( _zipFile, (const char*) [newname cStringUsingEncoding:self.stringEncoding], &zipInfo, NULL,0, NULL,0, NULL,//comment Z_DEFLATED, self.compression ); } else { uLong crcValue = crc32( 0L,NULL, 0L ); crcValue = crc32( crcValue, (const Bytef*)[data bytes], (unsigned int)[data length] ); ret = zipOpenNewFileInZip3( _zipFile, (const char*) [newname cStringUsingEncoding:self.stringEncoding], &zipInfo, NULL,0, NULL,0, NULL,//comment Z_DEFLATED, self.compression, 0, 15, 8, Z_DEFAULT_STRATEGY, [_password cStringUsingEncoding:NSASCIIStringEncoding], crcValue ); } if( ret!=Z_OK ) { return NO; } unsigned int dataLen = (unsigned int)[data length]; ret = zipWriteInFileInZip( _zipFile, (const void*)[data bytes], dataLen); if( ret!=Z_OK ) { return NO; } ret = zipCloseFileInZip( _zipFile ); if( ret!=Z_OK ) return NO; return YES; } /** * Close a zip file after creating and added files to it. * * @returns BOOL YES on success */ -(BOOL) CloseZipFile2 { self.password = nil; if( _zipFile==NULL ) return NO; BOOL ret = zipClose( _zipFile,NULL )==Z_OK?YES:NO; _zipFile = NULL; return ret; } /** * open an existing zip file ready for expanding. * * @param zipFile the path to a zip file to be opened. * @returns BOOL YES on success */ -(BOOL) UnzipOpenFile:(NSString*) zipFile { // create an array to receive the list of unzipped files. if (_unzippedFiles) [_unzippedFiles release]; _unzippedFiles = [[NSMutableArray alloc] initWithCapacity:1]; _unzFile = unzOpen( (const char*)[zipFile UTF8String] ); if( _unzFile ) { unz_global_info globalInfo = {0}; if( unzGetGlobalInfo(_unzFile, &globalInfo )==UNZ_OK ) { _numFiles = globalInfo.number_entry; NSLog(@"%lu entries in the zip file", globalInfo.number_entry); } } return _unzFile!=NULL; } /** * open an existing zip file with a password ready for expanding. * * @param zipFile the path to a zip file to be opened. * @param password the password to use decrpyting the file. * @returns BOOL YES on success */ -(BOOL) UnzipOpenFile:(NSString*) zipFile Password:(NSString*) password { self.password = password; return [self UnzipOpenFile:zipFile]; } /** * Expand all files in the zip archive into the specified directory. * * If a delegate has been set and responds to OverWriteOperation: it can * return YES to overwrite a file, or NO to skip that file. * * On completion, the property `unzippedFiles` will be an array populated * with the full paths of each file that was successfully expanded. * * @param path the directory where expanded files will be created * @param overwrite should existing files be overwritten * @returns BOOL YES on success */ -(BOOL) UnzipFileTo:(NSString*) path overWrite:(BOOL) overwrite { BOOL success = YES; int index = 0; int progress = -1; int ret = unzGoToFirstFile( _unzFile ); unsigned char buffer[4096] = {0}; if( ret!=UNZ_OK ) { [self OutputErrorMessage:@"Failed"]; } const char* password = [_password cStringUsingEncoding:NSASCIIStringEncoding]; do{ @autoreleasepool { if( [_password length]==0 ) ret = unzOpenCurrentFile( _unzFile ); else ret = unzOpenCurrentFilePassword( _unzFile, password ); if( ret!=UNZ_OK ) { [self OutputErrorMessage:@"Error occurs"]; success = NO; break; } // reading data and write to file int read ; unz_file_info fileInfo ={0}; ret = unzGetCurrentFileInfo(_unzFile, &fileInfo, NULL, 0, NULL, 0, NULL, 0); if( ret!=UNZ_OK ) { [self OutputErrorMessage:@"Error occurs while getting file info"]; success = NO; unzCloseCurrentFile( _unzFile ); break; } char* filename = (char*) malloc( fileInfo.size_filename +1 ); unzGetCurrentFileInfo(_unzFile, &fileInfo, filename, fileInfo.size_filename + 1, NULL, 0, NULL, 0); filename[fileInfo.size_filename] = '\0'; // check if it contains directory NSString * strPath = [NSString stringWithCString:filename encoding:self.stringEncoding]; BOOL isDirectory = NO; if( filename[fileInfo.size_filename-1]=='/' || filename[fileInfo.size_filename-1]=='\\') isDirectory = YES; free( filename ); if( [strPath rangeOfCharacterFromSet:[NSCharacterSet characterSetWithCharactersInString:@"/\\"]].location!=NSNotFound ) {// contains a path strPath = [strPath stringByReplacingOccurrencesOfString:@"\\" withString:@"/"]; } NSString* fullPath = [path stringByAppendingPathComponent:strPath]; if( isDirectory ) [_fileManager createDirectoryAtPath:fullPath withIntermediateDirectories:YES attributes:nil error:nil]; else [_fileManager createDirectoryAtPath:[fullPath stringByDeletingLastPathComponent] withIntermediateDirectories:YES attributes:nil error:nil]; FILE* fp = NULL; do { read = unzReadCurrentFile(_unzFile, buffer, 4096); if (read >= 0) { if (fp == NULL) { if( [_fileManager fileExistsAtPath:fullPath] && !isDirectory && !overwrite ) { if( ![self OverWrite:fullPath] ) { // don't process any more of the file, but continue break; } } if (!isDirectory) { fp = fopen( (const char*)[fullPath UTF8String], "wb"); if (fp == NULL) { [self OutputErrorMessage:@"Failed to open output file for writing"]; break; } } } fwrite(buffer, read, 1, fp ); } else // if (read < 0) { ret = read; // result will be an error code success = NO; [self OutputErrorMessage:@"Failed to read zip file"]; } } while (read > 0); if (fp) { fclose( fp ); // add the full path of this file to the output array [(NSMutableArray*)_unzippedFiles addObject:fullPath]; // set the orignal datetime property if( fileInfo.tmu_date.tm_year!=0 ) { NSDateComponents* components = [[NSDateComponents alloc] init]; components.second = fileInfo.tmu_date.tm_sec; components.minute = fileInfo.tmu_date.tm_min; components.hour = fileInfo.tmu_date.tm_hour; components.day = fileInfo.tmu_date.tm_mday; components.month = fileInfo.tmu_date.tm_mon + 1; components.year = fileInfo.tmu_date.tm_year; NSCalendar *gregorianCalendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian]; NSDate* orgDate = [[gregorianCalendar dateFromComponents:components] retain]; [components release]; [gregorianCalendar release]; NSDictionary* attr = [NSDictionary dictionaryWithObject:orgDate forKey:NSFileModificationDate]; //[_fileManager fileAttributesAtPath:fullPath traverseLink:YES]; if( attr ) { // [attr setValue:orgDate forKey:NSFileCreationDate]; if( ![_fileManager setAttributes:attr ofItemAtPath:fullPath error:nil] ) { // cann't set attributes NSLog(@"Failed to set attributes"); } } [orgDate release]; orgDate = nil; } } if (ret == UNZ_OK) { ret = unzCloseCurrentFile( _unzFile ); if (ret != UNZ_OK) { [self OutputErrorMessage:@"file was unzipped but failed crc check"]; success = NO; } } if (ret == UNZ_OK) { ret = unzGoToNextFile( _unzFile ); } if (_progressBlock && _numFiles) { index++; int p = index*100/_numFiles; progress = p; _progressBlock(progress, index, _numFiles); } } } while (ret==UNZ_OK && ret!=UNZ_END_OF_LIST_OF_FILE); return success; } -(NSDictionary *)UnzipFileToMemory { NSMutableDictionary *fileDictionary = [NSMutableDictionary dictionary]; BOOL success = YES; int index = 0; int progress = -1; int ret = unzGoToFirstFile( _unzFile ); unsigned char buffer[4096] = {0}; if( ret!=UNZ_OK ) { [self OutputErrorMessage:@"Failed"]; } const char* password = [_password cStringUsingEncoding:NSASCIIStringEncoding]; do{ @autoreleasepool { if( [_password length]==0 ) ret = unzOpenCurrentFile( _unzFile ); else ret = unzOpenCurrentFilePassword( _unzFile, password ); if( ret!=UNZ_OK ) { [self OutputErrorMessage:@"Error occurs"]; success = NO; break; } // reading data and write to file int read ; unz_file_info fileInfo ={0}; ret = unzGetCurrentFileInfo(_unzFile, &fileInfo, NULL, 0, NULL, 0, NULL, 0); if( ret!=UNZ_OK ) { [self OutputErrorMessage:@"Error occurs while getting file info"]; success = NO; unzCloseCurrentFile( _unzFile ); break; } char* filename = (char*) malloc( fileInfo.size_filename +1 ); unzGetCurrentFileInfo(_unzFile, &fileInfo, filename, fileInfo.size_filename + 1, NULL, 0, NULL, 0); filename[fileInfo.size_filename] = '\0'; // check if it contains directory NSString * strPath = [NSString stringWithCString:filename encoding:self.stringEncoding]; free( filename ); if( [strPath rangeOfCharacterFromSet:[NSCharacterSet characterSetWithCharactersInString:@"/\\"]].location!=NSNotFound ) {// contains a path strPath = [strPath stringByReplacingOccurrencesOfString:@"\\" withString:@"/"]; } NSMutableData *fileMutableData = [NSMutableData data]; do { read = unzReadCurrentFile(_unzFile, buffer, 4096); if (read >= 0) { if (read != 0) { [fileMutableData appendBytes:buffer length:read]; } } else // if (read < 0) { ret = read; // result will be an error code success = NO; [self OutputErrorMessage:@"Failed to read zip file"]; } } while (read > 0); if (fileMutableData.length > 0) { NSData *fileData = [NSData dataWithData:fileMutableData]; [fileDictionary setObject:fileData forKey:strPath]; } if (ret == UNZ_OK) { ret = unzCloseCurrentFile( _unzFile ); if (ret != UNZ_OK) { [self OutputErrorMessage:@"file was unzipped but failed crc check"]; success = NO; } } if (ret == UNZ_OK) { ret = unzGoToNextFile( _unzFile ); } if (_progressBlock && _numFiles) { index++; int p = index*100/_numFiles; progress = p; _progressBlock(progress, index, _numFiles); } } } while (ret==UNZ_OK && ret!=UNZ_END_OF_LIST_OF_FILE); NSDictionary *resultDictionary = [NSDictionary dictionaryWithDictionary:fileDictionary]; return resultDictionary; } /** * Close the zip file. * * @returns BOOL YES on success */ -(BOOL) UnzipCloseFile { self.password = nil; if( _unzFile ) { int err = unzClose( _unzFile ); _unzFile = nil; return err ==UNZ_OK; } return YES; } /** * Return a list of filenames that are in the zip archive. * No path information is available as this can be called before the zip is expanded. * * @returns NSArray list of filenames in the zip archive. */ -(NSArray*) getZipFileContents // list the contents of the zip archive. must be called after UnzipOpenFile { int ret = unzGoToFirstFile( _unzFile ); NSMutableArray * allFilenames = [NSMutableArray arrayWithCapacity:40]; if( ret!=UNZ_OK ) { [self OutputErrorMessage:@"Failed"]; } const char* password = [_password cStringUsingEncoding:NSASCIIStringEncoding]; do{ if( [_password length]==0 ) ret = unzOpenCurrentFile( _unzFile ); else ret = unzOpenCurrentFilePassword( _unzFile, password ); if( ret!=UNZ_OK ) { [self OutputErrorMessage:@"Error occured"]; break; } // reading data and write to file unz_file_info fileInfo ={0}; ret = unzGetCurrentFileInfo(_unzFile, &fileInfo, NULL, 0, NULL, 0, NULL, 0); if( ret!=UNZ_OK ) { [self OutputErrorMessage:@"Error occurs while getting file info"]; unzCloseCurrentFile( _unzFile ); break; } char* filename = (char*) malloc( fileInfo.size_filename +1 ); unzGetCurrentFileInfo(_unzFile, &fileInfo, filename, fileInfo.size_filename + 1, NULL, 0, NULL, 0); filename[fileInfo.size_filename] = '\0'; // check if it contains directory NSString * strPath = [NSString stringWithCString:filename encoding:NSASCIIStringEncoding]; free( filename ); if( [strPath rangeOfCharacterFromSet:[NSCharacterSet characterSetWithCharactersInString:@"/\\"]].location!=NSNotFound ) {// contains a path strPath = [strPath stringByReplacingOccurrencesOfString:@"\\" withString:@"/"]; } // Copy name to array [allFilenames addObject:strPath]; unzCloseCurrentFile( _unzFile ); ret = unzGoToNextFile( _unzFile ); } while( ret==UNZ_OK && UNZ_OK!=UNZ_END_OF_LIST_OF_FILE ); // return an immutable array. return [NSArray arrayWithArray:allFilenames]; } #pragma mark wrapper for delegate /** * send the ErrorMessage: to the delegate if it responds to it. */ -(void) OutputErrorMessage:(NSString*) msg { if( _delegate && [_delegate respondsToSelector:@selector(ErrorMessage:)] ) [_delegate ErrorMessage:msg]; } /** * send the OverWriteOperation: selector to the delegate if it responds to it, * returning the result, or YES by default. */ -(BOOL) OverWrite:(NSString*) file { if( _delegate && [_delegate respondsToSelector:@selector(OverWriteOperation:)] ) return [_delegate OverWriteOperation:file]; return YES; } #pragma mark get NSDate object for 1980-01-01 -(NSDate*) Date1980 { NSDateComponents *comps = [[NSDateComponents alloc] init]; [comps setDay:1]; [comps setMonth:1]; [comps setYear:1980]; NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian]; NSDate *date = [gregorian dateFromComponents:comps]; [comps release]; [gregorian release]; return date; } @end @implementation NSFileManager(ZipArchive) - (NSDictionary *)_attributesOfItemAtPath:(NSString *)path followingSymLinks:(BOOL)followingSymLinks error:(NSError **)error { // call file manager default action, which is to not follow symlinks NSDictionary* results = [self attributesOfItemAtPath:path error:error]; if (followingSymLinks && results && (error ? *error == nil : YES)) { if ([[results fileType] isEqualToString:NSFileTypeSymbolicLink]) { // follow the symlink NSString* realPath = [self destinationOfSymbolicLinkAtPath:path error:error]; if (realPath && (error ? *error == nil : YES)) { return [self _attributesOfItemAtPath:realPath followingSymLinks:followingSymLinks error:error]; } else { // failure to resolve symlink should be an error returning nil and error will already be set. return nil; } } } return results; } @end