From f900d38036c49b6378d495ad973759af5104fce5 Mon Sep 17 00:00:00 2001 From: Julius Parishy Date: Wed, 9 May 2012 11:12:04 -0400 Subject: [PATCH] Symbolic Link support. Adds support for ZIP files that include files that are symbolic links. See the note in SSZipArchive.m for more information about that. Includes a test that will probably only pass if you have GitHub for Mac installed because that's the program I used as a test to symlink against. I created the symbolic link with 'ln -s /Applications/GitHub.app SymbolicLink/GitHub.app' so I'm not entirely sure how it'll behave for hard links, but I can't test that right now. --- SSZipArchive.m | 129 ++++++++++++------- Tests/SSZipArchive.xcodeproj/project.pbxproj | 4 - Tests/SSZipArchiveTests.m | 9 +- Tests/SymbolicLink.zip | Bin 1156 -> 1196 bytes Tests/ZipTest.zip | Bin 760 -> 0 bytes 5 files changed, 90 insertions(+), 52 deletions(-) delete mode 100644 Tests/ZipTest.zip diff --git a/SSZipArchive.m b/SSZipArchive.m index 8665ca1..a9f9a46 100644 --- a/SSZipArchive.m +++ b/SSZipArchive.m @@ -106,27 +106,35 @@ archivePath:path fileInfo:fileInfo]; } - struct ZipExtraField - { - short headerId; - short dataSize; - }; - - struct ZipExtraField extra = { 0 }; - char *filename = (char *)malloc(fileInfo.size_filename + 1); - unzGetCurrentFileInfo(zip, &fileInfo, filename, fileInfo.size_filename + 1, &extra, sizeof(struct ZipExtraField), NULL, 0); + unzGetCurrentFileInfo(zip, &fileInfo, filename, fileInfo.size_filename + 1, NULL, 0, NULL, 0); filename[fileInfo.size_filename] = '\0'; - - NSLog(@"Filename: %s", filename); - - //extra.data = (char *)malloc(fileInfo.size_file_extra); - //unzGetLocalExtrafield(zip, &extra, sizeof(struct ZipExtraField)); - NSLog(@"Header ID: %ul", fileInfo.size_file_extra); + // + // NOTE + // I used the ZIP spec from here: + // http://www.pkware.com/documents/casestudies/APPNOTE.TXT + // + // ...to deduce this method of detecting whether the file in the ZIP is a symbolic link. + // If it is, it is listed as a directory but has an uncompressed data size greater than + // zero (real directories have it equal to 0) and the included, uncompressed data is the + // symbolic link path. + // - //NSLog(@"Data: %s", extra.data); - + const uLong ZipDirectoryVersion = 10; + const uLong ZipCompressionMethodStore = 0; + + BOOL fileIsSymbolicLink = NO; + + if((fileInfo.version_needed == ZipDirectoryVersion) && // Is it a directory? + (fileInfo.compression_method == ZipCompressionMethodStore) && // Is it compressed? + (fileInfo.compressed_size > 0)) // Is there any data there? + { + fileIsSymbolicLink = YES; + } + + //NSLog(@"\"%s\" is symbolic link? %@", filename, fileIsSymbolicLink ? @"Yes." : @"No."); + // Check if it contains directory NSString *strPath = [NSString stringWithCString:filename encoding:NSUTF8StringEncoding]; BOOL isDirectory = NO; @@ -154,7 +162,8 @@ NSLog(@"[SSZipArchive] Error: %@", err.localizedDescription); } - [directoriesModificationDates addObject: [NSDictionary dictionaryWithObjectsAndKeys:fullPath, @"path", modDate, @"modDate", nil]]; + if(!fileIsSymbolicLink) + [directoriesModificationDates addObject: [NSDictionary dictionaryWithObjectsAndKeys:fullPath, @"path", modDate, @"modDate", nil]]; if ([fileManager fileExistsAtPath:fullPath] && !isDirectory && !overwrite) { unzCloseCurrentFile(zip); @@ -162,35 +171,63 @@ continue; } - NSLog(@"External file attributes: %lu", fileInfo.external_fa); - - FILE *fp = fopen((const char*)[fullPath UTF8String], "wb"); - while (fp) { - int readBytes = unzReadCurrentFile(zip, buffer, 4096); + if(!fileIsSymbolicLink) + { + FILE *fp = fopen((const char*)[fullPath UTF8String], "wb"); + while (fp) { + int readBytes = unzReadCurrentFile(zip, buffer, 4096); - if (readBytes > 0) { - fwrite(buffer, readBytes, 1, fp ); - } else { - break; - } - } - - if (fp) { - fclose(fp); - - // Set the original datetime property - if (fileInfo.dosDate != 0) { - NSDate *orgDate = [[self class] _dateWithMSDOSFormat:(UInt32)fileInfo.dosDate]; - NSDictionary *attr = [NSDictionary dictionaryWithObject:orgDate forKey:NSFileModificationDate]; - - if (attr) { - if ([fileManager setAttributes:attr ofItemAtPath:fullPath error:nil] == NO) { - // Can't set attributes - NSLog(@"[SSZipArchive] Failed to set attributes"); - } - } - } - } + if (readBytes > 0) { + fwrite(buffer, readBytes, 1, fp ); + } else { + break; + } + } + + if (fp) { + fclose(fp); + + // Set the original datetime property + if (fileInfo.dosDate != 0) { + NSDate *orgDate = [[self class] _dateWithMSDOSFormat:(UInt32)fileInfo.dosDate]; + NSDictionary *attr = [NSDictionary dictionaryWithObject:orgDate forKey:NSFileModificationDate]; + + if (attr) { + if ([fileManager setAttributes:attr ofItemAtPath:fullPath error:nil] == NO) { + // Can't set attributes + NSLog(@"[SSZipArchive] Failed to set attributes"); + } + } + } + } + } + else + { + // Get the path for the symbolic link + + NSURL* symlinkURL = [NSURL fileURLWithPath:fullPath]; + NSMutableString* destinationPath = [NSMutableString string]; + + int bytesRead = 0; + while((bytesRead = unzReadCurrentFile(zip, buffer, 4096)) > 0) + { + buffer[bytesRead] = 0; + [destinationPath appendString:[NSString stringWithUTF8String:(const char*)buffer]]; + } + + //NSLog(@"Symlinking to: %@", destinationPath); + + NSURL* destinationURL = [NSURL fileURLWithPath:destinationPath]; + + // Create the symbolic link + NSError* symlinkError = nil; + [fileManager createSymbolicLinkAtURL:symlinkURL withDestinationURL:destinationURL error:&symlinkError]; + + if(symlinkError != nil) + { + NSLog(@"Failed to create symbolic link at \"%@\" to \"%@\". Error: %@", symlinkURL.absoluteString, destinationURL.absoluteString, symlinkError.localizedDescription); + } + } unzCloseCurrentFile( zip ); ret = unzGoToNextFile( zip ); diff --git a/Tests/SSZipArchive.xcodeproj/project.pbxproj b/Tests/SSZipArchive.xcodeproj/project.pbxproj index b22e991..d8c946b 100644 --- a/Tests/SSZipArchive.xcodeproj/project.pbxproj +++ b/Tests/SSZipArchive.xcodeproj/project.pbxproj @@ -20,7 +20,6 @@ B23FCC7F1558F1B70026375C /* TestPasswordArchive.zip in Resources */ = {isa = PBXBuildFile; fileRef = B23FCC7E1558F1B70026375C /* TestPasswordArchive.zip */; }; C5AE4E64155A12760045F3ED /* IncorrectHeaders.zip in Resources */ = {isa = PBXBuildFile; fileRef = C5AE4E63155A12760045F3ED /* IncorrectHeaders.zip */; }; C5AE4E6D155A8B010045F3ED /* SymbolicLink.zip in Resources */ = {isa = PBXBuildFile; fileRef = C5AE4E6C155A8B010045F3ED /* SymbolicLink.zip */; }; - C5AE4E70155A96CF0045F3ED /* ZipTest.zip in Resources */ = {isa = PBXBuildFile; fileRef = C5AE4E6F155A96CF0045F3ED /* ZipTest.zip */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -45,7 +44,6 @@ B23FCC7E1558F1B70026375C /* TestPasswordArchive.zip */ = {isa = PBXFileReference; lastKnownFileType = archive.zip; path = TestPasswordArchive.zip; sourceTree = ""; }; C5AE4E63155A12760045F3ED /* IncorrectHeaders.zip */ = {isa = PBXFileReference; lastKnownFileType = archive.zip; path = IncorrectHeaders.zip; sourceTree = ""; }; C5AE4E6C155A8B010045F3ED /* SymbolicLink.zip */ = {isa = PBXFileReference; lastKnownFileType = archive.zip; path = SymbolicLink.zip; sourceTree = ""; }; - C5AE4E6F155A96CF0045F3ED /* ZipTest.zip */ = {isa = PBXFileReference; lastKnownFileType = archive.zip; path = ZipTest.zip; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -120,7 +118,6 @@ B215FB5E143AD505003AC546 /* SSZipArchiveTests */ = { isa = PBXGroup; children = ( - C5AE4E6F155A96CF0045F3ED /* ZipTest.zip */, B215FB61143AD514003AC546 /* SSZipArchiveTests.m */, B215FB5F143AD514003AC546 /* SSZipArchiveTests-Info.plist */, C5AE4E63155A12760045F3ED /* IncorrectHeaders.zip */, @@ -187,7 +184,6 @@ B23FCC7F1558F1B70026375C /* TestPasswordArchive.zip in Resources */, C5AE4E64155A12760045F3ED /* IncorrectHeaders.zip in Resources */, C5AE4E6D155A8B010045F3ED /* SymbolicLink.zip in Resources */, - C5AE4E70155A96CF0045F3ED /* ZipTest.zip in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Tests/SSZipArchiveTests.m b/Tests/SSZipArchiveTests.m index 6e8db86..8bf92e9 100644 --- a/Tests/SSZipArchiveTests.m +++ b/Tests/SSZipArchiveTests.m @@ -86,12 +86,17 @@ - (void)testUnzippingWithSymlinkedFileInside { - NSString* zipPath = [[NSBundle bundleForClass:[self class]] pathForResource:@"ZipTest" ofType:@"zip"]; + NSString* zipPath = [[NSBundle bundleForClass:[self class]] pathForResource:@"SymbolicLink" ofType:@"zip"]; NSString* outputPath = [self _cachesPath:@"SymbolicLink"]; [SSZipArchive unzipFileAtPath:zipPath toDestination:outputPath delegate:self]; - BOOL symbolicLinkPersists = NO; + NSString* testSymlink = [outputPath stringByAppendingPathComponent:@"SymbolicLink/GitHub.app"]; + + NSError* error = nil; + NSString* symlinkPath = [[NSFileManager defaultManager] destinationOfSymbolicLinkAtPath:testSymlink error:&error]; + + bool symbolicLinkPersists = ([symlinkPath isEqualToString:@"/Applications/GitHub.app"]) && (error == nil); STAssertTrue(symbolicLinkPersists, @"Symbolic links should persist from the original archive to the outputted files."); } diff --git a/Tests/SymbolicLink.zip b/Tests/SymbolicLink.zip index bcb96a73221187d7a6582cb840439fd45633f2ad..952827dda232c43374924979b32b02eae747654e 100644 GIT binary patch literal 1196 zcmWIWW@h1H0D<6$l@4GAl;C9$U166$JXyhm_vMsJbIy0E zL;1x!TY8SR7p5e)CiQ(Q`Ehkp>ketTMKSgN_9txkC+_qoS!eySs}j$f9yI-1^R?Z~ zdor6IV`Pnv&SVRJ_FdiATpc>1H?jwKvvV9_D!Td(7{n|R4B!xi1bz%!;Bx{)Fh1Vb z(b+#Z0vzxPz(5CKSiob~1`2q%HjKzaHv@a*DdIFkFCJ$+CMSp{rza#OeDL)N`@kR8 z5zxRiL4sLbK$>wQlR)ziM;-+}A67n)kJ&js9sQxG0Stj4AO@u&MDi<9xGV__76uR& z2VxW-xM!AllqTsV78K|^78C#zWnxKYeqOOYLOj5mkx7&pVI0r|IN)XAfw3LlI)a!e zhKELQz|%Iw7)0P8$CwzBF-sb)(Tzb09}ajrM|KuwJR_X-*Rht70TF#5C&6M2pApDT z`s=s_*$50TqMC>BB65@{qImHKPV-Pw19snniWKxXg~o!z+lBQ^Xpw*%ldNnYg=|20 M3h06}K&1=}0EnYdMgRZ+ delta 592 zcmZ3(*}`cO;LXg!#Q*{i%vL&p8Bl_gL4YADvmhk3xTIJ=G=hiWyU!|rX5Ur*Ung3~ zDhnaY>bV5R2bbg*rGk`!lz^3Otl?&4dSW)YlTqH28z>0EUl~Oh5LP}xvl3)ze7vut zvwv`eK8oFN{Xo0*WU(sKi^uMa$u3N;GS`7Nl_*@6lmL1TgoS|^X2Il@O!`a@%qABy zOG&UZ~!4F<~|+$p{N0LP!I!bqx_c$6mTB^DF}c(byBRImZzHlXP{fXWyc0O!Jw A*#H0l diff --git a/Tests/ZipTest.zip b/Tests/ZipTest.zip deleted file mode 100644 index 2d30e40c7207abc64bcb9f1947b5e2094f0b9145..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 760 zcmWIWW@h1H0D%`~D;>ZLD8b1f!w{8O5RzJ4Qmh{u!pXoa?7PZeJ#LkMX$3a}Bg
L! zuAG1E?58XKmLl78R%KmWmzA*jZf(5fDgh^s!u=5;hP&RHvVZ%Rh zr$5O$>z7@Xc;57&>ED{K?PlJS+4LAAYjkubTlll@>b~ac&=I|n9Takiz%Nm_EC~!W z1`rlT4mtPC5|7d(y~Kh7cr<{b0xjtD9SaI_GLsWaGV}9_^$~gkycwC~m=V?hO@{+c zuw@{w0K;2H5RDY$tdJN-vj$=uuJ{JI9Rij#Is+MSXf7!Bv6_o;CTeIP%zeAC f7MSqh<|6zV8iLEgtZX1BF#{n3Q0pTgW?%pS&syaM