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.
This commit is contained in:
Julius Parishy
2012-05-09 11:12:04 -04:00
parent bfc0904d14
commit f900d38036
5 changed files with 90 additions and 52 deletions
+83 -46
View File
@@ -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 );
@@ -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 = "<group>"; };
C5AE4E63155A12760045F3ED /* IncorrectHeaders.zip */ = {isa = PBXFileReference; lastKnownFileType = archive.zip; path = IncorrectHeaders.zip; sourceTree = "<group>"; };
C5AE4E6C155A8B010045F3ED /* SymbolicLink.zip */ = {isa = PBXFileReference; lastKnownFileType = archive.zip; path = SymbolicLink.zip; sourceTree = "<group>"; };
C5AE4E6F155A96CF0045F3ED /* ZipTest.zip */ = {isa = PBXFileReference; lastKnownFileType = archive.zip; path = ZipTest.zip; sourceTree = "<group>"; };
/* 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;
};
+7 -2
View File
@@ -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.");
}
Binary file not shown.
BIN
View File
Binary file not shown.