SSZipArchive.m 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672
  1. //
  2. // SSZipArchive.m
  3. // SSZipArchive
  4. //
  5. // Created by Sam Soffes on 7/21/10.
  6. // Copyright (c) Sam Soffes 2010-2015. All rights reserved.
  7. //
  8. #import "SSZipArchive.h"
  9. #include "zip.h"
  10. #import "zlib.h"
  11. #import "zconf.h"
  12. #include <sys/stat.h>
  13. #define CHUNK 16384
  14. @interface SSZipArchive ()
  15. + (NSDate *)_dateWithMSDOSFormat:(UInt32)msdosDateTime;
  16. @end
  17. @implementation SSZipArchive
  18. {
  19. NSString *_path;
  20. NSString *_filename;
  21. zipFile _zip;
  22. }
  23. #pragma mark - Unzipping
  24. + (BOOL)unzipFileAtPath:(NSString *)path toDestination:(NSString *)destination
  25. {
  26. return [self unzipFileAtPath:path toDestination:destination delegate:nil];
  27. }
  28. + (BOOL)unzipFileAtPath:(NSString *)path toDestination:(NSString *)destination overwrite:(BOOL)overwrite password:(NSString *)password error:(NSError **)error
  29. {
  30. return [self unzipFileAtPath:path toDestination:destination overwrite:overwrite password:password error:error delegate:nil progressHandler:nil completionHandler:nil];
  31. }
  32. + (BOOL)unzipFileAtPath:(NSString *)path toDestination:(NSString *)destination delegate:(id<SSZipArchiveDelegate>)delegate
  33. {
  34. return [self unzipFileAtPath:path toDestination:destination overwrite:YES password:nil error:nil delegate:delegate progressHandler:nil completionHandler:nil];
  35. }
  36. + (BOOL)unzipFileAtPath:(NSString *)path toDestination:(NSString *)destination overwrite:(BOOL)overwrite password:(NSString *)password error:(NSError **)error delegate:(id<SSZipArchiveDelegate>)delegate
  37. {
  38. return [self unzipFileAtPath:path toDestination:destination overwrite:overwrite password:password error:error delegate:delegate progressHandler:nil completionHandler:nil];
  39. }
  40. + (BOOL)unzipFileAtPath:(NSString *)path
  41. toDestination:(NSString *)destination
  42. overwrite:(BOOL)overwrite
  43. password:(NSString *)password
  44. progressHandler:(void (^)(NSString *entry, unz_file_info zipInfo, long entryNumber, long total))progressHandler
  45. completionHandler:(void (^)(NSString *path, BOOL succeeded, NSError *error))completionHandler
  46. {
  47. return [self unzipFileAtPath:path toDestination:destination overwrite:overwrite password:password error:nil delegate:nil progressHandler:progressHandler completionHandler:completionHandler];
  48. }
  49. + (BOOL)unzipFileAtPath:(NSString *)path
  50. toDestination:(NSString *)destination
  51. progressHandler:(void (^)(NSString *entry, unz_file_info zipInfo, long entryNumber, long total))progressHandler
  52. completionHandler:(void (^)(NSString *path, BOOL succeeded, NSError *error))completionHandler
  53. {
  54. return [self unzipFileAtPath:path toDestination:destination overwrite:YES password:nil error:nil delegate:nil progressHandler:progressHandler completionHandler:completionHandler];
  55. }
  56. + (BOOL)unzipFileAtPath:(NSString *)path
  57. toDestination:(NSString *)destination
  58. overwrite:(BOOL)overwrite
  59. password:(NSString *)password
  60. error:(NSError **)error
  61. delegate:(id<SSZipArchiveDelegate>)delegate
  62. progressHandler:(void (^)(NSString *entry, unz_file_info zipInfo, long entryNumber, long total))progressHandler
  63. completionHandler:(void (^)(NSString *path, BOOL succeeded, NSError *error))completionHandler
  64. {
  65. // Begin opening
  66. zipFile zip = unzOpen((const char*)[path UTF8String]);
  67. if (zip == NULL)
  68. {
  69. NSDictionary *userInfo = [NSDictionary dictionaryWithObject:@"failed to open zip file" forKey:NSLocalizedDescriptionKey];
  70. NSError *err = [NSError errorWithDomain:@"SSZipArchiveErrorDomain" code:-1 userInfo:userInfo];
  71. if (error)
  72. {
  73. *error = err;
  74. }
  75. if (completionHandler)
  76. {
  77. completionHandler(nil, NO, err);
  78. }
  79. return NO;
  80. }
  81. NSDictionary * fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:path error:nil];
  82. unsigned long long fileSize = [[fileAttributes objectForKey:NSFileSize] unsignedLongLongValue];
  83. unsigned long long currentPosition = 0;
  84. unz_global_info globalInfo = {0ul, 0ul};
  85. unzGetGlobalInfo(zip, &globalInfo);
  86. // Begin unzipping
  87. if (unzGoToFirstFile(zip) != UNZ_OK)
  88. {
  89. NSDictionary *userInfo = [NSDictionary dictionaryWithObject:@"failed to open first file in zip file" forKey:NSLocalizedDescriptionKey];
  90. NSError *err = [NSError errorWithDomain:@"SSZipArchiveErrorDomain" code:-2 userInfo:userInfo];
  91. if (error)
  92. {
  93. *error = err;
  94. }
  95. if (completionHandler)
  96. {
  97. completionHandler(nil, NO, err);
  98. }
  99. return NO;
  100. }
  101. BOOL success = YES;
  102. BOOL canceled = NO;
  103. int ret = 0;
  104. unsigned char buffer[4096] = {0};
  105. NSFileManager *fileManager = [NSFileManager defaultManager];
  106. NSMutableSet *directoriesModificationDates = [[NSMutableSet alloc] init];
  107. // Message delegate
  108. if ([delegate respondsToSelector:@selector(zipArchiveWillUnzipArchiveAtPath:zipInfo:)]) {
  109. [delegate zipArchiveWillUnzipArchiveAtPath:path zipInfo:globalInfo];
  110. }
  111. if ([delegate respondsToSelector:@selector(zipArchiveProgressEvent:total:)]) {
  112. [delegate zipArchiveProgressEvent:(NSInteger)currentPosition total:(NSInteger)fileSize];
  113. }
  114. NSInteger currentFileNumber = 0;
  115. do {
  116. @autoreleasepool {
  117. if ([password length] == 0) {
  118. ret = unzOpenCurrentFile(zip);
  119. } else {
  120. ret = unzOpenCurrentFilePassword(zip, [password cStringUsingEncoding:NSASCIIStringEncoding]);
  121. }
  122. if (ret != UNZ_OK) {
  123. success = NO;
  124. break;
  125. }
  126. // Reading data and write to file
  127. unz_file_info fileInfo;
  128. memset(&fileInfo, 0, sizeof(unz_file_info));
  129. ret = unzGetCurrentFileInfo(zip, &fileInfo, NULL, 0, NULL, 0, NULL, 0);
  130. if (ret != UNZ_OK) {
  131. success = NO;
  132. unzCloseCurrentFile(zip);
  133. break;
  134. }
  135. currentPosition += fileInfo.compressed_size;
  136. // Message delegate
  137. if ([delegate respondsToSelector:@selector(zipArchiveShouldUnzipFileAtIndex:totalFiles:archivePath:fileInfo:)]) {
  138. if (![delegate zipArchiveShouldUnzipFileAtIndex:currentFileNumber
  139. totalFiles:(NSInteger)globalInfo.number_entry
  140. archivePath:path fileInfo:fileInfo]) {
  141. success = NO;
  142. canceled = YES;
  143. break;
  144. }
  145. }
  146. if ([delegate respondsToSelector:@selector(zipArchiveWillUnzipFileAtIndex:totalFiles:archivePath:fileInfo:)]) {
  147. [delegate zipArchiveWillUnzipFileAtIndex:currentFileNumber totalFiles:(NSInteger)globalInfo.number_entry
  148. archivePath:path fileInfo:fileInfo];
  149. }
  150. if ([delegate respondsToSelector:@selector(zipArchiveProgressEvent:total:)]) {
  151. [delegate zipArchiveProgressEvent:(NSInteger)currentPosition total:(NSInteger)fileSize];
  152. }
  153. char *filename = (char *)malloc(fileInfo.size_filename + 1);
  154. unzGetCurrentFileInfo(zip, &fileInfo, filename, fileInfo.size_filename + 1, NULL, 0, NULL, 0);
  155. filename[fileInfo.size_filename] = '\0';
  156. //
  157. // Determine whether this is a symbolic link:
  158. // - File is stored with 'version made by' value of UNIX (3),
  159. // as per http://www.pkware.com/documents/casestudies/APPNOTE.TXT
  160. // in the upper byte of the version field.
  161. // - BSD4.4 st_mode constants are stored in the high 16 bits of the
  162. // external file attributes (defacto standard, verified against libarchive)
  163. //
  164. // The original constants can be found here:
  165. // http://minnie.tuhs.org/cgi-bin/utree.pl?file=4.4BSD/usr/include/sys/stat.h
  166. //
  167. const uLong ZipUNIXVersion = 3;
  168. const uLong BSD_SFMT = 0170000;
  169. const uLong BSD_IFLNK = 0120000;
  170. BOOL fileIsSymbolicLink = NO;
  171. if (((fileInfo.version >> 8) == ZipUNIXVersion) && BSD_IFLNK == (BSD_SFMT & (fileInfo.external_fa >> 16))) {
  172. fileIsSymbolicLink = NO;
  173. }
  174. // Check if it contains directory
  175. NSString *strPath = [NSString stringWithCString:filename encoding:NSUTF8StringEncoding];
  176. BOOL isDirectory = NO;
  177. if (filename[fileInfo.size_filename-1] == '/' || filename[fileInfo.size_filename-1] == '\\') {
  178. isDirectory = YES;
  179. }
  180. free(filename);
  181. // Contains a path
  182. if ([strPath rangeOfCharacterFromSet:[NSCharacterSet characterSetWithCharactersInString:@"/\\"]].location != NSNotFound) {
  183. strPath = [strPath stringByReplacingOccurrencesOfString:@"\\" withString:@"/"];
  184. }
  185. NSString *fullPath = [destination stringByAppendingPathComponent:strPath];
  186. NSError *err = nil;
  187. NSDate *modDate = [[self class] _dateWithMSDOSFormat:(UInt32)fileInfo.dosDate];
  188. NSDictionary *directoryAttr = [NSDictionary dictionaryWithObjectsAndKeys:modDate, NSFileCreationDate, modDate, NSFileModificationDate, nil];
  189. if (isDirectory) {
  190. [fileManager createDirectoryAtPath:fullPath withIntermediateDirectories:YES attributes:directoryAttr error:&err];
  191. } else {
  192. [fileManager createDirectoryAtPath:[fullPath stringByDeletingLastPathComponent] withIntermediateDirectories:YES attributes:directoryAttr error:&err];
  193. }
  194. if (nil != err) {
  195. NSLog(@"[SSZipArchive] Error: %@", err.localizedDescription);
  196. }
  197. if(!fileIsSymbolicLink)
  198. [directoriesModificationDates addObject: [NSDictionary dictionaryWithObjectsAndKeys:fullPath, @"path", modDate, @"modDate", nil]];
  199. if ([fileManager fileExistsAtPath:fullPath] && !isDirectory && !overwrite) {
  200. unzCloseCurrentFile(zip);
  201. ret = unzGoToNextFile(zip);
  202. continue;
  203. }
  204. if (!fileIsSymbolicLink) {
  205. FILE *fp = fopen((const char*)[fullPath UTF8String], "wb");
  206. while (fp) {
  207. int readBytes = unzReadCurrentFile(zip, buffer, 4096);
  208. if (readBytes > 0) {
  209. fwrite(buffer, readBytes, 1, fp );
  210. } else {
  211. break;
  212. }
  213. }
  214. if (fp) {
  215. if ([[[fullPath pathExtension] lowercaseString] isEqualToString:@"zip"]) {
  216. NSLog(@"Unzipping nested .zip file: %@", [fullPath lastPathComponent]);
  217. if ([self unzipFileAtPath:fullPath toDestination:[fullPath stringByDeletingLastPathComponent] overwrite:overwrite password:password error:nil delegate:nil]) {
  218. [[NSFileManager defaultManager] removeItemAtPath:fullPath error:nil];
  219. }
  220. }
  221. fclose(fp);
  222. // Set the original datetime property
  223. if (fileInfo.dosDate != 0) {
  224. NSDate *orgDate = [[self class] _dateWithMSDOSFormat:(UInt32)fileInfo.dosDate];
  225. NSDictionary *attr = [NSDictionary dictionaryWithObject:orgDate forKey:NSFileModificationDate];
  226. if (attr) {
  227. if ([fileManager setAttributes:attr ofItemAtPath:fullPath error:nil] == NO) {
  228. // Can't set attributes
  229. NSLog(@"[SSZipArchive] Failed to set attributes - whilst setting modification date");
  230. }
  231. }
  232. }
  233. // Set the original permissions on the file
  234. uLong permissions = fileInfo.external_fa >> 16;
  235. if (permissions != 0) {
  236. // Store it into a NSNumber
  237. NSNumber *permissionsValue = @(permissions);
  238. // Retrieve any existing attributes
  239. NSMutableDictionary *attrs = [[NSMutableDictionary alloc] initWithDictionary:[fileManager attributesOfItemAtPath:fullPath error:nil]];
  240. // Set the value in the attributes dict
  241. attrs[NSFilePosixPermissions] = permissionsValue;
  242. // Update attributes
  243. if ([fileManager setAttributes:attrs ofItemAtPath:fullPath error:nil] == NO) {
  244. // Unable to set the permissions attribute
  245. NSLog(@"[SSZipArchive] Failed to set attributes - whilst setting permissions");
  246. }
  247. #if !__has_feature(objc_arc)
  248. [attrs release];
  249. #endif
  250. }
  251. }
  252. }
  253. else
  254. {
  255. // Assemble the path for the symbolic link
  256. NSMutableString* destinationPath = [NSMutableString string];
  257. int bytesRead = 0;
  258. while((bytesRead = unzReadCurrentFile(zip, buffer, 4096)) > 0)
  259. {
  260. buffer[bytesRead] = (int)0;
  261. [destinationPath appendString:[NSString stringWithUTF8String:(const char*)buffer]];
  262. }
  263. // Create the symbolic link (making sure it stays relative if it was relative before)
  264. int symlinkError = symlink([destinationPath cStringUsingEncoding:NSUTF8StringEncoding],
  265. [fullPath cStringUsingEncoding:NSUTF8StringEncoding]);
  266. if(symlinkError != 0)
  267. {
  268. NSLog(@"Failed to create symbolic link at \"%@\" to \"%@\". symlink() error code: %d", fullPath, destinationPath, errno);
  269. }
  270. }
  271. unzCloseCurrentFile( zip );
  272. ret = unzGoToNextFile( zip );
  273. // Message delegate
  274. if ([delegate respondsToSelector:@selector(zipArchiveDidUnzipFileAtIndex:totalFiles:archivePath:fileInfo:)]) {
  275. [delegate zipArchiveDidUnzipFileAtIndex:currentFileNumber totalFiles:(NSInteger)globalInfo.number_entry
  276. archivePath:path fileInfo:fileInfo];
  277. } else if ([delegate respondsToSelector: @selector(zipArchiveDidUnzipFileAtIndex:totalFiles:archivePath:unzippedFilePath:)]) {
  278. [delegate zipArchiveDidUnzipFileAtIndex: currentFileNumber totalFiles: (NSInteger)globalInfo.number_entry
  279. archivePath:path unzippedFilePath: fullPath];
  280. }
  281. currentFileNumber++;
  282. if (progressHandler)
  283. {
  284. progressHandler(strPath, fileInfo, currentFileNumber, globalInfo.number_entry);
  285. }
  286. }
  287. } while(ret == UNZ_OK && ret != UNZ_END_OF_LIST_OF_FILE);
  288. // Close
  289. unzClose(zip);
  290. // The process of decompressing the .zip archive causes the modification times on the folders
  291. // to be set to the present time. So, when we are done, they need to be explicitly set.
  292. // set the modification date on all of the directories.
  293. NSError * err = nil;
  294. for (NSDictionary * d in directoriesModificationDates) {
  295. if (![[NSFileManager defaultManager] setAttributes:[NSDictionary dictionaryWithObjectsAndKeys:[d objectForKey:@"modDate"], NSFileModificationDate, nil] ofItemAtPath:[d objectForKey:@"path"] error:&err]) {
  296. NSLog(@"[SSZipArchive] Set attributes failed for directory: %@.", [d objectForKey:@"path"]);
  297. }
  298. if (err) {
  299. NSLog(@"[SSZipArchive] Error setting directory file modification date attribute: %@",err.localizedDescription);
  300. }
  301. }
  302. #if !__has_feature(objc_arc)
  303. [directoriesModificationDates release];
  304. #endif
  305. // Message delegate
  306. if (success && [delegate respondsToSelector:@selector(zipArchiveDidUnzipArchiveAtPath:zipInfo:unzippedPath:)]) {
  307. [delegate zipArchiveDidUnzipArchiveAtPath:path zipInfo:globalInfo unzippedPath:destination];
  308. }
  309. // final progress event = 100%
  310. if (!canceled && [delegate respondsToSelector:@selector(zipArchiveProgressEvent:total:)]) {
  311. [delegate zipArchiveProgressEvent:fileSize total:fileSize];
  312. }
  313. if (completionHandler)
  314. {
  315. completionHandler(path, YES, nil);
  316. }
  317. return success;
  318. }
  319. #pragma mark - Zipping
  320. + (BOOL)createZipFileAtPath:(NSString *)path withFilesAtPaths:(NSArray *)paths
  321. {
  322. BOOL success = NO;
  323. SSZipArchive *zipArchive = [[SSZipArchive alloc] initWithPath:path];
  324. if ([zipArchive open]) {
  325. for (NSString *filePath in paths) {
  326. [zipArchive writeFile:filePath];
  327. }
  328. success = [zipArchive close];
  329. }
  330. #if !__has_feature(objc_arc)
  331. [zipArchive release];
  332. #endif
  333. return success;
  334. }
  335. + (BOOL)createZipFileAtPath:(NSString *)path withContentsOfDirectory:(NSString *)directoryPath {
  336. return [self createZipFileAtPath:path withContentsOfDirectory:directoryPath keepParentDirectory:NO];
  337. }
  338. + (BOOL)createZipFileAtPath:(NSString *)path withContentsOfDirectory:(NSString *)directoryPath keepParentDirectory:(BOOL)keepParentDirectory {
  339. BOOL success = NO;
  340. NSFileManager *fileManager = nil;
  341. SSZipArchive *zipArchive = [[SSZipArchive alloc] initWithPath:path];
  342. if ([zipArchive open]) {
  343. // use a local filemanager (queue/thread compatibility)
  344. fileManager = [[NSFileManager alloc] init];
  345. NSDirectoryEnumerator *dirEnumerator = [fileManager enumeratorAtPath:directoryPath];
  346. NSString *fileName;
  347. while ((fileName = [dirEnumerator nextObject])) {
  348. BOOL isDir;
  349. NSString *fullFilePath = [directoryPath stringByAppendingPathComponent:fileName];
  350. [fileManager fileExistsAtPath:fullFilePath isDirectory:&isDir];
  351. if (!isDir) {
  352. if (keepParentDirectory)
  353. {
  354. fileName = [[directoryPath lastPathComponent] stringByAppendingPathComponent:fileName];
  355. }
  356. [zipArchive writeFileAtPath:fullFilePath withFileName:fileName];
  357. }
  358. else
  359. {
  360. if([[NSFileManager defaultManager] subpathsOfDirectoryAtPath:fullFilePath error:nil].count == 0)
  361. {
  362. NSString *tempName = [fullFilePath stringByAppendingPathComponent:@".DS_Store"];
  363. [@"" writeToFile:tempName atomically:YES encoding:NSUTF8StringEncoding error:nil];
  364. [zipArchive writeFileAtPath:tempName withFileName:[fileName stringByAppendingPathComponent:@".DS_Store"]];
  365. [[NSFileManager defaultManager] removeItemAtPath:tempName error:nil];
  366. }
  367. }
  368. }
  369. success = [zipArchive close];
  370. }
  371. #if !__has_feature(objc_arc)
  372. [fileManager release];
  373. [zipArchive release];
  374. #endif
  375. return success;
  376. }
  377. - (id)initWithPath:(NSString *)path
  378. {
  379. if ((self = [super init])) {
  380. _path = [path copy];
  381. }
  382. return self;
  383. }
  384. #if !__has_feature(objc_arc)
  385. - (void)dealloc
  386. {
  387. [_path release];
  388. [super dealloc];
  389. }
  390. #endif
  391. - (BOOL)open
  392. {
  393. NSAssert((_zip == NULL), @"Attempting open an archive which is already open");
  394. _zip = zipOpen([_path UTF8String], APPEND_STATUS_CREATE);
  395. return (NULL != _zip);
  396. }
  397. - (void)zipInfo:(zip_fileinfo*)zipInfo setDate:(NSDate*)date
  398. {
  399. NSCalendar *currentCalendar = [NSCalendar currentCalendar];
  400. #if defined(__IPHONE_8_0) || defined(__MAC_10_10)
  401. uint flags = NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay | NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond;
  402. #else
  403. uint flags = NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit | NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit;
  404. #endif
  405. NSDateComponents *components = [currentCalendar components:flags fromDate:date];
  406. zipInfo->tmz_date.tm_sec = (unsigned int)components.second;
  407. zipInfo->tmz_date.tm_min = (unsigned int)components.minute;
  408. zipInfo->tmz_date.tm_hour = (unsigned int)components.hour;
  409. zipInfo->tmz_date.tm_mday = (unsigned int)components.day;
  410. zipInfo->tmz_date.tm_mon = (unsigned int)components.month - 1;
  411. zipInfo->tmz_date.tm_year = (unsigned int)components.year;
  412. }
  413. - (BOOL)writeFolderAtPath:(NSString *)path withFolderName:(NSString *)folderName
  414. {
  415. NSAssert((_zip != NULL), @"Attempting to write to an archive which was never opened");
  416. zip_fileinfo zipInfo = {{0}};
  417. NSDictionary *attr = [[NSFileManager defaultManager] attributesOfItemAtPath:path error: nil];
  418. if( attr )
  419. {
  420. NSDate *fileDate = (NSDate *)[attr objectForKey:NSFileModificationDate];
  421. if( fileDate )
  422. {
  423. [self zipInfo:&zipInfo setDate: fileDate ];
  424. }
  425. // Write permissions into the external attributes, for details on this see here: http://unix.stackexchange.com/a/14727
  426. // Get the permissions value from the files attributes
  427. NSNumber *permissionsValue = (NSNumber *)[attr objectForKey:NSFilePosixPermissions];
  428. if (permissionsValue) {
  429. // Get the short value for the permissions
  430. short permissionsShort = permissionsValue.shortValue;
  431. // Convert this into an octal by adding 010000, 010000 being the flag for a regular file
  432. NSInteger permissionsOctal = 0100000 + permissionsShort;
  433. // Convert this into a long value
  434. uLong permissionsLong = @(permissionsOctal).unsignedLongValue;
  435. // Store this into the external file attributes once it has been shifted 16 places left to form part of the second from last byte
  436. zipInfo.external_fa = permissionsLong << 16L;
  437. }
  438. }
  439. unsigned int len = 0;
  440. zipOpenNewFileInZip(_zip, [[folderName stringByAppendingString:@"/"] UTF8String], &zipInfo, NULL, 0, NULL, 0, NULL, Z_DEFLATED, Z_NO_COMPRESSION);
  441. zipWriteInFileInZip(_zip, &len, 0);
  442. zipCloseFileInZip(_zip);
  443. return YES;
  444. }
  445. - (BOOL)writeFile:(NSString *)path
  446. {
  447. return [self writeFileAtPath:path withFileName:nil];
  448. }
  449. // supports writing files with logical folder/directory structure
  450. // *path* is the absolute path of the file that will be compressed
  451. // *fileName* is the relative name of the file how it is stored within the zip e.g. /folder/subfolder/text1.txt
  452. - (BOOL)writeFileAtPath:(NSString *)path withFileName:(NSString *)fileName
  453. {
  454. NSAssert((_zip != NULL), @"Attempting to write to an archive which was never opened");
  455. FILE *input = fopen([path UTF8String], "r");
  456. if (NULL == input) {
  457. return NO;
  458. }
  459. const char *afileName;
  460. if (!fileName) {
  461. afileName = [path.lastPathComponent UTF8String];
  462. }
  463. else {
  464. afileName = [fileName UTF8String];
  465. }
  466. zip_fileinfo zipInfo = {{0}};
  467. NSDictionary *attr = [[NSFileManager defaultManager] attributesOfItemAtPath:path error: nil];
  468. if( attr )
  469. {
  470. NSDate *fileDate = (NSDate *)[attr objectForKey:NSFileModificationDate];
  471. if( fileDate )
  472. {
  473. [self zipInfo:&zipInfo setDate: fileDate ];
  474. }
  475. // Write permissions into the external attributes, for details on this see here: http://unix.stackexchange.com/a/14727
  476. // Get the permissions value from the files attributes
  477. NSNumber *permissionsValue = (NSNumber *)[attr objectForKey:NSFilePosixPermissions];
  478. if (permissionsValue) {
  479. // Get the short value for the permissions
  480. short permissionsShort = permissionsValue.shortValue;
  481. // Convert this into an octal by adding 010000, 010000 being the flag for a regular file
  482. NSInteger permissionsOctal = 0100000 + permissionsShort;
  483. // Convert this into a long value
  484. uLong permissionsLong = @(permissionsOctal).unsignedLongValue;
  485. // Store this into the external file attributes once it has been shifted 16 places left to form part of the second from last byte
  486. zipInfo.external_fa = permissionsLong << 16L;
  487. }
  488. }
  489. zipOpenNewFileInZip(_zip, afileName, &zipInfo, NULL, 0, NULL, 0, NULL, Z_DEFLATED, Z_DEFAULT_COMPRESSION);
  490. void *buffer = malloc(CHUNK);
  491. unsigned int len = 0;
  492. while (!feof(input))
  493. {
  494. len = (unsigned int) fread(buffer, 1, CHUNK, input);
  495. zipWriteInFileInZip(_zip, buffer, len);
  496. }
  497. zipCloseFileInZip(_zip);
  498. free(buffer);
  499. return YES;
  500. }
  501. - (BOOL)writeData:(NSData *)data filename:(NSString *)filename
  502. {
  503. if (!_zip) {
  504. return NO;
  505. }
  506. if (!data) {
  507. return NO;
  508. }
  509. zip_fileinfo zipInfo = {{0,0,0,0,0,0},0,0,0};
  510. [self zipInfo:&zipInfo setDate:[NSDate date]];
  511. zipOpenNewFileInZip(_zip, [filename UTF8String], &zipInfo, NULL, 0, NULL, 0, NULL, Z_DEFLATED, Z_DEFAULT_COMPRESSION);
  512. zipWriteInFileInZip(_zip, data.bytes, (unsigned int)data.length);
  513. zipCloseFileInZip(_zip);
  514. return YES;
  515. }
  516. - (BOOL)close
  517. {
  518. NSAssert((_zip != NULL), @"[SSZipArchive] Attempting to close an archive which was never opened");
  519. zipClose(_zip, NULL);
  520. return YES;
  521. }
  522. #pragma mark - Private
  523. // Format from http://newsgroups.derkeiler.com/Archive/Comp/comp.os.msdos.programmer/2009-04/msg00060.html
  524. // Two consecutive words, or a longword, YYYYYYYMMMMDDDDD hhhhhmmmmmmsssss
  525. // YYYYYYY is years from 1980 = 0
  526. // sssss is (seconds/2).
  527. //
  528. // 3658 = 0011 0110 0101 1000 = 0011011 0010 11000 = 27 2 24 = 2007-02-24
  529. // 7423 = 0111 0100 0010 0011 - 01110 100001 00011 = 14 33 2 = 14:33:06
  530. + (NSDate *)_dateWithMSDOSFormat:(UInt32)msdosDateTime
  531. {
  532. static const UInt32 kYearMask = 0xFE000000;
  533. static const UInt32 kMonthMask = 0x1E00000;
  534. static const UInt32 kDayMask = 0x1F0000;
  535. static const UInt32 kHourMask = 0xF800;
  536. static const UInt32 kMinuteMask = 0x7E0;
  537. static const UInt32 kSecondMask = 0x1F;
  538. static NSCalendar *gregorian;
  539. static dispatch_once_t onceToken;
  540. dispatch_once(&onceToken, ^{
  541. #if defined(__IPHONE_8_0) || defined(__MAC_10_10)
  542. gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
  543. #else
  544. gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
  545. #endif
  546. });
  547. NSDateComponents *components = [[NSDateComponents alloc] init];
  548. NSAssert(0xFFFFFFFF == (kYearMask | kMonthMask | kDayMask | kHourMask | kMinuteMask | kSecondMask), @"[SSZipArchive] MSDOS date masks don't add up");
  549. [components setYear:1980 + ((msdosDateTime & kYearMask) >> 25)];
  550. [components setMonth:(msdosDateTime & kMonthMask) >> 21];
  551. [components setDay:(msdosDateTime & kDayMask) >> 16];
  552. [components setHour:(msdosDateTime & kHourMask) >> 11];
  553. [components setMinute:(msdosDateTime & kMinuteMask) >> 5];
  554. [components setSecond:(msdosDateTime & kSecondMask) * 2];
  555. NSDate *date = [NSDate dateWithTimeInterval:0 sinceDate:[gregorian dateFromComponents:components]];
  556. #if !__has_feature(objc_arc)
  557. [components release];
  558. #endif
  559. return date;
  560. }
  561. @end