diff --git a/Examples/CodePushDemoApp/iOS/CodePushDemoApp.xcodeproj/project.pbxproj b/Examples/CodePushDemoApp/iOS/CodePushDemoApp.xcodeproj/project.pbxproj index 3eac84e..1ee3b8b 100644 --- a/Examples/CodePushDemoApp/iOS/CodePushDemoApp.xcodeproj/project.pbxproj +++ b/Examples/CodePushDemoApp/iOS/CodePushDemoApp.xcodeproj/project.pbxproj @@ -21,13 +21,12 @@ 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; 13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; }; 146834051AC3E58100842450 /* libReact.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 146834041AC3E56700842450 /* libReact.a */; }; - 542D39D41C22CED700D4B648 /* libCodePush.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 81551E0F1B3B427200F5B9F1 /* libCodePush.a */; }; + 540D20251C76EA0400D6EF41 /* libCodePush.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 540D20241C76E4D300D6EF41 /* libCodePush.a */; }; 544161591B8BCA81000D9E25 /* libRCTText.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 832341B51AAA6A8300B99B32 /* libRCTText.a */; }; 5451ACBA1B86A5B600E2A7DF /* CheckForUpdateTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 5451ACB81B86A5B600E2A7DF /* CheckForUpdateTests.m */; }; 5451ACEC1B86E40A00E2A7DF /* libRCTTest.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5451ACEB1B86E34300E2A7DF /* libRCTTest.a */; }; 54D774BA1B87DAF800F2ABF8 /* InstallUpdateTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 54D774B91B87DAF800F2ABF8 /* InstallUpdateTests.m */; }; 54F5F2B41BF6B45D007C3CEA /* DownloadProgressTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 54F5F2B31BF6B45D007C3CEA /* DownloadProgressTests.m */; }; - 81551E1B1B3B428000F5B9F1 /* libCodePush.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 81551E0F1B3B427200F5B9F1 /* libCodePush.a */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -101,6 +100,13 @@ remoteGlobalIDString = 83CBBA2E1A601D0E00E9B192; remoteInfo = React; }; + 540D20231C76E4D300D6EF41 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 81551E0A1B3B427200F5B9F1 /* CodePush.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 134814201AA4EA6300B7C361; + remoteInfo = CodePush; + }; 5451ACEA1B86E34300E2A7DF /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 5451ACE61B86E34300E2A7DF /* RCTTest.xcodeproj */; @@ -115,13 +121,6 @@ remoteGlobalIDString = 134814201AA4EA6300B7C361; remoteInfo = RCTLinking; }; - 81551E0E1B3B427200F5B9F1 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 81551E0A1B3B427200F5B9F1 /* CodePush.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = 134814201AA4EA6300B7C361; - remoteInfo = CodePush; - }; 832341B41AAA6A8300B99B32 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */; @@ -155,7 +154,7 @@ 54D774B91B87DAF800F2ABF8 /* InstallUpdateTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = InstallUpdateTests.m; sourceTree = ""; }; 54F5F2B31BF6B45D007C3CEA /* DownloadProgressTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DownloadProgressTests.m; sourceTree = ""; }; 78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTLinking.xcodeproj; path = "../node_modules/react-native/Libraries/LinkingIOS/RCTLinking.xcodeproj"; sourceTree = ""; }; - 81551E0A1B3B427200F5B9F1 /* CodePush.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = CodePush.xcodeproj; path = ../../../CodePush.xcodeproj; sourceTree = ""; }; + 81551E0A1B3B427200F5B9F1 /* CodePush.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = CodePush.xcodeproj; path = ../../../ios/CodePush.xcodeproj; sourceTree = ""; }; 832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTText.xcodeproj; path = "../node_modules/react-native/Libraries/Text/RCTText.xcodeproj"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -164,7 +163,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 542D39D41C22CED700D4B648 /* libCodePush.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -172,6 +170,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 540D20251C76EA0400D6EF41 /* libCodePush.a in Frameworks */, 544161591B8BCA81000D9E25 /* libRCTText.a in Frameworks */, 5451ACEC1B86E40A00E2A7DF /* libRCTTest.a in Frameworks */, 146834051AC3E58100842450 /* libReact.a in Frameworks */, @@ -184,7 +183,6 @@ 139105C61AF99C1200B5F7CC /* libRCTSettings.a in Frameworks */, 00C302EA1ABCBA2D00DB3ED1 /* libRCTVibration.a in Frameworks */, 139FDEF61B0652A700C62182 /* libRCTWebSocket.a in Frameworks */, - 81551E1B1B3B428000F5B9F1 /* libCodePush.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -295,6 +293,14 @@ name = Products; sourceTree = ""; }; + 540D20201C76E4D300D6EF41 /* Products */ = { + isa = PBXGroup; + children = ( + 540D20241C76E4D300D6EF41 /* libCodePush.a */, + ); + name = Products; + sourceTree = ""; + }; 5451ACE71B86E34300E2A7DF /* Products */ = { isa = PBXGroup; children = ( @@ -311,14 +317,6 @@ name = Products; sourceTree = ""; }; - 81551E0B1B3B427200F5B9F1 /* Products */ = { - isa = PBXGroup; - children = ( - 81551E0F1B3B427200F5B9F1 /* libCodePush.a */, - ); - name = Products; - sourceTree = ""; - }; 832341AE1AAA6A7D00B99B32 /* Libraries */ = { isa = PBXGroup; children = ( @@ -435,7 +433,7 @@ projectDirPath = ""; projectReferences = ( { - ProductGroup = 81551E0B1B3B427200F5B9F1 /* Products */; + ProductGroup = 540D20201C76E4D300D6EF41 /* Products */; ProjectRef = 81551E0A1B3B427200F5B9F1 /* CodePush.xcodeproj */; }, { @@ -559,6 +557,13 @@ remoteRef = 146834031AC3E56700842450 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; + 540D20241C76E4D300D6EF41 /* libCodePush.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libCodePush.a; + remoteRef = 540D20231C76E4D300D6EF41 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; 5451ACEB1B86E34300E2A7DF /* libRCTTest.a */ = { isa = PBXReferenceProxy; fileType = archive.ar; @@ -573,13 +578,6 @@ remoteRef = 78C398B81ACF4ADC00677621 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; - 81551E0F1B3B427200F5B9F1 /* libCodePush.a */ = { - isa = PBXReferenceProxy; - fileType = archive.ar; - path = libCodePush.a; - remoteRef = 81551E0E1B3B427200F5B9F1 /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; 832341B51AAA6A8300B99B32 /* libRCTText.a */ = { isa = PBXReferenceProxy; fileType = archive.ar; @@ -684,7 +682,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 +706,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 +725,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 +745,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/android/app/src/main/java/com/microsoft/codepush/react/CodePushInvalidUpdateException.java b/android/app/src/main/java/com/microsoft/codepush/react/CodePushInvalidUpdateException.java index b678959..d048751 100644 --- a/android/app/src/main/java/com/microsoft/codepush/react/CodePushInvalidUpdateException.java +++ b/android/app/src/main/java/com/microsoft/codepush/react/CodePushInvalidUpdateException.java @@ -1,7 +1,7 @@ package com.microsoft.codepush.react; public class CodePushInvalidUpdateException extends RuntimeException { - public CodePushInvalidUpdateException() { - super("Update is invalid - no files with extension .bundle, .js or .jsbundle were found in the update package."); + public CodePushInvalidUpdateException(String message) { + super(message); } } diff --git a/android/app/src/main/java/com/microsoft/codepush/react/CodePushPackage.java b/android/app/src/main/java/com/microsoft/codepush/react/CodePushPackage.java index 3a9b550..60a9569 100644 --- a/android/app/src/main/java/com/microsoft/codepush/react/CodePushPackage.java +++ b/android/app/src/main/java/com/microsoft/codepush/react/CodePushPackage.java @@ -153,12 +153,13 @@ public class CodePushPackage { public void downloadPackage(Context applicationContext, ReadableMap updatePackage, DownloadProgressCallback progressCallback) throws IOException { - - String newPackageFolderPath = getPackageFolderPath(CodePushUtils.tryGetString(updatePackage, PACKAGE_HASH_KEY)); - if (FileUtils.fileAtPathExists(newPackageFolderPath)) { + String newUpdateHash = CodePushUtils.tryGetString(updatePackage, PACKAGE_HASH_KEY); + String newUpdateFolderPath = getPackageFolderPath(newUpdateHash); + String newUpdateMetadataPath = CodePushUtils.appendPathComponent(newUpdateFolderPath, PACKAGE_FILE_NAME); + if (FileUtils.fileAtPathExists(newUpdateFolderPath)) { // This removes any stale data in newPackageFolderPath that could have been left // uncleared due to a crash or error during the download or install process. - FileUtils.deleteDirectoryAtPath(newPackageFolderPath); + FileUtils.deleteDirectoryAtPath(newUpdateFolderPath); } String downloadUrlString = CodePushUtils.tryGetString(updatePackage, DOWNLOAD_URL_KEY); @@ -229,21 +230,33 @@ public class CodePushPackage { // Merge contents with current update based on the manifest String diffManifestFilePath = CodePushUtils.appendPathComponent(unzippedFolderPath, DIFF_MANIFEST_FILE_NAME); - if (FileUtils.fileAtPathExists(diffManifestFilePath)) { + boolean isDiffUpdate = FileUtils.fileAtPathExists(diffManifestFilePath); + if (isDiffUpdate) { String currentPackageFolderPath = getCurrentPackageFolderPath(); - CodePushUpdateUtils.copyNecessaryFilesFromCurrentPackage(diffManifestFilePath, currentPackageFolderPath, newPackageFolderPath); + CodePushUpdateUtils.copyNecessaryFilesFromCurrentPackage(diffManifestFilePath, currentPackageFolderPath, newUpdateFolderPath); + File diffManifestFile = new File(diffManifestFilePath); + diffManifestFile.delete(); } - FileUtils.copyDirectoryContents(unzippedFolderPath, newPackageFolderPath); + FileUtils.copyDirectoryContents(unzippedFolderPath, newUpdateFolderPath); FileUtils.deleteFileAtPathSilently(unzippedFolderPath); // For zip updates, we need to find the relative path to the jsBundle and save it in the // metadata so that we can find and run it easily the next time. - String relativeBundlePath = CodePushUpdateUtils.findJSBundleInUpdateContents(newPackageFolderPath); + String relativeBundlePath = CodePushUpdateUtils.findJSBundleInUpdateContents(newUpdateFolderPath); if (relativeBundlePath == null) { - throw new CodePushInvalidUpdateException(); + throw new CodePushInvalidUpdateException("Update is invalid - no files with extension .bundle, .js or .jsbundle were found in the update package."); } else { + if (FileUtils.fileAtPathExists(newUpdateMetadataPath)) { + File metadataFileFromOldUpdate = new File(newUpdateMetadataPath); + metadataFileFromOldUpdate.delete(); + } + + if (isDiffUpdate) { + CodePushUpdateUtils.verifyHashForDiffUpdate(newUpdateFolderPath, newUpdateHash); + } + JSONObject updatePackageJSON = CodePushUtils.convertReadableToJsonObject(updatePackage); try { updatePackageJSON.put(RELATIVE_BUNDLE_PATH_KEY, relativeBundlePath); @@ -257,12 +270,11 @@ public class CodePushPackage { } } else { // File is a jsbundle, move it to a folder with the packageHash as its name - FileUtils.moveFile(downloadFile, newPackageFolderPath, UPDATE_BUNDLE_FILE_NAME); + FileUtils.moveFile(downloadFile, newUpdateFolderPath, UPDATE_BUNDLE_FILE_NAME); } // Save metadata to the folder. - String bundlePath = CodePushUtils.appendPathComponent(newPackageFolderPath, PACKAGE_FILE_NAME); - CodePushUtils.writeReadableMapToFile(updatePackage, bundlePath); + CodePushUtils.writeReadableMapToFile(updatePackage, newUpdateMetadataPath); } public void installPackage(ReadableMap updatePackage, boolean removePendingUpdate) throws IOException { diff --git a/android/app/src/main/java/com/microsoft/codepush/react/CodePushUpdateUtils.java b/android/app/src/main/java/com/microsoft/codepush/react/CodePushUpdateUtils.java index 8e43847..d8c38ff 100644 --- a/android/app/src/main/java/com/microsoft/codepush/react/CodePushUpdateUtils.java +++ b/android/app/src/main/java/com/microsoft/codepush/react/CodePushUpdateUtils.java @@ -1,13 +1,69 @@ package com.microsoft.codepush.react; +import android.util.Base64; + import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.WritableMap; +import org.json.JSONArray; + +import java.io.ByteArrayInputStream; import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.io.IOException; +import java.io.InputStream; +import java.security.DigestInputStream; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.Collections; public class CodePushUpdateUtils { + private static void addContentsOfFolderToManifest(String folderPath, String pathPrefix, ArrayList manifest) { + File folder = new File(folderPath); + File[] folderFiles = folder.listFiles(); + for (File file : folderFiles) { + String fullFilePath = file.getAbsolutePath(); + String relativePath = (pathPrefix.isEmpty() ? "" : (pathPrefix + "/")) + file.getName(); + if (file.isDirectory()) { + addContentsOfFolderToManifest(fullFilePath, relativePath, manifest); + } else { + try { + manifest.add(relativePath + ":" + computeHash(new FileInputStream(file))); + } catch (FileNotFoundException e) { + // Should not happen. + throw new CodePushUnknownException("Unable to compute hash of update contents.", e); + } + } + } + } + + private static String computeHash(InputStream dataStream) { + MessageDigest messageDigest = null; + DigestInputStream digestInputStream = null; + try { + messageDigest = MessageDigest.getInstance("SHA-256"); + digestInputStream = new DigestInputStream(dataStream, messageDigest); + byte[] byteBuffer = new byte[1024 * 8]; + while (digestInputStream.read(byteBuffer) != -1); + } catch (NoSuchAlgorithmException | IOException e) { + // Should not happen. + throw new CodePushUnknownException("Unable to compute hash of update contents.", e); + } finally { + try { + if (digestInputStream != null) digestInputStream.close(); + if (dataStream != null) dataStream.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + byte[] hash = messageDigest.digest(); + return Base64.encodeToString(hash, 0, hash.length, 0); + } + public static void copyNecessaryFilesFromCurrentPackage(String diffManifestFilePath, String currentPackageFolderPath, String newPackageFolderPath) throws IOException{ FileUtils.copyDirectoryContents(currentPackageFolderPath, newPackageFolderPath); WritableMap diffManifest = CodePushUtils.getWritableMapFromFile(diffManifestFilePath); @@ -15,7 +71,9 @@ public class CodePushUpdateUtils { for (int i = 0; i < deletedFiles.size(); i++) { String fileNameToDelete = deletedFiles.getString(i); File fileToDelete = new File(newPackageFolderPath, fileNameToDelete); - FileUtils.deleteFileOrFolderSilently(fileToDelete); + if (fileToDelete.exists()) { + fileToDelete.delete(); + } } } @@ -43,4 +101,21 @@ public class CodePushUpdateUtils { return null; } + + public static void verifyHashForDiffUpdate(String folderPath, String expectedHash) { + ArrayList updateContentsManifest = new ArrayList(); + addContentsOfFolderToManifest(folderPath, "", updateContentsManifest); + Collections.sort(updateContentsManifest); + JSONArray updateContentsJSONArray = new JSONArray(); + for (String manifestEntry : updateContentsManifest) { + updateContentsJSONArray.put(manifestEntry); + } + + // The JSON serialization turns path separators into "\/", e.g. "CodePush\/assets\/image.png" + String updateContentsManifestString = updateContentsJSONArray.toString().replace("\\/", "/"); + String updateContentsManifestHash = computeHash(new ByteArrayInputStream(updateContentsManifestString.getBytes())); + if (!expectedHash.equals(updateContentsManifestHash)) { + throw new CodePushInvalidUpdateException("The update contents failed the data integrity check."); + } + } } diff --git a/CodePush.xcodeproj/project.pbxproj b/ios/CodePush.xcodeproj/project.pbxproj similarity index 92% rename from CodePush.xcodeproj/project.pbxproj rename to ios/CodePush.xcodeproj/project.pbxproj index d3c086c..3eee8ce 100644 --- a/CodePush.xcodeproj/project.pbxproj +++ b/ios/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 /* CodePushUpdateUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 540D20111C7684FE00D6EF41 /* CodePushUpdateUtils.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 = CodePush/CodePush.h; sourceTree = ""; }; + 13BE3DED1AC21097009241FE /* CodePush.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CodePush.m; path = CodePush/CodePush.m; sourceTree = ""; }; + 1B23B9131BF9267B000BB2F0 /* RCTConvert+CodePushInstallMode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "RCTConvert+CodePushInstallMode.m"; path = "CodePush/RCTConvert+CodePushInstallMode.m"; sourceTree = ""; }; + 540D20111C7684FE00D6EF41 /* CodePushUpdateUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CodePushUpdateUtils.m; path = CodePush/CodePushUpdateUtils.m; sourceTree = ""; }; + 5421FE301C58AD5A00986A55 /* CodePushTelemetryManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CodePushTelemetryManager.m; path = CodePush/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 = CodePush/CodePushDownloadHandler.m; sourceTree = ""; }; + 810D4E6C1B96935000B397E9 /* CodePushPackage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CodePushPackage.m; path = CodePush/CodePushPackage.m; sourceTree = ""; }; + 81D51F391B6181C2000DA084 /* CodePushConfig.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CodePushConfig.m; path = CodePush/CodePushConfig.m; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -113,7 +115,8 @@ 54A0026A1C0E2880004C3CEC /* SSZipArchive.h */, 54A0026B1C0E2880004C3CEC /* SSZipArchive.m */, ); - path = SSZipArchive; + name = SSZipArchive; + path = CodePush/SSZipArchive; sourceTree = ""; }; 54A002491C0E2880004C3CEC /* aes */ = { @@ -169,6 +172,7 @@ 810D4E6C1B96935000B397E9 /* CodePushPackage.m */, 5421FE301C58AD5A00986A55 /* CodePushTelemetryManager.m */, 81D51F391B6181C2000DA084 /* CodePushConfig.m */, + 540D20111C7684FE00D6EF41 /* CodePushUpdateUtils.m */, 13BE3DEC1AC21097009241FE /* CodePush.h */, 13BE3DED1AC21097009241FE /* CodePush.m */, 134814211AA4EA7D00B7C361 /* Products */, @@ -232,6 +236,7 @@ buildActionMask = 2147483647; files = ( 54A0026D1C0E2880004C3CEC /* aeskey.c in Sources */, + 540D20121C7684FE00D6EF41 /* CodePushUpdateUtils.m in Sources */, 54A0026E1C0E2880004C3CEC /* aestab.c in Sources */, 54A002761C0E2880004C3CEC /* mztools.c in Sources */, 54A002781C0E2880004C3CEC /* zip.c in Sources */, @@ -337,9 +342,8 @@ HEADER_SEARCH_PATHS = ( "$(inherited)", /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, - "$(SRCROOT)/node_modules/react-native/React/**", - "$(SRCROOT)/Examples/CodePushDemoApp/node_modules/react-native/React/**", - "$(SRCROOT)/../react-native/React/**", + "$(SRCROOT)/../node_modules/react-native/React/**", + "$(SRCROOT)/../Examples/CodePushDemoApp/node_modules/react-native/React/**", ); LIBRARY_SEARCH_PATHS = "$(inherited)"; OTHER_LDFLAGS = ( @@ -357,9 +361,8 @@ HEADER_SEARCH_PATHS = ( "$(inherited)", /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, - "$(SRCROOT)/node_modules/react-native/React/**", - "$(SRCROOT)/Examples/CodePushDemoApp/node_modules/react-native/React/**", - "$(SRCROOT)/../react-native/React/**", + "$(SRCROOT)/../node_modules/react-native/React/**", + "$(SRCROOT)/../Examples/CodePushDemoApp/node_modules/react-native/React/**", ); LIBRARY_SEARCH_PATHS = "$(inherited)"; OTHER_LDFLAGS = ( diff --git a/CodePush.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/ios/CodePush.xcodeproj/project.xcworkspace/contents.xcworkspacedata similarity index 100% rename from CodePush.xcodeproj/project.xcworkspace/contents.xcworkspacedata rename to ios/CodePush.xcodeproj/project.xcworkspace/contents.xcworkspacedata diff --git a/CodePush.h b/ios/CodePush/CodePush.h similarity index 88% rename from CodePush.h rename to ios/CodePush/CodePush.h index 57e0770..5584325 100644 --- a/CodePush.h +++ b/ios/CodePush/CodePush.h @@ -104,6 +104,19 @@ failCallback:(void (^)(NSError *err))failCallback; @end +@interface CodePushUpdateUtils : NSObject + ++ (void)copyEntriesInFolder:(NSString *)sourceFolder + destFolder:(NSString *)destFolder + error:(NSError **)error; ++ (NSString *)findMainBundleInFolder:(NSString *)folderPath + error:(NSError **)error; ++ (BOOL)verifyHashForDiffUpdate:(NSString *)finalUpdateFolder + expectedHash:(NSString *)expectedHash + error:(NSError **)error; + +@end + typedef NS_ENUM(NSInteger, CodePushInstallMode) { CodePushInstallModeImmediate, CodePushInstallModeOnNextRestart, diff --git a/CodePush.m b/ios/CodePush/CodePush.m similarity index 100% rename from CodePush.m rename to ios/CodePush/CodePush.m diff --git a/CodePushConfig.m b/ios/CodePush/CodePushConfig.m similarity index 100% rename from CodePushConfig.m rename to ios/CodePush/CodePushConfig.m diff --git a/CodePushDownloadHandler.m b/ios/CodePush/CodePushDownloadHandler.m similarity index 100% rename from CodePushDownloadHandler.m rename to ios/CodePush/CodePushDownloadHandler.m diff --git a/CodePushPackage.m b/ios/CodePush/CodePushPackage.m similarity index 80% rename from CodePushPackage.m rename to ios/CodePush/CodePushPackage.m index 764e8e3..d5f4d4f 100644 --- a/CodePushPackage.m +++ b/ios/CodePush/CodePushPackage.m @@ -203,13 +203,15 @@ NSString * const UnzippedFolderName = @"unzipped"; doneCallback:(void (^)())doneCallback failCallback:(void (^)(NSError *err))failCallback { - NSString *newPackageFolderPath = [self getPackageFolderPath:updatePackage[@"packageHash"]]; + NSString *newUpdateHash = updatePackage[@"packageHash"]; + NSString *newUpdateFolderPath = [self getPackageFolderPath:newUpdateHash]; + NSString *newUpdateMetadataPath = [newUpdateFolderPath stringByAppendingPathComponent:@"app.json"]; NSError *error; - if ([[NSFileManager defaultManager] fileExistsAtPath:newPackageFolderPath]) { + if ([[NSFileManager defaultManager] fileExistsAtPath:newUpdateFolderPath]) { // This removes any stale data in newPackageFolderPath that could have been left // uncleared due to a crash or error during the download or install process. - [[NSFileManager defaultManager] removeItemAtPath:newPackageFolderPath + [[NSFileManager defaultManager] removeItemAtPath:newUpdateFolderPath error:&error]; } else if (![[NSFileManager defaultManager] fileExistsAtPath:[self getCodePushPath]]) { [[NSFileManager defaultManager] createDirectoryAtPath:[self getCodePushPath] @@ -223,7 +225,7 @@ NSString * const UnzippedFolderName = @"unzipped"; } NSString *downloadFilePath = [self getDownloadFilePath]; - NSString *bundleFilePath = [newPackageFolderPath stringByAppendingPathComponent:UpdateBundleFileName]; + NSString *bundleFilePath = [newUpdateFolderPath stringByAppendingPathComponent:UpdateBundleFileName]; CodePushDownloadHandler *downloadHandler = [[CodePushDownloadHandler alloc] init:downloadFilePath @@ -255,8 +257,9 @@ NSString * const UnzippedFolderName = @"unzipped"; } NSString *diffManifestFilePath = [unzippedFolderPath stringByAppendingPathComponent:DiffManifestFileName]; + BOOL isDiffUpdate = [[NSFileManager defaultManager] fileExistsAtPath:diffManifestFilePath]; - if ([[NSFileManager defaultManager] fileExistsAtPath:diffManifestFilePath]) { + if (isDiffUpdate) { // Copy the current package to the new package. NSString *currentPackageFolderPath = [self getCurrentPackageFolderPath:&error]; if (error) { @@ -265,7 +268,7 @@ NSString * const UnzippedFolderName = @"unzipped"; } [[NSFileManager defaultManager] copyItemAtPath:currentPackageFolderPath - toPath:newPackageFolderPath + toPath:newUpdateFolderPath error:&error]; if (error) { failCallback(error); @@ -287,19 +290,28 @@ NSString * const UnzippedFolderName = @"unzipped"; error:&error]; 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; + NSString *absoluteDeletedFilePath = [newUpdateFolderPath stringByAppendingPathComponent:deletedFileName]; + if ([[NSFileManager defaultManager] fileExistsAtPath:absoluteDeletedFilePath]) { + [[NSFileManager defaultManager] removeItemAtPath:absoluteDeletedFilePath + error:&error]; + if (error) { + failCallback(error); + return; + } } } + + [[NSFileManager defaultManager] removeItemAtPath:diffManifestFilePath + error:&error]; + if (error) { + failCallback(error); + return; + } } - [CodePushPackage copyEntriesInFolder:unzippedFolderPath - destFolder:newPackageFolderPath - error:&error]; + [CodePushUpdateUtils copyEntriesInFolder:unzippedFolderPath + destFolder:newUpdateFolderPath + error:&error]; if (error) { failCallback(error); return; @@ -312,15 +324,15 @@ NSString * const UnzippedFolderName = @"unzipped"; nonFailingError = nil; } - NSString *relativeBundlePath = [self findMainBundleInFolder:newPackageFolderPath - error:&error]; + NSString *relativeBundlePath = [CodePushUpdateUtils findMainBundleInFolder:newUpdateFolderPath + error:&error]; if (error) { failCallback(error); return; } if (relativeBundlePath) { - NSString *absoluteBundlePath = [newPackageFolderPath stringByAppendingPathComponent:relativeBundlePath]; + NSString *absoluteBundlePath = [newUpdateFolderPath stringByAppendingPathComponent:relativeBundlePath]; NSDictionary *bundleFileAttributes = [[[NSFileManager defaultManager] attributesOfItemAtPath:absoluteBundlePath error:&error] mutableCopy]; if (error) { failCallback(error); @@ -347,8 +359,35 @@ NSString * const UnzippedFolderName = @"unzipped"; failCallback(error); return; } + + if ([[NSFileManager defaultManager] fileExistsAtPath:newUpdateMetadataPath]) { + [[NSFileManager defaultManager] removeItemAtPath:newUpdateMetadataPath + error:&error]; + if (error) { + failCallback(error); + return; + } + } + + if (isDiffUpdate && ![CodePushUpdateUtils verifyHashForDiffUpdate:newUpdateFolderPath + expectedHash:newUpdateHash + 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 + [[NSFileManager defaultManager] createDirectoryAtPath:newUpdateFolderPath withIntermediateDirectories:YES attributes:nil error:&error]; @@ -367,7 +406,7 @@ NSString * const UnzippedFolderName = @"unzipped"; NSString *packageJsonString = [[NSString alloc] initWithData:updateSerializedData encoding:NSUTF8StringEncoding]; - [packageJsonString writeToFile:[newPackageFolderPath stringByAppendingPathComponent:@"app.json"] + [packageJsonString writeToFile:newUpdateMetadataPath atomically:YES encoding:NSUTF8StringEncoding error:&error]; @@ -383,87 +422,6 @@ NSString * const UnzippedFolderName = @"unzipped"; [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 diff --git a/CodePushTelemetryManager.m b/ios/CodePush/CodePushTelemetryManager.m similarity index 100% rename from CodePushTelemetryManager.m rename to ios/CodePush/CodePushTelemetryManager.m diff --git a/ios/CodePush/CodePushUpdateUtils.m b/ios/CodePush/CodePushUpdateUtils.m new file mode 100644 index 0000000..d48e95d --- /dev/null +++ b/ios/CodePush/CodePushUpdateUtils.m @@ -0,0 +1,156 @@ +#import "CodePush.h" +#include + +@implementation CodePushUpdateUtils + ++ (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)verifyHashForDiffUpdate:(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 separators 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/CodePush/RCTConvert+CodePushInstallMode.m similarity index 100% rename from RCTConvert+CodePushInstallMode.m rename to ios/CodePush/RCTConvert+CodePushInstallMode.m diff --git a/SSZipArchive/Common.h b/ios/CodePush/SSZipArchive/Common.h similarity index 100% rename from SSZipArchive/Common.h rename to ios/CodePush/SSZipArchive/Common.h diff --git a/SSZipArchive/README.md b/ios/CodePush/SSZipArchive/README.md similarity index 100% rename from SSZipArchive/README.md rename to ios/CodePush/SSZipArchive/README.md diff --git a/SSZipArchive/SSZipArchive.h b/ios/CodePush/SSZipArchive/SSZipArchive.h similarity index 100% rename from SSZipArchive/SSZipArchive.h rename to ios/CodePush/SSZipArchive/SSZipArchive.h diff --git a/SSZipArchive/SSZipArchive.m b/ios/CodePush/SSZipArchive/SSZipArchive.m similarity index 100% rename from SSZipArchive/SSZipArchive.m rename to ios/CodePush/SSZipArchive/SSZipArchive.m diff --git a/SSZipArchive/aes/aes.h b/ios/CodePush/SSZipArchive/aes/aes.h similarity index 100% rename from SSZipArchive/aes/aes.h rename to ios/CodePush/SSZipArchive/aes/aes.h diff --git a/SSZipArchive/aes/aes_via_ace.h b/ios/CodePush/SSZipArchive/aes/aes_via_ace.h similarity index 100% rename from SSZipArchive/aes/aes_via_ace.h rename to ios/CodePush/SSZipArchive/aes/aes_via_ace.h diff --git a/SSZipArchive/aes/aescrypt.c b/ios/CodePush/SSZipArchive/aes/aescrypt.c similarity index 100% rename from SSZipArchive/aes/aescrypt.c rename to ios/CodePush/SSZipArchive/aes/aescrypt.c diff --git a/SSZipArchive/aes/aeskey.c b/ios/CodePush/SSZipArchive/aes/aeskey.c similarity index 100% rename from SSZipArchive/aes/aeskey.c rename to ios/CodePush/SSZipArchive/aes/aeskey.c diff --git a/SSZipArchive/aes/aesopt.h b/ios/CodePush/SSZipArchive/aes/aesopt.h similarity index 100% rename from SSZipArchive/aes/aesopt.h rename to ios/CodePush/SSZipArchive/aes/aesopt.h diff --git a/SSZipArchive/aes/aestab.c b/ios/CodePush/SSZipArchive/aes/aestab.c similarity index 100% rename from SSZipArchive/aes/aestab.c rename to ios/CodePush/SSZipArchive/aes/aestab.c diff --git a/SSZipArchive/aes/aestab.h b/ios/CodePush/SSZipArchive/aes/aestab.h similarity index 100% rename from SSZipArchive/aes/aestab.h rename to ios/CodePush/SSZipArchive/aes/aestab.h diff --git a/SSZipArchive/aes/brg_endian.h b/ios/CodePush/SSZipArchive/aes/brg_endian.h similarity index 100% rename from SSZipArchive/aes/brg_endian.h rename to ios/CodePush/SSZipArchive/aes/brg_endian.h diff --git a/SSZipArchive/aes/brg_types.h b/ios/CodePush/SSZipArchive/aes/brg_types.h similarity index 100% rename from SSZipArchive/aes/brg_types.h rename to ios/CodePush/SSZipArchive/aes/brg_types.h diff --git a/SSZipArchive/aes/entropy.c b/ios/CodePush/SSZipArchive/aes/entropy.c similarity index 100% rename from SSZipArchive/aes/entropy.c rename to ios/CodePush/SSZipArchive/aes/entropy.c diff --git a/SSZipArchive/aes/entropy.h b/ios/CodePush/SSZipArchive/aes/entropy.h similarity index 100% rename from SSZipArchive/aes/entropy.h rename to ios/CodePush/SSZipArchive/aes/entropy.h diff --git a/SSZipArchive/aes/fileenc.c b/ios/CodePush/SSZipArchive/aes/fileenc.c similarity index 100% rename from SSZipArchive/aes/fileenc.c rename to ios/CodePush/SSZipArchive/aes/fileenc.c diff --git a/SSZipArchive/aes/fileenc.h b/ios/CodePush/SSZipArchive/aes/fileenc.h similarity index 100% rename from SSZipArchive/aes/fileenc.h rename to ios/CodePush/SSZipArchive/aes/fileenc.h diff --git a/SSZipArchive/aes/hmac.c b/ios/CodePush/SSZipArchive/aes/hmac.c similarity index 100% rename from SSZipArchive/aes/hmac.c rename to ios/CodePush/SSZipArchive/aes/hmac.c diff --git a/SSZipArchive/aes/hmac.h b/ios/CodePush/SSZipArchive/aes/hmac.h similarity index 100% rename from SSZipArchive/aes/hmac.h rename to ios/CodePush/SSZipArchive/aes/hmac.h diff --git a/SSZipArchive/aes/prng.c b/ios/CodePush/SSZipArchive/aes/prng.c similarity index 100% rename from SSZipArchive/aes/prng.c rename to ios/CodePush/SSZipArchive/aes/prng.c diff --git a/SSZipArchive/aes/prng.h b/ios/CodePush/SSZipArchive/aes/prng.h similarity index 100% rename from SSZipArchive/aes/prng.h rename to ios/CodePush/SSZipArchive/aes/prng.h diff --git a/SSZipArchive/aes/pwd2key.c b/ios/CodePush/SSZipArchive/aes/pwd2key.c similarity index 100% rename from SSZipArchive/aes/pwd2key.c rename to ios/CodePush/SSZipArchive/aes/pwd2key.c diff --git a/SSZipArchive/aes/pwd2key.h b/ios/CodePush/SSZipArchive/aes/pwd2key.h similarity index 100% rename from SSZipArchive/aes/pwd2key.h rename to ios/CodePush/SSZipArchive/aes/pwd2key.h diff --git a/SSZipArchive/aes/sha1.c b/ios/CodePush/SSZipArchive/aes/sha1.c similarity index 100% rename from SSZipArchive/aes/sha1.c rename to ios/CodePush/SSZipArchive/aes/sha1.c diff --git a/SSZipArchive/aes/sha1.h b/ios/CodePush/SSZipArchive/aes/sha1.h similarity index 100% rename from SSZipArchive/aes/sha1.h rename to ios/CodePush/SSZipArchive/aes/sha1.h diff --git a/SSZipArchive/minizip/crypt.h b/ios/CodePush/SSZipArchive/minizip/crypt.h similarity index 100% rename from SSZipArchive/minizip/crypt.h rename to ios/CodePush/SSZipArchive/minizip/crypt.h diff --git a/SSZipArchive/minizip/ioapi.c b/ios/CodePush/SSZipArchive/minizip/ioapi.c similarity index 100% rename from SSZipArchive/minizip/ioapi.c rename to ios/CodePush/SSZipArchive/minizip/ioapi.c diff --git a/SSZipArchive/minizip/ioapi.h b/ios/CodePush/SSZipArchive/minizip/ioapi.h similarity index 100% rename from SSZipArchive/minizip/ioapi.h rename to ios/CodePush/SSZipArchive/minizip/ioapi.h diff --git a/SSZipArchive/minizip/mztools.c b/ios/CodePush/SSZipArchive/minizip/mztools.c similarity index 100% rename from SSZipArchive/minizip/mztools.c rename to ios/CodePush/SSZipArchive/minizip/mztools.c diff --git a/SSZipArchive/minizip/mztools.h b/ios/CodePush/SSZipArchive/minizip/mztools.h similarity index 100% rename from SSZipArchive/minizip/mztools.h rename to ios/CodePush/SSZipArchive/minizip/mztools.h diff --git a/SSZipArchive/minizip/unzip.c b/ios/CodePush/SSZipArchive/minizip/unzip.c similarity index 100% rename from SSZipArchive/minizip/unzip.c rename to ios/CodePush/SSZipArchive/minizip/unzip.c diff --git a/SSZipArchive/minizip/unzip.h b/ios/CodePush/SSZipArchive/minizip/unzip.h similarity index 100% rename from SSZipArchive/minizip/unzip.h rename to ios/CodePush/SSZipArchive/minizip/unzip.h diff --git a/SSZipArchive/minizip/zip.c b/ios/CodePush/SSZipArchive/minizip/zip.c similarity index 100% rename from SSZipArchive/minizip/zip.c rename to ios/CodePush/SSZipArchive/minizip/zip.c diff --git a/SSZipArchive/minizip/zip.h b/ios/CodePush/SSZipArchive/minizip/zip.h similarity index 100% rename from SSZipArchive/minizip/zip.h rename to ios/CodePush/SSZipArchive/minizip/zip.h