Files
ZipArchive/Example/ObjectiveCExampleTests/SSZipArchiveTests.m
T
Cœur 2c7efe4382 withContentsOfDirectory: should return error when parameter isn't a directory (#718)
`withFilesAtPaths:` should not write a file when parameter is a directory
2024-09-22 21:29:09 +02:00

879 lines
41 KiB
Objective-C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//
// SSZipArchiveTests.m
// SSZipArchiveTests
//
// Created by Sam Soffes on 10/3/11.
// Copyright (c) 2011-2014 Sam Soffes. All rights reserved.
//
#if COCOAPODS
#import <SSZipArchive.h>
#else
#import <ZipArchive.h>
#endif
#import <XCTest/XCTest.h>
#import <CommonCrypto/CommonDigest.h>
#import "CollectingDelegate.h"
#import "CancelDelegate.h"
#import "ProgressDelegate.h"
@interface SSZipArchiveTests : XCTestCase
@end
@interface NSString (SSZipArchive)
- (NSString *)_sanitizedPath;
@end
@implementation SSZipArchiveTests
int twentyMB = 20 * 1024 * 1024;
- (void)setUp {
[super setUp];
}
- (void)tearDown {
[super tearDown];
[[NSFileManager defaultManager] removeItemAtPath:[self _cachesPath:nil] error:nil];
}
- (void)testZipping {
// use extracted files from [-testUnzipping]
[self testUnzipping];
NSString *inputPath = [self _cachesPath:@"Regular"];
NSArray *inputPaths = @[[inputPath stringByAppendingPathComponent:@"Readme.markdown"],
[inputPath stringByAppendingPathComponent:@"LICENSE"]];
NSString *outputPath = [self _cachesPath:@"Zipped"];
NSString *archivePath = [outputPath stringByAppendingPathComponent:@"CreatedArchive.zip"];
BOOL success = [SSZipArchive createZipFileAtPath:archivePath withFilesAtPaths:inputPaths];
XCTAssertTrue(success, @"create zip failure");
// TODO: Make sure the files are actually zipped. They are, but the test should be better.
XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:archivePath], @"Archive created");
BOOL protected = [SSZipArchive isFilePasswordProtectedAtPath:archivePath];
XCTAssertFalse(protected, @"has no password");
}
- (void)testZippingWithPassword {
// use extracted files from [-testUnzipping]
[self testUnzipping];
NSString *inputPath = [self _cachesPath:@"Regular"];
NSArray *inputPaths = @[[inputPath stringByAppendingPathComponent:@"Readme.markdown"],
[inputPath stringByAppendingPathComponent:@"LICENSE"]];
NSString *outputPath = [self _cachesPath:@"Zipped"];
NSString *archivePath = [outputPath stringByAppendingPathComponent:@"CreatedArchive.zip"];
BOOL success = [SSZipArchive createZipFileAtPath:archivePath withFilesAtPaths:inputPaths withPassword:@"dolphins🐋"];
XCTAssertTrue(success, @"create zip failure");
// TODO: Make sure the files are actually zipped. They are, but the test should be better.
XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:archivePath], @"Archive created");
BOOL protected = [SSZipArchive isFilePasswordProtectedAtPath:archivePath];
XCTAssertTrue(protected, @"has password");
}
- (void)testZippingWithZeroLengthPassword {
// use extracted files from [-testUnzipping]
[self testUnzipping];
NSString *inputPath = [self _cachesPath:@"Regular"];
NSArray *inputPaths = @[[inputPath stringByAppendingPathComponent:@"Readme.markdown"],
[inputPath stringByAppendingPathComponent:@"LICENSE"]];
NSString *outputPath = [self _cachesPath:@"Zipped"];
NSString *archivePath = [outputPath stringByAppendingPathComponent:@"CreatedArchive.zip"];
BOOL success = [SSZipArchive createZipFileAtPath:archivePath withFilesAtPaths:inputPaths withPassword:@""];
XCTAssertTrue(success, @"create zip failure");
// TODO: Make sure the files are actually zipped. They are, but the test should be better.
XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:archivePath], @"Archive created");
BOOL protected = [SSZipArchive isFilePasswordProtectedAtPath:archivePath];
XCTAssertTrue(protected, @"has password");
}
- (void)testDirectoryZipping {
// use Unicode as folder (has a file in root and a file in subfolder)
[self testUnzippingWithUnicodeFilenameInside];
NSString *inputPath = [self _cachesPath:@"Unicode"];
NSString *outputPath = [self _cachesPath:@"FolderZipped"];
NSString *archivePath = [outputPath stringByAppendingPathComponent:@"ArchiveWithFolders.zip"];
BOOL success = [SSZipArchive createZipFileAtPath:archivePath withContentsOfDirectory:inputPath];
XCTAssertTrue(success, @"create zip failure");
XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:archivePath], @"Folder Archive created");
}
- (void)testMultipleZippping{
NSArray *inputPaths = @[[[NSBundle bundleForClass: [self class]]pathForResource:@"0" ofType:@"m4a"],
[[NSBundle bundleForClass: [self class]]pathForResource:@"1" ofType:@"m4a"],
[[NSBundle bundleForClass: [self class]]pathForResource:@"2" ofType:@"m4a"],
[[NSBundle bundleForClass: [self class]]pathForResource:@"3" ofType:@"m4a"],
[[NSBundle bundleForClass: [self class]]pathForResource:@"4" ofType:@"m4a"],
[[NSBundle bundleForClass: [self class]]pathForResource:@"5" ofType:@"m4a"],
[[NSBundle bundleForClass: [self class]]pathForResource:@"6" ofType:@"m4a"],
[[NSBundle bundleForClass: [self class]]pathForResource:@"7" ofType:@"m4a"]
];
NSString *outputPath = [self _cachesPath:@"Zipped"];
// this is a monster
// if testing on iOS, within 30 loops it will fail; however, on OS X, it may take about 900 loops
for (int test = 0; test < 20; test++)
{
// Zipping
NSString *archivePath = [outputPath stringByAppendingPathComponent:[NSString stringWithFormat:@"queue_test_%d.zip", test]];
BOOL success = [SSZipArchive createZipFileAtPath:archivePath withFilesAtPaths:inputPaths];
XCTAssertTrue(success, @"create zip failure");
long long threshold = 510000; // 510kB:size slightly smaller than a successful zip, but much larger than a failed one
long long fileSize = [[[NSFileManager defaultManager] attributesOfItemAtPath:archivePath error:nil][NSFileSize] longLongValue];
XCTAssertTrue(fileSize > threshold, @"zipping failed at %@!", archivePath);
}
}
- (void)testUnzipping {
NSString *zipPath = [[NSBundle bundleForClass:[self class]] pathForResource:@"TestArchive" ofType:@"zip"];
NSString *outputPath = [self _cachesPath:@"Regular"];
id<SSZipArchiveDelegate> delegate = [ProgressDelegate new];
BOOL success = [SSZipArchive unzipFileAtPath:zipPath toDestination:outputPath delegate:delegate];
XCTAssertTrue(success, @"unzip failure");
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *testPath = [outputPath stringByAppendingPathComponent:@"Readme.markdown"];
XCTAssertTrue([fileManager fileExistsAtPath:testPath], @"Readme unzipped");
testPath = [outputPath stringByAppendingPathComponent:@"LICENSE"];
XCTAssertTrue([fileManager fileExistsAtPath:testPath], @"LICENSE unzipped");
}
- (void)testSmallFileUnzipping {
NSString *zipPath = [[NSBundle bundleForClass: [self class]] pathForResource:@"TestArchive" ofType:@"zip"];
NSString *outputPath = [self _cachesPath:@"Regular"];
id<SSZipArchiveDelegate> delegate = [ProgressDelegate new];
BOOL success = [SSZipArchive unzipFileAtPath:zipPath toDestination:outputPath delegate:delegate];
XCTAssertTrue(success, @"unzip failure");
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *testPath = [outputPath stringByAppendingPathComponent:@"Readme.markdown"];
XCTAssertTrue([fileManager fileExistsAtPath:testPath], @"Readme unzipped");
testPath = [outputPath stringByAppendingPathComponent:@"LICENSE"];
XCTAssertTrue([fileManager fileExistsAtPath:testPath], @"LICENSE unzipped");
}
-(void)testAppendingToZip {
// zip files and create "CreatedArchive.zip"
[self testZipping];
NSString *outputPath = [self _cachesPath:@"Zipped"];
NSString *archivePath = [outputPath stringByAppendingPathComponent:@"CreatedArchive.zip"];
long long initialFileSize = [[[NSFileManager defaultManager] attributesOfItemAtPath:archivePath error:NULL][NSFileSize] longLongValue];
SSZipArchive* zip = [[SSZipArchive alloc] initWithPath:archivePath];
BOOL didOpenForAppending = [zip openForAppending];
XCTAssertTrue(didOpenForAppending, @"Opened for appending");
NSData* testData = [@"test contents" dataUsingEncoding:NSUTF8StringEncoding];
BOOL didAppendFile = [zip writeData:testData filename:@"testData.txt" withPassword:NULL];
XCTAssertTrue(didAppendFile, @"Did add file");
BOOL didClose = [zip close];
XCTAssertTrue(didClose, @"Can close zip");
// TODO: Make sure the files are actually zipped. They are, but the test should be better.
XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:archivePath], @"Archive created");
long long fileSizeAfterAppend = [[[NSFileManager defaultManager] attributesOfItemAtPath:archivePath error:NULL][NSFileSize] longLongValue];
XCTAssertGreaterThan(fileSizeAfterAppend, initialFileSize);
}
- (void)testUnzippingProgress {
NSString *zipPath = [[NSBundle bundleForClass:[self class]] pathForResource:@"TestArchive" ofType:@"zip"];
NSString *outputPath = [self _cachesPath:@"Progress"];
ProgressDelegate *delegate = [ProgressDelegate new];
BOOL success = [SSZipArchive unzipFileAtPath:zipPath toDestination:outputPath delegate:delegate];
XCTAssertTrue(success, @"unzip failure");
// 4 events: the first, then for each of the two files one, then the final event
XCTAssertTrue(4 == [delegate->progressEvents count], @"Expected 4 progress events");
XCTAssertTrue(0 == [delegate->progressEvents[0] intValue]);
XCTAssertTrue(619 == [delegate->progressEvents[1] intValue]);
XCTAssertTrue(1114 == [delegate->progressEvents[2] intValue]);
XCTAssertTrue(1436 == [delegate->progressEvents[3] intValue]);
}
- (void)testUnzippingWithPassword {
NSString *zipPath = [[NSBundle bundleForClass:[self class]] pathForResource:@"TestPasswordArchive" ofType:@"zip"];
NSString *outputPath = [self _cachesPath:@"Password"];
NSError *error = nil;
id<SSZipArchiveDelegate> delegate = [ProgressDelegate new];
BOOL success = [SSZipArchive unzipFileAtPath:zipPath toDestination:outputPath overwrite:YES password:@"passw0rd" error:&error delegate:delegate];
XCTAssertTrue(success, @"unzip failure");
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *testPath = [outputPath stringByAppendingPathComponent:@"Readme.markdown"];
XCTAssertTrue([fileManager fileExistsAtPath:testPath], @"Readme unzipped");
testPath = [outputPath stringByAppendingPathComponent:@"LICENSE"];
XCTAssertTrue([fileManager fileExistsAtPath:testPath], @"LICENSE unzipped");
}
- (void)testUnzippingWithAESPassword {
NSString *zipPath = [[NSBundle bundleForClass:[self class]] pathForResource:@"TestAESPasswordArchive" ofType:@"zip"];
NSString *outputPath = [self _cachesPath:@"Password"];
NSError *error = nil;
id<SSZipArchiveDelegate> delegate = [ProgressDelegate new];
BOOL success = [SSZipArchive unzipFileAtPath:zipPath toDestination:outputPath overwrite:YES password:@"passw0rd" error:&error delegate:delegate];
XCTAssertTrue(success, @"unzip failure");
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *testPath = [outputPath stringByAppendingPathComponent:@"README.md"];
XCTAssertTrue([fileManager fileExistsAtPath:testPath], @"Readme unzipped");
testPath = [outputPath stringByAppendingPathComponent:@"LICENSE.txt"];
XCTAssertTrue([fileManager fileExistsAtPath:testPath], @"LICENSE unzipped");
}
- (void)testUnzippingWithInvalidPassword {
NSString *zipPath = [[NSBundle bundleForClass:[self class]] pathForResource:@"TestPasswordArchive" ofType:@"zip"];
NSString *outputPath = [self _cachesPath:@"Password"];
NSError *error = nil;
id<SSZipArchiveDelegate> delegate = [ProgressDelegate new];
BOOL success = [SSZipArchive unzipFileAtPath:zipPath toDestination:outputPath overwrite:YES password:@"passw0rd123" error:&error delegate:delegate];
XCTAssertFalse(success, @"unzip failure");
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *testPath = [outputPath stringByAppendingPathComponent:@"Readme.markdown"];
XCTAssertFalse([fileManager fileExistsAtPath:testPath], @"Readme not unzipped");
testPath = [outputPath stringByAppendingPathComponent:@"LICENSE"];
XCTAssertFalse([fileManager fileExistsAtPath:testPath], @"LICENSE not unzipped");
}
- (void)testIsPasswordInvalidForArchiveAtPath {
NSString *zipPath = [[NSBundle bundleForClass:[self class]] pathForResource:@"TestPasswordArchive" ofType:@"zip"];
NSError *error = nil;
BOOL fileHasValidPassword = [SSZipArchive isPasswordValidForArchiveAtPath:zipPath password:@"passw0rd" error:&error];
XCTAssertTrue(fileHasValidPassword, @"Valid password reports false.");
BOOL fileHasInvalidValidPassword = [SSZipArchive isPasswordValidForArchiveAtPath:zipPath password:@"passw0rd123" error:&error];
XCTAssertFalse(fileHasInvalidValidPassword, @"Invalid password reports true.");
}
- (void)testIsFilePasswordProtectedAtPath {
NSString *zipPath = [[NSBundle bundleForClass:[self class]] pathForResource:@"TestArchive" ofType:@"zip"];
BOOL protected = [SSZipArchive isFilePasswordProtectedAtPath:zipPath];
XCTAssertFalse(protected, @"has no password");
NSString *zipWithPasswordPath = [[NSBundle bundleForClass:[self class]] pathForResource:@"TestPasswordArchive" ofType:@"zip"];
protected = [SSZipArchive isFilePasswordProtectedAtPath:zipWithPasswordPath];
XCTAssertTrue(protected, @"has password");
}
- (void)testZippingAndUnzippingWithUnicodePassword {
/********** Zipping ********/
// use extracted files from [-testUnzipping]
[self testUnzipping];
NSString *inputPath = [self _cachesPath:@"Regular"];
NSArray *inputPaths = @[[inputPath stringByAppendingPathComponent:@"Readme.markdown"],
[inputPath stringByAppendingPathComponent:@"LICENSE"]];
NSString *outputPath = [self _cachesPath:@"Zipped"];
NSString *archivePath = [outputPath stringByAppendingPathComponent:@"CreatedUnicodePasswordArchive.zip"];
BOOL success = [SSZipArchive createZipFileAtPath:archivePath withFilesAtPaths:inputPaths withPassword:@"ꊐ⌒Ⅳ🤐"];
XCTAssertTrue(success, @"create zip failure");
XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:archivePath], @"Archive created");
/********** Unzipping ********/
outputPath = [self _cachesPath:@"UnicodePassword"];
NSError *error = nil;
id<SSZipArchiveDelegate> delegate = [ProgressDelegate new];
success = [SSZipArchive unzipFileAtPath:archivePath toDestination:outputPath overwrite:YES password:@"ꊐ⌒Ⅳ🤐" error:&error delegate:delegate];
XCTAssertTrue(success, @"unzip failure");
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *testPath = [outputPath stringByAppendingPathComponent:@"Readme.markdown"];
XCTAssertTrue([fileManager fileExistsAtPath:testPath], @"Readme unzipped");
testPath = [outputPath stringByAppendingPathComponent:@"LICENSE"];
XCTAssertTrue([fileManager fileExistsAtPath:testPath], @"LICENSE unzipped");
}
- (void)testUnzippingTruncatedFileFix {
NSString *zipPath = [[NSBundle bundleForClass:[self class]] pathForResource:@"IncorrectHeaders" ofType:@"zip"];
NSString *outputPath = [self _cachesPath:@"IncorrectHeaders"];
id<SSZipArchiveDelegate> delegate = [ProgressDelegate new];
BOOL success = [SSZipArchive unzipFileAtPath:zipPath toDestination:outputPath delegate:delegate];
XCTAssertTrue(success, @"unzip failure");
NSString *intendedReadmeTxtSHA256 = @"b389c1b182306c9fa68a38f667a6ac35863b6d670ae98954403c265ffe67e9bc";
NSString *filePath = [outputPath stringByAppendingPathComponent:@"IncorrectHeaders/Readme.txt"];
NSData *data = [NSData dataWithContentsOfFile:filePath];
NSString *actualReadmeTxtSHA256 = [self _calculateSHA256Hash:data];
XCTAssertTrue([actualReadmeTxtSHA256 isEqualToString:intendedReadmeTxtSHA256], @"Readme.txt SHA256 digest should match original.");
}
- (void)testUnzippingWithSymlinkedFileInside {
NSString *zipPath = [[NSBundle bundleForClass:[self class]] pathForResource:@"SymbolicLink" ofType:@"zip"];
NSString *outputPath = [self _cachesPath:@"SymbolicLink"];
id<SSZipArchiveDelegate> delegate = [ProgressDelegate new];
BOOL success = [SSZipArchive unzipFileAtPath:zipPath toDestination:outputPath preserveAttributes:YES overwrite:YES symlinksValidWithin:nil nestedZipLevel:0 password:nil error:nil delegate:delegate progressHandler:nil completionHandler:nil];
XCTAssertTrue(success, @"unzip failure");
NSString *testSymlink = [outputPath stringByAppendingPathComponent:@"SymbolicLink/Xcode.app"];
NSError *error = nil;
NSDictionary *info = [[NSFileManager defaultManager] attributesOfItemAtPath: testSymlink error: &error];
BOOL fileIsSymbolicLink = info[NSFileType] == NSFileTypeSymbolicLink;
XCTAssertTrue(fileIsSymbolicLink, @"Symbolic links should persist from the original archive to the outputted files.");
}
- (void)testUnzippingWithSymlinkedFileEscapingOutputDirectory {
NSString *zipPath = [[NSBundle bundleForClass:[self class]] pathForResource:@"SymbolicLink" ofType:@"zip"];
NSString *outputPath = [self _cachesPath:@"SymbolicLink"];
id<SSZipArchiveDelegate> delegate = [ProgressDelegate new];
NSError *error = nil;
BOOL success = [SSZipArchive unzipFileAtPath:zipPath toDestination:outputPath overwrite:YES password:nil error:&error delegate:delegate];
XCTAssertFalse(success, @"Escaping symlink unpacked");
XCTAssertNotNil(error, @"Error not reported");
XCTAssertEqualObjects(error.domain, SSZipArchiveErrorDomain, @"Invalid error domain");
XCTAssertEqual(error.code, SSZipArchiveErrorCodeSymlinkEscapesTargetDirectory, @"Invalid error code");
}
- (void)testUnzippingWithRelativeSymlink {
NSString *resourceName = @"RelativeSymbolicLink";
NSString *zipPath = [[NSBundle bundleForClass:[self class]] pathForResource:resourceName ofType:@"zip"];
NSString *outputPath = [self _cachesPath:resourceName];
id<SSZipArchiveDelegate> delegate = [ProgressDelegate new];
BOOL success = [SSZipArchive unzipFileAtPath:zipPath toDestination:outputPath delegate:delegate];
XCTAssertTrue(success, @"unzip failure");
// Determine where the symlinks are
NSString *subfolderName = @"symlinks";
NSString *testBasePath = [NSString pathWithComponents:@[outputPath]];
NSString *testSymlinkFolder = [NSString pathWithComponents:@[testBasePath, subfolderName, @"folderSymlink"]];
NSString *testSymlinkFile = [NSString pathWithComponents:@[testBasePath, subfolderName, @"fileSymlink"]];
NSDictionary *fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:testSymlinkFile error: nil];
BOOL fileIsSymbolicLink = fileAttributes[NSFileType] == NSFileTypeSymbolicLink;
XCTAssertTrue(fileIsSymbolicLink, @"Relative symbolic links should persist from the original archive to the outputted files.");
NSDictionary *folderAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:testSymlinkFolder error: nil];
BOOL folderIsSymbolicLink = folderAttributes[NSFileType] == NSFileTypeSymbolicLink;
XCTAssertTrue(folderIsSymbolicLink, @"Relative symbolic links should persist from the original archive to the outputted files.");
}
- (void)testUnzippingWithUnicodeFilenameInside {
NSString *zipPath = [[NSBundle bundleForClass:[self class]] pathForResource:@"Unicode" ofType:@"zip"];
NSString *outputPath = [self _cachesPath:@"Unicode"];
id<SSZipArchiveDelegate> delegate = [ProgressDelegate new];
BOOL success = [SSZipArchive unzipFileAtPath:zipPath toDestination:outputPath delegate:delegate];
XCTAssertTrue(success, @"unzip failure");
bool unicodeFilenameWasExtracted = [[NSFileManager defaultManager] fileExistsAtPath:[outputPath stringByAppendingPathComponent:@"Accént.txt"]];
bool unicodeFolderWasExtracted = [[NSFileManager defaultManager] fileExistsAtPath:[outputPath stringByAppendingPathComponent:@"Fólder/Nothing.txt"]];
XCTAssertTrue(unicodeFilenameWasExtracted, @"Files with filenames in unicode should be extracted properly.");
XCTAssertTrue(unicodeFolderWasExtracted, @"Folders with names in unicode should be extracted propertly.");
}
/// Issue #507
- (void)testZippingWithUnicodeFilenameInside {
NSString *workPath = [self _cachesPath:@"UnicodeZipping"];
NSString *filePath = [workPath stringByAppendingPathComponent:@"17 What Ive Been Looking For.pdf"];
NSString *zipPath = [workPath stringByAppendingPathComponent:@"UnicodeZipping.zip"];
[[NSFileManager defaultManager] createFileAtPath:filePath contents:nil attributes:nil];
BOOL success = [SSZipArchive createZipFileAtPath:zipPath withFilesAtPaths:@[filePath]];
XCTAssertTrue(success, @"zip failure");
[[NSFileManager defaultManager] removeItemAtPath:filePath error:nil];
success = [SSZipArchive unzipFileAtPath:zipPath toDestination:workPath delegate:nil];
XCTAssertTrue(success, @"unzip failure");
bool unicodeFilenameWasExtracted = [[NSFileManager defaultManager] fileExistsAtPath:filePath];
XCTAssertTrue(unicodeFilenameWasExtracted, @"Files with filenames in unicode should be extracted properly.");
}
- (void)testZippingEmptyArchive {
NSString *inputPath = [self _cachesPath:@"Empty"];
XCTAssert(![[NSFileManager defaultManager] enumeratorAtPath:inputPath].nextObject, @"The Empty cache folder should always be empty.");
NSString *outputPath = [self _cachesPath:@"Zipped"];
NSString *zipPath = [outputPath stringByAppendingPathComponent:@"Empty.zip"];
NSString *zipPath2 = [outputPath stringByAppendingPathComponent:@"EmptyWithParentDirectory.zip"];
BOOL success = [SSZipArchive createZipFileAtPath:zipPath withContentsOfDirectory:inputPath keepParentDirectory:NO];
XCTAssertTrue(success, @"create zip failure");
success = [SSZipArchive createZipFileAtPath:zipPath2 withContentsOfDirectory:inputPath keepParentDirectory:YES];
XCTAssertTrue(success, @"create zip failure");
long long fileSize = [[[NSFileManager defaultManager] attributesOfItemAtPath:zipPath error:nil][NSFileSize] longLongValue];
long long fileSize2 = [[[NSFileManager defaultManager] attributesOfItemAtPath:zipPath2 error:nil][NSFileSize] longLongValue];
XCTAssert(fileSize < fileSize2, @"keepParentDirectory should produce a strictly bigger archive.");
}
/// <https://github.com/ZipArchive/ZipArchive/issues/621>
- (void)testZippingNonDirectoryWithContentsOfDirectory {
NSString *inputPath = [[NSBundle bundleForClass:[self class]] pathForResource:@"TestArchive" ofType:@"zip"];
NSString *outputPath = [self _cachesPath:@"ZippingNonDirectory"];
NSString *zipPath = [outputPath stringByAppendingPathComponent:@"ZippingNonDirectory.zip"];
BOOL success = [SSZipArchive createZipFileAtPath:zipPath withContentsOfDirectory:inputPath];
XCTAssertTrue(!success, @"create zip success");
}
/// Using `keepParentDirectory:YES`
- (void)testZippingAndUnzippingEmptyDirectoryWithPassword {
NSString *inputPath = [self _cachesPath:@"Empty"];
XCTAssert(![[NSFileManager defaultManager] enumeratorAtPath:inputPath].nextObject, @"The Empty cache folder should always be empty.");
NSString *outputPath = [self _cachesPath:@"Zipped"];
NSString *zipPath = [outputPath stringByAppendingPathComponent:@"EmptyWithParentDirectory.zip"];
BOOL success = [SSZipArchive createZipFileAtPath:zipPath withContentsOfDirectory:inputPath keepParentDirectory:YES withPassword:@"password"];
XCTAssertTrue(success, @"create zip failure");
outputPath = [self _cachesPath:@"EmptyOutput"];
// unzipping a directory doesn't require a password
id<SSZipArchiveDelegate> delegate = [ProgressDelegate new];
success = [SSZipArchive unzipFileAtPath:zipPath toDestination:outputPath overwrite:YES password:nil error:nil delegate:delegate];
XCTAssertTrue(success, @"unzip failure");
NSString *emptyPath = [outputPath stringByAppendingPathComponent:@"Empty"];
BOOL isDirectory;
XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:emptyPath isDirectory:&isDirectory], @"Empty unzipped");
XCTAssertTrue(isDirectory, @"Empty is a directory");
}
/// Using `withFilesAtPaths:`
- (void)testZippingAndUnzippingEmptyDirectoryWithFilesAtPaths {
NSString *inputPath = [self _cachesPath:@"EmptyWithSubdirectory"];
NSString *emptySubdirectory = [inputPath stringByAppendingPathComponent:@"Empty"];
BOOL success = [[NSFileManager defaultManager] createDirectoryAtPath:emptySubdirectory withIntermediateDirectories:YES attributes:nil error:NULL];
XCTAssertTrue(success, @"create folder failure");
// zipping files
NSString *outputPath = [self _cachesPath:@"Zipped"];
NSString *zipPath = [outputPath stringByAppendingPathComponent:@"EmptyWithSubdirectory.zip"];
success = [SSZipArchive createZipFileAtPath:zipPath withFilesAtPaths:@[emptySubdirectory]];
XCTAssertTrue(success, @"create zip failure");
// unzipping files
outputPath = [self _cachesPath:@"EmptyWithSubdirectoryOutput"];
id<SSZipArchiveDelegate> delegate = [ProgressDelegate new];
success = [SSZipArchive unzipFileAtPath:zipPath toDestination:outputPath overwrite:YES password:nil error:nil delegate:delegate];
XCTAssertTrue(success, @"unzip failure");
NSString *emptyPath = [outputPath stringByAppendingPathComponent:@"Empty"];
BOOL isDirectory;
XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:emptyPath isDirectory:&isDirectory], @"Empty unzipped");
XCTAssertTrue(isDirectory, @"Empty is a directory");
}
- (void)testUnzippingEmptyArchive {
NSString *zipPath = [[NSBundle bundleForClass:[self class]] pathForResource:@"Empty" ofType:@"zip"];
NSString *outputPath = [self _cachesPath:@"Empty"];
BOOL success = [SSZipArchive unzipFileAtPath:zipPath toDestination:outputPath delegate:nil];
XCTAssertTrue(success, @"unzip failure");
}
- (void)testZippingAndUnzippingForDate {
// use extracted files from [-testUnzipping]
[self testUnzipping];
NSString *inputPath = [self _cachesPath:@"Regular"];
NSArray *inputPaths = @[[inputPath stringByAppendingPathComponent:@"Readme.markdown"]];
NSDictionary *originalFileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:[inputPath stringByAppendingPathComponent:@"Readme.markdown"] error:nil];
NSString *outputPath = [self _cachesPath:@"ZippedDate"];
NSString *archivePath = [outputPath stringByAppendingPathComponent:@"CreatedArchive.zip"];
BOOL success = [SSZipArchive createZipFileAtPath:archivePath withFilesAtPaths:inputPaths];
XCTAssertTrue(success, @"create zip failure");
id<SSZipArchiveDelegate> delegate = [ProgressDelegate new];
success = [SSZipArchive unzipFileAtPath:archivePath toDestination:outputPath delegate:delegate];
XCTAssertTrue(success, @"unzip failure");
NSDictionary *createdFileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:[outputPath stringByAppendingPathComponent:@"Readme.markdown"] error:nil];
XCTAssertEqualObjects(originalFileAttributes[NSFileCreationDate], createdFileAttributes[@"NSFileCreationDate"], @"Original file creationDate should match created one");
}
- (void)testZippingAndUnzippingForPermissions {
// File we're going to test permissions on before and after zipping
NSString *targetFile = @"/Contents/MacOS/TestProject";
/********** Zipping ********/
// The .app file we're going to zip up
NSString *inputFile = [[NSBundle bundleForClass: [self class]] pathForResource:@"PermissionsTestApp" ofType:@"app"];
// The path to the target file in the app before zipping
NSString *targetFilePreZipPath = [inputFile stringByAppendingPathComponent:targetFile];
// Atribtues for the target file before zipping
NSDictionary *preZipAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:targetFilePreZipPath error:nil];
// Directory to output our created zip file
NSString *outputDir = [self _cachesPath:@"PermissionsTest"];
// The path to where the archive shall be created
NSString *archivePath = [outputDir stringByAppendingPathComponent:@"TestAppArchive.zip"];
// Create the zip file using the contents of the .app file as the input
BOOL success = [SSZipArchive createZipFileAtPath:archivePath withContentsOfDirectory:inputFile];
XCTAssertTrue(success, @"create zip failure");
/********** Un-zipping *******/
// Using this newly created zip file, unzip it
success = [SSZipArchive unzipFileAtPath:archivePath toDestination:outputDir];
XCTAssertTrue(success, @"unzip failure");
// Get the path to the target file after unzipping
NSString *targetFilePath = [outputDir stringByAppendingPathComponent:@"/Contents/MacOS/TestProject"];
// Get the file attributes of the target file following the unzipping
NSDictionary *fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:targetFilePath error:nil];
NSInteger permissions = ((NSNumber *)fileAttributes[NSFilePosixPermissions]).longValue;
NSInteger preZipPermissions = ((NSNumber *)preZipAttributes[NSFilePosixPermissions]).longValue;
// Compare the value of the permissions attribute to assert equality
XCTAssertEqual(permissions, preZipPermissions, @"File permissions should be retained during compression and de-compression");
}
- (void)testUnzippingWithCancel {
NSString *zipPath = [[NSBundle bundleForClass:[self class]] pathForResource:@"TestArchive" ofType:@"zip"];
NSString *outputPath = [self _cachesPath:@"Cancel1"];
CancelDelegate *delegate = [[CancelDelegate alloc] init];
delegate.numFilesToUnzip = 1;
BOOL success = [SSZipArchive unzipFileAtPath:zipPath toDestination:outputPath delegate:delegate];
XCTAssertFalse(success);
XCTAssertEqual(delegate.numFilesUnzipped, 1);
XCTAssertFalse(delegate.didUnzipArchive);
XCTAssertNotEqual(delegate.loaded, delegate.total);
outputPath = [self _cachesPath:@"Cancel2"];
delegate = [[CancelDelegate alloc] init];
delegate.numFilesToUnzip = 1000;
success = [SSZipArchive unzipFileAtPath:zipPath toDestination:outputPath delegate:delegate];
XCTAssertTrue(success, @"unzip failure");
XCTAssertEqual(delegate.numFilesUnzipped, 2);
XCTAssertTrue(delegate.didUnzipArchive);
XCTAssertEqual(delegate.loaded, delegate.total);
}
// Commented out to avoid checking in several gig file into the repository. Simply add a file named
// `LargeArchive.zip` to the project and uncomment out these lines to test.
//
//- (void)testUnzippingLargeFiles {
// NSString *zipPath = [[NSBundle bundleForClass:[self class]] pathForResource:@"LargeArchive" ofType:@"zip"];
// NSString *outputPath = [self _cachesPath:@"Large"];
//
// BOOL success = [SSZipArchive unzipFileAtPath:zipPath toDestination:outputPath];
// XCTAssertTrue(success, @"unzip failure");
//}
- (void)testShouldProvidePathOfUnzippedFileInDelegateCallback {
CollectingDelegate *collector = [CollectingDelegate new];
NSString *zipPath = [[NSBundle bundleForClass:[self class]] pathForResource:@"TestArchive" ofType:@"zip"];
NSString *outputPath = [self _cachesPath:@"Regular"];
BOOL success = [SSZipArchive unzipFileAtPath:zipPath toDestination:outputPath delegate:collector];
XCTAssertTrue(success, @"unzip failure");
XCTAssertEqualObjects(collector.files[0], [outputPath stringByAppendingString:@"/LICENSE"]);
XCTAssertEqualObjects(collector.files[1], [outputPath stringByAppendingString:@"/Readme.markdown"]);
}
- (void)testUnzippingFileWithPathTraversalName {
// This zip archive contains a file titled '../../../../../../../../../../..//tmp/test.txt'. SSZipArchive should
// ignore the path traversing and write the file to "tmp/test.txt"
NSString *zipPath = [[NSBundle bundleForClass:[self class]] pathForResource:@"PathTraversal" ofType:@"zip"];
NSString *outputPath = [self _cachesPath:@"Traversal"];
id<SSZipArchiveDelegate> delegate = [ProgressDelegate new];
BOOL success = [SSZipArchive unzipFileAtPath:zipPath toDestination:outputPath delegate:delegate];
XCTAssertTrue(success, @"unzip failure");
NSString *expectedFile = [outputPath stringByAppendingPathComponent:@"tmp/test.txt"];
BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:expectedFile];
XCTAssertTrue(fileExists, @"Path traversal characters should not be followed when unarchiving a file");
}
- (void)testPathSanitation {
NSDictionary<NSString *, NSString *> *tests =
@{
// path traversal
@"../../../../../../../../../../../tmp/test.txt": @"tmp/test.txt",
// path traversal, Windows style
@"..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\tmp\\test.txt": @"tmp/test.txt",
// relative path
@"a/b/../c.txt": @"a/c.txt",
// path traversal with slash (#680)
@"/..": @"",
// path traversal without slash
@"..": @"",
// permissions override
@"./": @"",
@".": @"",
// unicode and spaces in folder name and file name
@"example/../à: 𝚨 󌞑/你 好.txt": @"à: 𝚨 󌞑/你 好.txt",
// unicode that looks like '.' or '/' in ascii decomposition
@"a/ğ/../b": @"a/b",
@"a/⸮/b/.ⸯ/c/⼮./d": @"a/⸮/b/.ⸯ/c/⼮./d",
// scheme in name
@"file:a/../../../usr/bin": @"usr/bin",
};
for (NSString *str in tests) {
//NSLog(@"%@", str);
XCTAssertTrue([tests[str] isEqualToString:[str _sanitizedPath]], @"Path should be sanitized for traversal");
}
}
// This tests whether the payload size of the zip file containing 4.8Gb of files with compression 0 is correct,
// and creates a zip file containing 240 20Mb sized files with multiple compression levels. It then unpacks the file and checks whether
// the same number of files is present in the unpacked folder.
- (void)testPayloadSizeCorrectAndUnpackFileNo {
long long int iterations = 240;
NSString *unpackPath = [self _cachesPath:@"Unpacked/testFile"];
NSNumber *goldenSize = [NSNumber numberWithLongLong:iterations * twentyMB];
NSData *data = [self get20MbNSData];
NSString *filenName = @"TestFile.zip";
NSString *filePath = [NSString stringWithFormat:@"%@%@", [self _cachesPath:@""], filenName];
NSString *password = @"TestPW";
SSZipArchive *archive = [[SSZipArchive alloc] initWithPath:filePath];
[archive open];
for (int i = 0; i < iterations; i++) {
NSString *fileName = [NSString stringWithFormat:@"File_%i", i];
[archive writeData:data filename:fileName compressionLevel:0 password:password AES:true];
}
bool close = [archive close];
NSNumber *fileSize = [SSZipArchive payloadSizeForArchiveAtPath:filePath error:nil];
XCTAssertTrue(close, "Should be able to close the archive.");
XCTAssertTrue(fileSize.longLongValue == goldenSize.longLongValue,
"Payload size should be equal to the sum of the size of all files included.");
[SSZipArchive unzipFileAtPath:filePath toDestination:unpackPath overwrite:true password:password error:nil];
long int noFiles = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:unpackPath error:nil].count;
XCTAssertTrue(iterations == noFiles, "All files should be present in the exported directory");
}
- (void)testSymlinkZippingWithFilesAtPaths {
NSString *outputDir = [self _cachesPath:@"ZippingSymlinkWithFilesAtPaths"];
//directory
NSString *directory = [outputDir stringByAppendingPathComponent:@"directory"];
BOOL success = [[NSFileManager defaultManager] createDirectoryAtPath:directory withIntermediateDirectories:YES attributes:nil error:NULL];
XCTAssertTrue(success);
//write real file
NSString *realFilePath = [directory stringByAppendingPathComponent:@"originfile"];
success = [@"Hello World!" writeToFile:realFilePath atomically:NO encoding:NSUTF8StringEncoding error:NULL];
XCTAssertTrue(success);
//create symlink to real file
NSString *symlinkPath = [directory stringByAppendingPathComponent:@"linkfile"];
success = [[NSFileManager defaultManager] createSymbolicLinkAtPath:symlinkPath withDestinationPath:realFilePath error:NULL];
XCTAssertTrue(success);
NSArray *files = @[realFilePath,symlinkPath];
//zipping
NSString *archivePath = [outputDir stringByAppendingPathComponent:@"SymlinkZippingWithFilesAtPaths.zip"];
success = [SSZipArchive createZipFileAtPath:archivePath withFilesAtPaths:files withPassword:nil keepSymlinks:YES];
XCTAssertTrue(success);
//remove files in directory dir, to make sure what we check is unzipped
success = [[NSFileManager defaultManager] removeItemAtPath:directory error:NULL];
XCTAssertTrue(success);
success = [[NSFileManager defaultManager] createDirectoryAtPath:directory withIntermediateDirectories:YES attributes:nil error:NULL];
XCTAssertTrue(success);
//unzip
success = [SSZipArchive unzipFileAtPath:archivePath toDestination:directory];
XCTAssertTrue(success);
//check if it's still a symlink
NSDictionary *attr = [[NSFileManager defaultManager] attributesOfItemAtPath:symlinkPath error:NULL];
NSString *fileType = attr[NSFileType];
XCTAssertTrue([fileType isEqualToString:NSFileTypeSymbolicLink]);
//check symlink points correctly
NSString *realFilePathUnzip = [[NSFileManager defaultManager] destinationOfSymbolicLinkAtPath:symlinkPath error:NULL];
XCTAssertEqualObjects(realFilePath, realFilePathUnzip);
}
- (void)testSymlinkZippingWithContentsOfDirectory {
NSString *outputDir = [self _cachesPath:@"ZippingSymlinkWithContentsOfDirectory"];
//directory
NSString *directory = [outputDir stringByAppendingPathComponent:@"directory"];
BOOL success = [[NSFileManager defaultManager] createDirectoryAtPath:directory withIntermediateDirectories:YES attributes:nil error:NULL];
XCTAssertTrue(success);
//write real file
NSString *realFilePath = [directory stringByAppendingPathComponent:@"origin"];
success = [@"Hello World!" writeToFile:realFilePath atomically:NO encoding:NSUTF8StringEncoding error:NULL];
XCTAssertTrue(success);
//create symlink to real file
NSString *symlinkPath = [directory stringByAppendingPathComponent:@"link"];
success = [[NSFileManager defaultManager] createSymbolicLinkAtPath:symlinkPath withDestinationPath:realFilePath error:NULL];
XCTAssertTrue(success);
//zipping
NSString *archivePath = [outputDir stringByAppendingPathComponent:@"SymlinkZippingWithContentsOfDirectory.zip"];
success = [SSZipArchive createZipFileAtPath:archivePath
withContentsOfDirectory:directory
keepParentDirectory:NO
compressionLevel:-1
password:nil
AES:YES
progressHandler:nil
keepSymlinks:YES];
XCTAssertTrue(success);
//remove files in directory dir, to make sure what we check is unzipped
success = [[NSFileManager defaultManager] removeItemAtPath:directory error:NULL];
XCTAssertTrue(success);
success = [[NSFileManager defaultManager] createDirectoryAtPath:directory withIntermediateDirectories:YES attributes:nil error:NULL];
XCTAssertTrue(success);
//unzip
success = [SSZipArchive unzipFileAtPath:archivePath toDestination:directory];
XCTAssertTrue(success);
//check if it's still a symlink
NSDictionary *attr = [[NSFileManager defaultManager] attributesOfItemAtPath:symlinkPath error:NULL];
NSString *fileType = attr[NSFileType];
XCTAssertTrue([fileType isEqualToString:NSFileTypeSymbolicLink]);
//check symlink points correctly
NSString *realFilePathUnzip = [[NSFileManager defaultManager] destinationOfSymbolicLinkAtPath:symlinkPath error:NULL];
XCTAssertEqualObjects(realFilePath, realFilePathUnzip);
}
#pragma mark - Private
// Returns 20Mb of data
-(NSData*)get20MbNSData {
NSMutableData* theData = [NSMutableData dataWithCapacity:twentyMB];
for (long long int i = 0; i < twentyMB/4; i++) {
u_int32_t randomBits = arc4random();
[theData appendBytes:(void *)&randomBits length:4];
}
return theData;
}
- (NSString *)_cachesPath:(NSString *)directory {
NSString *path = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).lastObject
stringByAppendingPathComponent:@"com.samsoffes.ssziparchive.tests"];
if (directory) {
path = [path stringByAppendingPathComponent:directory];
}
BOOL success = [[NSFileManager defaultManager] createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:nil];
XCTAssertTrue(success, @"%@", path);
return path;
}
- (NSString *)_calculateSHA256Hash:(NSData *)data {
uint8_t digest[CC_SHA256_DIGEST_LENGTH];
CC_SHA256(data.bytes, (CC_LONG) data.length, digest);
//convert the SHA hash to a string
NSMutableString* ms = [NSMutableString stringWithCapacity:CC_SHA256_DIGEST_LENGTH];
for(int i = 0; i < CC_SHA256_DIGEST_LENGTH; i++) {
[ms appendFormat:@"%02x", digest[i]];
}
return [ms copy];
}
@end