diff --git a/SSZipArchive/SSZipArchive.h b/SSZipArchive/SSZipArchive.h index 3931bc4..8c4b8b2 100755 --- a/SSZipArchive/SSZipArchive.h +++ b/SSZipArchive/SSZipArchive.h @@ -20,6 +20,7 @@ NS_ASSUME_NONNULL_BEGIN // Password check + (BOOL)isFilePasswordProtectedAtPath:(NSString *)path; ++ (BOOL)isPasswordValidForArchiveAtPath:(NSString *)path password:(NSString *)pw error:(NSError * __nullable * __nullable)error NS_SWIFT_NOTHROW; // Unzip + (BOOL)unzipFileAtPath:(NSString *)path toDestination:(NSString *)destination; diff --git a/SSZipArchive/SSZipArchive.m b/SSZipArchive/SSZipArchive.m index 1b9d073..22009d4 100755 --- a/SSZipArchive/SSZipArchive.m +++ b/SSZipArchive/SSZipArchive.m @@ -59,6 +59,75 @@ return NO; } ++ (BOOL)isPasswordValidForArchiveAtPath:(NSString *)path password:(NSString *)pw error:(NSError **)error { + if (error) { + *error = nil; + } + + zipFile zip = unzOpen((const char*)[path UTF8String]); + if (zip == NULL) { + if (error) { + *error = [NSError errorWithDomain:@"SSZipArchiveErrorDomain" + code:-1 + userInfo:@{NSLocalizedDescriptionKey: @"failed to open zip file"}]; + } + return NO; + } + + int ret = unzGoToFirstFile(zip); + if (ret == UNZ_OK) { + do { + if ([pw length] == 0) { + ret = unzOpenCurrentFile(zip); + } else { + ret = unzOpenCurrentFilePassword(zip, [pw cStringUsingEncoding:NSASCIIStringEncoding]); + } + if (ret != UNZ_OK) { + if (ret != UNZ_BADPASSWORD) { + if (error) { + *error = [NSError errorWithDomain:@"SSZipArchiveErrorDomain" + code:-2 + userInfo:@{NSLocalizedDescriptionKey: @"failed to open first file in zip file"}]; + } + } + return NO; + } + unz_file_info fileInfo = {0}; + ret = unzGetCurrentFileInfo(zip, &fileInfo, NULL, 0, NULL, 0, NULL, 0); + if (ret != UNZ_OK) { + if (error) { + *error = [NSError errorWithDomain:@"SSZipArchiveErrorDomain" + code:-3 + userInfo:@{NSLocalizedDescriptionKey: @"failed to retrieve info for file"}]; + } + return NO; + } else if((fileInfo.flag & 1) == 1) { + unsigned char buffer[10] = {0}; + int readBytes = unzReadCurrentFile(zip, buffer, (unsigned)MIN(10UL,fileInfo.uncompressed_size)); + if (readBytes < 0) { + // Let's assume the invalid password caused this error + if (readBytes != Z_DATA_ERROR) { + if (error) { + *error = [NSError errorWithDomain:@"SSZipArchiveErrorDomain" + code:-4 + userInfo:@{NSLocalizedDescriptionKey: @"failed to read contents of file entry"}]; + } + } + return NO; + } + return YES; + } + + unzCloseCurrentFile(zip); + ret = unzGoToNextFile(zip); + } while (ret==UNZ_OK && UNZ_OK!=UNZ_END_OF_LIST_OF_FILE); + + } + + // No password required + return YES; +} + #pragma mark - Unzipping + (BOOL)unzipFileAtPath:(NSString *)path toDestination:(NSString *)destination @@ -304,77 +373,80 @@ } 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) { - if ([[[fullPath pathExtension] lowercaseString] isEqualToString:@"zip"]) { - NSLog(@"Unzipping nested .zip file: %@", [fullPath lastPathComponent]); - if ([self unzipFileAtPath:fullPath toDestination:[fullPath stringByDeletingLastPathComponent] overwrite:overwrite password:password error:nil delegate:nil ]) { - [[NSFileManager defaultManager] removeItemAtPath:fullPath error:nil]; + // ensure we are not creating stale file entries + int readBytes = unzReadCurrentFile(zip, buffer, 4096); + if (readBytes >= 0) { + FILE *fp = fopen((const char*)[fullPath UTF8String], "wb"); + while (fp) { + if (readBytes > 0) { + fwrite(buffer, readBytes, 1, fp ); + } else { + break; } + readBytes = unzReadCurrentFile(zip, buffer, 4096); } - - fclose(fp); - - if (preserveAttributes) { - - // Set the original datetime property - if (fileInfo.dosDate != 0) { - NSDate *orgDate = [[self class] _dateWithMSDOSFormat:(UInt32)fileInfo.dosDate]; - NSDictionary *attr = @{NSFileModificationDate: orgDate}; - if (attr) { - if ([fileManager setAttributes:attr ofItemAtPath:fullPath error:nil] == NO) { - // Can't set attributes - NSLog(@"[SSZipArchive] Failed to set attributes - whilst setting modification date"); + if (fp) { + if ([[[fullPath pathExtension] lowercaseString] isEqualToString:@"zip"]) { + NSLog(@"Unzipping nested .zip file: %@", [fullPath lastPathComponent]); + if ([self unzipFileAtPath:fullPath toDestination:[fullPath stringByDeletingLastPathComponent] overwrite:overwrite password:password error:nil delegate:nil ]) { + [[NSFileManager defaultManager] removeItemAtPath:fullPath error:nil]; + } + } + + fclose(fp); + + if (preserveAttributes) { + + // Set the original datetime property + if (fileInfo.dosDate != 0) { + NSDate *orgDate = [[self class] _dateWithMSDOSFormat:(UInt32)fileInfo.dosDate]; + NSDictionary *attr = @{NSFileModificationDate: orgDate}; + + if (attr) { + if ([fileManager setAttributes:attr ofItemAtPath:fullPath error:nil] == NO) { + // Can't set attributes + NSLog(@"[SSZipArchive] Failed to set attributes - whilst setting modification date"); + } } } - } - - // Set the original permissions on the file - uLong permissions = fileInfo.external_fa >> 16; - if (permissions != 0) { - // Store it into a NSNumber - NSNumber *permissionsValue = @(permissions); - // Retrieve any existing attributes - NSMutableDictionary *attrs = [[NSMutableDictionary alloc] initWithDictionary:[fileManager attributesOfItemAtPath:fullPath error:nil]]; + // Set the original permissions on the file + uLong permissions = fileInfo.external_fa >> 16; + if (permissions != 0) { + // Store it into a NSNumber + NSNumber *permissionsValue = @(permissions); - // Set the value in the attributes dict - attrs[NSFilePosixPermissions] = permissionsValue; + // Retrieve any existing attributes + NSMutableDictionary *attrs = [[NSMutableDictionary alloc] initWithDictionary:[fileManager attributesOfItemAtPath:fullPath error:nil]]; - // Update attributes - if ([fileManager setAttributes:attrs ofItemAtPath:fullPath error:nil] == NO) { - // Unable to set the permissions attribute - NSLog(@"[SSZipArchive] Failed to set attributes - whilst setting permissions"); - } + // Set the value in the attributes dict + attrs[NSFilePosixPermissions] = permissionsValue; + + // Update attributes + if ([fileManager setAttributes:attrs ofItemAtPath:fullPath error:nil] == NO) { + // Unable to set the permissions attribute + NSLog(@"[SSZipArchive] Failed to set attributes - whilst setting permissions"); + } #if !__has_feature(objc_arc) - [attrs release]; + [attrs release]; #endif + } } } - } - else - { - // if we couldn't open file descriptor we can validate global errno to see the reason - if (errno == ENOSPC) { - NSError *enospcError = [NSError errorWithDomain:NSPOSIXErrorDomain - code:ENOSPC - userInfo:nil]; - unzippingError = enospcError; - unzCloseCurrentFile(zip); - success = NO; - break; + else + { + // if we couldn't open file descriptor we can validate global errno to see the reason + if (errno == ENOSPC) { + NSError *enospcError = [NSError errorWithDomain:NSPOSIXErrorDomain + code:ENOSPC + userInfo:nil]; + unzippingError = enospcError; + unzCloseCurrentFile(zip); + success = NO; + break; + } } } } diff --git a/SSZipArchive/minizip/unzip.c b/SSZipArchive/minizip/unzip.c index 4b8eabc..35aaf88 100755 --- a/SSZipArchive/minizip/unzip.c +++ b/SSZipArchive/minizip/unzip.c @@ -1190,7 +1190,8 @@ extern int ZEXPORT unzOpenCurrentFile3(unzFile file, int *method, int *level, in return UNZ_INTERNALERROR; #ifdef HAVE_AES if (s->cur_file_info.compression_method == AES_METHOD) { - unsigned char passverify[AES_PWVERIFYSIZE]; + unsigned char passverify_archive[AES_PWVERIFYSIZE]; + unsigned char passverify_password[AES_PWVERIFYSIZE]; unsigned char saltvalue[AES_MAXSALTLENGTH]; uInt saltlength; @@ -1202,11 +1203,14 @@ extern int ZEXPORT unzOpenCurrentFile3(unzFile file, int *method, int *level, in if (ZREAD64(s->z_filefunc, s->filestream, saltvalue, saltlength) != saltlength) return UNZ_INTERNALERROR; - if (ZREAD64(s->z_filefunc, s->filestream, passverify, AES_PWVERIFYSIZE) != AES_PWVERIFYSIZE) + if (ZREAD64(s->z_filefunc, s->filestream, passverify_archive, AES_PWVERIFYSIZE) != AES_PWVERIFYSIZE) return UNZ_INTERNALERROR; - fcrypt_init((int)s->cur_file_info_internal.aes_encryption_mode, (unsigned char *)password, (unsigned int)strlen(password), saltvalue, - passverify, &s->pfile_in_zip_read->aes_ctx); + fcrypt_init(s->cur_file_info_internal.aes_encryption_mode, password, strlen(password), saltvalue, + passverify_password, &s->pfile_in_zip_read->aes_ctx); + + if (memcmp(passverify_archive, passverify_password, AES_PWVERIFYSIZE) != 0) + return UNZ_BADPASSWORD; pfile_in_zip_read_info->rest_read_compressed -= saltlength + AES_PWVERIFYSIZE; pfile_in_zip_read_info->rest_read_compressed -= AES_AUTHCODESIZE; diff --git a/SSZipArchive/minizip/unzip.h b/SSZipArchive/minizip/unzip.h index 7b614ff..d6954d3 100755 --- a/SSZipArchive/minizip/unzip.h +++ b/SSZipArchive/minizip/unzip.h @@ -57,6 +57,7 @@ typedef voidp unzFile; #define UNZ_BADZIPFILE (-103) #define UNZ_INTERNALERROR (-104) #define UNZ_CRCERROR (-105) +#define UNZ_BADPASSWORD (-106) /***************************************************************************/