Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
25 changes: 22 additions & 3 deletions Classes/URKArchive.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
82 changes: 71 additions & 11 deletions Classes/URKArchive.mm
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
#import "UnrarKitMacros.h"
#import "NSString+UnrarKit.h"

#import "zlib.h"

RarHppIgnore
#import "rar.hpp"
#pragma clang diagnostic pop
Expand Down Expand Up @@ -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;
Expand All @@ -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;
}
}];
}

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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];
Expand All @@ -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,
Expand All @@ -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];
Expand Down Expand Up @@ -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];
Expand Down
7 changes: 0 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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];
```

Expand Down
94 changes: 94 additions & 0 deletions Tests/CheckDataTests.m
Original file line number Diff line number Diff line change
@@ -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 <NSString*> *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
18 changes: 9 additions & 9 deletions Tests/ProgressReportingTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand Down Expand Up @@ -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];
Expand Down Expand Up @@ -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];
Expand Down Expand Up @@ -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];
Expand Down Expand Up @@ -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");

Expand Down Expand Up @@ -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];
Expand Down Expand Up @@ -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];
Expand Down Expand Up @@ -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");

Expand Down Expand Up @@ -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");

Expand Down
Binary file added Tests/Test Data/Good CRC Archive.rar
Binary file not shown.
Binary file added Tests/Test Data/Modified CRC Archive.rar
Binary file not shown.
Loading