Skip to content

Commit c05ee25

Browse files
committed
Fixes for unzipping modification dates and add ARC support
* Add ARC support * Fixes: One of the bugs is that the file modification dates for files being unzipped were wrong. Apparently the code was taking the MS-DOS date and time and treating it as an NSTimeInterval since Jan 1, 1980. This is not correct and was resulting in modification dates for me in 2014. — Brant Sears * Fixes: The second bug was that the file modification dates for folders were showing up as the current date/time, but when I unzip the same archive using Apple's implementation of zip, the file modification dates for those folders were set sometime in the past. — Brant Sears
1 parent 2ad10f1 commit c05ee25

File tree

1 file changed

+73
-18
lines changed

1 file changed

+73
-18
lines changed

SSZipArchive.m

+73-18
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
#define CHUNK 16384
1616

1717
@interface SSZipArchive ()
18-
+ (NSDate *)_dateFor1980;
18+
+ (NSDate *)_dateWithMSDOSFormat:(UInt32)msdosDateTime;
1919
@end
2020

2121

@@ -60,7 +60,7 @@ + (BOOL)unzipFileAtPath:(NSString *)path toDestination:(NSString *)destination o
6060
int ret;
6161
unsigned char buffer[4096] = {0};
6262
NSFileManager *fileManager = [NSFileManager defaultManager];
63-
NSDate *nineteenEighty = [self _dateFor1980];
63+
NSMutableSet *directoriesModificationDates = [[NSMutableSet alloc] init];
6464

6565
do {
6666
if ([password length] == 0) {
@@ -102,15 +102,24 @@ + (BOOL)unzipFileAtPath:(NSString *)path toDestination:(NSString *)destination o
102102
strPath = [strPath stringByReplacingOccurrencesOfString:@"\\" withString:@"/"];
103103
}
104104

105-
NSString* fullPath = [destination stringByAppendingPathComponent:strPath];
105+
NSString *fullPath = [destination stringByAppendingPathComponent:strPath];
106+
NSError *err = nil;
107+
NSDate *modDate = [[self class] _dateWithMSDOSFormat:(UInt32)fileInfo.dosDate];
108+
NSDictionary *directoryAttr = [NSDictionary dictionaryWithObjectsAndKeys:modDate, NSFileCreationDate, modDate, NSFileModificationDate, nil];
106109

107110
if (isDirectory) {
108-
[fileManager createDirectoryAtPath:fullPath withIntermediateDirectories:YES attributes:nil error:nil];
111+
[fileManager createDirectoryAtPath:fullPath withIntermediateDirectories:YES attributes:directoryAttr error:&err];
109112
} else {
110-
[fileManager createDirectoryAtPath:[fullPath stringByDeletingLastPathComponent] withIntermediateDirectories:YES attributes:nil error:nil];
113+
[fileManager createDirectoryAtPath:[fullPath stringByDeletingLastPathComponent] withIntermediateDirectories:YES attributes:directoryAttr error:&err];
111114
}
115+
if (nil != err) {
116+
NSLog(@"[SSZipArchive] Error: %@", err.localizedDescription);
117+
}
112118

113-
if ([fileManager fileExistsAtPath:fullPath] && !isDirectory && !overwrite) {
119+
[directoriesModificationDates addObject: [NSDictionary dictionaryWithObjectsAndKeys:fullPath, @"path", modDate, @"modDate", nil]];
120+
121+
122+
if ([fileManager fileExistsAtPath:fullPath] && !isDirectory && !overwrite) {
114123
unzCloseCurrentFile(zip);
115124
ret = unzGoToNextFile(zip);
116125
continue;
@@ -132,16 +141,15 @@ + (BOOL)unzipFileAtPath:(NSString *)path toDestination:(NSString *)destination o
132141

133142
// Set the original datetime property
134143
if (fileInfo.dosDate != 0) {
135-
NSDate *orgDate = [[NSDate alloc] initWithTimeInterval:(NSTimeInterval)fileInfo.dosDate sinceDate:nineteenEighty];
144+
NSDate *orgDate = [[self class] _dateWithMSDOSFormat:(UInt32)fileInfo.dosDate];
136145
NSDictionary *attr = [NSDictionary dictionaryWithObject:orgDate forKey:NSFileModificationDate];
137146

138147
if (attr) {
139148
if ([fileManager setAttributes:attr ofItemAtPath:fullPath error:nil] == NO) {
140149
// Can't set attributes
141-
NSLog(@"Failed to set attributes");
150+
NSLog(@"[SSZipArchive] Failed to set attributes");
142151
}
143152
}
144-
[orgDate release];
145153
}
146154
}
147155

@@ -152,6 +160,23 @@ + (BOOL)unzipFileAtPath:(NSString *)path toDestination:(NSString *)destination o
152160
// Close
153161
unzClose(zip);
154162

163+
// The process of decompressing the .zip archive causes the modification times on the folders
164+
// to be set to the present time. So, when we are done, they need to be explicitly set.
165+
// set the modification date on all of the directories.
166+
NSError * err = nil;
167+
for (NSDictionary * d in directoriesModificationDates) {
168+
if (![[NSFileManager defaultManager] setAttributes:[NSDictionary dictionaryWithObjectsAndKeys:[d objectForKey:@"modDate"], NSFileModificationDate, nil] ofItemAtPath:[d objectForKey:@"path"] error:&err]) {
169+
NSLog(@"[SSZipArchive] Set attributes failed for directory: %@.", [d objectForKey:@"path"]);
170+
}
171+
if (err) {
172+
NSLog(@"[SSZipArchive] Error setting directory file modification date attribute: %@",err.localizedDescription);
173+
}
174+
}
175+
176+
#if !__has_feature(objc_arc)
177+
[directoriesModificationDates release];
178+
#endif
179+
155180
return success;
156181
}
157182

@@ -167,7 +192,10 @@ + (BOOL)createZipFileAtPath:(NSString *)path withFilesAtPaths:(NSArray *)paths {
167192
}
168193
success = [zipArchive close];
169194
}
195+
196+
#if !__has_feature(objc_arc)
170197
[zipArchive release];
198+
#endif
171199

172200
return success;
173201
}
@@ -181,10 +209,12 @@ - (id)initWithPath:(NSString *)path {
181209
}
182210

183211

212+
#if !__has_feature(objc_arc)
184213
- (void)dealloc {
185-
[_path release];
214+
[_path release];
186215
[super dealloc];
187216
}
217+
#endif
188218

189219

190220
- (BOOL)open {
@@ -249,25 +279,50 @@ - (BOOL)writeData:(NSData *)data filename:(NSString *)filename {
249279
return YES;
250280
}
251281

282+
252283
- (BOOL)close {
253-
NSAssert((_zip != NULL), @"Attempting to close an archive which was never opened");
284+
NSAssert((_zip != NULL), @"[SSZipArchive] Attempting to close an archive which was never opened");
254285
zipClose(_zip, NULL);
255286
return YES;
256287
}
257288

258289

259290
#pragma mark - Private
260291

261-
+ (NSDate *)_dateFor1980 {
262-
NSDateComponents *comps = [[NSDateComponents alloc] init];
263-
[comps setDay:1];
264-
[comps setMonth:1];
265-
[comps setYear:1980];
292+
// Format from http://newsgroups.derkeiler.com/Archive/Comp/comp.os.msdos.programmer/2009-04/msg00060.html
293+
// Two consecutive words, or a longword, YYYYYYYMMMMDDDDD hhhhhmmmmmmsssss
294+
// YYYYYYY is years from 1980 = 0
295+
// sssss is (seconds/2).
296+
//
297+
// 3658 = 0011 0110 0101 1000 = 0011011 0010 11000 = 27 2 24 = 2007-02-24
298+
// 7423 = 0111 0100 0010 0011 - 01110 100001 00011 = 14 33 2 = 14:33:06
299+
+ (NSDate *)_dateWithMSDOSFormat:(UInt32)msdosDateTime {
300+
static const UInt32 kYearMask = 0xFE000000;
301+
static const UInt32 kMonthMask = 0x1E00000;
302+
static const UInt32 kDayMask = 0x1F0000;
303+
static const UInt32 kHourMask = 0xF800;
304+
static const UInt32 kMinuteMask = 0x7E0;
305+
static const UInt32 kSecondMask = 0x1F;
306+
266307
NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
267-
NSDate *date = [gregorian dateFromComponents:comps];
308+
NSDateComponents *components = [[NSDateComponents alloc] init];
268309

269-
[comps release];
310+
NSAssert(0xFFFFFFFF == (kYearMask | kMonthMask | kDayMask | kHourMask | kMinuteMask | kSecondMask), @"[SSZipArchive] MSDOS date masks don't add up");
311+
312+
[components setYear:1980 + ((msdosDateTime & kYearMask) >> 25)];
313+
[components setMonth:(msdosDateTime & kMonthMask) >> 21];
314+
[components setDay:(msdosDateTime & kDayMask) >> 16];
315+
[components setHour:(msdosDateTime & kHourMask) >> 11];
316+
[components setMinute:(msdosDateTime & kMinuteMask) >> 5];
317+
[components setSecond:(msdosDateTime & kSecondMask) * 2];
318+
319+
NSDate *date = [NSDate dateWithTimeInterval:0 sinceDate:[gregorian dateFromComponents:components]];
320+
321+
#if !__has_feature(objc_arc)
270322
[gregorian release];
323+
[components release];
324+
#endif
325+
271326
return date;
272327
}
273328

0 commit comments

Comments
 (0)