2010-07-21 18:05:38 -05:00
//
2010-08-19 15:27:48 -05:00
// SSZipArchive.m
// SSZipArchive
2010-07-21 18:05:38 -05:00
//
// Created by Sam Soffes on 7/21/10.
2011-10-03 23:28:54 -07:00
// Copyright (c) Sam Soffes 2010-2011. All rights reserved.
2010-07-21 18:05:38 -05:00
//
2010-08-19 15:27:48 -05:00
# import "SSZipArchive.h"
2010-07-21 18:05:38 -05:00
# include "minizip/zip.h"
# import "zlib.h"
# import "zconf.h"
2011-12-26 23:32:55 -05:00
# define CHUNK 16384
2011-10-03 23:28:54 -07:00
@interface SSZipArchive ( )
2012-05-07 22:27:45 -07:00
+ ( NSDate * ) _dateWithMSDOSFormat : ( UInt32 ) msdosDateTime ;
2010-07-21 18:05:38 -05:00
@end
2011-12-26 23:32:55 -05:00
@implementation SSZipArchive {
NSString * _path ;
NSString * _filename ;
zipFile _zip ;
}
2010-07-21 18:05:38 -05:00
2011-10-03 23:28:54 -07:00
# pragma mark - Unzipping
2010-07-21 18:05:38 -05:00
+ ( BOOL ) unzipFileAtPath: ( NSString * ) path toDestination: ( NSString * ) destination {
2012-01-14 20:46:32 +01:00
return [ self unzipFileAtPath : path toDestination : destination delegate : nil ] ;
2010-07-21 18:05:38 -05:00
}
2011-10-03 23:28:54 -07:00
2010-07-23 10:41:46 -05:00
+ ( BOOL ) unzipFileAtPath: ( NSString * ) path toDestination: ( NSString * ) destination overwrite: ( BOOL ) overwrite password: ( NSString * ) password error: ( NSError * * ) error {
2012-01-14 20:46:32 +01:00
return [ self unzipFileAtPath : path toDestination : destination overwrite : overwrite password : password error : error delegate : nil ] ;
}
+ ( BOOL ) unzipFileAtPath: ( NSString * ) path toDestination: ( NSString * ) destination delegate: ( id < SSZipArchiveDelegate > ) delegate {
return [ self unzipFileAtPath : path toDestination : destination overwrite : YES password : nil error : nil delegate : delegate ] ;
}
+ ( BOOL ) unzipFileAtPath: ( NSString * ) path toDestination: ( NSString * ) destination overwrite: ( BOOL ) overwrite password: ( NSString * ) password error: ( NSError * * ) error delegate: ( id < SSZipArchiveDelegate > ) delegate {
2010-07-21 18:05:38 -05:00
// Begin opening
zipFile zip = unzOpen ( ( const char * ) [ path UTF8String ] ) ;
if ( zip = = NULL ) {
2010-07-23 10:41:46 -05:00
NSDictionary * userInfo = [ NSDictionary dictionaryWithObject : @" failed to open zip file " forKey : NSLocalizedDescriptionKey ] ;
2010-07-26 15:18:32 -05:00
if ( error ) {
2010-08-19 15:27:48 -05:00
* error = [ NSError errorWithDomain : @" SSZipArchiveErrorDomain " code : - 1 userInfo : userInfo ] ;
2010-07-26 15:18:32 -05:00
}
2010-07-21 18:05:38 -05:00
return NO ;
}
2011-07-21 15:39:03 -07:00
unz_global_info globalInfo = { 0ul , 0ul } ;
2010-07-23 10:41:46 -05:00
unzGetGlobalInfo ( zip , & globalInfo ) ;
2010-07-21 18:05:38 -05:00
// Begin unzipping
if ( unzGoToFirstFile ( zip ) ! = UNZ_OK ) {
2010-07-23 10:41:46 -05:00
NSDictionary * userInfo = [ NSDictionary dictionaryWithObject : @" failed to open first file in zip file " forKey : NSLocalizedDescriptionKey ] ;
2010-07-26 15:18:32 -05:00
if ( error ) {
2010-08-19 15:27:48 -05:00
* error = [ NSError errorWithDomain : @" SSZipArchiveErrorDomain " code : - 2 userInfo : userInfo ] ;
2010-07-26 15:18:32 -05:00
}
2010-07-21 18:05:38 -05:00
return NO ;
}
BOOL success = YES ;
2012-05-07 22:27:45 -07:00
int ret = 0 ;
2010-07-21 18:05:38 -05:00
unsigned char buffer [ 4096 ] = { 0 } ;
NSFileManager * fileManager = [ NSFileManager defaultManager ] ;
2012-05-07 22:27:45 -07:00
NSMutableSet * directoriesModificationDates = [ [ NSMutableSet alloc ] init ] ;
2010-07-21 18:05:38 -05:00
2012-05-07 22:27:45 -07:00
// Message delegate
2012-05-07 23:05:06 -07:00
if ( [ delegate respondsToSelector : @selector ( zipArchiveWillUnzipArchiveAtPath : zipInfo : ) ] ) {
[ delegate zipArchiveWillUnzipArchiveAtPath : path zipInfo : globalInfo ] ;
2012-05-07 22:27:45 -07:00
}
2012-01-14 20:46:32 +01:00
NSInteger currentFileNumber = 0 ;
2010-07-21 18:05:38 -05:00
do {
if ( [ password length ] = = 0 ) {
ret = unzOpenCurrentFile ( zip ) ;
} else {
ret = unzOpenCurrentFilePassword ( zip , [ password cStringUsingEncoding : NSASCIIStringEncoding ] ) ;
}
if ( ret ! = UNZ_OK ) {
success = NO ;
break ;
}
// Reading data and write to file
2011-07-21 15:39:03 -07:00
unz_file_info fileInfo ;
memset ( & fileInfo , 0 , sizeof ( unz_file_info ) ) ;
2010-07-21 18:05:38 -05:00
ret = unzGetCurrentFileInfo ( zip , & fileInfo , NULL , 0 , NULL , 0 , NULL , 0 ) ;
if ( ret ! = UNZ_OK ) {
success = NO ;
unzCloseCurrentFile ( zip ) ;
break ;
}
2012-05-07 22:27:45 -07:00
// Message delegate
if ( [ delegate respondsToSelector : @selector ( zipArchiveWillUnzipFileAtIndex : totalFiles : archivePath : fileInfo : ) ] ) {
[ delegate zipArchiveWillUnzipFileAtIndex : currentFileNumber totalFiles : ( NSInteger ) globalInfo . number_entry
archivePath : path fileInfo : fileInfo ] ;
}
2012-05-09 08:18:19 -04:00
2010-07-21 18:05:38 -05:00
char * filename = ( char * ) malloc ( fileInfo . size_filename + 1 ) ;
2012-05-09 11:12:04 -04:00
unzGetCurrentFileInfo ( zip , & fileInfo , filename , fileInfo . size_filename + 1 , NULL , 0 , NULL , 0 ) ;
2010-07-21 18:05:38 -05:00
filename [ fileInfo . size_filename ] = ' \0 ' ;
2012-05-09 08:18:19 -04:00
2012-05-09 11:12:04 -04:00
//
// NOTE
// I used the ZIP spec from here:
// http://www.pkware.com/documents/casestudies/APPNOTE.TXT
//
// ...to deduce this method of detecting whether the file in the ZIP is a symbolic link.
// If it is, it is listed as a directory but has an uncompressed data size greater than
// zero (real directories have it equal to 0) and the included, uncompressed data is the
// symbolic link path.
//
2012-05-09 08:18:19 -04:00
2012-05-09 11:12:04 -04:00
const uLong ZipDirectoryVersion = 10 ;
const uLong ZipCompressionMethodStore = 0 ;
BOOL fileIsSymbolicLink = NO ;
if ( ( fileInfo . version_needed = = ZipDirectoryVersion ) & & // Is it a directory?
( fileInfo . compression_method = = ZipCompressionMethodStore ) & & // Is it compressed?
( fileInfo . compressed_size > 0 ) ) // Is there any data there?
{
fileIsSymbolicLink = YES ;
}
//NSLog(@"\"%s\" is symbolic link? %@", filename, fileIsSymbolicLink ? @"Yes." : @"No.");
2010-07-21 18:05:38 -05:00
// Check if it contains directory
NSString * strPath = [ NSString stringWithCString : filename encoding : NSUTF8StringEncoding ] ;
BOOL isDirectory = NO ;
if ( filename [ fileInfo . size_filename - 1 ] = = ' / ' | | filename [ fileInfo . size_filename - 1 ] = = ' \\ ' ) {
isDirectory = YES ;
}
free ( filename ) ;
// Contains a path
if ( [ strPath rangeOfCharacterFromSet : [ NSCharacterSet characterSetWithCharactersInString : @" / \\ " ] ] . location ! = NSNotFound ) {
strPath = [ strPath stringByReplacingOccurrencesOfString : @" \\ " withString : @" / " ] ;
}
2012-05-07 22:27:45 -07:00
NSString * fullPath = [ destination stringByAppendingPathComponent : strPath ] ;
NSError * err = nil ;
NSDate * modDate = [ [ self class ] _dateWithMSDOSFormat : ( UInt32 ) fileInfo . dosDate ] ;
NSDictionary * directoryAttr = [ NSDictionary dictionaryWithObjectsAndKeys : modDate , NSFileCreationDate , modDate , NSFileModificationDate , nil ] ;
2010-07-21 18:05:38 -05:00
if ( isDirectory ) {
2012-05-07 22:27:45 -07:00
[ fileManager createDirectoryAtPath : fullPath withIntermediateDirectories : YES attributes : directoryAttr error : & err ] ;
2010-07-21 18:05:38 -05:00
} else {
2012-05-07 22:27:45 -07:00
[ fileManager createDirectoryAtPath : [ fullPath stringByDeletingLastPathComponent ] withIntermediateDirectories : YES attributes : directoryAttr error : & err ] ;
2010-07-21 18:05:38 -05:00
}
2012-05-07 22:27:45 -07:00
if ( nil ! = err ) {
NSLog ( @" [SSZipArchive] Error: %@ " , err . localizedDescription ) ;
}
2012-05-07 22:27:45 -07:00
2012-05-09 11:12:04 -04:00
if ( ! fileIsSymbolicLink )
[ directoriesModificationDates addObject : [ NSDictionary dictionaryWithObjectsAndKeys : fullPath , @" path " , modDate , @" modDate " , nil ] ] ;
2012-05-07 22:27:45 -07:00
2012-05-07 22:27:45 -07:00
if ( [ fileManager fileExistsAtPath : fullPath ] & & ! isDirectory & & ! overwrite ) {
2010-07-21 18:05:38 -05:00
unzCloseCurrentFile ( zip ) ;
ret = unzGoToNextFile ( zip ) ;
continue ;
}
2012-05-09 08:18:19 -04:00
2012-05-09 11:12:04 -04:00
if ( ! fileIsSymbolicLink )
{
FILE * fp = fopen ( ( const char * ) [ fullPath UTF8String ] , " wb " ) ;
while ( fp ) {
int readBytes = unzReadCurrentFile ( zip , buffer , 4096 ) ;
if ( readBytes > 0 ) {
fwrite ( buffer , readBytes , 1 , fp ) ;
} else {
break ;
}
}
if ( fp ) {
fclose ( fp ) ;
// Set the original datetime property
if ( fileInfo . dosDate ! = 0 ) {
NSDate * orgDate = [ [ self class ] _dateWithMSDOSFormat : ( UInt32 ) fileInfo . dosDate ] ;
NSDictionary * attr = [ NSDictionary dictionaryWithObject : orgDate forKey : NSFileModificationDate ] ;
if ( attr ) {
if ( [ fileManager setAttributes : attr ofItemAtPath : fullPath error : nil ] = = NO ) {
// Can't set attributes
NSLog ( @" [SSZipArchive] Failed to set attributes " ) ;
}
}
}
}
}
else
{
// Get the path for the symbolic link
NSURL * symlinkURL = [ NSURL fileURLWithPath : fullPath ] ;
NSMutableString * destinationPath = [ NSMutableString string ] ;
int bytesRead = 0 ;
while ( ( bytesRead = unzReadCurrentFile ( zip , buffer , 4096 ) ) > 0 )
{
buffer [ bytesRead ] = 0 ;
[ destinationPath appendString : [ NSString stringWithUTF8String : ( const char * ) buffer ] ] ;
}
//NSLog(@"Symlinking to: %@", destinationPath);
NSURL * destinationURL = [ NSURL fileURLWithPath : destinationPath ] ;
// Create the symbolic link
NSError * symlinkError = nil ;
[ fileManager createSymbolicLinkAtURL : symlinkURL withDestinationURL : destinationURL error : & symlinkError ] ;
if ( symlinkError ! = nil )
{
NSLog ( @" Failed to create symbolic link at \" %@ \" to \" %@ \" . Error: %@ " , symlinkURL . absoluteString , destinationURL . absoluteString , symlinkError . localizedDescription ) ;
}
}
2010-07-21 18:05:38 -05:00
unzCloseCurrentFile ( zip ) ;
ret = unzGoToNextFile ( zip ) ;
2012-01-14 20:46:32 +01:00
2012-05-07 22:27:45 -07:00
// Message delegate
if ( [ delegate respondsToSelector : @selector ( zipArchiveDidUnzipFileAtIndex : totalFiles : archivePath : fileInfo : ) ] ) {
[ delegate zipArchiveDidUnzipFileAtIndex : currentFileNumber totalFiles : ( NSInteger ) globalInfo . number_entry
archivePath : path fileInfo : fileInfo ] ;
}
2012-01-14 20:46:32 +01:00
currentFileNumber + + ;
2010-07-21 18:05:38 -05:00
} while ( ret = = UNZ_OK & & UNZ_OK ! = UNZ_END_OF_LIST_OF_FILE ) ;
// Close
unzClose ( zip ) ;
2012-05-07 22:27:45 -07:00
// The process of decompressing the .zip archive causes the modification times on the folders
// to be set to the present time. So, when we are done, they need to be explicitly set.
// set the modification date on all of the directories.
NSError * err = nil ;
for ( NSDictionary * d in directoriesModificationDates ) {
if ( ! [ [ NSFileManager defaultManager ] setAttributes : [ NSDictionary dictionaryWithObjectsAndKeys : [ d objectForKey : @" modDate " ] , NSFileModificationDate , nil ] ofItemAtPath : [ d objectForKey : @" path " ] error : & err ] ) {
NSLog ( @" [SSZipArchive] Set attributes failed for directory: %@. " , [ d objectForKey : @" path " ] ) ;
}
if ( err ) {
NSLog ( @" [SSZipArchive] Error setting directory file modification date attribute: %@ " , err . localizedDescription ) ;
}
}
# if !__has_feature(objc_arc)
[ directoriesModificationDates release ] ;
# endif
2012-05-07 23:05:06 -07:00
// Message delegate
if ( success & & [ delegate respondsToSelector : @selector ( zipArchiveDidUnzipArchiveAtPath : zipInfo : unzippedPath : ) ] ) {
[ delegate zipArchiveDidUnzipArchiveAtPath : path zipInfo : globalInfo unzippedPath : destination ] ;
}
2010-07-21 18:05:38 -05:00
return success ;
}
2011-12-20 09:24:56 +00:00
2011-12-26 23:32:55 -05:00
# pragma mark - Zipping
2011-12-20 09:24:56 +00:00
+ ( BOOL ) createZipFileAtPath: ( NSString * ) path withFilesAtPaths: ( NSArray * ) paths {
2011-12-26 23:32:55 -05:00
BOOL success = NO ;
SSZipArchive * zipArchive = [ [ SSZipArchive alloc ] initWithPath : path ] ;
if ( [ zipArchive open ] ) {
for ( NSString * path in paths ) {
[ zipArchive writeFile : path ] ;
}
success = [ zipArchive close ] ;
}
2012-05-07 22:27:45 -07:00
# if !__has_feature(objc_arc)
2011-12-26 23:32:55 -05:00
[ zipArchive release ] ;
2012-05-07 22:27:45 -07:00
# endif
2011-12-26 23:32:55 -05:00
return success ;
2011-12-20 09:24:56 +00:00
}
2011-12-26 23:32:55 -05:00
- ( id ) initWithPath: ( NSString * ) path {
if ( ( self = [ super init ] ) ) {
_path = [ path copy ] ;
}
return self ;
2011-12-20 09:24:56 +00:00
}
2011-12-26 23:32:55 -05:00
2012-05-07 22:27:45 -07:00
# if !__has_feature(objc_arc)
2011-12-20 09:24:56 +00:00
- ( void ) dealloc {
2012-05-07 22:27:45 -07:00
[ _path release ] ;
2011-12-26 23:32:55 -05:00
[ super dealloc ] ;
2011-12-20 09:24:56 +00:00
}
2012-05-07 22:27:45 -07:00
# endif
2011-12-20 09:24:56 +00:00
2011-12-26 23:32:55 -05:00
- ( BOOL ) open {
NSAssert ( ( _zip = = NULL ) , @" Attempting open an archive which is already open " ) ;
_zip = zipOpen ( [ _path UTF8String ] , APPEND_STATUS_CREATE ) ;
return ( NULL ! = _zip ) ;
2011-12-20 09:24:56 +00:00
}
2011-12-26 23:32:55 -05:00
2012-05-07 22:12:09 -07:00
- ( void ) zipInfo: ( zip_fileinfo * ) zipInfo setDate: ( NSDate * ) date {
NSCalendar * currentCalendar = [ NSCalendar currentCalendar ] ;
2011-12-27 17:25:34 +01:00
uint flags = NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit | NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit ;
2012-05-07 22:12:09 -07:00
NSDateComponents * components = [ currentCalendar components : flags fromDate : date ] ;
zipInfo - > tmz_date . tm_sec = ( unsigned int ) components . second ;
zipInfo - > tmz_date . tm_min = ( unsigned int ) components . minute ;
zipInfo - > tmz_date . tm_hour = ( unsigned int ) components . hour ;
zipInfo - > tmz_date . tm_mday = ( unsigned int ) components . day ;
zipInfo - > tmz_date . tm_mon = ( unsigned int ) components . month - 1 ;
zipInfo - > tmz_date . tm_year = ( unsigned int ) components . year ;
2011-12-27 17:25:34 +01:00
}
2011-12-20 09:24:56 +00:00
- ( BOOL ) writeFile: ( NSString * ) path {
2011-12-26 23:32:55 -05:00
NSAssert ( ( _zip ! = NULL ) , @" Attempting to write to an archive which was never opened " ) ;
FILE * input = fopen ( [ path UTF8String ] , " r " ) ;
if ( NULL = = input ) {
return NO ;
}
zipOpenNewFileInZip ( _zip , [ [ path lastPathComponent ] UTF8String ] , NULL , NULL , 0 , NULL , 0 , NULL , Z_DEFLATED ,
Z_DEFAULT_COMPRESSION ) ;
void * buffer = malloc ( CHUNK ) ;
unsigned int len = 0 ;
while ( ! feof ( input ) ) {
len = ( unsigned int ) fread ( buffer , 1 , CHUNK , input ) ;
zipWriteInFileInZip ( _zip , buffer , len ) ;
}
zipCloseFileInZip ( _zip ) ;
free ( buffer ) ;
return YES ;
2011-12-20 09:24:56 +00:00
}
2011-12-26 23:32:55 -05:00
2011-12-20 09:24:56 +00:00
- ( BOOL ) writeData: ( NSData * ) data filename: ( NSString * ) filename {
2011-12-27 17:25:34 +01:00
if ( ! _zip ) {
return NO ;
}
if ( ! data ) {
return NO ;
}
zip_fileinfo zipInfo = { 0 } ;
[ self zipInfo : & zipInfo setDate : [ NSDate date ] ] ;
2011-12-26 23:32:55 -05:00
2011-12-27 17:25:34 +01:00
zipOpenNewFileInZip ( _zip , [ filename UTF8String ] , & zipInfo , NULL , 0 , NULL , 0 , NULL , Z_DEFLATED , Z_DEFAULT_COMPRESSION ) ;
2011-12-26 23:32:55 -05:00
2012-05-07 22:12:09 -07:00
zipWriteInFileInZip ( _zip , data . bytes , ( unsigned int ) data . length ) ;
2011-12-26 23:32:55 -05:00
zipCloseFileInZip ( _zip ) ;
return YES ;
2011-12-20 09:24:56 +00:00
}
2012-05-07 22:27:45 -07:00
2011-12-20 09:24:56 +00:00
- ( BOOL ) close {
2012-05-07 22:27:45 -07:00
NSAssert ( ( _zip ! = NULL ) , @" [SSZipArchive] Attempting to close an archive which was never opened " ) ;
2011-12-26 23:32:55 -05:00
zipClose ( _zip , NULL ) ;
return YES ;
2011-12-20 09:24:56 +00:00
}
2010-07-21 18:05:38 -05:00
2011-12-26 23:32:55 -05:00
2011-10-03 23:28:54 -07:00
# pragma mark - Private
2012-05-07 22:27:45 -07:00
// Format from http://newsgroups.derkeiler.com/Archive/Comp/comp.os.msdos.programmer/2009-04/msg00060.html
// Two consecutive words, or a longword, YYYYYYYMMMMDDDDD hhhhhmmmmmmsssss
// YYYYYYY is years from 1980 = 0
// sssss is (seconds/2).
//
// 3658 = 0011 0110 0101 1000 = 0011011 0010 11000 = 27 2 24 = 2007-02-24
// 7423 = 0111 0100 0010 0011 - 01110 100001 00011 = 14 33 2 = 14:33:06
+ ( NSDate * ) _dateWithMSDOSFormat: ( UInt32 ) msdosDateTime {
static const UInt32 kYearMask = 0xFE000000 ;
static const UInt32 kMonthMask = 0x1E00000 ;
static const UInt32 kDayMask = 0x1F0000 ;
static const UInt32 kHourMask = 0xF800 ;
static const UInt32 kMinuteMask = 0x7E0 ;
static const UInt32 kSecondMask = 0x1F ;
2010-07-21 18:05:38 -05:00
NSCalendar * gregorian = [ [ NSCalendar alloc ] initWithCalendarIdentifier : NSGregorianCalendar ] ;
2012-05-07 22:27:45 -07:00
NSDateComponents * components = [ [ NSDateComponents alloc ] init ] ;
2012-05-07 22:27:45 -07:00
2012-05-07 22:27:45 -07:00
NSAssert ( 0xFFFFFFFF = = ( kYearMask | kMonthMask | kDayMask | kHourMask | kMinuteMask | kSecondMask ) , @" [SSZipArchive] MSDOS date masks don't add up " ) ;
[ components setYear : 1980 + ( ( msdosDateTime & kYearMask ) > > 25 ) ] ;
[ components setMonth : ( msdosDateTime & kMonthMask ) > > 21 ] ;
[ components setDay : ( msdosDateTime & kDayMask ) > > 16 ] ;
[ components setHour : ( msdosDateTime & kHourMask ) > > 11 ] ;
[ components setMinute : ( msdosDateTime & kMinuteMask ) > > 5 ] ;
[ components setSecond : ( msdosDateTime & kSecondMask ) * 2 ] ;
NSDate * date = [ NSDate dateWithTimeInterval : 0 sinceDate : [ gregorian dateFromComponents : components ] ] ;
# if !__has_feature(objc_arc)
2010-07-21 18:05:38 -05:00
[ gregorian release ] ;
2012-05-07 22:27:45 -07:00
[ components release ] ;
# endif
2010-07-21 18:05:38 -05:00
return date ;
}
@end