diff --git a/CodePush.xcodeproj/project.pbxproj b/CodePush.xcodeproj/project.pbxproj index d3c086c..fcf5bcf 100644 --- a/CodePush.xcodeproj/project.pbxproj +++ b/CodePush.xcodeproj/project.pbxproj @@ -9,6 +9,7 @@ /* Begin PBXBuildFile section */ 13BE3DEE1AC21097009241FE /* CodePush.m in Sources */ = {isa = PBXBuildFile; fileRef = 13BE3DED1AC21097009241FE /* CodePush.m */; }; 1B23B9141BF9267B000BB2F0 /* RCTConvert+CodePushInstallMode.m in Sources */ = {isa = PBXBuildFile; fileRef = 1B23B9131BF9267B000BB2F0 /* RCTConvert+CodePushInstallMode.m */; }; + 540D20121C7684FE00D6EF41 /* CodePushUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 540D20111C7684FE00D6EF41 /* CodePushUtils.m */; }; 5421FE311C58AD5A00986A55 /* CodePushTelemetryManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 5421FE301C58AD5A00986A55 /* CodePushTelemetryManager.m */; }; 54A0026C1C0E2880004C3CEC /* aescrypt.c in Sources */ = {isa = PBXBuildFile; fileRef = 54A0024C1C0E2880004C3CEC /* aescrypt.c */; }; 54A0026D1C0E2880004C3CEC /* aeskey.c in Sources */ = {isa = PBXBuildFile; fileRef = 54A0024D1C0E2880004C3CEC /* aeskey.c */; }; @@ -43,10 +44,11 @@ /* Begin PBXFileReference section */ 134814201AA4EA6300B7C361 /* libCodePush.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libCodePush.a; sourceTree = BUILT_PRODUCTS_DIR; }; - 13BE3DEC1AC21097009241FE /* CodePush.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CodePush.h; sourceTree = ""; }; - 13BE3DED1AC21097009241FE /* CodePush.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CodePush.m; sourceTree = ""; }; - 1B23B9131BF9267B000BB2F0 /* RCTConvert+CodePushInstallMode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "RCTConvert+CodePushInstallMode.m"; sourceTree = ""; }; - 5421FE301C58AD5A00986A55 /* CodePushTelemetryManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CodePushTelemetryManager.m; sourceTree = ""; }; + 13BE3DEC1AC21097009241FE /* CodePush.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CodePush.h; path = iOS/CodePush.h; sourceTree = ""; }; + 13BE3DED1AC21097009241FE /* CodePush.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CodePush.m; path = iOS/CodePush.m; sourceTree = ""; }; + 1B23B9131BF9267B000BB2F0 /* RCTConvert+CodePushInstallMode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "RCTConvert+CodePushInstallMode.m"; path = "iOS/RCTConvert+CodePushInstallMode.m"; sourceTree = ""; }; + 540D20111C7684FE00D6EF41 /* CodePushUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CodePushUtils.m; path = iOS/CodePushUtils.m; sourceTree = ""; }; + 5421FE301C58AD5A00986A55 /* CodePushTelemetryManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CodePushTelemetryManager.m; path = iOS/CodePushTelemetryManager.m; sourceTree = ""; }; 54A0024A1C0E2880004C3CEC /* aes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = aes.h; sourceTree = ""; }; 54A0024B1C0E2880004C3CEC /* aes_via_ace.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = aes_via_ace.h; sourceTree = ""; }; 54A0024C1C0E2880004C3CEC /* aescrypt.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = aescrypt.c; sourceTree = ""; }; @@ -80,9 +82,9 @@ 54A002691C0E2880004C3CEC /* zip.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = zip.h; sourceTree = ""; }; 54A0026A1C0E2880004C3CEC /* SSZipArchive.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SSZipArchive.h; sourceTree = ""; }; 54A0026B1C0E2880004C3CEC /* SSZipArchive.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SSZipArchive.m; sourceTree = ""; }; - 54FFEDDF1BF550630061DD23 /* CodePushDownloadHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CodePushDownloadHandler.m; sourceTree = ""; }; - 810D4E6C1B96935000B397E9 /* CodePushPackage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CodePushPackage.m; sourceTree = ""; }; - 81D51F391B6181C2000DA084 /* CodePushConfig.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CodePushConfig.m; sourceTree = ""; }; + 54FFEDDF1BF550630061DD23 /* CodePushDownloadHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CodePushDownloadHandler.m; path = iOS/CodePushDownloadHandler.m; sourceTree = ""; }; + 810D4E6C1B96935000B397E9 /* CodePushPackage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CodePushPackage.m; path = iOS/CodePushPackage.m; sourceTree = ""; }; + 81D51F391B6181C2000DA084 /* CodePushConfig.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CodePushConfig.m; path = iOS/CodePushConfig.m; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -113,7 +115,8 @@ 54A0026A1C0E2880004C3CEC /* SSZipArchive.h */, 54A0026B1C0E2880004C3CEC /* SSZipArchive.m */, ); - path = SSZipArchive; + name = SSZipArchive; + path = iOS/SSZipArchive; sourceTree = ""; }; 54A002491C0E2880004C3CEC /* aes */ = { @@ -169,6 +172,7 @@ 810D4E6C1B96935000B397E9 /* CodePushPackage.m */, 5421FE301C58AD5A00986A55 /* CodePushTelemetryManager.m */, 81D51F391B6181C2000DA084 /* CodePushConfig.m */, + 540D20111C7684FE00D6EF41 /* CodePushUtils.m */, 13BE3DEC1AC21097009241FE /* CodePush.h */, 13BE3DED1AC21097009241FE /* CodePush.m */, 134814211AA4EA7D00B7C361 /* Products */, @@ -232,6 +236,7 @@ buildActionMask = 2147483647; files = ( 54A0026D1C0E2880004C3CEC /* aeskey.c in Sources */, + 540D20121C7684FE00D6EF41 /* CodePushUtils.m in Sources */, 54A0026E1C0E2880004C3CEC /* aestab.c in Sources */, 54A002761C0E2880004C3CEC /* mztools.c in Sources */, 54A002781C0E2880004C3CEC /* zip.c in Sources */, diff --git a/Examples/CodePushDemoApp/iOS/CodePushDemoApp.xcodeproj/project.pbxproj b/Examples/CodePushDemoApp/iOS/CodePushDemoApp.xcodeproj/project.pbxproj index 3eac84e..2e8d08a 100644 --- a/Examples/CodePushDemoApp/iOS/CodePushDemoApp.xcodeproj/project.pbxproj +++ b/Examples/CodePushDemoApp/iOS/CodePushDemoApp.xcodeproj/project.pbxproj @@ -684,7 +684,7 @@ "$(inherited)", /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, "$(SRCROOT)/../node_modules/react-native/React/**", - "$(SRCROOT)/../../..", + "$(SRCROOT)/../../../iOS/**", "$(SRCROOT)/../node_modules/react-native/Libraries/Text/", ); INFOPLIST_FILE = ../CodePushDemoAppTests/Info.plist; @@ -708,7 +708,7 @@ "$(inherited)", /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, "$(SRCROOT)/../node_modules/react-native/React/**", - "$(SRCROOT)/../../..", + "$(SRCROOT)/../../../iOS/**", "$(SRCROOT)/../node_modules/react-native/Libraries/Text/", ); INFOPLIST_FILE = ../CodePushDemoAppTests/Info.plist; @@ -727,7 +727,7 @@ "$(inherited)", /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, "$(SRCROOT)/../node_modules/react-native/React/**", - "$(SRCROOT)/../../..", + "$(SRCROOT)/../../../iOS/**", ); INFOPLIST_FILE = "$(SRCROOT)/CodePushDemoApp/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; @@ -747,7 +747,7 @@ "$(inherited)", /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, "$(SRCROOT)/../node_modules/react-native/React/**", - "$(SRCROOT)/../../..", + "$(SRCROOT)/../../../iOS/**", ); INFOPLIST_FILE = "$(SRCROOT)/CodePushDemoApp/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; diff --git a/CodePush.h b/iOS/CodePush.h similarity index 82% rename from CodePush.h rename to iOS/CodePush.h index 57e0770..4920700 100644 --- a/CodePush.h +++ b/iOS/CodePush.h @@ -104,6 +104,24 @@ failCallback:(void (^)(NSError *err))failCallback; @end +@interface CodePushUtils : NSObject + ++ (void)addContentsOfFolderToManifest:(NSString *)folderPath + pathPrefix:(NSString *)pathPrefix + manifest:(NSMutableArray *)manifest + error:(NSError **)error; ++ (NSString *)computeHash:(NSData *)inputData; ++ (void)copyEntriesInFolder:(NSString *)sourceFolder + destFolder:(NSString *)destFolder + error:(NSError **)error; ++ (NSString *)findMainBundleInFolder:(NSString *)folderPath + error:(NSError **)error; ++ (BOOL)verifyHashForZipUpdate:(NSString *)finalUpdateFolder + expectedHash:(NSString *)expectedHash + error:(NSError **)error; + +@end + typedef NS_ENUM(NSInteger, CodePushInstallMode) { CodePushInstallModeImmediate, CodePushInstallModeOnNextRestart, diff --git a/CodePush.m b/iOS/CodePush.m similarity index 100% rename from CodePush.m rename to iOS/CodePush.m diff --git a/CodePushConfig.m b/iOS/CodePushConfig.m similarity index 100% rename from CodePushConfig.m rename to iOS/CodePushConfig.m diff --git a/CodePushDownloadHandler.m b/iOS/CodePushDownloadHandler.m similarity index 100% rename from CodePushDownloadHandler.m rename to iOS/CodePushDownloadHandler.m diff --git a/CodePushPackage.m b/iOS/CodePushPackage.m similarity index 83% rename from CodePushPackage.m rename to iOS/CodePushPackage.m index 764e8e3..52d16f9 100644 --- a/CodePushPackage.m +++ b/iOS/CodePushPackage.m @@ -1,5 +1,6 @@ #import "CodePush.h" #import "SSZipArchive.h" +#include @implementation CodePushPackage @@ -154,7 +155,7 @@ NSString * const UnzippedFolderName = @"unzipped"; NSDictionary* jsonDict = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:error]; - + return jsonDict; } } @@ -203,7 +204,9 @@ NSString * const UnzippedFolderName = @"unzipped"; doneCallback:(void (^)())doneCallback failCallback:(void (^)(NSError *err))failCallback { - NSString *newPackageFolderPath = [self getPackageFolderPath:updatePackage[@"packageHash"]]; + NSString *newPackageHash = updatePackage[@"packageHash"]; + NSString *newPackageFolderPath = [self getPackageFolderPath:newPackageHash]; + NSString *newPackageMetadataPath = [newPackageFolderPath stringByAppendingPathComponent:@"app.json"]; NSError *error; if ([[NSFileManager defaultManager] fileExistsAtPath:newPackageFolderPath]) { @@ -288,18 +291,26 @@ NSString * const UnzippedFolderName = @"unzipped"; NSArray *deletedFiles = manifestJSON[@"deletedFiles"]; for (NSString *deletedFileName in deletedFiles) { [[NSFileManager defaultManager] removeItemAtPath:[newPackageFolderPath stringByAppendingPathComponent:deletedFileName] - error:&nonFailingError]; - - if (nonFailingError) { - NSLog(@"Error deleting file from current package: %@", nonFailingError); - nonFailingError = nil; + error:&error]; + if (error) { + failCallback(error); + return; } } } - [CodePushPackage copyEntriesInFolder:unzippedFolderPath - destFolder:newPackageFolderPath - error:&error]; + if ([[NSFileManager defaultManager] fileExistsAtPath:diffManifestFilePath]) { + [[NSFileManager defaultManager] removeItemAtPath:diffManifestFilePath + error:&error]; + if (error) { + failCallback(error); + return; + } + } + + [CodePushUtils copyEntriesInFolder:unzippedFolderPath + destFolder:newPackageFolderPath + error:&error]; if (error) { failCallback(error); return; @@ -312,8 +323,8 @@ NSString * const UnzippedFolderName = @"unzipped"; nonFailingError = nil; } - NSString *relativeBundlePath = [self findMainBundleInFolder:newPackageFolderPath - error:&error]; + NSString *relativeBundlePath = [CodePushUtils findMainBundleInFolder:newPackageFolderPath + error:&error]; if (error) { failCallback(error); return; @@ -347,6 +358,33 @@ NSString * const UnzippedFolderName = @"unzipped"; failCallback(error); return; } + + if ([[NSFileManager defaultManager] fileExistsAtPath:newPackageMetadataPath]) { + [[NSFileManager defaultManager] removeItemAtPath:newPackageMetadataPath + error:&error]; + if (error) { + failCallback(error); + return; + } + } + + if (![CodePushUtils verifyHashForZipUpdate:newPackageFolderPath + expectedHash:newPackageHash + error:&error]) { + if (error) { + failCallback(error); + return; + } + + error = [[NSError alloc] initWithDomain:CodePushErrorDomain + code:CodePushErrorCode + userInfo:@{ + NSLocalizedDescriptionKey: + NSLocalizedString(@"The update contents failed the data integrity check.", nil) + }]; + failCallback(error); + return; + } } else { [[NSFileManager defaultManager] createDirectoryAtPath:newPackageFolderPath withIntermediateDirectories:YES @@ -367,7 +405,7 @@ NSString * const UnzippedFolderName = @"unzipped"; NSString *packageJsonString = [[NSString alloc] initWithData:updateSerializedData encoding:NSUTF8StringEncoding]; - [packageJsonString writeToFile:[newPackageFolderPath stringByAppendingPathComponent:@"app.json"] + [packageJsonString writeToFile:newPackageMetadataPath atomically:YES encoding:NSUTF8StringEncoding error:&error]; @@ -377,93 +415,12 @@ NSString * const UnzippedFolderName = @"unzipped"; doneCallback(); } } - + failCallback:failCallback]; [downloadHandler download:updatePackage[@"downloadUrl"]]; } -+ (NSString *)findMainBundleInFolder:(NSString *)folderPath - error:(NSError **)error -{ - NSArray* folderFiles = [[NSFileManager defaultManager] - contentsOfDirectoryAtPath:folderPath - error:error]; - if (*error) { - return nil; - } - - for (NSString *fileName in folderFiles) { - NSString *fullFilePath = [folderPath stringByAppendingPathComponent:fileName]; - BOOL isDir = NO; - if ([[NSFileManager defaultManager] fileExistsAtPath:fullFilePath - isDirectory:&isDir] && isDir) { - NSString *mainBundlePathInFolder = [self findMainBundleInFolder:fullFilePath error:error]; - if (*error) { - return nil; - } - - if (mainBundlePathInFolder) { - return [fileName stringByAppendingPathComponent:mainBundlePathInFolder]; - } - } else if ([[fileName pathExtension] isEqualToString:@"bundle"] || - [[fileName pathExtension] isEqualToString:@"jsbundle"] || - [[fileName pathExtension] isEqualToString:@"js"]) { - return fileName; - } - } - - return nil; -} - - -+ (void)copyEntriesInFolder:(NSString *)sourceFolder - destFolder:(NSString *)destFolder - error:(NSError **)error - -{ - NSArray* files = [[NSFileManager defaultManager] - contentsOfDirectoryAtPath:sourceFolder - error:error]; - if (*error) { - return; - } - - for (NSString *fileName in files) { - NSString * fullFilePath = [sourceFolder stringByAppendingPathComponent:fileName]; - BOOL isDir = NO; - if ([[NSFileManager defaultManager] fileExistsAtPath:fullFilePath - isDirectory:&isDir] && isDir) { - NSString *nestedDestFolder = [destFolder stringByAppendingPathComponent:fileName]; - [self copyEntriesInFolder:fullFilePath - destFolder:nestedDestFolder - error:error]; - } else { - NSString *destFileName = [destFolder stringByAppendingPathComponent:fileName]; - if ([[NSFileManager defaultManager] fileExistsAtPath:destFileName]) { - [[NSFileManager defaultManager] removeItemAtPath:destFileName error:error]; - if (*error) { - return; - } - } - if (![[NSFileManager defaultManager] fileExistsAtPath:destFolder]) { - [[NSFileManager defaultManager] createDirectoryAtPath:destFolder - withIntermediateDirectories:YES - attributes:nil - error:error]; - if (*error) { - return; - } - } - - [[NSFileManager defaultManager] copyItemAtPath:fullFilePath toPath:destFileName error:error]; - if (*error) { - return; - } - } - } -} - + (void)installPackage:(NSDictionary *)updatePackage removePendingUpdate:(BOOL)removePendingUpdate error:(NSError **)error @@ -502,7 +459,7 @@ NSString * const UnzippedFolderName = @"unzipped"; } [info setValue:packageHash forKey:@"currentPackage"]; - + [self updateCurrentPackageInfo:info error:error]; } diff --git a/CodePushTelemetryManager.m b/iOS/CodePushTelemetryManager.m similarity index 100% rename from CodePushTelemetryManager.m rename to iOS/CodePushTelemetryManager.m diff --git a/iOS/CodePushUtils.m b/iOS/CodePushUtils.m new file mode 100644 index 0000000..c187862 --- /dev/null +++ b/iOS/CodePushUtils.m @@ -0,0 +1,156 @@ +#import "CodePush.h" +#include + +@implementation CodePushUtils + ++ (void)addContentsOfFolderToManifest:(NSString *)folderPath + pathPrefix:(NSString *)pathPrefix + manifest:(NSMutableArray *)manifest + error:(NSError **)error +{ + NSArray* folderFiles = [[NSFileManager defaultManager] + contentsOfDirectoryAtPath:folderPath + error:error]; + if (*error) { + return; + } + + for (NSString *fileName in folderFiles) { + NSString *fullFilePath = [folderPath stringByAppendingPathComponent:fileName]; + NSString *relativePath = [pathPrefix stringByAppendingPathComponent:fileName]; + BOOL isDir = NO; + if ([[NSFileManager defaultManager] fileExistsAtPath:fullFilePath + isDirectory:&isDir] && isDir) { + [self addContentsOfFolderToManifest:fullFilePath + pathPrefix:relativePath + manifest:manifest + error:error]; + if (*error) { + return; + } + } else { + NSData *fileContents = [NSData dataWithContentsOfFile:fullFilePath]; + NSString *fileContentsHash = [self computeHash:fileContents]; + [manifest addObject:[[relativePath stringByAppendingString:@":"] stringByAppendingString:fileContentsHash]]; + } + } +} + ++ (NSString *)computeHash:(NSData *)inputData +{ + uint8_t digest[CC_SHA256_DIGEST_LENGTH]; + CC_SHA256(inputData.bytes, 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; +} + ++ (void)copyEntriesInFolder:(NSString *)sourceFolder + destFolder:(NSString *)destFolder + error:(NSError **)error +{ + NSArray* files = [[NSFileManager defaultManager] + contentsOfDirectoryAtPath:sourceFolder + error:error]; + if (*error) { + return; + } + + for (NSString *fileName in files) { + NSString * fullFilePath = [sourceFolder stringByAppendingPathComponent:fileName]; + BOOL isDir = NO; + if ([[NSFileManager defaultManager] fileExistsAtPath:fullFilePath + isDirectory:&isDir] && isDir) { + NSString *nestedDestFolder = [destFolder stringByAppendingPathComponent:fileName]; + [self copyEntriesInFolder:fullFilePath + destFolder:nestedDestFolder + error:error]; + } else { + NSString *destFileName = [destFolder stringByAppendingPathComponent:fileName]; + if ([[NSFileManager defaultManager] fileExistsAtPath:destFileName]) { + [[NSFileManager defaultManager] removeItemAtPath:destFileName error:error]; + if (*error) { + return; + } + } + if (![[NSFileManager defaultManager] fileExistsAtPath:destFolder]) { + [[NSFileManager defaultManager] createDirectoryAtPath:destFolder + withIntermediateDirectories:YES + attributes:nil + error:error]; + if (*error) { + return; + } + } + + [[NSFileManager defaultManager] copyItemAtPath:fullFilePath toPath:destFileName error:error]; + if (*error) { + return; + } + } + } +} + ++ (NSString *)findMainBundleInFolder:(NSString *)folderPath + error:(NSError **)error +{ + NSArray* folderFiles = [[NSFileManager defaultManager] + contentsOfDirectoryAtPath:folderPath + error:error]; + if (*error) { + return nil; + } + + for (NSString *fileName in folderFiles) { + NSString *fullFilePath = [folderPath stringByAppendingPathComponent:fileName]; + BOOL isDir = NO; + if ([[NSFileManager defaultManager] fileExistsAtPath:fullFilePath + isDirectory:&isDir] && isDir) { + NSString *mainBundlePathInFolder = [self findMainBundleInFolder:fullFilePath error:error]; + if (*error) { + return nil; + } + + if (mainBundlePathInFolder) { + return [fileName stringByAppendingPathComponent:mainBundlePathInFolder]; + } + } else if ([[fileName pathExtension] isEqualToString:@"bundle"] || + [[fileName pathExtension] isEqualToString:@"jsbundle"] || + [[fileName pathExtension] isEqualToString:@"js"]) { + return fileName; + } + } + + return nil; +} + ++ (BOOL)verifyHashForZipUpdate:(NSString *)finalUpdateFolder + expectedHash:(NSString *)expectedHash + error:(NSError **)error +{ + NSMutableArray *updateContentsManifest = [NSMutableArray array]; + [self addContentsOfFolderToManifest:finalUpdateFolder + pathPrefix:@"" + manifest:updateContentsManifest + error:error]; + if (*error) { + return NO; + } + + NSArray *sortedUpdateContentsManifest = [updateContentsManifest sortedArrayUsingSelector:@selector(compare:)]; + NSData *updateContentsManifestData = [NSJSONSerialization dataWithJSONObject:sortedUpdateContentsManifest + options:kNilOptions + error:error]; + NSString *updateContentsManifestString = [[NSString alloc] initWithData:updateContentsManifestData + encoding:NSUTF8StringEncoding]; + // The JSON serialization turns path seperators into "\/", e.g. "CodePush\/assets\/image.png" + updateContentsManifestString = [updateContentsManifestString stringByReplacingOccurrencesOfString:@"\\/" + withString:@"/"]; + NSString *updateContentsManifestHash = [self computeHash:[NSData dataWithBytes:updateContentsManifestString.UTF8String length:updateContentsManifestString.length]]; + return [updateContentsManifestHash isEqualToString:expectedHash]; +} + +@end \ No newline at end of file diff --git a/RCTConvert+CodePushInstallMode.m b/iOS/RCTConvert+CodePushInstallMode.m similarity index 100% rename from RCTConvert+CodePushInstallMode.m rename to iOS/RCTConvert+CodePushInstallMode.m diff --git a/SSZipArchive/Common.h b/iOS/SSZipArchive/Common.h similarity index 100% rename from SSZipArchive/Common.h rename to iOS/SSZipArchive/Common.h diff --git a/SSZipArchive/README.md b/iOS/SSZipArchive/README.md similarity index 100% rename from SSZipArchive/README.md rename to iOS/SSZipArchive/README.md diff --git a/SSZipArchive/SSZipArchive.h b/iOS/SSZipArchive/SSZipArchive.h similarity index 100% rename from SSZipArchive/SSZipArchive.h rename to iOS/SSZipArchive/SSZipArchive.h diff --git a/SSZipArchive/SSZipArchive.m b/iOS/SSZipArchive/SSZipArchive.m similarity index 100% rename from SSZipArchive/SSZipArchive.m rename to iOS/SSZipArchive/SSZipArchive.m diff --git a/SSZipArchive/aes/aes.h b/iOS/SSZipArchive/aes/aes.h similarity index 100% rename from SSZipArchive/aes/aes.h rename to iOS/SSZipArchive/aes/aes.h diff --git a/SSZipArchive/aes/aes_via_ace.h b/iOS/SSZipArchive/aes/aes_via_ace.h similarity index 100% rename from SSZipArchive/aes/aes_via_ace.h rename to iOS/SSZipArchive/aes/aes_via_ace.h diff --git a/SSZipArchive/aes/aescrypt.c b/iOS/SSZipArchive/aes/aescrypt.c similarity index 100% rename from SSZipArchive/aes/aescrypt.c rename to iOS/SSZipArchive/aes/aescrypt.c diff --git a/SSZipArchive/aes/aeskey.c b/iOS/SSZipArchive/aes/aeskey.c similarity index 100% rename from SSZipArchive/aes/aeskey.c rename to iOS/SSZipArchive/aes/aeskey.c diff --git a/SSZipArchive/aes/aesopt.h b/iOS/SSZipArchive/aes/aesopt.h similarity index 100% rename from SSZipArchive/aes/aesopt.h rename to iOS/SSZipArchive/aes/aesopt.h diff --git a/SSZipArchive/aes/aestab.c b/iOS/SSZipArchive/aes/aestab.c similarity index 100% rename from SSZipArchive/aes/aestab.c rename to iOS/SSZipArchive/aes/aestab.c diff --git a/SSZipArchive/aes/aestab.h b/iOS/SSZipArchive/aes/aestab.h similarity index 100% rename from SSZipArchive/aes/aestab.h rename to iOS/SSZipArchive/aes/aestab.h diff --git a/SSZipArchive/aes/brg_endian.h b/iOS/SSZipArchive/aes/brg_endian.h similarity index 100% rename from SSZipArchive/aes/brg_endian.h rename to iOS/SSZipArchive/aes/brg_endian.h diff --git a/SSZipArchive/aes/brg_types.h b/iOS/SSZipArchive/aes/brg_types.h similarity index 100% rename from SSZipArchive/aes/brg_types.h rename to iOS/SSZipArchive/aes/brg_types.h diff --git a/SSZipArchive/aes/entropy.c b/iOS/SSZipArchive/aes/entropy.c similarity index 100% rename from SSZipArchive/aes/entropy.c rename to iOS/SSZipArchive/aes/entropy.c diff --git a/SSZipArchive/aes/entropy.h b/iOS/SSZipArchive/aes/entropy.h similarity index 100% rename from SSZipArchive/aes/entropy.h rename to iOS/SSZipArchive/aes/entropy.h diff --git a/SSZipArchive/aes/fileenc.c b/iOS/SSZipArchive/aes/fileenc.c similarity index 100% rename from SSZipArchive/aes/fileenc.c rename to iOS/SSZipArchive/aes/fileenc.c diff --git a/SSZipArchive/aes/fileenc.h b/iOS/SSZipArchive/aes/fileenc.h similarity index 100% rename from SSZipArchive/aes/fileenc.h rename to iOS/SSZipArchive/aes/fileenc.h diff --git a/SSZipArchive/aes/hmac.c b/iOS/SSZipArchive/aes/hmac.c similarity index 100% rename from SSZipArchive/aes/hmac.c rename to iOS/SSZipArchive/aes/hmac.c diff --git a/SSZipArchive/aes/hmac.h b/iOS/SSZipArchive/aes/hmac.h similarity index 100% rename from SSZipArchive/aes/hmac.h rename to iOS/SSZipArchive/aes/hmac.h diff --git a/SSZipArchive/aes/prng.c b/iOS/SSZipArchive/aes/prng.c similarity index 100% rename from SSZipArchive/aes/prng.c rename to iOS/SSZipArchive/aes/prng.c diff --git a/SSZipArchive/aes/prng.h b/iOS/SSZipArchive/aes/prng.h similarity index 100% rename from SSZipArchive/aes/prng.h rename to iOS/SSZipArchive/aes/prng.h diff --git a/SSZipArchive/aes/pwd2key.c b/iOS/SSZipArchive/aes/pwd2key.c similarity index 100% rename from SSZipArchive/aes/pwd2key.c rename to iOS/SSZipArchive/aes/pwd2key.c diff --git a/SSZipArchive/aes/pwd2key.h b/iOS/SSZipArchive/aes/pwd2key.h similarity index 100% rename from SSZipArchive/aes/pwd2key.h rename to iOS/SSZipArchive/aes/pwd2key.h diff --git a/SSZipArchive/aes/sha1.c b/iOS/SSZipArchive/aes/sha1.c similarity index 100% rename from SSZipArchive/aes/sha1.c rename to iOS/SSZipArchive/aes/sha1.c diff --git a/SSZipArchive/aes/sha1.h b/iOS/SSZipArchive/aes/sha1.h similarity index 100% rename from SSZipArchive/aes/sha1.h rename to iOS/SSZipArchive/aes/sha1.h diff --git a/SSZipArchive/minizip/crypt.h b/iOS/SSZipArchive/minizip/crypt.h similarity index 100% rename from SSZipArchive/minizip/crypt.h rename to iOS/SSZipArchive/minizip/crypt.h diff --git a/SSZipArchive/minizip/ioapi.c b/iOS/SSZipArchive/minizip/ioapi.c similarity index 100% rename from SSZipArchive/minizip/ioapi.c rename to iOS/SSZipArchive/minizip/ioapi.c diff --git a/SSZipArchive/minizip/ioapi.h b/iOS/SSZipArchive/minizip/ioapi.h similarity index 100% rename from SSZipArchive/minizip/ioapi.h rename to iOS/SSZipArchive/minizip/ioapi.h diff --git a/SSZipArchive/minizip/mztools.c b/iOS/SSZipArchive/minizip/mztools.c similarity index 100% rename from SSZipArchive/minizip/mztools.c rename to iOS/SSZipArchive/minizip/mztools.c diff --git a/SSZipArchive/minizip/mztools.h b/iOS/SSZipArchive/minizip/mztools.h similarity index 100% rename from SSZipArchive/minizip/mztools.h rename to iOS/SSZipArchive/minizip/mztools.h diff --git a/SSZipArchive/minizip/unzip.c b/iOS/SSZipArchive/minizip/unzip.c similarity index 100% rename from SSZipArchive/minizip/unzip.c rename to iOS/SSZipArchive/minizip/unzip.c diff --git a/SSZipArchive/minizip/unzip.h b/iOS/SSZipArchive/minizip/unzip.h similarity index 100% rename from SSZipArchive/minizip/unzip.h rename to iOS/SSZipArchive/minizip/unzip.h diff --git a/SSZipArchive/minizip/zip.c b/iOS/SSZipArchive/minizip/zip.c similarity index 100% rename from SSZipArchive/minizip/zip.c rename to iOS/SSZipArchive/minizip/zip.c diff --git a/SSZipArchive/minizip/zip.h b/iOS/SSZipArchive/minizip/zip.h similarity index 100% rename from SSZipArchive/minizip/zip.h rename to iOS/SSZipArchive/minizip/zip.h