diff --git a/CHANGELOG.md b/CHANGELOG.md index a8938635..a6a7f64a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ * Added support for `NSProgress` and `NSProgressReporting` in all extraction and iteration methods (Issue #34) * Added enhanced support for multivolume archives (PRs #59, #38 - Thanks to [@aonez](https://github.com/aonez) for the idea and implementation!) +* Added methods for checking data integrity of archived files (Issue #26, PR #61 - Thanks to [@amosavian](https://github.com/amosavian) for the suggestion!) * Added detailed logging using new unified logging framework. See [the readme](README.md) for more details (Issue #35) * Added localized details to returned `NSError` objects (Issue #45) * Moved `unrar` sources into a static library, and addressed a wide variety of warnings exposed by the `-Weverything` flag (Issue #56) diff --git a/Classes/URKArchive.h b/Classes/URKArchive.h index 2db0a6a3..26947456 100644 --- a/Classes/URKArchive.h +++ b/Classes/URKArchive.h @@ -326,7 +326,7 @@ extern NSString *URKErrorDomain; - (BOOL)extractFilesTo:(NSString *)filePath overwrite:(BOOL)overwrite progress:(nullable void (^)(URKFileInfo *currentFile, CGFloat percentArchiveDecompressed))progressBlock - error:(NSError **)error __deprecated_msg("Use extractFilesTo:overwrite:error: instead, and if using the progress block, replace with NSProgress as described in the README"); + error:(NSError **)error __deprecated_msg("Use -extractFilesTo:overwrite:error: instead, and if using the progress block, replace with NSProgress as described in the README"); /** * Unarchive a single file from the archive into memory. Supports NSProgress for progress reporting, which also @@ -354,7 +354,7 @@ extern NSString *URKErrorDomain; */ - (nullable NSData *)extractData:(URKFileInfo *)fileInfo progress:(nullable void (^)(CGFloat percentDecompressed))progressBlock - error:(NSError **)error __deprecated_msg("Use extractData:error: instead, and if using the progress block, replace with NSProgress as described in the README"); + error:(NSError **)error __deprecated_msg("Use -extractData:error: instead, and if using the progress block, replace with NSProgress as described in the README"); /** * Unarchive a single file from the archive into memory. Supports NSProgress for progress reporting, which also @@ -385,7 +385,7 @@ extern NSString *URKErrorDomain; */ - (nullable NSData *)extractDataFromFile:(NSString *)filePath progress:(nullable void (^)(CGFloat percentDecompressed))progressBlock - error:(NSError **)error __deprecated_msg("Use extractDataFromFile:error: instead, and if using the progress block, replace with NSProgress as described in the README"); + error:(NSError **)error __deprecated_msg("Use -extractDataFromFile:error: instead, and if using the progress block, replace with NSProgress as described in the README"); /** * Loops through each file in the archive in alphabetical order, allowing you to perform an @@ -452,5 +452,24 @@ extern NSString *URKErrorDomain; - (BOOL)validatePassword; +/** + Extract each file in the archive, checking whether the data matches the CRC checksum + stored at the time it was written + + @return YES if the data is all correct, false if any check failed + */ +- (BOOL)checkDataIntegrity; + + +/** + Extract a particular file, to determine if its data matches the CRC + checksum stored at the time it written + + @param filePath The file in the archive to check + + @return YES if the data is correct, false if any check failed + */ +- (BOOL)checkDataIntegrityOfFile:(NSString *)filePath; + @end NS_ASSUME_NONNULL_END diff --git a/Classes/URKArchive.mm b/Classes/URKArchive.mm index 465c4f69..5a0f870e 100644 --- a/Classes/URKArchive.mm +++ b/Classes/URKArchive.mm @@ -9,6 +9,8 @@ #import "UnrarKitMacros.h" #import "NSString+UnrarKit.h" +#import "zlib.h" + RarHppIgnore #import "rar.hpp" #pragma clang diagnostic pop @@ -721,6 +723,11 @@ - (BOOL)performOnFilesInArchive:(void(^)(URKFileInfo *fileInfo, BOOL *stop))acti URKCreateActivity("Iterating Each File Info"); [sortedFileInfo enumerateObjectsUsingBlock:^(URKFileInfo *info, NSUInteger idx, BOOL *stop) { + if (progress.isCancelled) { + URKLogInfo("PerformOnFiles iteration was cancelled"); + *stop = YES; + } + URKLogDebug("Performing action on %{public}@", info.filename); action(info, stop); progress.completedUnitCount += 1; @@ -729,11 +736,6 @@ - (BOOL)performOnFilesInArchive:(void(^)(URKFileInfo *fileInfo, BOOL *stop))acti URKLogInfo("Action dictated an early stop"); progress.completedUnitCount = progress.totalUnitCount; } - - if (progress.isCancelled) { - URKLogInfo("File info iteration was cancelled"); - *stop = YES; - } }]; } @@ -1030,6 +1032,53 @@ - (BOOL)validatePassword return passwordIsGood; } +- (BOOL)checkDataIntegrity +{ + return [self checkDataIntegrityOfFile:(NSString *_Nonnull)nil]; +} + +- (BOOL)checkDataIntegrityOfFile:(NSString *)filePath +{ + URKCreateActivity("Checking Data Integrity"); + + URKLogInfo("Checking integrity of %{public}@", filePath ? filePath : @"whole archive"); + + __block BOOL corruptDataFound = YES; + + NSError *performOnFilesError = nil; + [self performOnFilesInArchive:^(URKFileInfo *fileInfo, BOOL *stop) { + URKCreateActivity("Iterating through each file"); + corruptDataFound = NO; // Set inside here so invalid archives are marked as corrupt + if (filePath && ![fileInfo.filename isEqualToString:filePath]) return; + + URKLogDebug("Extracting '%{public}@ to check its CRC...", fileInfo.filename); + NSError *extractError = nil; + NSData *fileData = [self extractData:fileInfo error:&extractError]; + if (!fileData) { + URKLogError("Error extracting %{public}@: %{public}@", fileInfo.filename, extractError); + *stop = YES; + return; + } + + uLong expectedCRC = fileInfo.CRC; + uLong actualCRC = crc32((uLong)0, (const Bytef*)fileData.bytes, (uint)fileData.length); + URKLogDebug("Checking integrity of %{public}@. Expected CRC: %010lu vs. Actual: %010lu", + fileInfo.filename, expectedCRC, actualCRC); + if (expectedCRC != actualCRC) { + corruptDataFound = YES; + URKLogError("Corrupt data found (filename: %{public}@, expected CRC: %010lu, actual CRC: %010lu", + fileInfo.filename, expectedCRC, actualCRC); + } + + if (filePath) *stop = YES; + } error:&performOnFilesError]; + + if (performOnFilesError) { + URKLogError("Error checking data integrity: %{public}@", performOnFilesError); + } + + return !corruptDataFound; +} #pragma mark - Callback Functions @@ -1278,7 +1327,7 @@ - (NSString *)errorNameForErrorCode:(NSInteger)errorCode detail:(NSString * __au case URKErrorCodeUserCancelled: errorName = @"ERAR_USER_CANCELLED"; detail = NSLocalizedStringFromTableInBundle(@"User cancelled the operation in progress", @"UnrarKit", _resources, @"Error detail string"); - break; + break; default: errorName = [NSString stringWithFormat:@"Unknown (%ld)", (long)errorCode]; @@ -1292,12 +1341,17 @@ - (NSString *)errorNameForErrorCode:(NSInteger)errorCode detail:(NSString * __au - (BOOL)assignError:(NSError * __autoreleasing *)error code:(NSInteger)errorCode errorName:(NSString * __autoreleasing *)outErrorName { - if (error) { - NSAssert(outErrorName, @"An out variable for errorName must be provided"); - - NSString *errorDetail = nil; - *outErrorName = [self errorNameForErrorCode:errorCode detail:&errorDetail]; + return [self assignError:error code:errorCode underlyer:nil errorName:outErrorName]; +} + +- (BOOL)assignError:(NSError * __autoreleasing *)error code:(NSInteger)errorCode underlyer:(NSError *)underlyingError errorName:(NSString * __autoreleasing *)outErrorName +{ + NSAssert(outErrorName, @"An out variable for errorName must be provided"); + + NSString *errorDetail = nil; + *outErrorName = [self errorNameForErrorCode:errorCode detail:&errorDetail]; + if (error) { NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithDictionary: @{NSLocalizedFailureReasonErrorKey: *outErrorName, NSLocalizedDescriptionKey: errorDetail, @@ -1307,6 +1361,10 @@ - (BOOL)assignError:(NSError * __autoreleasing *)error code:(NSInteger)errorCode userInfo[NSURLErrorKey] = self.fileURL; } + if (underlyingError) { + userInfo[NSUnderlyingErrorKey] = underlyingError; + } + *error = [NSError errorWithDomain:URKErrorDomain code:errorCode userInfo:userInfo]; @@ -1337,6 +1395,8 @@ - (NSProgress *)beginProgressOperation:(unsigned long long)totalUnitCount NSProgress *progress; progress = self.progress; + self.progress = nil; + if (!progress) { progress = [[NSProgress alloc] initWithParent:[NSProgress currentProgress] userInfo:nil]; diff --git a/README.md b/README.md index b5b5e874..c7bca0a2 100644 --- a/README.md +++ b/README.md @@ -61,19 +61,12 @@ if (archive.isPasswordProtected) { ```Objective-C BOOL extractFilesSuccessful = [archive extractFilesTo:@"some/directory" overWrite:NO - progress: - ^(URKFileInfo *currentFile, CGFloat percentArchiveDecompressed) { - NSLog(@"Extracting %@: %f%% complete", currentFile.filename, percentArchiveDecompressed); - } error:&error]; ``` ## Extracting a file into memory ```Objective-C NSData *extractedData = [archive extractDataFromFile:@"a file in the archive.jpg" - progress:^(CGFloat percentDecompressed) { - NSLog(@"Extracting, %f%% complete", percentDecompressed); - } error:&error]; ``` diff --git a/Tests/CheckDataTests.m b/Tests/CheckDataTests.m new file mode 100644 index 00000000..6228c67b --- /dev/null +++ b/Tests/CheckDataTests.m @@ -0,0 +1,94 @@ +// +// CheckDataTests.m +// UnrarKit +// +// Created by Dov Frankel on 10/6/17. +// + +#import "URKArchiveTestCase.h" + +@interface CheckDataTests : URKArchiveTestCase @end + +@implementation CheckDataTests + +#pragma mark - checkDataIntegrity + +- (void)testCheckDataIntegrity { + NSArray *testArchives = @[@"Test Archive.rar", + @"Test Archive (Password).rar", + @"Test Archive (Header Password).rar"]; + + for (NSString *testArchiveName in testArchives) { + NSLog(@"Testing data integrity of archive %@", testArchiveName); + NSURL *testArchiveURL = self.testFileURLs[testArchiveName]; + NSString *password = ([testArchiveName rangeOfString:@"Password"].location != NSNotFound + ? @"password" + : nil); + URKArchive *archive = [[URKArchive alloc] initWithURL:testArchiveURL password:password error:nil]; + + BOOL success = [archive checkDataIntegrity]; + XCTAssertTrue(success, @"Data integrity check failed for %@", testArchiveName); + } +} + +- (void)testCheckDataIntegrity_NotAnArchive { + NSURL *testArchiveURL = self.testFileURLs[@"Test File B.jpg"]; + URKArchive *archive = [[URKArchive alloc] initWithURL:testArchiveURL error:nil]; + + BOOL success = [archive checkDataIntegrity]; + XCTAssertFalse(success, @"Data integrity check passed for non-archive"); +} + +- (void)testCheckDataIntegrity_ModifiedCRC { + NSURL *testArchiveURL = self.testFileURLs[@"Modified CRC Archive.rar"]; + URKArchive *archive = [[URKArchive alloc] initWithURL:testArchiveURL error:nil]; + + BOOL success = [archive checkDataIntegrity]; + XCTAssertFalse(success, @"Data integrity check passed for archive with a modified CRC"); +} + +#pragma mark - checkDataIntegrityOfFile + +- (void)testCheckDataIntegrityForFile { + NSArray *testArchives = @[@"Test Archive.rar", + @"Test Archive (Password).rar", + @"Test Archive (Header Password).rar"]; + + for (NSString *testArchiveName in testArchives) { + NSLog(@"Testing data integrity of file in archive %@", testArchiveName); + NSURL *testArchiveURL = self.testFileURLs[testArchiveName]; + NSString *password = ([testArchiveName rangeOfString:@"Password"].location != NSNotFound + ? @"password" + : nil); + URKArchive *archive = [[URKArchive alloc] initWithURL:testArchiveURL password:password error:nil]; + + NSError *listFilenamesError = nil; + NSArray *filenames = [archive listFilenames:&listFilenamesError]; + + XCTAssertNotNil(filenames, @"No file info returned for %@", testArchiveName); + XCTAssertNil(listFilenamesError, @"Error returned for %@: %@", testArchiveName, listFilenamesError); + + NSString *firstFilename = filenames.firstObject; + BOOL success = [archive checkDataIntegrityOfFile:firstFilename]; + + XCTAssertTrue(success, @"Data integrity check failed for %@ in %@", firstFilename, testArchiveName); + } +} + +- (void)testCheckDataIntegrityForFile_NotAnArchive { + NSURL *testArchiveURL = self.testFileURLs[@"Test File B.jpg"]; + URKArchive *archive = [[URKArchive alloc] initWithURL:testArchiveURL error:nil]; + + BOOL success = [archive checkDataIntegrityOfFile:@"README.md"]; + XCTAssertFalse(success, @"Data integrity check passed for non-archive"); +} + +- (void)testCheckDataIntegrityForFile_ModifiedCRC { + NSURL *testArchiveURL = self.testFileURLs[@"Modified CRC Archive.rar"]; + URKArchive *archive = [[URKArchive alloc] initWithURL:testArchiveURL error:nil]; + + BOOL success = [archive checkDataIntegrityOfFile:@"README.md"]; + XCTAssertFalse(success, @"Data integrity check passed for archive with modified CRC"); +} + +@end diff --git a/Tests/ProgressReportingTests.m b/Tests/ProgressReportingTests.m index 347f2687..a0ba66a1 100644 --- a/Tests/ProgressReportingTests.m +++ b/Tests/ProgressReportingTests.m @@ -64,7 +64,7 @@ - (void)testProgressReporting_ExtractFiles_FractionCompleted overwrite:NO error:&extractError]; - XCTAssertNil(extractError, @"Error returned by extractFilesTo:overwrite:progress:error:"); + XCTAssertNil(extractError, @"Error returned by extractFilesTo:overwrite:error:"); XCTAssertTrue(success, @"Unrar failed to extract %@ to %@", testArchiveName, extractURL); [extractFilesProgress resignCurrent]; @@ -113,7 +113,7 @@ - (void)testProgressReporting_ExtractFiles_Description overwrite:NO error:&extractError]; - XCTAssertNil(extractError, @"Error returned by extractFilesTo:overwrite:progress:error:"); + XCTAssertNil(extractError, @"Error returned by extractFilesTo:overwrite:error:"); XCTAssertTrue(success, @"Unrar failed to extract %@ to %@", testArchiveName, extractURL); [extractFilesProgress removeObserver:self forKeyPath:observedSelector]; @@ -153,7 +153,7 @@ - (void)testProgressReporting_ExtractFiles_AdditionalDescription overwrite:NO error:&extractError]; - XCTAssertNil(extractError, @"Error returned by extractFilesTo:overwrite:progress:error:"); + XCTAssertNil(extractError, @"Error returned by extractFilesTo:overwrite:error:"); XCTAssertTrue(success, @"Unrar failed to extract %@ to %@", testArchiveName, extractURL); [extractFilesProgress removeObserver:self forKeyPath:observedSelector]; @@ -194,7 +194,7 @@ - (void)testProgressReporting_ExtractFiles_FileInfo overwrite:NO error:&extractError]; - XCTAssertNil(extractError, @"Error returned by extractFilesTo:overwrite:progress:error:"); + XCTAssertNil(extractError, @"Error returned by extractFilesTo:overwrite:error:"); XCTAssertTrue(success, @"Unrar failed to extract %@ to %@", testArchiveName, extractURL); [extractFilesProgress removeObserver:self forKeyPath:observedSelector]; @@ -321,7 +321,7 @@ - (void)testProgressCancellation_ExtractFiles { overwrite:NO error:&extractError]; - XCTAssertNotNil(extractError, @"Error not returned by extractFilesTo:overwrite:progress:error:"); + XCTAssertNotNil(extractError, @"Error not returned by extractFilesTo:overwrite:error:"); XCTAssertEqual(extractError.code, URKErrorCodeUserCancelled, @"Incorrect error code returned from user cancellation"); XCTAssertFalse(success, @"Unrar didn't cancel extraction"); @@ -365,7 +365,7 @@ - (void)testProgressReporting_ExtractData { NSError *extractError = nil; NSData *data = [archive extractDataFromFile:firstFile error:&extractError]; - XCTAssertNil(extractError, @"Error returned by extractDataFromFile:progress:error:"); + XCTAssertNil(extractError, @"Error returned by extractDataFromFile:error:"); XCTAssertNotNil(data, @"Unrar failed to extract large archive"); [extractFileProgress resignCurrent]; @@ -409,7 +409,7 @@ - (void)testProgressReporting_ExtractBufferedData { error:&extractError action:^(NSData * _Nonnull dataChunk, CGFloat percentDecompressed) {}]; - XCTAssertNil(extractError, @"Error returned by extractDataFromFile:progress:error:"); + XCTAssertNil(extractError, @"Error returned by extractDataFromFile:error:"); XCTAssertTrue(success, @"Unrar failed to extract large archive into buffer"); [extractFileProgress resignCurrent]; @@ -451,7 +451,7 @@ - (void)testProgressCancellation_ExtractData { NSError *extractError = nil; NSData *data = [archive extractDataFromFile:firstFile error:&extractError]; - XCTAssertNotNil(extractError, @"No error returned by cancelled extractDataFromFile:progress:error:"); + XCTAssertNotNil(extractError, @"No error returned by cancelled extractDataFromFile:error:"); XCTAssertEqual(extractError.code, URKErrorCodeUserCancelled, @"Incorrect error code returned from user cancellation"); XCTAssertNil(data, @"extractData didn't return nil when cancelled"); @@ -487,7 +487,7 @@ - (void)testProgressCancellation_ExtractBufferedData { blockCallCount++; }]; - XCTAssertNotNil(extractError, @"No error returned by cancelled extractDataFromFile:progress:error:"); + XCTAssertNotNil(extractError, @"No error returned by cancelled extractDataFromFile:error:"); XCTAssertEqual(extractError.code, URKErrorCodeUserCancelled, @"Incorrect error code returned from user cancellation"); XCTAssertFalse(success, @"extractBufferedData didn't return false when cancelled"); diff --git a/Tests/Test Data/Good CRC Archive.rar b/Tests/Test Data/Good CRC Archive.rar new file mode 100644 index 00000000..1c4a52e9 Binary files /dev/null and b/Tests/Test Data/Good CRC Archive.rar differ diff --git a/Tests/Test Data/Modified CRC Archive.rar b/Tests/Test Data/Modified CRC Archive.rar new file mode 100644 index 00000000..472fa449 Binary files /dev/null and b/Tests/Test Data/Modified CRC Archive.rar differ diff --git a/Tests/URKArchiveTestCase.m b/Tests/URKArchiveTestCase.m index 68d440b5..3172baa7 100644 --- a/Tests/URKArchiveTestCase.m +++ b/Tests/URKArchiveTestCase.m @@ -35,6 +35,7 @@ - (void)setUp @"Test Archive (RAR5, Password).rar", @"Test Archive (RAR5).rar", @"Folder Archive.rar", + @"Modified CRC Archive.rar", @"Test File A.txt", @"Test File B.jpg", @"Test File C.m4a", diff --git a/UnrarKit.podspec b/UnrarKit.podspec index 6a6c278c..2382be19 100644 --- a/UnrarKit.podspec +++ b/UnrarKit.podspec @@ -15,6 +15,7 @@ Pod::Spec.new do |s| s.resource_bundles = { 'UnrarKitResources' => ['Resources/**/*'] } + s.library = "z" s.subspec "unrar-lib" do |ss| ss.public_header_files = "Libraries/unrar/raros.hpp", diff --git a/UnrarKit.xcodeproj/project.pbxproj b/UnrarKit.xcodeproj/project.pbxproj index 817f1f07..de250af9 100644 --- a/UnrarKit.xcodeproj/project.pbxproj +++ b/UnrarKit.xcodeproj/project.pbxproj @@ -120,6 +120,7 @@ 7AC29ACD1F83DB0400DA4DE6 /* dll.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 96853F1A18DB722E00B5651B /* dll.hpp */; settings = {ATTRIBUTES = (Public, ); }; }; 7AC29ACE1F83DB1000DA4DE6 /* raros.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 96853F4E18DB722E00B5651B /* raros.hpp */; settings = {ATTRIBUTES = (Public, ); }; }; 7AC29AD01F850A6A00DA4DE6 /* UnrarKitMacros.h in Headers */ = {isa = PBXBuildFile; fileRef = 96BF58BA1F3A487100BC24E1 /* UnrarKitMacros.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 7ADC7A091F8831BC00023C2E /* CheckDataTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 7ADC7A081F8831BC00023C2E /* CheckDataTests.m */; }; 964C8AC718D28EE000AD7321 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 964C8AC518D28EE000AD7321 /* InfoPlist.strings */; }; 9660D7AF1A3F4FF90059AC1E /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 9660D7AE1A3F4FF90059AC1E /* libz.dylib */; }; 967872741E460FA70048A54C /* ListVolumesTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 967872731E460FA70048A54C /* ListVolumesTests.m */; }; @@ -170,6 +171,7 @@ 7A22B1F91F60A2D3004B8050 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/UnrarKit.strings; sourceTree = ""; }; 7A267F6D1F713B970004EAA6 /* ProgressReportingTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ProgressReportingTests.m; sourceTree = ""; }; 7AC29A5D1F83C08200DA4DE6 /* libunrar.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libunrar.a; sourceTree = BUILT_PRODUCTS_DIR; }; + 7ADC7A081F8831BC00023C2E /* CheckDataTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CheckDataTests.m; sourceTree = ""; }; 96370FB319ED8A8200DAF8F1 /* blake2s_sse.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = blake2s_sse.cpp; sourceTree = ""; }; 96370FB419ED8A8200DAF8F1 /* blake2s.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = blake2s.cpp; sourceTree = ""; }; 96370FB519ED8A8200DAF8F1 /* blake2s.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = blake2s.hpp; sourceTree = ""; }; @@ -440,6 +442,7 @@ 964547D01B384F7D00202B28 /* URKArchiveTestCase.h */, 964547D11B384F7D00202B28 /* URKArchiveTestCase.m */, 964C8AC818D28EE000AD7321 /* URKArchiveTests.m */, + 7ADC7A081F8831BC00023C2E /* CheckDataTests.m */, 96F4507A1B385BCD00679597 /* ExtractFilesTests.m */, 96A043DD1E4CC8D500BD7013 /* FirstVolumeTests.m */, 96A043DF1E4D232F00BD7013 /* HasMultipleVolumesTests.m */, @@ -945,6 +948,7 @@ 9699FA8A1B3D9B6F00B6D373 /* URKArchiveTestCase.m in Sources */, 96A043E01E4D232F00BD7013 /* HasMultipleVolumesTests.m in Sources */, 9699FA8D1B3D9B6F00B6D373 /* ListFilenamesTests.m in Sources */, + 7ADC7A091F8831BC00023C2E /* CheckDataTests.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1312,7 +1316,10 @@ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; MTL_ENABLE_DEBUG_INFO = YES; - OTHER_LDFLAGS = "-ObjC"; + OTHER_LDFLAGS = ( + "-ObjC", + "-lz", + ); PRODUCT_BUNDLE_IDENTIFIER = "com.abbey-code.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -1360,7 +1367,10 @@ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; MTL_ENABLE_DEBUG_INFO = NO; - OTHER_LDFLAGS = "-ObjC"; + OTHER_LDFLAGS = ( + "-ObjC", + "-lz", + ); PRODUCT_BUNDLE_IDENTIFIER = "com.abbey-code.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES;