diff --git a/ios/CodePush/CodePushSha256.h b/ios/CodePush/CodePushSha256.h new file mode 100644 index 00000000..dab213b9 --- /dev/null +++ b/ios/CodePush/CodePushSha256.h @@ -0,0 +1,12 @@ +#import + +NS_ASSUME_NONNULL_BEGIN + +// SHA-256 hex digest of a file's contents. +// Returns nil and sets *error if the file can't be opened/read. +NSString * _Nullable CodePushSha256HexForFile(NSString *filePath, NSError **error); + +// SHA-256 hex digest of an in-memory buffer. +NSString *CodePushSha256HexForData(NSData *data); + +NS_ASSUME_NONNULL_END diff --git a/ios/CodePush/CodePushSha256.m b/ios/CodePush/CodePushSha256.m new file mode 100644 index 00000000..3003c5b7 --- /dev/null +++ b/ios/CodePush/CodePushSha256.m @@ -0,0 +1,61 @@ +#import "CodePushSha256.h" +#include + +static NSString *const CodePushSha256ErrorDomain = @"CodePushSha256Error"; + +static NSString *hexStringForDigest(unsigned char digest[CC_SHA256_DIGEST_LENGTH]) +{ + NSMutableString *hex = [NSMutableString stringWithCapacity:CC_SHA256_DIGEST_LENGTH * 2]; + for (int i = 0; i < CC_SHA256_DIGEST_LENGTH; i++) { + [hex appendFormat:@"%02x", digest[i]]; + } + return hex; +} + +NSString *CodePushSha256HexForData(NSData *data) +{ + unsigned char digest[CC_SHA256_DIGEST_LENGTH]; + CC_SHA256(data.bytes, (CC_LONG)data.length, digest); + return hexStringForDigest(digest); +} + +NSString *CodePushSha256HexForFile(NSString *filePath, NSError **error) +{ + NSFileHandle *fileHandle = [NSFileHandle fileHandleForReadingAtPath:filePath]; + if (!fileHandle) { + if (error) { + *error = [NSError errorWithDomain:CodePushSha256ErrorDomain + code:1 + userInfo:@{ NSLocalizedDescriptionKey: [NSString stringWithFormat:@"Could not open file for reading: %@", filePath] }]; + } + return nil; + } + + CC_SHA256_CTX context; + CC_SHA256_Init(&context); + + static const NSUInteger kChunkSize = 1024 * 8; + NSError *readError; + while (YES) { + NSData *chunk = [fileHandle readDataUpToLength:kChunkSize error:&readError]; + if (readError) { + [fileHandle closeFile]; + if (error) { + *error = [NSError errorWithDomain:CodePushSha256ErrorDomain + code:2 + userInfo:@{ NSLocalizedDescriptionKey: [NSString stringWithFormat:@"Could not read file: %@", filePath], + NSUnderlyingErrorKey: readError }]; + } + return nil; + } + if (chunk.length == 0) { + break; + } + CC_SHA256_Update(&context, chunk.bytes, (CC_LONG)chunk.length); + } + [fileHandle closeFile]; + + unsigned char digest[CC_SHA256_DIGEST_LENGTH]; + CC_SHA256_Final(digest, &context); + return hexStringForDigest(digest); +} diff --git a/ios/CodePush/CodePushUpdateUtils.m b/ios/CodePush/CodePushUpdateUtils.m index e0f170b7..ceada205 100644 --- a/ios/CodePush/CodePushUpdateUtils.m +++ b/ios/CodePush/CodePushUpdateUtils.m @@ -1,5 +1,5 @@ #import "CodePush.h" -#include +#import "CodePushSha256.h" #import "JWT.h" @implementation CodePushUpdateUtils @@ -57,8 +57,10 @@ + (BOOL)addContentsOfFolderToManifest:(NSString *)folderPath return NO; } } else { - NSData *fileContents = [NSData dataWithContentsOfFile:fullFilePath]; - NSString *fileContentsHash = [self computeHashForData:fileContents]; + NSString *fileContentsHash = CodePushSha256HexForFile(fullFilePath, error); + if (!fileContentsHash) { + return NO; + } [manifest addObject:[[relativePath stringByAppendingString:@":"] stringByAppendingString:fileContentsHash]]; } } @@ -66,14 +68,18 @@ + (BOOL)addContentsOfFolderToManifest:(NSString *)folderPath return YES; } -+ (void)addFileToManifest:(NSURL *)fileURL ++ (BOOL)addFileToManifest:(NSURL *)fileURL manifest:(NSMutableArray *)manifest + error:(NSError **)error { if ([[NSFileManager defaultManager] fileExistsAtPath:[fileURL path]]) { - NSData *fileContents = [NSData dataWithContentsOfURL:fileURL]; - NSString *fileContentsHash = [self computeHashForData:fileContents]; + NSString *fileContentsHash = CodePushSha256HexForFile([fileURL path], error); + if (!fileContentsHash) { + return NO; + } [manifest addObject:[NSString stringWithFormat:@"%@/%@:%@", [self manifestFolderPrefix], [fileURL lastPathComponent], fileContentsHash]]; } + return YES; } + (NSString *)computeFinalHashFromManifest:(NSMutableArray *)manifest @@ -93,19 +99,7 @@ + (NSString *)computeFinalHashFromManifest:(NSMutableArray *)manifest // The JSON serialization turns path separators into "\/", e.g. "CodePush\/assets\/image.png" manifestString = [manifestString stringByReplacingOccurrencesOfString:@"\\/" withString:@"/"]; - return [self computeHashForData:[NSData dataWithBytes:manifestString.UTF8String length:[manifestString lengthOfBytesUsingEncoding:NSUTF8StringEncoding]]]; -} - -+ (NSString *)computeHashForData:(NSData *)inputData -{ - uint8_t digest[CC_SHA256_DIGEST_LENGTH]; - CC_SHA256(inputData.bytes, (CC_LONG)inputData.length, digest); - NSMutableString* inputHash = [NSMutableString stringWithCapacity:CC_SHA256_DIGEST_LENGTH * 2]; - for (int i = 0; i < CC_SHA256_DIGEST_LENGTH; i++) { - [inputHash appendFormat:@"%02x", digest[i]]; - } - - return inputHash; + return CodePushSha256HexForData([NSData dataWithBytes:manifestString.UTF8String length:[manifestString lengthOfBytesUsingEncoding:NSUTF8StringEncoding]]); } + (BOOL)copyEntriesInFolder:(NSString *)sourceFolder @@ -230,8 +224,12 @@ + (NSString *)getHashForBinaryContents:(NSURL *)binaryBundleUrl } } - [self addFileToManifest:binaryBundleUrl manifest:manifest]; - [self addFileToManifest:[binaryBundleUrl URLByAppendingPathExtension:@"meta"] manifest:manifest]; + if (![self addFileToManifest:binaryBundleUrl manifest:manifest error:error]) { + return nil; + } + if (![self addFileToManifest:[binaryBundleUrl URLByAppendingPathExtension:@"meta"] manifest:manifest error:error]) { + return nil; + } binaryHash = [self computeFinalHashFromManifest:manifest error:error];