mirror of
https://github.com/zhigang1992/react-native-code-push.git
synced 2026-06-11 08:04:23 +08:00
Merge pull request #205 from Microsoft/verify-hash-after-download
Verify hash after download
This commit is contained in:
@@ -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 = "<group>"; };
|
||||
54F5F2B31BF6B45D007C3CEA /* DownloadProgressTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DownloadProgressTests.m; sourceTree = "<group>"; };
|
||||
78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTLinking.xcodeproj; path = "../node_modules/react-native/Libraries/LinkingIOS/RCTLinking.xcodeproj"; sourceTree = "<group>"; };
|
||||
81551E0A1B3B427200F5B9F1 /* CodePush.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = CodePush.xcodeproj; path = ../../../CodePush.xcodeproj; sourceTree = "<group>"; };
|
||||
81551E0A1B3B427200F5B9F1 /* CodePush.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = CodePush.xcodeproj; path = ../../../ios/CodePush.xcodeproj; sourceTree = "<group>"; };
|
||||
832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTText.xcodeproj; path = "../node_modules/react-native/Libraries/Text/RCTText.xcodeproj"; sourceTree = "<group>"; };
|
||||
/* 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 = "<group>";
|
||||
};
|
||||
540D20201C76E4D300D6EF41 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
540D20241C76E4D300D6EF41 /* libCodePush.a */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
5451ACE71B86E34300E2A7DF /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -311,14 +317,6 @@
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
81551E0B1B3B427200F5B9F1 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
81551E0F1B3B427200F5B9F1 /* libCodePush.a */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
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";
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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<String> 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<String> updateContentsManifest = new ArrayList<String>();
|
||||
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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 = "<group>"; };
|
||||
13BE3DED1AC21097009241FE /* CodePush.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CodePush.m; sourceTree = "<group>"; };
|
||||
1B23B9131BF9267B000BB2F0 /* RCTConvert+CodePushInstallMode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "RCTConvert+CodePushInstallMode.m"; sourceTree = "<group>"; };
|
||||
5421FE301C58AD5A00986A55 /* CodePushTelemetryManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CodePushTelemetryManager.m; sourceTree = "<group>"; };
|
||||
13BE3DEC1AC21097009241FE /* CodePush.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CodePush.h; path = CodePush/CodePush.h; sourceTree = "<group>"; };
|
||||
13BE3DED1AC21097009241FE /* CodePush.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CodePush.m; path = CodePush/CodePush.m; sourceTree = "<group>"; };
|
||||
1B23B9131BF9267B000BB2F0 /* RCTConvert+CodePushInstallMode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "RCTConvert+CodePushInstallMode.m"; path = "CodePush/RCTConvert+CodePushInstallMode.m"; sourceTree = "<group>"; };
|
||||
540D20111C7684FE00D6EF41 /* CodePushUpdateUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CodePushUpdateUtils.m; path = CodePush/CodePushUpdateUtils.m; sourceTree = "<group>"; };
|
||||
5421FE301C58AD5A00986A55 /* CodePushTelemetryManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CodePushTelemetryManager.m; path = CodePush/CodePushTelemetryManager.m; sourceTree = "<group>"; };
|
||||
54A0024A1C0E2880004C3CEC /* aes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = aes.h; sourceTree = "<group>"; };
|
||||
54A0024B1C0E2880004C3CEC /* aes_via_ace.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = aes_via_ace.h; sourceTree = "<group>"; };
|
||||
54A0024C1C0E2880004C3CEC /* aescrypt.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = aescrypt.c; sourceTree = "<group>"; };
|
||||
@@ -80,9 +82,9 @@
|
||||
54A002691C0E2880004C3CEC /* zip.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = zip.h; sourceTree = "<group>"; };
|
||||
54A0026A1C0E2880004C3CEC /* SSZipArchive.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SSZipArchive.h; sourceTree = "<group>"; };
|
||||
54A0026B1C0E2880004C3CEC /* SSZipArchive.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SSZipArchive.m; sourceTree = "<group>"; };
|
||||
54FFEDDF1BF550630061DD23 /* CodePushDownloadHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CodePushDownloadHandler.m; sourceTree = "<group>"; };
|
||||
810D4E6C1B96935000B397E9 /* CodePushPackage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CodePushPackage.m; sourceTree = "<group>"; };
|
||||
81D51F391B6181C2000DA084 /* CodePushConfig.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CodePushConfig.m; sourceTree = "<group>"; };
|
||||
54FFEDDF1BF550630061DD23 /* CodePushDownloadHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CodePushDownloadHandler.m; path = CodePush/CodePushDownloadHandler.m; sourceTree = "<group>"; };
|
||||
810D4E6C1B96935000B397E9 /* CodePushPackage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CodePushPackage.m; path = CodePush/CodePushPackage.m; sourceTree = "<group>"; };
|
||||
81D51F391B6181C2000DA084 /* CodePushConfig.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CodePushConfig.m; path = CodePush/CodePushConfig.m; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@@ -113,7 +115,8 @@
|
||||
54A0026A1C0E2880004C3CEC /* SSZipArchive.h */,
|
||||
54A0026B1C0E2880004C3CEC /* SSZipArchive.m */,
|
||||
);
|
||||
path = SSZipArchive;
|
||||
name = SSZipArchive;
|
||||
path = CodePush/SSZipArchive;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
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 = (
|
||||
@@ -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,
|
||||
@@ -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
|
||||
156
ios/CodePush/CodePushUpdateUtils.m
Normal file
156
ios/CodePush/CodePushUpdateUtils.m
Normal file
@@ -0,0 +1,156 @@
|
||||
#import "CodePush.h"
|
||||
#include <CommonCrypto/CommonDigest.h>
|
||||
|
||||
@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
|
||||
Reference in New Issue
Block a user