cordova-plugin-mserviceinterface-2.0 1.0.26 → 1.0.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/package.json +28 -28
  2. package/plugin.xml +153 -153
  3. package/src/android/CallbackProgressDialog.java +53 -53
  4. package/src/android/FileProvider.java +7 -7
  5. package/src/android/backgroundNotification.java +120 -120
  6. package/src/android/doubleogradle.gradle +5 -5
  7. package/src/android/mInterface.java +2123 -2087
  8. package/src/android/mInterfaceReceiver.java +59 -59
  9. package/src/android/mInterfaceService.java +749 -749
  10. package/src/android/mInterfaceUtil.java +1686 -1686
  11. package/src/android/res/xml/network_security_config.xml +8 -8
  12. package/src/android/res/xml/opener_paths.xml +16 -16
  13. package/src/ios/InternetConnection.h +64 -64
  14. package/src/ios/InternetConnection.m +242 -242
  15. package/src/ios/SSZipArchive.h +146 -146
  16. package/src/ios/SSZipArchive.m +1180 -1180
  17. package/src/ios/SSZipCommon.h +66 -66
  18. package/src/ios/UICKeyChainStore.h +64 -64
  19. package/src/ios/UICKeyChainStore.m +434 -434
  20. package/src/ios/XMLConverter.h +61 -61
  21. package/src/ios/XMLConverter.m +243 -243
  22. package/src/ios/ZipArchive.h +19 -19
  23. package/src/ios/aes/aes.h +270 -270
  24. package/src/ios/aes/aes_ni.c +687 -687
  25. package/src/ios/aes/aes_ni.h +59 -59
  26. package/src/ios/aes/aescrypt.c +301 -301
  27. package/src/ios/aes/aeskey.c +554 -554
  28. package/src/ios/aes/aesopt.h +776 -776
  29. package/src/ios/aes/aestab.c +418 -418
  30. package/src/ios/aes/aestab.h +173 -173
  31. package/src/ios/aes/brg_endian.h +127 -127
  32. package/src/ios/aes/brg_types.h +191 -191
  33. package/src/ios/aes/fileenc.c +145 -145
  34. package/src/ios/aes/fileenc.h +121 -121
  35. package/src/ios/aes/hmac.c +209 -209
  36. package/src/ios/aes/hmac.h +119 -119
  37. package/src/ios/aes/prng.c +155 -155
  38. package/src/ios/aes/prng.h +82 -82
  39. package/src/ios/aes/pwd2key.c +181 -181
  40. package/src/ios/aes/pwd2key.h +45 -45
  41. package/src/ios/aes/sha1.c +283 -283
  42. package/src/ios/aes/sha1.h +72 -72
  43. package/src/ios/minizip/crypt.c +146 -146
  44. package/src/ios/minizip/crypt.h +66 -66
  45. package/src/ios/minizip/ioapi.c +353 -353
  46. package/src/ios/minizip/ioapi.h +154 -154
  47. package/src/ios/minizip/ioapi_buf.c +452 -452
  48. package/src/ios/minizip/ioapi_buf.h +52 -52
  49. package/src/ios/minizip/ioapi_mem.c +167 -167
  50. package/src/ios/minizip/ioapi_mem.h +52 -52
  51. package/src/ios/minizip/minishared.c +292 -292
  52. package/src/ios/minizip/minishared.h +50 -50
  53. package/src/ios/minizip/unzip.c +2007 -2007
  54. package/src/ios/minizip/unzip.h +254 -254
  55. package/src/ios/minizip/zip.c +2007 -2007
  56. package/src/ios/minizip/zip.h +215 -215
  57. package/www/mInterface.js +3 -3
@@ -1,1180 +1,1180 @@
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
-
9
- #import "SSZipArchive.h"
10
- #include "unzip.h"
11
- #include "zip.h"
12
- #include "minishared.h"
13
-
14
- #include <sys/stat.h>
15
-
16
- NSString *const SSZipArchiveErrorDomain = @"SSZipArchiveErrorDomain";
17
-
18
- #define CHUNK 16384
19
-
20
- int _zipOpenEntry(zipFile entry, NSString *name, const zip_fileinfo *zipfi, int level, NSString *password, BOOL aes);
21
- BOOL _fileIsSymbolicLink(const unz_file_info *fileInfo);
22
-
23
- #ifndef API_AVAILABLE
24
- // Xcode 7- compatibility
25
- #define API_AVAILABLE(...)
26
- #endif
27
-
28
- @interface NSData(SSZipArchive)
29
- - (NSString *)_base64RFC4648 API_AVAILABLE(macos(10.9), ios(7.0), watchos(2.0), tvos(9.0));
30
- - (NSString *)_hexString;
31
- @end
32
-
33
- @interface NSString (SSZipArchive)
34
- - (NSString *)_sanitizedPath;
35
- @end
36
-
37
- @interface SSZipArchive ()
38
- - (instancetype)init NS_DESIGNATED_INITIALIZER;
39
- @end
40
-
41
- @implementation SSZipArchive
42
- {
43
- /// path for zip file
44
- NSString *_path;
45
- zipFile _zip;
46
- }
47
-
48
- #pragma mark - Password check
49
-
50
- + (BOOL)isFilePasswordProtectedAtPath:(NSString *)path {
51
- // Begin opening
52
- zipFile zip = unzOpen(path.fileSystemRepresentation);
53
- if (zip == NULL) {
54
- return NO;
55
- }
56
-
57
- int ret = unzGoToFirstFile(zip);
58
- if (ret == UNZ_OK) {
59
- do {
60
- ret = unzOpenCurrentFile(zip);
61
- if (ret != UNZ_OK) {
62
- // attempting with an arbitrary password to workaround `unzOpenCurrentFile` limitation on AES encrypted files
63
- ret = unzOpenCurrentFilePassword(zip, "");
64
- unzCloseCurrentFile(zip);
65
- if (ret == UNZ_OK || ret == UNZ_BADPASSWORD) {
66
- return YES;
67
- }
68
- return NO;
69
- }
70
- unz_file_info fileInfo = {};
71
- ret = unzGetCurrentFileInfo(zip, &fileInfo, NULL, 0, NULL, 0, NULL, 0);
72
- unzCloseCurrentFile(zip);
73
- if (ret != UNZ_OK) {
74
- return NO;
75
- } else if ((fileInfo.flag & 1) == 1) {
76
- return YES;
77
- }
78
-
79
- ret = unzGoToNextFile(zip);
80
- } while (ret == UNZ_OK);
81
- }
82
-
83
- return NO;
84
- }
85
-
86
- + (BOOL)isPasswordValidForArchiveAtPath:(NSString *)path password:(NSString *)pw error:(NSError **)error {
87
- if (error) {
88
- *error = nil;
89
- }
90
-
91
- zipFile zip = unzOpen(path.fileSystemRepresentation);
92
- if (zip == NULL) {
93
- if (error) {
94
- *error = [NSError errorWithDomain:SSZipArchiveErrorDomain
95
- code:SSZipArchiveErrorCodeFailedOpenZipFile
96
- userInfo:@{NSLocalizedDescriptionKey: @"failed to open zip file"}];
97
- }
98
- return NO;
99
- }
100
-
101
- int ret = unzGoToFirstFile(zip);
102
- if (ret == UNZ_OK) {
103
- do {
104
- if (pw.length == 0) {
105
- ret = unzOpenCurrentFile(zip);
106
- } else {
107
- ret = unzOpenCurrentFilePassword(zip, [pw cStringUsingEncoding:NSUTF8StringEncoding]);
108
- }
109
- if (ret != UNZ_OK) {
110
- if (ret != UNZ_BADPASSWORD) {
111
- if (error) {
112
- *error = [NSError errorWithDomain:SSZipArchiveErrorDomain
113
- code:SSZipArchiveErrorCodeFailedOpenFileInZip
114
- userInfo:@{NSLocalizedDescriptionKey: @"failed to open first file in zip file"}];
115
- }
116
- }
117
- return NO;
118
- }
119
- unz_file_info fileInfo = {};
120
- ret = unzGetCurrentFileInfo(zip, &fileInfo, NULL, 0, NULL, 0, NULL, 0);
121
- if (ret != UNZ_OK) {
122
- if (error) {
123
- *error = [NSError errorWithDomain:SSZipArchiveErrorDomain
124
- code:SSZipArchiveErrorCodeFileInfoNotLoadable
125
- userInfo:@{NSLocalizedDescriptionKey: @"failed to retrieve info for file"}];
126
- }
127
- return NO;
128
- } else if ((fileInfo.flag & 1) == 1) {
129
- unsigned char buffer[10] = {0};
130
- int readBytes = unzReadCurrentFile(zip, buffer, (unsigned)MIN(10UL,fileInfo.uncompressed_size));
131
- if (readBytes < 0) {
132
- // Let's assume error Z_DATA_ERROR is caused by an invalid password
133
- // Let's assume other errors are caused by Content Not Readable
134
- if (readBytes != Z_DATA_ERROR) {
135
- if (error) {
136
- *error = [NSError errorWithDomain:SSZipArchiveErrorDomain
137
- code:SSZipArchiveErrorCodeFileContentNotReadable
138
- userInfo:@{NSLocalizedDescriptionKey: @"failed to read contents of file entry"}];
139
- }
140
- }
141
- return NO;
142
- }
143
- return YES;
144
- }
145
-
146
- unzCloseCurrentFile(zip);
147
- ret = unzGoToNextFile(zip);
148
- } while (ret == UNZ_OK);
149
- }
150
-
151
- // No password required
152
- return YES;
153
- }
154
-
155
- #pragma mark - Unzipping
156
-
157
- + (BOOL)unzipFileAtPath:(NSString *)path toDestination:(NSString *)destination
158
- {
159
- return [self unzipFileAtPath:path toDestination:destination delegate:nil];
160
- }
161
-
162
- + (BOOL)unzipFileAtPath:(NSString *)path toDestination:(NSString *)destination overwrite:(BOOL)overwrite password:(nullable NSString *)password error:(NSError **)error
163
- {
164
- return [self unzipFileAtPath:path toDestination:destination preserveAttributes:YES overwrite:overwrite password:password error:error delegate:nil progressHandler:nil completionHandler:nil];
165
- }
166
-
167
- + (BOOL)unzipFileAtPath:(NSString *)path toDestination:(NSString *)destination delegate:(nullable id<SSZipArchiveDelegate>)delegate
168
- {
169
- return [self unzipFileAtPath:path toDestination:destination preserveAttributes:YES overwrite:YES password:nil error:nil delegate:delegate progressHandler:nil completionHandler:nil];
170
- }
171
-
172
- + (BOOL)unzipFileAtPath:(NSString *)path
173
- toDestination:(NSString *)destination
174
- overwrite:(BOOL)overwrite
175
- password:(nullable NSString *)password
176
- error:(NSError **)error
177
- delegate:(nullable id<SSZipArchiveDelegate>)delegate
178
- {
179
- return [self unzipFileAtPath:path toDestination:destination preserveAttributes:YES overwrite:overwrite password:password error:error delegate:delegate progressHandler:nil completionHandler:nil];
180
- }
181
-
182
- + (BOOL)unzipFileAtPath:(NSString *)path
183
- toDestination:(NSString *)destination
184
- overwrite:(BOOL)overwrite
185
- password:(NSString *)password
186
- progressHandler:(void (^)(NSString *entry, unz_file_info zipInfo, long entryNumber, long total))progressHandler
187
- completionHandler:(void (^)(NSString *path, BOOL succeeded, NSError * _Nullable error))completionHandler
188
- {
189
- return [self unzipFileAtPath:path toDestination:destination preserveAttributes:YES overwrite:overwrite password:password error:nil delegate:nil progressHandler:progressHandler completionHandler:completionHandler];
190
- }
191
-
192
- + (BOOL)unzipFileAtPath:(NSString *)path
193
- toDestination:(NSString *)destination
194
- progressHandler:(void (^_Nullable)(NSString *entry, unz_file_info zipInfo, long entryNumber, long total))progressHandler
195
- completionHandler:(void (^_Nullable)(NSString *path, BOOL succeeded, NSError * _Nullable error))completionHandler
196
- {
197
- return [self unzipFileAtPath:path toDestination:destination preserveAttributes:YES overwrite:YES password:nil error:nil delegate:nil progressHandler:progressHandler completionHandler:completionHandler];
198
- }
199
-
200
- + (BOOL)unzipFileAtPath:(NSString *)path
201
- toDestination:(NSString *)destination
202
- preserveAttributes:(BOOL)preserveAttributes
203
- overwrite:(BOOL)overwrite
204
- password:(nullable NSString *)password
205
- error:(NSError * *)error
206
- delegate:(nullable id<SSZipArchiveDelegate>)delegate
207
- {
208
- return [self unzipFileAtPath:path toDestination:destination preserveAttributes:preserveAttributes overwrite:overwrite password:password error:error delegate:delegate progressHandler:nil completionHandler:nil];
209
- }
210
-
211
- + (BOOL)unzipFileAtPath:(NSString *)path
212
- toDestination:(NSString *)destination
213
- preserveAttributes:(BOOL)preserveAttributes
214
- overwrite:(BOOL)overwrite
215
- password:(nullable NSString *)password
216
- error:(NSError **)error
217
- delegate:(nullable id<SSZipArchiveDelegate>)delegate
218
- progressHandler:(void (^_Nullable)(NSString *entry, unz_file_info zipInfo, long entryNumber, long total))progressHandler
219
- completionHandler:(void (^_Nullable)(NSString *path, BOOL succeeded, NSError * _Nullable error))completionHandler
220
- {
221
- return [self unzipFileAtPath:path toDestination:destination preserveAttributes:preserveAttributes overwrite:overwrite nestedZipLevel:0 password:password error:error delegate:delegate progressHandler:progressHandler completionHandler:completionHandler];
222
- }
223
-
224
- + (BOOL)unzipFileAtPath:(NSString *)path
225
- toDestination:(NSString *)destination
226
- preserveAttributes:(BOOL)preserveAttributes
227
- overwrite:(BOOL)overwrite
228
- nestedZipLevel:(NSInteger)nestedZipLevel
229
- password:(nullable NSString *)password
230
- error:(NSError **)error
231
- delegate:(nullable id<SSZipArchiveDelegate>)delegate
232
- progressHandler:(void (^_Nullable)(NSString *entry, unz_file_info zipInfo, long entryNumber, long total))progressHandler
233
- completionHandler:(void (^_Nullable)(NSString *path, BOOL succeeded, NSError * _Nullable error))completionHandler
234
- {
235
- // Guard against empty strings
236
- if (path.length == 0 || destination.length == 0)
237
- {
238
- NSDictionary *userInfo = @{NSLocalizedDescriptionKey: @"received invalid argument(s)"};
239
- NSError *err = [NSError errorWithDomain:SSZipArchiveErrorDomain code:SSZipArchiveErrorCodeInvalidArguments userInfo:userInfo];
240
- if (error)
241
- {
242
- *error = err;
243
- }
244
- if (completionHandler)
245
- {
246
- completionHandler(nil, NO, err);
247
- }
248
- return NO;
249
- }
250
-
251
- // Begin opening
252
- zipFile zip = unzOpen(path.fileSystemRepresentation);
253
- if (zip == NULL)
254
- {
255
- NSDictionary *userInfo = @{NSLocalizedDescriptionKey: @"failed to open zip file"};
256
- NSError *err = [NSError errorWithDomain:SSZipArchiveErrorDomain code:SSZipArchiveErrorCodeFailedOpenZipFile userInfo:userInfo];
257
- if (error)
258
- {
259
- *error = err;
260
- }
261
- if (completionHandler)
262
- {
263
- completionHandler(nil, NO, err);
264
- }
265
- return NO;
266
- }
267
-
268
- NSDictionary * fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:path error:nil];
269
- unsigned long long fileSize = [[fileAttributes objectForKey:NSFileSize] unsignedLongLongValue];
270
- unsigned long long currentPosition = 0;
271
-
272
- unz_global_info globalInfo = {};
273
- unzGetGlobalInfo(zip, &globalInfo);
274
-
275
- // Begin unzipping
276
- int ret = 0;
277
- ret = unzGoToFirstFile(zip);
278
- if (ret != UNZ_OK && ret != UNZ_END_OF_LIST_OF_FILE)
279
- {
280
- NSDictionary *userInfo = @{NSLocalizedDescriptionKey: @"failed to open first file in zip file"};
281
- NSError *err = [NSError errorWithDomain:SSZipArchiveErrorDomain code:SSZipArchiveErrorCodeFailedOpenFileInZip userInfo:userInfo];
282
- if (error)
283
- {
284
- *error = err;
285
- }
286
- if (completionHandler)
287
- {
288
- completionHandler(nil, NO, err);
289
- }
290
- return NO;
291
- }
292
-
293
- BOOL success = YES;
294
- BOOL canceled = NO;
295
- int crc_ret = 0;
296
- unsigned char buffer[4096] = {0};
297
- NSFileManager *fileManager = [NSFileManager defaultManager];
298
- NSMutableArray<NSDictionary *> *directoriesModificationDates = [[NSMutableArray alloc] init];
299
-
300
- // Message delegate
301
- if ([delegate respondsToSelector:@selector(zipArchiveWillUnzipArchiveAtPath:zipInfo:)]) {
302
- [delegate zipArchiveWillUnzipArchiveAtPath:path zipInfo:globalInfo];
303
- }
304
- if ([delegate respondsToSelector:@selector(zipArchiveProgressEvent:total:)]) {
305
- [delegate zipArchiveProgressEvent:currentPosition total:fileSize];
306
- }
307
-
308
- NSInteger currentFileNumber = -1;
309
- NSError *unzippingError;
310
- do {
311
- currentFileNumber++;
312
- if (ret == UNZ_END_OF_LIST_OF_FILE) {
313
- break;
314
- }
315
- @autoreleasepool {
316
- if (password.length == 0) {
317
- ret = unzOpenCurrentFile(zip);
318
- } else {
319
- ret = unzOpenCurrentFilePassword(zip, [password cStringUsingEncoding:NSUTF8StringEncoding]);
320
- }
321
-
322
- if (ret != UNZ_OK) {
323
- unzippingError = [NSError errorWithDomain:@"SSZipArchiveErrorDomain" code:SSZipArchiveErrorCodeFailedOpenFileInZip userInfo:@{NSLocalizedDescriptionKey: @"failed to open file in zip file"}];
324
- success = NO;
325
- break;
326
- }
327
-
328
- // Reading data and write to file
329
- unz_file_info fileInfo;
330
- memset(&fileInfo, 0, sizeof(unz_file_info));
331
-
332
- ret = unzGetCurrentFileInfo(zip, &fileInfo, NULL, 0, NULL, 0, NULL, 0);
333
- if (ret != UNZ_OK) {
334
- unzippingError = [NSError errorWithDomain:@"SSZipArchiveErrorDomain" code:SSZipArchiveErrorCodeFileInfoNotLoadable userInfo:@{NSLocalizedDescriptionKey: @"failed to retrieve info for file"}];
335
- success = NO;
336
- unzCloseCurrentFile(zip);
337
- break;
338
- }
339
-
340
- currentPosition += fileInfo.compressed_size;
341
-
342
- // Message delegate
343
- if ([delegate respondsToSelector:@selector(zipArchiveShouldUnzipFileAtIndex:totalFiles:archivePath:fileInfo:)]) {
344
- if (![delegate zipArchiveShouldUnzipFileAtIndex:currentFileNumber
345
- totalFiles:(NSInteger)globalInfo.number_entry
346
- archivePath:path
347
- fileInfo:fileInfo]) {
348
- success = NO;
349
- canceled = YES;
350
- break;
351
- }
352
- }
353
- if ([delegate respondsToSelector:@selector(zipArchiveWillUnzipFileAtIndex:totalFiles:archivePath:fileInfo:)]) {
354
- [delegate zipArchiveWillUnzipFileAtIndex:currentFileNumber totalFiles:(NSInteger)globalInfo.number_entry
355
- archivePath:path fileInfo:fileInfo];
356
- }
357
- if ([delegate respondsToSelector:@selector(zipArchiveProgressEvent:total:)]) {
358
- [delegate zipArchiveProgressEvent:(NSInteger)currentPosition total:(NSInteger)fileSize];
359
- }
360
-
361
- char *filename = (char *)malloc(fileInfo.size_filename + 1);
362
- if (filename == NULL)
363
- {
364
- success = NO;
365
- break;
366
- }
367
-
368
- unzGetCurrentFileInfo(zip, &fileInfo, filename, fileInfo.size_filename + 1, NULL, 0, NULL, 0);
369
- filename[fileInfo.size_filename] = '\0';
370
-
371
- BOOL fileIsSymbolicLink = _fileIsSymbolicLink(&fileInfo);
372
-
373
- NSString * strPath = [SSZipArchive _filenameStringWithCString:filename
374
- version_made_by:fileInfo.version
375
- general_purpose_flag:fileInfo.flag
376
- size:fileInfo.size_filename];
377
- if ([strPath hasPrefix:@"__MACOSX/"]) {
378
- // ignoring resource forks: https://superuser.com/questions/104500/what-is-macosx-folder
379
- unzCloseCurrentFile(zip);
380
- ret = unzGoToNextFile(zip);
381
- free(filename);
382
- continue;
383
- }
384
-
385
- // Check if it contains directory
386
- BOOL isDirectory = NO;
387
- if (filename[fileInfo.size_filename-1] == '/' || filename[fileInfo.size_filename-1] == '\\') {
388
- isDirectory = YES;
389
- }
390
- free(filename);
391
-
392
- // Sanitize paths in the file name.
393
- strPath = [strPath _sanitizedPath];
394
- if (!strPath.length) {
395
- // if filename data is unsalvageable, we default to currentFileNumber
396
- strPath = @(currentFileNumber).stringValue;
397
- }
398
-
399
- NSString *fullPath = [destination stringByAppendingPathComponent:strPath];
400
- NSError *err = nil;
401
- NSDictionary *directoryAttr;
402
- if (preserveAttributes) {
403
- NSDate *modDate = [[self class] _dateWithMSDOSFormat:(UInt32)fileInfo.dos_date];
404
- directoryAttr = @{NSFileCreationDate: modDate, NSFileModificationDate: modDate};
405
- [directoriesModificationDates addObject: @{@"path": fullPath, @"modDate": modDate}];
406
- }
407
- if (isDirectory) {
408
- [fileManager createDirectoryAtPath:fullPath withIntermediateDirectories:YES attributes:directoryAttr error:&err];
409
- } else {
410
- [fileManager createDirectoryAtPath:fullPath.stringByDeletingLastPathComponent withIntermediateDirectories:YES attributes:directoryAttr error:&err];
411
- }
412
- if (err != nil) {
413
- if ([err.domain isEqualToString:NSCocoaErrorDomain] &&
414
- err.code == 640) {
415
- unzippingError = err;
416
- unzCloseCurrentFile(zip);
417
- success = NO;
418
- break;
419
- }
420
- NSLog(@"[SSZipArchive] Error: %@", err.localizedDescription);
421
- }
422
-
423
- if ([fileManager fileExistsAtPath:fullPath] && !isDirectory && !overwrite) {
424
- //FIXME: couldBe CRC Check?
425
- unzCloseCurrentFile(zip);
426
- ret = unzGoToNextFile(zip);
427
- continue;
428
- }
429
-
430
- if (isDirectory && !fileIsSymbolicLink) {
431
- // nothing to read/write for a directory
432
- } else if (!fileIsSymbolicLink) {
433
- // ensure we are not creating stale file entries
434
- int readBytes = unzReadCurrentFile(zip, buffer, 4096);
435
- if (readBytes >= 0) {
436
- FILE *fp = fopen(fullPath.fileSystemRepresentation, "wb");
437
- while (fp) {
438
- if (readBytes > 0) {
439
- if (0 == fwrite(buffer, readBytes, 1, fp)) {
440
- if (ferror(fp)) {
441
- NSString *message = [NSString stringWithFormat:@"Failed to write file (check your free space)"];
442
- NSLog(@"[SSZipArchive] %@", message);
443
- success = NO;
444
- unzippingError = [NSError errorWithDomain:@"SSZipArchiveErrorDomain" code:SSZipArchiveErrorCodeFailedToWriteFile userInfo:@{NSLocalizedDescriptionKey: message}];
445
- break;
446
- }
447
- }
448
- } else {
449
- break;
450
- }
451
- readBytes = unzReadCurrentFile(zip, buffer, 4096);
452
- if (readBytes < 0) {
453
- // Let's assume error Z_DATA_ERROR is caused by an invalid password
454
- // Let's assume other errors are caused by Content Not Readable
455
- success = NO;
456
- }
457
- }
458
-
459
- if (fp) {
460
- fclose(fp);
461
-
462
- if (nestedZipLevel
463
- && [fullPath.pathExtension.lowercaseString isEqualToString:@"zip"]
464
- && [self unzipFileAtPath:fullPath
465
- toDestination:fullPath.stringByDeletingLastPathComponent
466
- preserveAttributes:preserveAttributes
467
- overwrite:overwrite
468
- nestedZipLevel:nestedZipLevel - 1
469
- password:password
470
- error:nil
471
- delegate:nil
472
- progressHandler:nil
473
- completionHandler:nil]) {
474
- [directoriesModificationDates removeLastObject];
475
- [[NSFileManager defaultManager] removeItemAtPath:fullPath error:nil];
476
- } else if (preserveAttributes) {
477
-
478
- // Set the original datetime property
479
- if (fileInfo.dos_date != 0) {
480
- NSDate *orgDate = [[self class] _dateWithMSDOSFormat:(UInt32)fileInfo.dos_date];
481
- NSDictionary *attr = @{NSFileModificationDate: orgDate};
482
-
483
- if (attr) {
484
- if (![fileManager setAttributes:attr ofItemAtPath:fullPath error:nil]) {
485
- // Can't set attributes
486
- NSLog(@"[SSZipArchive] Failed to set attributes - whilst setting modification date");
487
- }
488
- }
489
- }
490
-
491
- // Set the original permissions on the file (+read/write to solve #293)
492
- uLong permissions = fileInfo.external_fa >> 16 | 0b110000000;
493
- if (permissions != 0) {
494
- // Store it into a NSNumber
495
- NSNumber *permissionsValue = @(permissions);
496
-
497
- // Retrieve any existing attributes
498
- NSMutableDictionary *attrs = [[NSMutableDictionary alloc] initWithDictionary:[fileManager attributesOfItemAtPath:fullPath error:nil]];
499
-
500
- // Set the value in the attributes dict
501
- [attrs setObject:permissionsValue forKey:NSFilePosixPermissions];
502
-
503
- // Update attributes
504
- if (![fileManager setAttributes:attrs ofItemAtPath:fullPath error:nil]) {
505
- // Unable to set the permissions attribute
506
- NSLog(@"[SSZipArchive] Failed to set attributes - whilst setting permissions");
507
- }
508
- }
509
- }
510
- }
511
- else
512
- {
513
- // if we couldn't open file descriptor we can validate global errno to see the reason
514
- if (errno == ENOSPC) {
515
- NSError *enospcError = [NSError errorWithDomain:NSPOSIXErrorDomain
516
- code:ENOSPC
517
- userInfo:nil];
518
- unzippingError = enospcError;
519
- unzCloseCurrentFile(zip);
520
- success = NO;
521
- break;
522
- }
523
- }
524
- } else {
525
- // Let's assume error Z_DATA_ERROR is caused by an invalid password
526
- // Let's assume other errors are caused by Content Not Readable
527
- success = NO;
528
- break;
529
- }
530
- }
531
- else
532
- {
533
- // Assemble the path for the symbolic link
534
- NSMutableString *destinationPath = [NSMutableString string];
535
- int bytesRead = 0;
536
- while ((bytesRead = unzReadCurrentFile(zip, buffer, 4096)) > 0)
537
- {
538
- buffer[bytesRead] = 0;
539
- [destinationPath appendString:@((const char *)buffer)];
540
- }
541
- if (bytesRead < 0) {
542
- // Let's assume error Z_DATA_ERROR is caused by an invalid password
543
- // Let's assume other errors are caused by Content Not Readable
544
- success = NO;
545
- break;
546
- }
547
-
548
- // Check if the symlink exists and delete it if we're overwriting
549
- if (overwrite)
550
- {
551
- if ([fileManager fileExistsAtPath:fullPath])
552
- {
553
- NSError *error = nil;
554
- BOOL removeSuccess = [fileManager removeItemAtPath:fullPath error:&error];
555
- if (!removeSuccess)
556
- {
557
- NSString *message = [NSString stringWithFormat:@"Failed to delete existing symbolic link at \"%@\"", error.localizedDescription];
558
- NSLog(@"[SSZipArchive] %@", message);
559
- success = NO;
560
- unzippingError = [NSError errorWithDomain:SSZipArchiveErrorDomain code:error.code userInfo:@{NSLocalizedDescriptionKey: message}];
561
- }
562
- }
563
- }
564
-
565
- // Create the symbolic link (making sure it stays relative if it was relative before)
566
- int symlinkError = symlink([destinationPath cStringUsingEncoding:NSUTF8StringEncoding],
567
- [fullPath cStringUsingEncoding:NSUTF8StringEncoding]);
568
-
569
- if (symlinkError != 0)
570
- {
571
- // Bubble the error up to the completion handler
572
- NSString *message = [NSString stringWithFormat:@"Failed to create symbolic link at \"%@\" to \"%@\" - symlink() error code: %d", fullPath, destinationPath, errno];
573
- NSLog(@"[SSZipArchive] %@", message);
574
- success = NO;
575
- unzippingError = [NSError errorWithDomain:NSPOSIXErrorDomain code:symlinkError userInfo:@{NSLocalizedDescriptionKey: message}];
576
- }
577
- }
578
-
579
- crc_ret = unzCloseCurrentFile(zip);
580
- if (crc_ret == UNZ_CRCERROR) {
581
- // CRC ERROR
582
- success = NO;
583
- break;
584
- }
585
- ret = unzGoToNextFile(zip);
586
-
587
- // Message delegate
588
- if ([delegate respondsToSelector:@selector(zipArchiveDidUnzipFileAtIndex:totalFiles:archivePath:fileInfo:)]) {
589
- [delegate zipArchiveDidUnzipFileAtIndex:currentFileNumber totalFiles:(NSInteger)globalInfo.number_entry
590
- archivePath:path fileInfo:fileInfo];
591
- } else if ([delegate respondsToSelector: @selector(zipArchiveDidUnzipFileAtIndex:totalFiles:archivePath:unzippedFilePath:)]) {
592
- [delegate zipArchiveDidUnzipFileAtIndex: currentFileNumber totalFiles: (NSInteger)globalInfo.number_entry
593
- archivePath:path unzippedFilePath: fullPath];
594
- }
595
-
596
- if (progressHandler)
597
- {
598
- progressHandler(strPath, fileInfo, currentFileNumber, globalInfo.number_entry);
599
- }
600
- }
601
- } while (ret == UNZ_OK && success);
602
-
603
- // Close
604
- unzClose(zip);
605
-
606
- // The process of decompressing the .zip archive causes the modification times on the folders
607
- // to be set to the present time. So, when we are done, they need to be explicitly set.
608
- // set the modification date on all of the directories.
609
- if (success && preserveAttributes) {
610
- NSError * err = nil;
611
- for (NSDictionary * d in directoriesModificationDates) {
612
- if (![[NSFileManager defaultManager] setAttributes:@{NSFileModificationDate: [d objectForKey:@"modDate"]} ofItemAtPath:[d objectForKey:@"path"] error:&err]) {
613
- NSLog(@"[SSZipArchive] Set attributes failed for directory: %@.", [d objectForKey:@"path"]);
614
- }
615
- if (err) {
616
- NSLog(@"[SSZipArchive] Error setting directory file modification date attribute: %@", err.localizedDescription);
617
- }
618
- }
619
- }
620
-
621
- // Message delegate
622
- if (success && [delegate respondsToSelector:@selector(zipArchiveDidUnzipArchiveAtPath:zipInfo:unzippedPath:)]) {
623
- [delegate zipArchiveDidUnzipArchiveAtPath:path zipInfo:globalInfo unzippedPath:destination];
624
- }
625
- // final progress event = 100%
626
- if (!canceled && [delegate respondsToSelector:@selector(zipArchiveProgressEvent:total:)]) {
627
- [delegate zipArchiveProgressEvent:fileSize total:fileSize];
628
- }
629
-
630
- NSError *retErr = nil;
631
- if (crc_ret == UNZ_CRCERROR)
632
- {
633
- NSDictionary *userInfo = @{NSLocalizedDescriptionKey: @"crc check failed for file"};
634
- retErr = [NSError errorWithDomain:SSZipArchiveErrorDomain code:SSZipArchiveErrorCodeFileInfoNotLoadable userInfo:userInfo];
635
- }
636
-
637
- if (error) {
638
- if (unzippingError) {
639
- *error = unzippingError;
640
- }
641
- else {
642
- *error = retErr;
643
- }
644
- }
645
- if (completionHandler)
646
- {
647
- if (unzippingError) {
648
- completionHandler(path, success, unzippingError);
649
- }
650
- else
651
- {
652
- completionHandler(path, success, retErr);
653
- }
654
- }
655
- return success;
656
- }
657
-
658
- #pragma mark - Zipping
659
- + (BOOL)createZipFileAtPath:(NSString *)path withFilesAtPaths:(NSArray<NSString *> *)paths
660
- {
661
- return [SSZipArchive createZipFileAtPath:path withFilesAtPaths:paths withPassword:nil];
662
- }
663
- + (BOOL)createZipFileAtPath:(NSString *)path withContentsOfDirectory:(NSString *)directoryPath {
664
- return [SSZipArchive createZipFileAtPath:path withContentsOfDirectory:directoryPath withPassword:nil];
665
- }
666
-
667
- + (BOOL)createZipFileAtPath:(NSString *)path withContentsOfDirectory:(NSString *)directoryPath keepParentDirectory:(BOOL)keepParentDirectory {
668
- return [SSZipArchive createZipFileAtPath:path withContentsOfDirectory:directoryPath keepParentDirectory:keepParentDirectory withPassword:nil];
669
- }
670
-
671
- + (BOOL)createZipFileAtPath:(NSString *)path withFilesAtPaths:(NSArray<NSString *> *)paths withPassword:(NSString *)password
672
- {
673
- SSZipArchive *zipArchive = [[SSZipArchive alloc] initWithPath:path];
674
- BOOL success = [zipArchive open];
675
- if (success) {
676
- for (NSString *filePath in paths) {
677
- success &= [zipArchive writeFile:filePath withPassword:password];
678
- }
679
- success &= [zipArchive close];
680
- }
681
- return success;
682
- }
683
-
684
- + (BOOL)createZipFileAtPath:(NSString *)path withContentsOfDirectory:(NSString *)directoryPath withPassword:(nullable NSString *)password {
685
- return [SSZipArchive createZipFileAtPath:path withContentsOfDirectory:directoryPath keepParentDirectory:NO withPassword:password];
686
- }
687
-
688
-
689
- + (BOOL)createZipFileAtPath:(NSString *)path withContentsOfDirectory:(NSString *)directoryPath keepParentDirectory:(BOOL)keepParentDirectory withPassword:(nullable NSString *)password {
690
- return [SSZipArchive createZipFileAtPath:path
691
- withContentsOfDirectory:directoryPath
692
- keepParentDirectory:keepParentDirectory
693
- withPassword:password
694
- andProgressHandler:nil
695
- ];
696
- }
697
-
698
- + (BOOL)createZipFileAtPath:(NSString *)path
699
- withContentsOfDirectory:(NSString *)directoryPath
700
- keepParentDirectory:(BOOL)keepParentDirectory
701
- withPassword:(nullable NSString *)password
702
- andProgressHandler:(void(^ _Nullable)(NSUInteger entryNumber, NSUInteger total))progressHandler {
703
- return [self createZipFileAtPath:path withContentsOfDirectory:directoryPath keepParentDirectory:keepParentDirectory compressionLevel:Z_DEFAULT_COMPRESSION password:password AES:YES progressHandler:progressHandler];
704
- }
705
-
706
- + (BOOL)createZipFileAtPath:(NSString *)path
707
- withContentsOfDirectory:(NSString *)directoryPath
708
- keepParentDirectory:(BOOL)keepParentDirectory
709
- compressionLevel:(int)compressionLevel
710
- password:(nullable NSString *)password
711
- AES:(BOOL)aes
712
- progressHandler:(void(^ _Nullable)(NSUInteger entryNumber, NSUInteger total))progressHandler {
713
-
714
- SSZipArchive *zipArchive = [[SSZipArchive alloc] initWithPath:path];
715
- BOOL success = [zipArchive open];
716
- if (success) {
717
- // use a local fileManager (queue/thread compatibility)
718
- NSFileManager *fileManager = [[NSFileManager alloc] init];
719
- NSDirectoryEnumerator *dirEnumerator = [fileManager enumeratorAtPath:directoryPath];
720
- NSArray<NSString *> *allObjects = dirEnumerator.allObjects;
721
- NSUInteger total = allObjects.count, complete = 0;
722
- if (keepParentDirectory && !total) {
723
- allObjects = @[@""];
724
- total = 1;
725
- }
726
- for (__strong NSString *fileName in allObjects) {
727
- NSString *fullFilePath = [directoryPath stringByAppendingPathComponent:fileName];
728
-
729
- if (keepParentDirectory) {
730
- fileName = [directoryPath.lastPathComponent stringByAppendingPathComponent:fileName];
731
- }
732
-
733
- BOOL isDir;
734
- [fileManager fileExistsAtPath:fullFilePath isDirectory:&isDir];
735
- if (!isDir) {
736
- // file
737
- success &= [zipArchive writeFileAtPath:fullFilePath withFileName:fileName compressionLevel:compressionLevel password:password AES:aes];
738
- } else {
739
- // directory
740
- if (![fileManager enumeratorAtPath:fullFilePath].nextObject) {
741
- // empty directory
742
- success &= [zipArchive writeFolderAtPath:fullFilePath withFolderName:fileName withPassword:password];
743
- }
744
- }
745
- if (progressHandler) {
746
- complete++;
747
- progressHandler(complete, total);
748
- }
749
- }
750
- success &= [zipArchive close];
751
- }
752
- return success;
753
- }
754
-
755
- // disabling `init` because designated initializer is `initWithPath:`
756
- - (instancetype)init { @throw nil; }
757
-
758
- // designated initializer
759
- - (instancetype)initWithPath:(NSString *)path
760
- {
761
- if ((self = [super init])) {
762
- _path = [path copy];
763
- }
764
- return self;
765
- }
766
-
767
-
768
- - (BOOL)open
769
- {
770
- NSAssert((_zip == NULL), @"Attempting to open an archive which is already open");
771
- _zip = zipOpen(_path.fileSystemRepresentation, APPEND_STATUS_CREATE);
772
- return (NULL != _zip);
773
- }
774
-
775
- - (BOOL)writeFolderAtPath:(NSString *)path withFolderName:(NSString *)folderName withPassword:(nullable NSString *)password
776
- {
777
- NSAssert((_zip != NULL), @"Attempting to write to an archive which was never opened");
778
-
779
- zip_fileinfo zipInfo = {};
780
-
781
- [SSZipArchive zipInfo:&zipInfo setAttributesOfItemAtPath:path];
782
-
783
- int error = _zipOpenEntry(_zip, [folderName stringByAppendingString:@"/"], &zipInfo, Z_NO_COMPRESSION, password, 0);
784
- const void *buffer = NULL;
785
- zipWriteInFileInZip(_zip, buffer, 0);
786
- zipCloseFileInZip(_zip);
787
- return error == ZIP_OK;
788
- }
789
-
790
- - (BOOL)writeFile:(NSString *)path withPassword:(nullable NSString *)password
791
- {
792
- return [self writeFileAtPath:path withFileName:nil withPassword:password];
793
- }
794
-
795
- - (BOOL)writeFileAtPath:(NSString *)path withFileName:(nullable NSString *)fileName withPassword:(nullable NSString *)password
796
- {
797
- return [self writeFileAtPath:path withFileName:fileName compressionLevel:Z_DEFAULT_COMPRESSION password:password AES:YES];
798
- }
799
-
800
- // supports writing files with logical folder/directory structure
801
- // *path* is the absolute path of the file that will be compressed
802
- // *fileName* is the relative name of the file how it is stored within the zip e.g. /folder/subfolder/text1.txt
803
- - (BOOL)writeFileAtPath:(NSString *)path withFileName:(nullable NSString *)fileName compressionLevel:(int)compressionLevel password:(nullable NSString *)password AES:(BOOL)aes
804
- {
805
- NSAssert((_zip != NULL), @"Attempting to write to an archive which was never opened");
806
-
807
- FILE *input = fopen(path.fileSystemRepresentation, "r");
808
- if (NULL == input) {
809
- return NO;
810
- }
811
-
812
- if (!fileName) {
813
- fileName = path.lastPathComponent;
814
- }
815
-
816
- zip_fileinfo zipInfo = {};
817
-
818
- [SSZipArchive zipInfo:&zipInfo setAttributesOfItemAtPath:path];
819
-
820
- void *buffer = malloc(CHUNK);
821
- if (buffer == NULL)
822
- {
823
- fclose(input);
824
- return NO;
825
- }
826
-
827
- int error = _zipOpenEntry(_zip, fileName, &zipInfo, compressionLevel, password, aes);
828
-
829
- while (!feof(input) && !ferror(input))
830
- {
831
- unsigned int len = (unsigned int) fread(buffer, 1, CHUNK, input);
832
- zipWriteInFileInZip(_zip, buffer, len);
833
- }
834
-
835
- zipCloseFileInZip(_zip);
836
- free(buffer);
837
- fclose(input);
838
- return error == ZIP_OK;
839
- }
840
-
841
- - (BOOL)writeData:(NSData *)data filename:(nullable NSString *)filename withPassword:(nullable NSString *)password
842
- {
843
- return [self writeData:data filename:filename compressionLevel:Z_DEFAULT_COMPRESSION password:password AES:YES];
844
- }
845
-
846
- - (BOOL)writeData:(NSData *)data filename:(nullable NSString *)filename compressionLevel:(int)compressionLevel password:(nullable NSString *)password AES:(BOOL)aes
847
- {
848
- if (!_zip) {
849
- return NO;
850
- }
851
- if (!data) {
852
- return NO;
853
- }
854
- zip_fileinfo zipInfo = {};
855
- [SSZipArchive zipInfo:&zipInfo setDate:[NSDate date]];
856
-
857
- int error = _zipOpenEntry(_zip, filename, &zipInfo, compressionLevel, password, aes);
858
-
859
- zipWriteInFileInZip(_zip, data.bytes, (unsigned int)data.length);
860
-
861
- zipCloseFileInZip(_zip);
862
- return error == ZIP_OK;
863
- }
864
-
865
- - (BOOL)close
866
- {
867
- NSAssert((_zip != NULL), @"[SSZipArchive] Attempting to close an archive which was never opened");
868
- int error = zipClose(_zip, NULL);
869
- _zip = nil;
870
- return error == ZIP_OK;
871
- }
872
-
873
- #pragma mark - Private
874
-
875
- + (NSString *)_filenameStringWithCString:(const char *)filename
876
- version_made_by:(uint16_t)version_made_by
877
- general_purpose_flag:(uint16_t)flag
878
- size:(uint16_t)size_filename {
879
-
880
- // Respect Language encoding flag only reading filename as UTF-8 when this is set
881
- // when file entry created on dos system.
882
- //
883
- // https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT
884
- // Bit 11: Language encoding flag (EFS). If this bit is set,
885
- // the filename and comment fields for this file
886
- // MUST be encoded using UTF-8. (see APPENDIX D)
887
- uint16_t made_by = version_made_by >> 8;
888
- BOOL made_on_dos = made_by == 0;
889
- BOOL languageEncoding = (flag & (1 << 11)) != 0;
890
- if (!languageEncoding && made_on_dos) {
891
- // APPNOTE.TXT D.1:
892
- // D.2 If general purpose bit 11 is unset, the file name and comment should conform
893
- // to the original ZIP character encoding. If general purpose bit 11 is set, the
894
- // filename and comment must support The Unicode Standard, Version 4.1.0 or
895
- // greater using the character encoding form defined by the UTF-8 storage
896
- // specification. The Unicode Standard is published by the The Unicode
897
- // Consortium (www.unicode.org). UTF-8 encoded data stored within ZIP files
898
- // is expected to not include a byte order mark (BOM).
899
-
900
- // Code Page 437 corresponds to kCFStringEncodingDOSLatinUS
901
- NSStringEncoding encoding = CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingDOSLatinUS);
902
- NSString* strPath = [NSString stringWithCString:filename encoding:encoding];
903
- if (strPath) {
904
- return strPath;
905
- }
906
- }
907
-
908
- // attempting unicode encoding
909
- NSString * strPath = @(filename);
910
- if (strPath) {
911
- return strPath;
912
- }
913
-
914
- // if filename is non-unicode, detect and transform Encoding
915
- NSData *data = [NSData dataWithBytes:(const void *)filename length:sizeof(unsigned char) * size_filename];
916
- // Testing availability of @available (https://stackoverflow.com/a/46927445/1033581)
917
- #if __clang_major__ < 9
918
- // Xcode 8-
919
- if (floor(NSFoundationVersionNumber) > NSFoundationVersionNumber10_9_2) {
920
- #else
921
- // Xcode 9+
922
- if (@available(macOS 10.10, iOS 8.0, watchOS 2.0, tvOS 9.0, *)) {
923
- #endif
924
- // supported encodings are in [NSString availableStringEncodings]
925
- [NSString stringEncodingForData:data encodingOptions:nil convertedString:&strPath usedLossyConversion:nil];
926
- } else {
927
- // fallback to a simple manual detect for macOS 10.9 or older
928
- NSArray<NSNumber *> *encodings = @[@(kCFStringEncodingGB_18030_2000), @(kCFStringEncodingShiftJIS)];
929
- for (NSNumber *encoding in encodings) {
930
- strPath = [NSString stringWithCString:filename encoding:(NSStringEncoding)CFStringConvertEncodingToNSStringEncoding(encoding.unsignedIntValue)];
931
- if (strPath) {
932
- break;
933
- }
934
- }
935
- }
936
- if (strPath) {
937
- return strPath;
938
- }
939
-
940
- // if filename encoding is non-detected, we default to something based on data
941
- // _hexString is more readable than _base64RFC4648 for debugging unknown encodings
942
- strPath = [data _hexString];
943
- return strPath;
944
- }
945
-
946
- + (void)zipInfo:(zip_fileinfo *)zipInfo setAttributesOfItemAtPath:(NSString *)path
947
- {
948
- NSDictionary *attr = [[NSFileManager defaultManager] attributesOfItemAtPath:path error: nil];
949
- if (attr)
950
- {
951
- NSDate *fileDate = (NSDate *)[attr objectForKey:NSFileModificationDate];
952
- if (fileDate)
953
- {
954
- [self zipInfo:zipInfo setDate:fileDate];
955
- }
956
-
957
- // Write permissions into the external attributes, for details on this see here: http://unix.stackexchange.com/a/14727
958
- // Get the permissions value from the files attributes
959
- NSNumber *permissionsValue = (NSNumber *)[attr objectForKey:NSFilePosixPermissions];
960
- if (permissionsValue != nil) {
961
- // Get the short value for the permissions
962
- short permissionsShort = permissionsValue.shortValue;
963
-
964
- // Convert this into an octal by adding 010000, 010000 being the flag for a regular file
965
- NSInteger permissionsOctal = 0100000 + permissionsShort;
966
-
967
- // Convert this into a long value
968
- uLong permissionsLong = @(permissionsOctal).unsignedLongValue;
969
-
970
- // Store this into the external file attributes once it has been shifted 16 places left to form part of the second from last byte
971
-
972
- // Casted back to an unsigned int to match type of external_fa in minizip
973
- zipInfo->external_fa = (unsigned int)(permissionsLong << 16L);
974
- }
975
- }
976
- }
977
-
978
- + (void)zipInfo:(zip_fileinfo *)zipInfo setDate:(NSDate *)date
979
- {
980
- NSCalendar *currentCalendar = SSZipArchive._gregorian;
981
- NSCalendarUnit flags = NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay | NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond;
982
- NSDateComponents *components = [currentCalendar components:flags fromDate:date];
983
- struct tm tmz_date;
984
- tmz_date.tm_sec = (unsigned int)components.second;
985
- tmz_date.tm_min = (unsigned int)components.minute;
986
- tmz_date.tm_hour = (unsigned int)components.hour;
987
- tmz_date.tm_mday = (unsigned int)components.day;
988
- // ISO/IEC 9899 struct tm is 0-indexed for January but NSDateComponents for gregorianCalendar is 1-indexed for January
989
- tmz_date.tm_mon = (unsigned int)components.month - 1;
990
- // ISO/IEC 9899 struct tm is 0-indexed for AD 1900 but NSDateComponents for gregorianCalendar is 1-indexed for AD 1
991
- tmz_date.tm_year = (unsigned int)components.year - 1900;
992
- zipInfo->dos_date = tm_to_dosdate(&tmz_date);
993
- }
994
-
995
- + (NSCalendar *)_gregorian
996
- {
997
- static NSCalendar *gregorian;
998
- static dispatch_once_t onceToken;
999
- dispatch_once(&onceToken, ^{
1000
- gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
1001
- });
1002
-
1003
- return gregorian;
1004
- }
1005
-
1006
- // Format from http://newsgroups.derkeiler.com/Archive/Comp/comp.os.msdos.programmer/2009-04/msg00060.html
1007
- // Two consecutive words, or a longword, YYYYYYYMMMMDDDDD hhhhhmmmmmmsssss
1008
- // YYYYYYY is years from 1980 = 0
1009
- // sssss is (seconds/2).
1010
- //
1011
- // 3658 = 0011 0110 0101 1000 = 0011011 0010 11000 = 27 2 24 = 2007-02-24
1012
- // 7423 = 0111 0100 0010 0011 - 01110 100001 00011 = 14 33 3 = 14:33:06
1013
- + (NSDate *)_dateWithMSDOSFormat:(UInt32)msdosDateTime
1014
- {
1015
- // the whole `_dateWithMSDOSFormat:` method is equivalent but faster than this one line,
1016
- // essentially because `mktime` is slow:
1017
- //NSDate *date = [NSDate dateWithTimeIntervalSince1970:dosdate_to_time_t(msdosDateTime)];
1018
- static const UInt32 kYearMask = 0xFE000000;
1019
- static const UInt32 kMonthMask = 0x1E00000;
1020
- static const UInt32 kDayMask = 0x1F0000;
1021
- static const UInt32 kHourMask = 0xF800;
1022
- static const UInt32 kMinuteMask = 0x7E0;
1023
- static const UInt32 kSecondMask = 0x1F;
1024
-
1025
- NSAssert(0xFFFFFFFF == (kYearMask | kMonthMask | kDayMask | kHourMask | kMinuteMask | kSecondMask), @"[SSZipArchive] MSDOS date masks don't add up");
1026
-
1027
- NSDateComponents *components = [[NSDateComponents alloc] init];
1028
- components.year = 1980 + ((msdosDateTime & kYearMask) >> 25);
1029
- components.month = (msdosDateTime & kMonthMask) >> 21;
1030
- components.day = (msdosDateTime & kDayMask) >> 16;
1031
- components.hour = (msdosDateTime & kHourMask) >> 11;
1032
- components.minute = (msdosDateTime & kMinuteMask) >> 5;
1033
- components.second = (msdosDateTime & kSecondMask) * 2;
1034
-
1035
- NSDate *date = [self._gregorian dateFromComponents:components];
1036
- return date;
1037
- }
1038
-
1039
- @end
1040
-
1041
- int _zipOpenEntry(zipFile entry, NSString *name, const zip_fileinfo *zipfi, int level, NSString *password, BOOL aes)
1042
- {
1043
- return zipOpenNewFileInZip5(entry, name.fileSystemRepresentation, zipfi, NULL, 0, NULL, 0, NULL, 0, 0, Z_DEFLATED, level, 0, -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY, password.UTF8String, aes, 0);
1044
- }
1045
-
1046
- #pragma mark - Private tools for file info
1047
-
1048
- BOOL _fileIsSymbolicLink(const unz_file_info *fileInfo)
1049
- {
1050
- //
1051
- // Determine whether this is a symbolic link:
1052
- // - File is stored with 'version made by' value of UNIX (3),
1053
- // as per http://www.pkware.com/documents/casestudies/APPNOTE.TXT
1054
- // in the upper byte of the version field.
1055
- // - BSD4.4 st_mode constants are stored in the high 16 bits of the
1056
- // external file attributes (defacto standard, verified against libarchive)
1057
- //
1058
- // The original constants can be found here:
1059
- // http://minnie.tuhs.org/cgi-bin/utree.pl?file=4.4BSD/usr/include/sys/stat.h
1060
- //
1061
- const uLong ZipUNIXVersion = 3;
1062
- const uLong BSD_SFMT = 0170000;
1063
- const uLong BSD_IFLNK = 0120000;
1064
-
1065
- BOOL fileIsSymbolicLink = ((fileInfo->version >> 8) == ZipUNIXVersion) && BSD_IFLNK == (BSD_SFMT & (fileInfo->external_fa >> 16));
1066
- return fileIsSymbolicLink;
1067
- }
1068
-
1069
- #pragma mark - Private tools for unreadable encodings
1070
-
1071
- @implementation NSData (SSZipArchive)
1072
-
1073
- // `base64EncodedStringWithOptions` uses a base64 alphabet with '+' and '/'.
1074
- // we got those alternatives to make it compatible with filenames: https://en.wikipedia.org/wiki/Base64
1075
- // * modified Base64 encoding for IMAP mailbox names (RFC 3501): uses '+' and ','
1076
- // * modified Base64 for URL and filenames (RFC 4648): uses '-' and '_'
1077
- - (NSString *)_base64RFC4648
1078
- {
1079
- NSString *strName = [self base64EncodedStringWithOptions:0];
1080
- strName = [strName stringByReplacingOccurrencesOfString:@"+" withString:@"-"];
1081
- strName = [strName stringByReplacingOccurrencesOfString:@"/" withString:@"_"];
1082
- return strName;
1083
- }
1084
-
1085
- // initWithBytesNoCopy from NSProgrammer, Jan 25 '12: https://stackoverflow.com/a/9009321/1033581
1086
- // hexChars from Peter, Aug 19 '14: https://stackoverflow.com/a/25378464/1033581
1087
- // not implemented as too lengthy: a potential mapping improvement from Moose, Nov 3 '15: https://stackoverflow.com/a/33501154/1033581
1088
- - (NSString *)_hexString
1089
- {
1090
- const char *hexChars = "0123456789ABCDEF";
1091
- NSUInteger length = self.length;
1092
- const unsigned char *bytes = self.bytes;
1093
- char *chars = malloc(length * 2);
1094
- if (chars == NULL) {
1095
- // we directly raise an exception instead of using NSAssert to make sure assertion is not disabled as this is irrecoverable
1096
- [NSException raise:@"NSInternalInconsistencyException" format:@"failed malloc" arguments:nil];
1097
- return nil;
1098
- }
1099
- char *s = chars;
1100
- NSUInteger i = length;
1101
- while (i--) {
1102
- *s++ = hexChars[*bytes >> 4];
1103
- *s++ = hexChars[*bytes & 0xF];
1104
- bytes++;
1105
- }
1106
- NSString *str = [[NSString alloc] initWithBytesNoCopy:chars
1107
- length:length * 2
1108
- encoding:NSASCIIStringEncoding
1109
- freeWhenDone:YES];
1110
- return str;
1111
- }
1112
-
1113
- @end
1114
-
1115
- #pragma mark Private tools for security
1116
-
1117
- @implementation NSString (SSZipArchive)
1118
-
1119
- // One implementation alternative would be to use the algorithm found at mz_path_resolve from https://github.com/nmoinvaz/blob/dev/mz_os.c,
1120
- // but making sure to work with unichar values and not ascii values to avoid breaking Unicode characters containing 2E ('.') or 2F ('/') in their decomposition
1121
- /// Sanitize path traversal characters to prevent directory backtracking. Ignoring these characters mimicks the default behavior of the Unarchiving tool on macOS.
1122
- - (NSString *)_sanitizedPath
1123
- {
1124
- // Change Windows paths to Unix paths: https://en.wikipedia.org/wiki/Path_(computing)
1125
- // Possible improvement: only do this if the archive was created on a non-Unix system
1126
- NSString *strPath = [self stringByReplacingOccurrencesOfString:@"\\" withString:@"/"];
1127
-
1128
- // Percent-encode file path (where path is defined by https://tools.ietf.org/html/rfc8089)
1129
- // The key part is to allow characters "." and "/" and disallow "%".
1130
- // CharacterSet.urlPathAllowed seems to do the job
1131
- // Testing availability of @available (https://stackoverflow.com/a/46927445/1033581)
1132
- #if __clang_major__ < 9
1133
- // Xcode 8-
1134
- if (floor(NSFoundationVersionNumber) > NSFoundationVersionNumber10_8_4) {
1135
- #else
1136
- // Xcode 9+
1137
- if (@available(macOS 10.9, iOS 7.0, watchOS 2.0, tvOS 9.0, *)) {
1138
- #endif
1139
- strPath = [strPath stringByAddingPercentEncodingWithAllowedCharacters:NSCharacterSet.URLPathAllowedCharacterSet];
1140
- } else {
1141
- strPath = [strPath stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
1142
- }
1143
-
1144
- // `NSString.stringByAddingPercentEncodingWithAllowedCharacters:` may theorically fail: https://stackoverflow.com/questions/33558933/
1145
- // But because we auto-detect encoding using `NSString.stringEncodingForData:encodingOptions:convertedString:usedLossyConversion:`,
1146
- // we likely already prevent UTF-16, UTF-32 and invalid Unicode in the form of unpaired surrogate chars: https://stackoverflow.com/questions/53043876/
1147
- // To be on the safe side, we will still perform a guard check.
1148
- if (strPath == nil) {
1149
- return nil;
1150
- }
1151
-
1152
- // Add scheme "file:///" to support sanitation on names with a colon like "file:a/../../../usr/bin"
1153
- strPath = [@"file:///" stringByAppendingString:strPath];
1154
-
1155
- // Sanitize path traversal characters to prevent directory backtracking. Ignoring these characters mimicks the default behavior of the Unarchiving tool on macOS.
1156
- // "../../../../../../../../../../../tmp/test.txt" -> "tmp/test.txt"
1157
- // "a/b/../c.txt" -> "a/c.txt"
1158
- strPath = [NSURL URLWithString:strPath].standardizedURL.absoluteString;
1159
-
1160
- // Remove the "file:///" scheme
1161
- strPath = [strPath substringFromIndex:8];
1162
-
1163
- // Remove the percent-encoding
1164
- // Testing availability of @available (https://stackoverflow.com/a/46927445/1033581)
1165
- #if __clang_major__ < 9
1166
- // Xcode 8-
1167
- if (floor(NSFoundationVersionNumber) > NSFoundationVersionNumber10_8_4) {
1168
- #else
1169
- // Xcode 9+
1170
- if (@available(macOS 10.9, iOS 7.0, watchOS 2.0, tvOS 9.0, *)) {
1171
- #endif
1172
- strPath = strPath.stringByRemovingPercentEncoding;
1173
- } else {
1174
- strPath = [strPath stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
1175
- }
1176
-
1177
- return strPath;
1178
- }
1179
-
1180
- @end
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
+
9
+ #import "SSZipArchive.h"
10
+ #include "unzip.h"
11
+ #include "zip.h"
12
+ #include "minishared.h"
13
+
14
+ #include <sys/stat.h>
15
+
16
+ NSString *const SSZipArchiveErrorDomain = @"SSZipArchiveErrorDomain";
17
+
18
+ #define CHUNK 16384
19
+
20
+ int _zipOpenEntry(zipFile entry, NSString *name, const zip_fileinfo *zipfi, int level, NSString *password, BOOL aes);
21
+ BOOL _fileIsSymbolicLink(const unz_file_info *fileInfo);
22
+
23
+ #ifndef API_AVAILABLE
24
+ // Xcode 7- compatibility
25
+ #define API_AVAILABLE(...)
26
+ #endif
27
+
28
+ @interface NSData(SSZipArchive)
29
+ - (NSString *)_base64RFC4648 API_AVAILABLE(macos(10.9), ios(7.0), watchos(2.0), tvos(9.0));
30
+ - (NSString *)_hexString;
31
+ @end
32
+
33
+ @interface NSString (SSZipArchive)
34
+ - (NSString *)_sanitizedPath;
35
+ @end
36
+
37
+ @interface SSZipArchive ()
38
+ - (instancetype)init NS_DESIGNATED_INITIALIZER;
39
+ @end
40
+
41
+ @implementation SSZipArchive
42
+ {
43
+ /// path for zip file
44
+ NSString *_path;
45
+ zipFile _zip;
46
+ }
47
+
48
+ #pragma mark - Password check
49
+
50
+ + (BOOL)isFilePasswordProtectedAtPath:(NSString *)path {
51
+ // Begin opening
52
+ zipFile zip = unzOpen(path.fileSystemRepresentation);
53
+ if (zip == NULL) {
54
+ return NO;
55
+ }
56
+
57
+ int ret = unzGoToFirstFile(zip);
58
+ if (ret == UNZ_OK) {
59
+ do {
60
+ ret = unzOpenCurrentFile(zip);
61
+ if (ret != UNZ_OK) {
62
+ // attempting with an arbitrary password to workaround `unzOpenCurrentFile` limitation on AES encrypted files
63
+ ret = unzOpenCurrentFilePassword(zip, "");
64
+ unzCloseCurrentFile(zip);
65
+ if (ret == UNZ_OK || ret == UNZ_BADPASSWORD) {
66
+ return YES;
67
+ }
68
+ return NO;
69
+ }
70
+ unz_file_info fileInfo = {};
71
+ ret = unzGetCurrentFileInfo(zip, &fileInfo, NULL, 0, NULL, 0, NULL, 0);
72
+ unzCloseCurrentFile(zip);
73
+ if (ret != UNZ_OK) {
74
+ return NO;
75
+ } else if ((fileInfo.flag & 1) == 1) {
76
+ return YES;
77
+ }
78
+
79
+ ret = unzGoToNextFile(zip);
80
+ } while (ret == UNZ_OK);
81
+ }
82
+
83
+ return NO;
84
+ }
85
+
86
+ + (BOOL)isPasswordValidForArchiveAtPath:(NSString *)path password:(NSString *)pw error:(NSError **)error {
87
+ if (error) {
88
+ *error = nil;
89
+ }
90
+
91
+ zipFile zip = unzOpen(path.fileSystemRepresentation);
92
+ if (zip == NULL) {
93
+ if (error) {
94
+ *error = [NSError errorWithDomain:SSZipArchiveErrorDomain
95
+ code:SSZipArchiveErrorCodeFailedOpenZipFile
96
+ userInfo:@{NSLocalizedDescriptionKey: @"failed to open zip file"}];
97
+ }
98
+ return NO;
99
+ }
100
+
101
+ int ret = unzGoToFirstFile(zip);
102
+ if (ret == UNZ_OK) {
103
+ do {
104
+ if (pw.length == 0) {
105
+ ret = unzOpenCurrentFile(zip);
106
+ } else {
107
+ ret = unzOpenCurrentFilePassword(zip, [pw cStringUsingEncoding:NSUTF8StringEncoding]);
108
+ }
109
+ if (ret != UNZ_OK) {
110
+ if (ret != UNZ_BADPASSWORD) {
111
+ if (error) {
112
+ *error = [NSError errorWithDomain:SSZipArchiveErrorDomain
113
+ code:SSZipArchiveErrorCodeFailedOpenFileInZip
114
+ userInfo:@{NSLocalizedDescriptionKey: @"failed to open first file in zip file"}];
115
+ }
116
+ }
117
+ return NO;
118
+ }
119
+ unz_file_info fileInfo = {};
120
+ ret = unzGetCurrentFileInfo(zip, &fileInfo, NULL, 0, NULL, 0, NULL, 0);
121
+ if (ret != UNZ_OK) {
122
+ if (error) {
123
+ *error = [NSError errorWithDomain:SSZipArchiveErrorDomain
124
+ code:SSZipArchiveErrorCodeFileInfoNotLoadable
125
+ userInfo:@{NSLocalizedDescriptionKey: @"failed to retrieve info for file"}];
126
+ }
127
+ return NO;
128
+ } else if ((fileInfo.flag & 1) == 1) {
129
+ unsigned char buffer[10] = {0};
130
+ int readBytes = unzReadCurrentFile(zip, buffer, (unsigned)MIN(10UL,fileInfo.uncompressed_size));
131
+ if (readBytes < 0) {
132
+ // Let's assume error Z_DATA_ERROR is caused by an invalid password
133
+ // Let's assume other errors are caused by Content Not Readable
134
+ if (readBytes != Z_DATA_ERROR) {
135
+ if (error) {
136
+ *error = [NSError errorWithDomain:SSZipArchiveErrorDomain
137
+ code:SSZipArchiveErrorCodeFileContentNotReadable
138
+ userInfo:@{NSLocalizedDescriptionKey: @"failed to read contents of file entry"}];
139
+ }
140
+ }
141
+ return NO;
142
+ }
143
+ return YES;
144
+ }
145
+
146
+ unzCloseCurrentFile(zip);
147
+ ret = unzGoToNextFile(zip);
148
+ } while (ret == UNZ_OK);
149
+ }
150
+
151
+ // No password required
152
+ return YES;
153
+ }
154
+
155
+ #pragma mark - Unzipping
156
+
157
+ + (BOOL)unzipFileAtPath:(NSString *)path toDestination:(NSString *)destination
158
+ {
159
+ return [self unzipFileAtPath:path toDestination:destination delegate:nil];
160
+ }
161
+
162
+ + (BOOL)unzipFileAtPath:(NSString *)path toDestination:(NSString *)destination overwrite:(BOOL)overwrite password:(nullable NSString *)password error:(NSError **)error
163
+ {
164
+ return [self unzipFileAtPath:path toDestination:destination preserveAttributes:YES overwrite:overwrite password:password error:error delegate:nil progressHandler:nil completionHandler:nil];
165
+ }
166
+
167
+ + (BOOL)unzipFileAtPath:(NSString *)path toDestination:(NSString *)destination delegate:(nullable id<SSZipArchiveDelegate>)delegate
168
+ {
169
+ return [self unzipFileAtPath:path toDestination:destination preserveAttributes:YES overwrite:YES password:nil error:nil delegate:delegate progressHandler:nil completionHandler:nil];
170
+ }
171
+
172
+ + (BOOL)unzipFileAtPath:(NSString *)path
173
+ toDestination:(NSString *)destination
174
+ overwrite:(BOOL)overwrite
175
+ password:(nullable NSString *)password
176
+ error:(NSError **)error
177
+ delegate:(nullable id<SSZipArchiveDelegate>)delegate
178
+ {
179
+ return [self unzipFileAtPath:path toDestination:destination preserveAttributes:YES overwrite:overwrite password:password error:error delegate:delegate progressHandler:nil completionHandler:nil];
180
+ }
181
+
182
+ + (BOOL)unzipFileAtPath:(NSString *)path
183
+ toDestination:(NSString *)destination
184
+ overwrite:(BOOL)overwrite
185
+ password:(NSString *)password
186
+ progressHandler:(void (^)(NSString *entry, unz_file_info zipInfo, long entryNumber, long total))progressHandler
187
+ completionHandler:(void (^)(NSString *path, BOOL succeeded, NSError * _Nullable error))completionHandler
188
+ {
189
+ return [self unzipFileAtPath:path toDestination:destination preserveAttributes:YES overwrite:overwrite password:password error:nil delegate:nil progressHandler:progressHandler completionHandler:completionHandler];
190
+ }
191
+
192
+ + (BOOL)unzipFileAtPath:(NSString *)path
193
+ toDestination:(NSString *)destination
194
+ progressHandler:(void (^_Nullable)(NSString *entry, unz_file_info zipInfo, long entryNumber, long total))progressHandler
195
+ completionHandler:(void (^_Nullable)(NSString *path, BOOL succeeded, NSError * _Nullable error))completionHandler
196
+ {
197
+ return [self unzipFileAtPath:path toDestination:destination preserveAttributes:YES overwrite:YES password:nil error:nil delegate:nil progressHandler:progressHandler completionHandler:completionHandler];
198
+ }
199
+
200
+ + (BOOL)unzipFileAtPath:(NSString *)path
201
+ toDestination:(NSString *)destination
202
+ preserveAttributes:(BOOL)preserveAttributes
203
+ overwrite:(BOOL)overwrite
204
+ password:(nullable NSString *)password
205
+ error:(NSError * *)error
206
+ delegate:(nullable id<SSZipArchiveDelegate>)delegate
207
+ {
208
+ return [self unzipFileAtPath:path toDestination:destination preserveAttributes:preserveAttributes overwrite:overwrite password:password error:error delegate:delegate progressHandler:nil completionHandler:nil];
209
+ }
210
+
211
+ + (BOOL)unzipFileAtPath:(NSString *)path
212
+ toDestination:(NSString *)destination
213
+ preserveAttributes:(BOOL)preserveAttributes
214
+ overwrite:(BOOL)overwrite
215
+ password:(nullable NSString *)password
216
+ error:(NSError **)error
217
+ delegate:(nullable id<SSZipArchiveDelegate>)delegate
218
+ progressHandler:(void (^_Nullable)(NSString *entry, unz_file_info zipInfo, long entryNumber, long total))progressHandler
219
+ completionHandler:(void (^_Nullable)(NSString *path, BOOL succeeded, NSError * _Nullable error))completionHandler
220
+ {
221
+ return [self unzipFileAtPath:path toDestination:destination preserveAttributes:preserveAttributes overwrite:overwrite nestedZipLevel:0 password:password error:error delegate:delegate progressHandler:progressHandler completionHandler:completionHandler];
222
+ }
223
+
224
+ + (BOOL)unzipFileAtPath:(NSString *)path
225
+ toDestination:(NSString *)destination
226
+ preserveAttributes:(BOOL)preserveAttributes
227
+ overwrite:(BOOL)overwrite
228
+ nestedZipLevel:(NSInteger)nestedZipLevel
229
+ password:(nullable NSString *)password
230
+ error:(NSError **)error
231
+ delegate:(nullable id<SSZipArchiveDelegate>)delegate
232
+ progressHandler:(void (^_Nullable)(NSString *entry, unz_file_info zipInfo, long entryNumber, long total))progressHandler
233
+ completionHandler:(void (^_Nullable)(NSString *path, BOOL succeeded, NSError * _Nullable error))completionHandler
234
+ {
235
+ // Guard against empty strings
236
+ if (path.length == 0 || destination.length == 0)
237
+ {
238
+ NSDictionary *userInfo = @{NSLocalizedDescriptionKey: @"received invalid argument(s)"};
239
+ NSError *err = [NSError errorWithDomain:SSZipArchiveErrorDomain code:SSZipArchiveErrorCodeInvalidArguments userInfo:userInfo];
240
+ if (error)
241
+ {
242
+ *error = err;
243
+ }
244
+ if (completionHandler)
245
+ {
246
+ completionHandler(nil, NO, err);
247
+ }
248
+ return NO;
249
+ }
250
+
251
+ // Begin opening
252
+ zipFile zip = unzOpen(path.fileSystemRepresentation);
253
+ if (zip == NULL)
254
+ {
255
+ NSDictionary *userInfo = @{NSLocalizedDescriptionKey: @"failed to open zip file"};
256
+ NSError *err = [NSError errorWithDomain:SSZipArchiveErrorDomain code:SSZipArchiveErrorCodeFailedOpenZipFile userInfo:userInfo];
257
+ if (error)
258
+ {
259
+ *error = err;
260
+ }
261
+ if (completionHandler)
262
+ {
263
+ completionHandler(nil, NO, err);
264
+ }
265
+ return NO;
266
+ }
267
+
268
+ NSDictionary * fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:path error:nil];
269
+ unsigned long long fileSize = [[fileAttributes objectForKey:NSFileSize] unsignedLongLongValue];
270
+ unsigned long long currentPosition = 0;
271
+
272
+ unz_global_info globalInfo = {};
273
+ unzGetGlobalInfo(zip, &globalInfo);
274
+
275
+ // Begin unzipping
276
+ int ret = 0;
277
+ ret = unzGoToFirstFile(zip);
278
+ if (ret != UNZ_OK && ret != UNZ_END_OF_LIST_OF_FILE)
279
+ {
280
+ NSDictionary *userInfo = @{NSLocalizedDescriptionKey: @"failed to open first file in zip file"};
281
+ NSError *err = [NSError errorWithDomain:SSZipArchiveErrorDomain code:SSZipArchiveErrorCodeFailedOpenFileInZip userInfo:userInfo];
282
+ if (error)
283
+ {
284
+ *error = err;
285
+ }
286
+ if (completionHandler)
287
+ {
288
+ completionHandler(nil, NO, err);
289
+ }
290
+ return NO;
291
+ }
292
+
293
+ BOOL success = YES;
294
+ BOOL canceled = NO;
295
+ int crc_ret = 0;
296
+ unsigned char buffer[4096] = {0};
297
+ NSFileManager *fileManager = [NSFileManager defaultManager];
298
+ NSMutableArray<NSDictionary *> *directoriesModificationDates = [[NSMutableArray alloc] init];
299
+
300
+ // Message delegate
301
+ if ([delegate respondsToSelector:@selector(zipArchiveWillUnzipArchiveAtPath:zipInfo:)]) {
302
+ [delegate zipArchiveWillUnzipArchiveAtPath:path zipInfo:globalInfo];
303
+ }
304
+ if ([delegate respondsToSelector:@selector(zipArchiveProgressEvent:total:)]) {
305
+ [delegate zipArchiveProgressEvent:currentPosition total:fileSize];
306
+ }
307
+
308
+ NSInteger currentFileNumber = -1;
309
+ NSError *unzippingError;
310
+ do {
311
+ currentFileNumber++;
312
+ if (ret == UNZ_END_OF_LIST_OF_FILE) {
313
+ break;
314
+ }
315
+ @autoreleasepool {
316
+ if (password.length == 0) {
317
+ ret = unzOpenCurrentFile(zip);
318
+ } else {
319
+ ret = unzOpenCurrentFilePassword(zip, [password cStringUsingEncoding:NSUTF8StringEncoding]);
320
+ }
321
+
322
+ if (ret != UNZ_OK) {
323
+ unzippingError = [NSError errorWithDomain:@"SSZipArchiveErrorDomain" code:SSZipArchiveErrorCodeFailedOpenFileInZip userInfo:@{NSLocalizedDescriptionKey: @"failed to open file in zip file"}];
324
+ success = NO;
325
+ break;
326
+ }
327
+
328
+ // Reading data and write to file
329
+ unz_file_info fileInfo;
330
+ memset(&fileInfo, 0, sizeof(unz_file_info));
331
+
332
+ ret = unzGetCurrentFileInfo(zip, &fileInfo, NULL, 0, NULL, 0, NULL, 0);
333
+ if (ret != UNZ_OK) {
334
+ unzippingError = [NSError errorWithDomain:@"SSZipArchiveErrorDomain" code:SSZipArchiveErrorCodeFileInfoNotLoadable userInfo:@{NSLocalizedDescriptionKey: @"failed to retrieve info for file"}];
335
+ success = NO;
336
+ unzCloseCurrentFile(zip);
337
+ break;
338
+ }
339
+
340
+ currentPosition += fileInfo.compressed_size;
341
+
342
+ // Message delegate
343
+ if ([delegate respondsToSelector:@selector(zipArchiveShouldUnzipFileAtIndex:totalFiles:archivePath:fileInfo:)]) {
344
+ if (![delegate zipArchiveShouldUnzipFileAtIndex:currentFileNumber
345
+ totalFiles:(NSInteger)globalInfo.number_entry
346
+ archivePath:path
347
+ fileInfo:fileInfo]) {
348
+ success = NO;
349
+ canceled = YES;
350
+ break;
351
+ }
352
+ }
353
+ if ([delegate respondsToSelector:@selector(zipArchiveWillUnzipFileAtIndex:totalFiles:archivePath:fileInfo:)]) {
354
+ [delegate zipArchiveWillUnzipFileAtIndex:currentFileNumber totalFiles:(NSInteger)globalInfo.number_entry
355
+ archivePath:path fileInfo:fileInfo];
356
+ }
357
+ if ([delegate respondsToSelector:@selector(zipArchiveProgressEvent:total:)]) {
358
+ [delegate zipArchiveProgressEvent:(NSInteger)currentPosition total:(NSInteger)fileSize];
359
+ }
360
+
361
+ char *filename = (char *)malloc(fileInfo.size_filename + 1);
362
+ if (filename == NULL)
363
+ {
364
+ success = NO;
365
+ break;
366
+ }
367
+
368
+ unzGetCurrentFileInfo(zip, &fileInfo, filename, fileInfo.size_filename + 1, NULL, 0, NULL, 0);
369
+ filename[fileInfo.size_filename] = '\0';
370
+
371
+ BOOL fileIsSymbolicLink = _fileIsSymbolicLink(&fileInfo);
372
+
373
+ NSString * strPath = [SSZipArchive _filenameStringWithCString:filename
374
+ version_made_by:fileInfo.version
375
+ general_purpose_flag:fileInfo.flag
376
+ size:fileInfo.size_filename];
377
+ if ([strPath hasPrefix:@"__MACOSX/"]) {
378
+ // ignoring resource forks: https://superuser.com/questions/104500/what-is-macosx-folder
379
+ unzCloseCurrentFile(zip);
380
+ ret = unzGoToNextFile(zip);
381
+ free(filename);
382
+ continue;
383
+ }
384
+
385
+ // Check if it contains directory
386
+ BOOL isDirectory = NO;
387
+ if (filename[fileInfo.size_filename-1] == '/' || filename[fileInfo.size_filename-1] == '\\') {
388
+ isDirectory = YES;
389
+ }
390
+ free(filename);
391
+
392
+ // Sanitize paths in the file name.
393
+ strPath = [strPath _sanitizedPath];
394
+ if (!strPath.length) {
395
+ // if filename data is unsalvageable, we default to currentFileNumber
396
+ strPath = @(currentFileNumber).stringValue;
397
+ }
398
+
399
+ NSString *fullPath = [destination stringByAppendingPathComponent:strPath];
400
+ NSError *err = nil;
401
+ NSDictionary *directoryAttr;
402
+ if (preserveAttributes) {
403
+ NSDate *modDate = [[self class] _dateWithMSDOSFormat:(UInt32)fileInfo.dos_date];
404
+ directoryAttr = @{NSFileCreationDate: modDate, NSFileModificationDate: modDate};
405
+ [directoriesModificationDates addObject: @{@"path": fullPath, @"modDate": modDate}];
406
+ }
407
+ if (isDirectory) {
408
+ [fileManager createDirectoryAtPath:fullPath withIntermediateDirectories:YES attributes:directoryAttr error:&err];
409
+ } else {
410
+ [fileManager createDirectoryAtPath:fullPath.stringByDeletingLastPathComponent withIntermediateDirectories:YES attributes:directoryAttr error:&err];
411
+ }
412
+ if (err != nil) {
413
+ if ([err.domain isEqualToString:NSCocoaErrorDomain] &&
414
+ err.code == 640) {
415
+ unzippingError = err;
416
+ unzCloseCurrentFile(zip);
417
+ success = NO;
418
+ break;
419
+ }
420
+ NSLog(@"[SSZipArchive] Error: %@", err.localizedDescription);
421
+ }
422
+
423
+ if ([fileManager fileExistsAtPath:fullPath] && !isDirectory && !overwrite) {
424
+ //FIXME: couldBe CRC Check?
425
+ unzCloseCurrentFile(zip);
426
+ ret = unzGoToNextFile(zip);
427
+ continue;
428
+ }
429
+
430
+ if (isDirectory && !fileIsSymbolicLink) {
431
+ // nothing to read/write for a directory
432
+ } else if (!fileIsSymbolicLink) {
433
+ // ensure we are not creating stale file entries
434
+ int readBytes = unzReadCurrentFile(zip, buffer, 4096);
435
+ if (readBytes >= 0) {
436
+ FILE *fp = fopen(fullPath.fileSystemRepresentation, "wb");
437
+ while (fp) {
438
+ if (readBytes > 0) {
439
+ if (0 == fwrite(buffer, readBytes, 1, fp)) {
440
+ if (ferror(fp)) {
441
+ NSString *message = [NSString stringWithFormat:@"Failed to write file (check your free space)"];
442
+ NSLog(@"[SSZipArchive] %@", message);
443
+ success = NO;
444
+ unzippingError = [NSError errorWithDomain:@"SSZipArchiveErrorDomain" code:SSZipArchiveErrorCodeFailedToWriteFile userInfo:@{NSLocalizedDescriptionKey: message}];
445
+ break;
446
+ }
447
+ }
448
+ } else {
449
+ break;
450
+ }
451
+ readBytes = unzReadCurrentFile(zip, buffer, 4096);
452
+ if (readBytes < 0) {
453
+ // Let's assume error Z_DATA_ERROR is caused by an invalid password
454
+ // Let's assume other errors are caused by Content Not Readable
455
+ success = NO;
456
+ }
457
+ }
458
+
459
+ if (fp) {
460
+ fclose(fp);
461
+
462
+ if (nestedZipLevel
463
+ && [fullPath.pathExtension.lowercaseString isEqualToString:@"zip"]
464
+ && [self unzipFileAtPath:fullPath
465
+ toDestination:fullPath.stringByDeletingLastPathComponent
466
+ preserveAttributes:preserveAttributes
467
+ overwrite:overwrite
468
+ nestedZipLevel:nestedZipLevel - 1
469
+ password:password
470
+ error:nil
471
+ delegate:nil
472
+ progressHandler:nil
473
+ completionHandler:nil]) {
474
+ [directoriesModificationDates removeLastObject];
475
+ [[NSFileManager defaultManager] removeItemAtPath:fullPath error:nil];
476
+ } else if (preserveAttributes) {
477
+
478
+ // Set the original datetime property
479
+ if (fileInfo.dos_date != 0) {
480
+ NSDate *orgDate = [[self class] _dateWithMSDOSFormat:(UInt32)fileInfo.dos_date];
481
+ NSDictionary *attr = @{NSFileModificationDate: orgDate};
482
+
483
+ if (attr) {
484
+ if (![fileManager setAttributes:attr ofItemAtPath:fullPath error:nil]) {
485
+ // Can't set attributes
486
+ NSLog(@"[SSZipArchive] Failed to set attributes - whilst setting modification date");
487
+ }
488
+ }
489
+ }
490
+
491
+ // Set the original permissions on the file (+read/write to solve #293)
492
+ uLong permissions = fileInfo.external_fa >> 16 | 0b110000000;
493
+ if (permissions != 0) {
494
+ // Store it into a NSNumber
495
+ NSNumber *permissionsValue = @(permissions);
496
+
497
+ // Retrieve any existing attributes
498
+ NSMutableDictionary *attrs = [[NSMutableDictionary alloc] initWithDictionary:[fileManager attributesOfItemAtPath:fullPath error:nil]];
499
+
500
+ // Set the value in the attributes dict
501
+ [attrs setObject:permissionsValue forKey:NSFilePosixPermissions];
502
+
503
+ // Update attributes
504
+ if (![fileManager setAttributes:attrs ofItemAtPath:fullPath error:nil]) {
505
+ // Unable to set the permissions attribute
506
+ NSLog(@"[SSZipArchive] Failed to set attributes - whilst setting permissions");
507
+ }
508
+ }
509
+ }
510
+ }
511
+ else
512
+ {
513
+ // if we couldn't open file descriptor we can validate global errno to see the reason
514
+ if (errno == ENOSPC) {
515
+ NSError *enospcError = [NSError errorWithDomain:NSPOSIXErrorDomain
516
+ code:ENOSPC
517
+ userInfo:nil];
518
+ unzippingError = enospcError;
519
+ unzCloseCurrentFile(zip);
520
+ success = NO;
521
+ break;
522
+ }
523
+ }
524
+ } else {
525
+ // Let's assume error Z_DATA_ERROR is caused by an invalid password
526
+ // Let's assume other errors are caused by Content Not Readable
527
+ success = NO;
528
+ break;
529
+ }
530
+ }
531
+ else
532
+ {
533
+ // Assemble the path for the symbolic link
534
+ NSMutableString *destinationPath = [NSMutableString string];
535
+ int bytesRead = 0;
536
+ while ((bytesRead = unzReadCurrentFile(zip, buffer, 4096)) > 0)
537
+ {
538
+ buffer[bytesRead] = 0;
539
+ [destinationPath appendString:@((const char *)buffer)];
540
+ }
541
+ if (bytesRead < 0) {
542
+ // Let's assume error Z_DATA_ERROR is caused by an invalid password
543
+ // Let's assume other errors are caused by Content Not Readable
544
+ success = NO;
545
+ break;
546
+ }
547
+
548
+ // Check if the symlink exists and delete it if we're overwriting
549
+ if (overwrite)
550
+ {
551
+ if ([fileManager fileExistsAtPath:fullPath])
552
+ {
553
+ NSError *error = nil;
554
+ BOOL removeSuccess = [fileManager removeItemAtPath:fullPath error:&error];
555
+ if (!removeSuccess)
556
+ {
557
+ NSString *message = [NSString stringWithFormat:@"Failed to delete existing symbolic link at \"%@\"", error.localizedDescription];
558
+ NSLog(@"[SSZipArchive] %@", message);
559
+ success = NO;
560
+ unzippingError = [NSError errorWithDomain:SSZipArchiveErrorDomain code:error.code userInfo:@{NSLocalizedDescriptionKey: message}];
561
+ }
562
+ }
563
+ }
564
+
565
+ // Create the symbolic link (making sure it stays relative if it was relative before)
566
+ int symlinkError = symlink([destinationPath cStringUsingEncoding:NSUTF8StringEncoding],
567
+ [fullPath cStringUsingEncoding:NSUTF8StringEncoding]);
568
+
569
+ if (symlinkError != 0)
570
+ {
571
+ // Bubble the error up to the completion handler
572
+ NSString *message = [NSString stringWithFormat:@"Failed to create symbolic link at \"%@\" to \"%@\" - symlink() error code: %d", fullPath, destinationPath, errno];
573
+ NSLog(@"[SSZipArchive] %@", message);
574
+ success = NO;
575
+ unzippingError = [NSError errorWithDomain:NSPOSIXErrorDomain code:symlinkError userInfo:@{NSLocalizedDescriptionKey: message}];
576
+ }
577
+ }
578
+
579
+ crc_ret = unzCloseCurrentFile(zip);
580
+ if (crc_ret == UNZ_CRCERROR) {
581
+ // CRC ERROR
582
+ success = NO;
583
+ break;
584
+ }
585
+ ret = unzGoToNextFile(zip);
586
+
587
+ // Message delegate
588
+ if ([delegate respondsToSelector:@selector(zipArchiveDidUnzipFileAtIndex:totalFiles:archivePath:fileInfo:)]) {
589
+ [delegate zipArchiveDidUnzipFileAtIndex:currentFileNumber totalFiles:(NSInteger)globalInfo.number_entry
590
+ archivePath:path fileInfo:fileInfo];
591
+ } else if ([delegate respondsToSelector: @selector(zipArchiveDidUnzipFileAtIndex:totalFiles:archivePath:unzippedFilePath:)]) {
592
+ [delegate zipArchiveDidUnzipFileAtIndex: currentFileNumber totalFiles: (NSInteger)globalInfo.number_entry
593
+ archivePath:path unzippedFilePath: fullPath];
594
+ }
595
+
596
+ if (progressHandler)
597
+ {
598
+ progressHandler(strPath, fileInfo, currentFileNumber, globalInfo.number_entry);
599
+ }
600
+ }
601
+ } while (ret == UNZ_OK && success);
602
+
603
+ // Close
604
+ unzClose(zip);
605
+
606
+ // The process of decompressing the .zip archive causes the modification times on the folders
607
+ // to be set to the present time. So, when we are done, they need to be explicitly set.
608
+ // set the modification date on all of the directories.
609
+ if (success && preserveAttributes) {
610
+ NSError * err = nil;
611
+ for (NSDictionary * d in directoriesModificationDates) {
612
+ if (![[NSFileManager defaultManager] setAttributes:@{NSFileModificationDate: [d objectForKey:@"modDate"]} ofItemAtPath:[d objectForKey:@"path"] error:&err]) {
613
+ NSLog(@"[SSZipArchive] Set attributes failed for directory: %@.", [d objectForKey:@"path"]);
614
+ }
615
+ if (err) {
616
+ NSLog(@"[SSZipArchive] Error setting directory file modification date attribute: %@", err.localizedDescription);
617
+ }
618
+ }
619
+ }
620
+
621
+ // Message delegate
622
+ if (success && [delegate respondsToSelector:@selector(zipArchiveDidUnzipArchiveAtPath:zipInfo:unzippedPath:)]) {
623
+ [delegate zipArchiveDidUnzipArchiveAtPath:path zipInfo:globalInfo unzippedPath:destination];
624
+ }
625
+ // final progress event = 100%
626
+ if (!canceled && [delegate respondsToSelector:@selector(zipArchiveProgressEvent:total:)]) {
627
+ [delegate zipArchiveProgressEvent:fileSize total:fileSize];
628
+ }
629
+
630
+ NSError *retErr = nil;
631
+ if (crc_ret == UNZ_CRCERROR)
632
+ {
633
+ NSDictionary *userInfo = @{NSLocalizedDescriptionKey: @"crc check failed for file"};
634
+ retErr = [NSError errorWithDomain:SSZipArchiveErrorDomain code:SSZipArchiveErrorCodeFileInfoNotLoadable userInfo:userInfo];
635
+ }
636
+
637
+ if (error) {
638
+ if (unzippingError) {
639
+ *error = unzippingError;
640
+ }
641
+ else {
642
+ *error = retErr;
643
+ }
644
+ }
645
+ if (completionHandler)
646
+ {
647
+ if (unzippingError) {
648
+ completionHandler(path, success, unzippingError);
649
+ }
650
+ else
651
+ {
652
+ completionHandler(path, success, retErr);
653
+ }
654
+ }
655
+ return success;
656
+ }
657
+
658
+ #pragma mark - Zipping
659
+ + (BOOL)createZipFileAtPath:(NSString *)path withFilesAtPaths:(NSArray<NSString *> *)paths
660
+ {
661
+ return [SSZipArchive createZipFileAtPath:path withFilesAtPaths:paths withPassword:nil];
662
+ }
663
+ + (BOOL)createZipFileAtPath:(NSString *)path withContentsOfDirectory:(NSString *)directoryPath {
664
+ return [SSZipArchive createZipFileAtPath:path withContentsOfDirectory:directoryPath withPassword:nil];
665
+ }
666
+
667
+ + (BOOL)createZipFileAtPath:(NSString *)path withContentsOfDirectory:(NSString *)directoryPath keepParentDirectory:(BOOL)keepParentDirectory {
668
+ return [SSZipArchive createZipFileAtPath:path withContentsOfDirectory:directoryPath keepParentDirectory:keepParentDirectory withPassword:nil];
669
+ }
670
+
671
+ + (BOOL)createZipFileAtPath:(NSString *)path withFilesAtPaths:(NSArray<NSString *> *)paths withPassword:(NSString *)password
672
+ {
673
+ SSZipArchive *zipArchive = [[SSZipArchive alloc] initWithPath:path];
674
+ BOOL success = [zipArchive open];
675
+ if (success) {
676
+ for (NSString *filePath in paths) {
677
+ success &= [zipArchive writeFile:filePath withPassword:password];
678
+ }
679
+ success &= [zipArchive close];
680
+ }
681
+ return success;
682
+ }
683
+
684
+ + (BOOL)createZipFileAtPath:(NSString *)path withContentsOfDirectory:(NSString *)directoryPath withPassword:(nullable NSString *)password {
685
+ return [SSZipArchive createZipFileAtPath:path withContentsOfDirectory:directoryPath keepParentDirectory:NO withPassword:password];
686
+ }
687
+
688
+
689
+ + (BOOL)createZipFileAtPath:(NSString *)path withContentsOfDirectory:(NSString *)directoryPath keepParentDirectory:(BOOL)keepParentDirectory withPassword:(nullable NSString *)password {
690
+ return [SSZipArchive createZipFileAtPath:path
691
+ withContentsOfDirectory:directoryPath
692
+ keepParentDirectory:keepParentDirectory
693
+ withPassword:password
694
+ andProgressHandler:nil
695
+ ];
696
+ }
697
+
698
+ + (BOOL)createZipFileAtPath:(NSString *)path
699
+ withContentsOfDirectory:(NSString *)directoryPath
700
+ keepParentDirectory:(BOOL)keepParentDirectory
701
+ withPassword:(nullable NSString *)password
702
+ andProgressHandler:(void(^ _Nullable)(NSUInteger entryNumber, NSUInteger total))progressHandler {
703
+ return [self createZipFileAtPath:path withContentsOfDirectory:directoryPath keepParentDirectory:keepParentDirectory compressionLevel:Z_DEFAULT_COMPRESSION password:password AES:YES progressHandler:progressHandler];
704
+ }
705
+
706
+ + (BOOL)createZipFileAtPath:(NSString *)path
707
+ withContentsOfDirectory:(NSString *)directoryPath
708
+ keepParentDirectory:(BOOL)keepParentDirectory
709
+ compressionLevel:(int)compressionLevel
710
+ password:(nullable NSString *)password
711
+ AES:(BOOL)aes
712
+ progressHandler:(void(^ _Nullable)(NSUInteger entryNumber, NSUInteger total))progressHandler {
713
+
714
+ SSZipArchive *zipArchive = [[SSZipArchive alloc] initWithPath:path];
715
+ BOOL success = [zipArchive open];
716
+ if (success) {
717
+ // use a local fileManager (queue/thread compatibility)
718
+ NSFileManager *fileManager = [[NSFileManager alloc] init];
719
+ NSDirectoryEnumerator *dirEnumerator = [fileManager enumeratorAtPath:directoryPath];
720
+ NSArray<NSString *> *allObjects = dirEnumerator.allObjects;
721
+ NSUInteger total = allObjects.count, complete = 0;
722
+ if (keepParentDirectory && !total) {
723
+ allObjects = @[@""];
724
+ total = 1;
725
+ }
726
+ for (__strong NSString *fileName in allObjects) {
727
+ NSString *fullFilePath = [directoryPath stringByAppendingPathComponent:fileName];
728
+
729
+ if (keepParentDirectory) {
730
+ fileName = [directoryPath.lastPathComponent stringByAppendingPathComponent:fileName];
731
+ }
732
+
733
+ BOOL isDir;
734
+ [fileManager fileExistsAtPath:fullFilePath isDirectory:&isDir];
735
+ if (!isDir) {
736
+ // file
737
+ success &= [zipArchive writeFileAtPath:fullFilePath withFileName:fileName compressionLevel:compressionLevel password:password AES:aes];
738
+ } else {
739
+ // directory
740
+ if (![fileManager enumeratorAtPath:fullFilePath].nextObject) {
741
+ // empty directory
742
+ success &= [zipArchive writeFolderAtPath:fullFilePath withFolderName:fileName withPassword:password];
743
+ }
744
+ }
745
+ if (progressHandler) {
746
+ complete++;
747
+ progressHandler(complete, total);
748
+ }
749
+ }
750
+ success &= [zipArchive close];
751
+ }
752
+ return success;
753
+ }
754
+
755
+ // disabling `init` because designated initializer is `initWithPath:`
756
+ - (instancetype)init { @throw nil; }
757
+
758
+ // designated initializer
759
+ - (instancetype)initWithPath:(NSString *)path
760
+ {
761
+ if ((self = [super init])) {
762
+ _path = [path copy];
763
+ }
764
+ return self;
765
+ }
766
+
767
+
768
+ - (BOOL)open
769
+ {
770
+ NSAssert((_zip == NULL), @"Attempting to open an archive which is already open");
771
+ _zip = zipOpen(_path.fileSystemRepresentation, APPEND_STATUS_CREATE);
772
+ return (NULL != _zip);
773
+ }
774
+
775
+ - (BOOL)writeFolderAtPath:(NSString *)path withFolderName:(NSString *)folderName withPassword:(nullable NSString *)password
776
+ {
777
+ NSAssert((_zip != NULL), @"Attempting to write to an archive which was never opened");
778
+
779
+ zip_fileinfo zipInfo = {};
780
+
781
+ [SSZipArchive zipInfo:&zipInfo setAttributesOfItemAtPath:path];
782
+
783
+ int error = _zipOpenEntry(_zip, [folderName stringByAppendingString:@"/"], &zipInfo, Z_NO_COMPRESSION, password, 0);
784
+ const void *buffer = NULL;
785
+ zipWriteInFileInZip(_zip, buffer, 0);
786
+ zipCloseFileInZip(_zip);
787
+ return error == ZIP_OK;
788
+ }
789
+
790
+ - (BOOL)writeFile:(NSString *)path withPassword:(nullable NSString *)password
791
+ {
792
+ return [self writeFileAtPath:path withFileName:nil withPassword:password];
793
+ }
794
+
795
+ - (BOOL)writeFileAtPath:(NSString *)path withFileName:(nullable NSString *)fileName withPassword:(nullable NSString *)password
796
+ {
797
+ return [self writeFileAtPath:path withFileName:fileName compressionLevel:Z_DEFAULT_COMPRESSION password:password AES:YES];
798
+ }
799
+
800
+ // supports writing files with logical folder/directory structure
801
+ // *path* is the absolute path of the file that will be compressed
802
+ // *fileName* is the relative name of the file how it is stored within the zip e.g. /folder/subfolder/text1.txt
803
+ - (BOOL)writeFileAtPath:(NSString *)path withFileName:(nullable NSString *)fileName compressionLevel:(int)compressionLevel password:(nullable NSString *)password AES:(BOOL)aes
804
+ {
805
+ NSAssert((_zip != NULL), @"Attempting to write to an archive which was never opened");
806
+
807
+ FILE *input = fopen(path.fileSystemRepresentation, "r");
808
+ if (NULL == input) {
809
+ return NO;
810
+ }
811
+
812
+ if (!fileName) {
813
+ fileName = path.lastPathComponent;
814
+ }
815
+
816
+ zip_fileinfo zipInfo = {};
817
+
818
+ [SSZipArchive zipInfo:&zipInfo setAttributesOfItemAtPath:path];
819
+
820
+ void *buffer = malloc(CHUNK);
821
+ if (buffer == NULL)
822
+ {
823
+ fclose(input);
824
+ return NO;
825
+ }
826
+
827
+ int error = _zipOpenEntry(_zip, fileName, &zipInfo, compressionLevel, password, aes);
828
+
829
+ while (!feof(input) && !ferror(input))
830
+ {
831
+ unsigned int len = (unsigned int) fread(buffer, 1, CHUNK, input);
832
+ zipWriteInFileInZip(_zip, buffer, len);
833
+ }
834
+
835
+ zipCloseFileInZip(_zip);
836
+ free(buffer);
837
+ fclose(input);
838
+ return error == ZIP_OK;
839
+ }
840
+
841
+ - (BOOL)writeData:(NSData *)data filename:(nullable NSString *)filename withPassword:(nullable NSString *)password
842
+ {
843
+ return [self writeData:data filename:filename compressionLevel:Z_DEFAULT_COMPRESSION password:password AES:YES];
844
+ }
845
+
846
+ - (BOOL)writeData:(NSData *)data filename:(nullable NSString *)filename compressionLevel:(int)compressionLevel password:(nullable NSString *)password AES:(BOOL)aes
847
+ {
848
+ if (!_zip) {
849
+ return NO;
850
+ }
851
+ if (!data) {
852
+ return NO;
853
+ }
854
+ zip_fileinfo zipInfo = {};
855
+ [SSZipArchive zipInfo:&zipInfo setDate:[NSDate date]];
856
+
857
+ int error = _zipOpenEntry(_zip, filename, &zipInfo, compressionLevel, password, aes);
858
+
859
+ zipWriteInFileInZip(_zip, data.bytes, (unsigned int)data.length);
860
+
861
+ zipCloseFileInZip(_zip);
862
+ return error == ZIP_OK;
863
+ }
864
+
865
+ - (BOOL)close
866
+ {
867
+ NSAssert((_zip != NULL), @"[SSZipArchive] Attempting to close an archive which was never opened");
868
+ int error = zipClose(_zip, NULL);
869
+ _zip = nil;
870
+ return error == ZIP_OK;
871
+ }
872
+
873
+ #pragma mark - Private
874
+
875
+ + (NSString *)_filenameStringWithCString:(const char *)filename
876
+ version_made_by:(uint16_t)version_made_by
877
+ general_purpose_flag:(uint16_t)flag
878
+ size:(uint16_t)size_filename {
879
+
880
+ // Respect Language encoding flag only reading filename as UTF-8 when this is set
881
+ // when file entry created on dos system.
882
+ //
883
+ // https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT
884
+ // Bit 11: Language encoding flag (EFS). If this bit is set,
885
+ // the filename and comment fields for this file
886
+ // MUST be encoded using UTF-8. (see APPENDIX D)
887
+ uint16_t made_by = version_made_by >> 8;
888
+ BOOL made_on_dos = made_by == 0;
889
+ BOOL languageEncoding = (flag & (1 << 11)) != 0;
890
+ if (!languageEncoding && made_on_dos) {
891
+ // APPNOTE.TXT D.1:
892
+ // D.2 If general purpose bit 11 is unset, the file name and comment should conform
893
+ // to the original ZIP character encoding. If general purpose bit 11 is set, the
894
+ // filename and comment must support The Unicode Standard, Version 4.1.0 or
895
+ // greater using the character encoding form defined by the UTF-8 storage
896
+ // specification. The Unicode Standard is published by the The Unicode
897
+ // Consortium (www.unicode.org). UTF-8 encoded data stored within ZIP files
898
+ // is expected to not include a byte order mark (BOM).
899
+
900
+ // Code Page 437 corresponds to kCFStringEncodingDOSLatinUS
901
+ NSStringEncoding encoding = CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingDOSLatinUS);
902
+ NSString* strPath = [NSString stringWithCString:filename encoding:encoding];
903
+ if (strPath) {
904
+ return strPath;
905
+ }
906
+ }
907
+
908
+ // attempting unicode encoding
909
+ NSString * strPath = @(filename);
910
+ if (strPath) {
911
+ return strPath;
912
+ }
913
+
914
+ // if filename is non-unicode, detect and transform Encoding
915
+ NSData *data = [NSData dataWithBytes:(const void *)filename length:sizeof(unsigned char) * size_filename];
916
+ // Testing availability of @available (https://stackoverflow.com/a/46927445/1033581)
917
+ #if __clang_major__ < 9
918
+ // Xcode 8-
919
+ if (floor(NSFoundationVersionNumber) > NSFoundationVersionNumber10_9_2) {
920
+ #else
921
+ // Xcode 9+
922
+ if (@available(macOS 10.10, iOS 8.0, watchOS 2.0, tvOS 9.0, *)) {
923
+ #endif
924
+ // supported encodings are in [NSString availableStringEncodings]
925
+ [NSString stringEncodingForData:data encodingOptions:nil convertedString:&strPath usedLossyConversion:nil];
926
+ } else {
927
+ // fallback to a simple manual detect for macOS 10.9 or older
928
+ NSArray<NSNumber *> *encodings = @[@(kCFStringEncodingGB_18030_2000), @(kCFStringEncodingShiftJIS)];
929
+ for (NSNumber *encoding in encodings) {
930
+ strPath = [NSString stringWithCString:filename encoding:(NSStringEncoding)CFStringConvertEncodingToNSStringEncoding(encoding.unsignedIntValue)];
931
+ if (strPath) {
932
+ break;
933
+ }
934
+ }
935
+ }
936
+ if (strPath) {
937
+ return strPath;
938
+ }
939
+
940
+ // if filename encoding is non-detected, we default to something based on data
941
+ // _hexString is more readable than _base64RFC4648 for debugging unknown encodings
942
+ strPath = [data _hexString];
943
+ return strPath;
944
+ }
945
+
946
+ + (void)zipInfo:(zip_fileinfo *)zipInfo setAttributesOfItemAtPath:(NSString *)path
947
+ {
948
+ NSDictionary *attr = [[NSFileManager defaultManager] attributesOfItemAtPath:path error: nil];
949
+ if (attr)
950
+ {
951
+ NSDate *fileDate = (NSDate *)[attr objectForKey:NSFileModificationDate];
952
+ if (fileDate)
953
+ {
954
+ [self zipInfo:zipInfo setDate:fileDate];
955
+ }
956
+
957
+ // Write permissions into the external attributes, for details on this see here: http://unix.stackexchange.com/a/14727
958
+ // Get the permissions value from the files attributes
959
+ NSNumber *permissionsValue = (NSNumber *)[attr objectForKey:NSFilePosixPermissions];
960
+ if (permissionsValue != nil) {
961
+ // Get the short value for the permissions
962
+ short permissionsShort = permissionsValue.shortValue;
963
+
964
+ // Convert this into an octal by adding 010000, 010000 being the flag for a regular file
965
+ NSInteger permissionsOctal = 0100000 + permissionsShort;
966
+
967
+ // Convert this into a long value
968
+ uLong permissionsLong = @(permissionsOctal).unsignedLongValue;
969
+
970
+ // Store this into the external file attributes once it has been shifted 16 places left to form part of the second from last byte
971
+
972
+ // Casted back to an unsigned int to match type of external_fa in minizip
973
+ zipInfo->external_fa = (unsigned int)(permissionsLong << 16L);
974
+ }
975
+ }
976
+ }
977
+
978
+ + (void)zipInfo:(zip_fileinfo *)zipInfo setDate:(NSDate *)date
979
+ {
980
+ NSCalendar *currentCalendar = SSZipArchive._gregorian;
981
+ NSCalendarUnit flags = NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay | NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond;
982
+ NSDateComponents *components = [currentCalendar components:flags fromDate:date];
983
+ struct tm tmz_date;
984
+ tmz_date.tm_sec = (unsigned int)components.second;
985
+ tmz_date.tm_min = (unsigned int)components.minute;
986
+ tmz_date.tm_hour = (unsigned int)components.hour;
987
+ tmz_date.tm_mday = (unsigned int)components.day;
988
+ // ISO/IEC 9899 struct tm is 0-indexed for January but NSDateComponents for gregorianCalendar is 1-indexed for January
989
+ tmz_date.tm_mon = (unsigned int)components.month - 1;
990
+ // ISO/IEC 9899 struct tm is 0-indexed for AD 1900 but NSDateComponents for gregorianCalendar is 1-indexed for AD 1
991
+ tmz_date.tm_year = (unsigned int)components.year - 1900;
992
+ zipInfo->dos_date = tm_to_dosdate(&tmz_date);
993
+ }
994
+
995
+ + (NSCalendar *)_gregorian
996
+ {
997
+ static NSCalendar *gregorian;
998
+ static dispatch_once_t onceToken;
999
+ dispatch_once(&onceToken, ^{
1000
+ gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
1001
+ });
1002
+
1003
+ return gregorian;
1004
+ }
1005
+
1006
+ // Format from http://newsgroups.derkeiler.com/Archive/Comp/comp.os.msdos.programmer/2009-04/msg00060.html
1007
+ // Two consecutive words, or a longword, YYYYYYYMMMMDDDDD hhhhhmmmmmmsssss
1008
+ // YYYYYYY is years from 1980 = 0
1009
+ // sssss is (seconds/2).
1010
+ //
1011
+ // 3658 = 0011 0110 0101 1000 = 0011011 0010 11000 = 27 2 24 = 2007-02-24
1012
+ // 7423 = 0111 0100 0010 0011 - 01110 100001 00011 = 14 33 3 = 14:33:06
1013
+ + (NSDate *)_dateWithMSDOSFormat:(UInt32)msdosDateTime
1014
+ {
1015
+ // the whole `_dateWithMSDOSFormat:` method is equivalent but faster than this one line,
1016
+ // essentially because `mktime` is slow:
1017
+ //NSDate *date = [NSDate dateWithTimeIntervalSince1970:dosdate_to_time_t(msdosDateTime)];
1018
+ static const UInt32 kYearMask = 0xFE000000;
1019
+ static const UInt32 kMonthMask = 0x1E00000;
1020
+ static const UInt32 kDayMask = 0x1F0000;
1021
+ static const UInt32 kHourMask = 0xF800;
1022
+ static const UInt32 kMinuteMask = 0x7E0;
1023
+ static const UInt32 kSecondMask = 0x1F;
1024
+
1025
+ NSAssert(0xFFFFFFFF == (kYearMask | kMonthMask | kDayMask | kHourMask | kMinuteMask | kSecondMask), @"[SSZipArchive] MSDOS date masks don't add up");
1026
+
1027
+ NSDateComponents *components = [[NSDateComponents alloc] init];
1028
+ components.year = 1980 + ((msdosDateTime & kYearMask) >> 25);
1029
+ components.month = (msdosDateTime & kMonthMask) >> 21;
1030
+ components.day = (msdosDateTime & kDayMask) >> 16;
1031
+ components.hour = (msdosDateTime & kHourMask) >> 11;
1032
+ components.minute = (msdosDateTime & kMinuteMask) >> 5;
1033
+ components.second = (msdosDateTime & kSecondMask) * 2;
1034
+
1035
+ NSDate *date = [self._gregorian dateFromComponents:components];
1036
+ return date;
1037
+ }
1038
+
1039
+ @end
1040
+
1041
+ int _zipOpenEntry(zipFile entry, NSString *name, const zip_fileinfo *zipfi, int level, NSString *password, BOOL aes)
1042
+ {
1043
+ return zipOpenNewFileInZip5(entry, name.fileSystemRepresentation, zipfi, NULL, 0, NULL, 0, NULL, 0, 0, Z_DEFLATED, level, 0, -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY, password.UTF8String, aes, 0);
1044
+ }
1045
+
1046
+ #pragma mark - Private tools for file info
1047
+
1048
+ BOOL _fileIsSymbolicLink(const unz_file_info *fileInfo)
1049
+ {
1050
+ //
1051
+ // Determine whether this is a symbolic link:
1052
+ // - File is stored with 'version made by' value of UNIX (3),
1053
+ // as per http://www.pkware.com/documents/casestudies/APPNOTE.TXT
1054
+ // in the upper byte of the version field.
1055
+ // - BSD4.4 st_mode constants are stored in the high 16 bits of the
1056
+ // external file attributes (defacto standard, verified against libarchive)
1057
+ //
1058
+ // The original constants can be found here:
1059
+ // http://minnie.tuhs.org/cgi-bin/utree.pl?file=4.4BSD/usr/include/sys/stat.h
1060
+ //
1061
+ const uLong ZipUNIXVersion = 3;
1062
+ const uLong BSD_SFMT = 0170000;
1063
+ const uLong BSD_IFLNK = 0120000;
1064
+
1065
+ BOOL fileIsSymbolicLink = ((fileInfo->version >> 8) == ZipUNIXVersion) && BSD_IFLNK == (BSD_SFMT & (fileInfo->external_fa >> 16));
1066
+ return fileIsSymbolicLink;
1067
+ }
1068
+
1069
+ #pragma mark - Private tools for unreadable encodings
1070
+
1071
+ @implementation NSData (SSZipArchive)
1072
+
1073
+ // `base64EncodedStringWithOptions` uses a base64 alphabet with '+' and '/'.
1074
+ // we got those alternatives to make it compatible with filenames: https://en.wikipedia.org/wiki/Base64
1075
+ // * modified Base64 encoding for IMAP mailbox names (RFC 3501): uses '+' and ','
1076
+ // * modified Base64 for URL and filenames (RFC 4648): uses '-' and '_'
1077
+ - (NSString *)_base64RFC4648
1078
+ {
1079
+ NSString *strName = [self base64EncodedStringWithOptions:0];
1080
+ strName = [strName stringByReplacingOccurrencesOfString:@"+" withString:@"-"];
1081
+ strName = [strName stringByReplacingOccurrencesOfString:@"/" withString:@"_"];
1082
+ return strName;
1083
+ }
1084
+
1085
+ // initWithBytesNoCopy from NSProgrammer, Jan 25 '12: https://stackoverflow.com/a/9009321/1033581
1086
+ // hexChars from Peter, Aug 19 '14: https://stackoverflow.com/a/25378464/1033581
1087
+ // not implemented as too lengthy: a potential mapping improvement from Moose, Nov 3 '15: https://stackoverflow.com/a/33501154/1033581
1088
+ - (NSString *)_hexString
1089
+ {
1090
+ const char *hexChars = "0123456789ABCDEF";
1091
+ NSUInteger length = self.length;
1092
+ const unsigned char *bytes = self.bytes;
1093
+ char *chars = malloc(length * 2);
1094
+ if (chars == NULL) {
1095
+ // we directly raise an exception instead of using NSAssert to make sure assertion is not disabled as this is irrecoverable
1096
+ [NSException raise:@"NSInternalInconsistencyException" format:@"failed malloc" arguments:nil];
1097
+ return nil;
1098
+ }
1099
+ char *s = chars;
1100
+ NSUInteger i = length;
1101
+ while (i--) {
1102
+ *s++ = hexChars[*bytes >> 4];
1103
+ *s++ = hexChars[*bytes & 0xF];
1104
+ bytes++;
1105
+ }
1106
+ NSString *str = [[NSString alloc] initWithBytesNoCopy:chars
1107
+ length:length * 2
1108
+ encoding:NSASCIIStringEncoding
1109
+ freeWhenDone:YES];
1110
+ return str;
1111
+ }
1112
+
1113
+ @end
1114
+
1115
+ #pragma mark Private tools for security
1116
+
1117
+ @implementation NSString (SSZipArchive)
1118
+
1119
+ // One implementation alternative would be to use the algorithm found at mz_path_resolve from https://github.com/nmoinvaz/blob/dev/mz_os.c,
1120
+ // but making sure to work with unichar values and not ascii values to avoid breaking Unicode characters containing 2E ('.') or 2F ('/') in their decomposition
1121
+ /// Sanitize path traversal characters to prevent directory backtracking. Ignoring these characters mimicks the default behavior of the Unarchiving tool on macOS.
1122
+ - (NSString *)_sanitizedPath
1123
+ {
1124
+ // Change Windows paths to Unix paths: https://en.wikipedia.org/wiki/Path_(computing)
1125
+ // Possible improvement: only do this if the archive was created on a non-Unix system
1126
+ NSString *strPath = [self stringByReplacingOccurrencesOfString:@"\\" withString:@"/"];
1127
+
1128
+ // Percent-encode file path (where path is defined by https://tools.ietf.org/html/rfc8089)
1129
+ // The key part is to allow characters "." and "/" and disallow "%".
1130
+ // CharacterSet.urlPathAllowed seems to do the job
1131
+ // Testing availability of @available (https://stackoverflow.com/a/46927445/1033581)
1132
+ #if __clang_major__ < 9
1133
+ // Xcode 8-
1134
+ if (floor(NSFoundationVersionNumber) > NSFoundationVersionNumber10_8_4) {
1135
+ #else
1136
+ // Xcode 9+
1137
+ if (@available(macOS 10.9, iOS 7.0, watchOS 2.0, tvOS 9.0, *)) {
1138
+ #endif
1139
+ strPath = [strPath stringByAddingPercentEncodingWithAllowedCharacters:NSCharacterSet.URLPathAllowedCharacterSet];
1140
+ } else {
1141
+ strPath = [strPath stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
1142
+ }
1143
+
1144
+ // `NSString.stringByAddingPercentEncodingWithAllowedCharacters:` may theorically fail: https://stackoverflow.com/questions/33558933/
1145
+ // But because we auto-detect encoding using `NSString.stringEncodingForData:encodingOptions:convertedString:usedLossyConversion:`,
1146
+ // we likely already prevent UTF-16, UTF-32 and invalid Unicode in the form of unpaired surrogate chars: https://stackoverflow.com/questions/53043876/
1147
+ // To be on the safe side, we will still perform a guard check.
1148
+ if (strPath == nil) {
1149
+ return nil;
1150
+ }
1151
+
1152
+ // Add scheme "file:///" to support sanitation on names with a colon like "file:a/../../../usr/bin"
1153
+ strPath = [@"file:///" stringByAppendingString:strPath];
1154
+
1155
+ // Sanitize path traversal characters to prevent directory backtracking. Ignoring these characters mimicks the default behavior of the Unarchiving tool on macOS.
1156
+ // "../../../../../../../../../../../tmp/test.txt" -> "tmp/test.txt"
1157
+ // "a/b/../c.txt" -> "a/c.txt"
1158
+ strPath = [NSURL URLWithString:strPath].standardizedURL.absoluteString;
1159
+
1160
+ // Remove the "file:///" scheme
1161
+ strPath = [strPath substringFromIndex:8];
1162
+
1163
+ // Remove the percent-encoding
1164
+ // Testing availability of @available (https://stackoverflow.com/a/46927445/1033581)
1165
+ #if __clang_major__ < 9
1166
+ // Xcode 8-
1167
+ if (floor(NSFoundationVersionNumber) > NSFoundationVersionNumber10_8_4) {
1168
+ #else
1169
+ // Xcode 9+
1170
+ if (@available(macOS 10.9, iOS 7.0, watchOS 2.0, tvOS 9.0, *)) {
1171
+ #endif
1172
+ strPath = strPath.stringByRemovingPercentEncoding;
1173
+ } else {
1174
+ strPath = [strPath stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
1175
+ }
1176
+
1177
+ return strPath;
1178
+ }
1179
+
1180
+ @end