@revopush/react-native-code-push 1.4.0 → 2.5.0-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/CodePush.js +8 -5
  2. package/CodePush.podspec +9 -0
  3. package/README.md +7 -6
  4. package/android/build.gradle +52 -40
  5. package/android/gradle/wrapper/gradle-wrapper.jar +0 -0
  6. package/android/gradle/wrapper/gradle-wrapper.properties +3 -1
  7. package/android/gradle.properties +1 -1
  8. package/android/libs/revopush-diff-release-v0.0.1.aar +0 -0
  9. package/android/proguard-rules.pro +1 -1
  10. package/android/settings.gradle +31 -0
  11. package/android/src/main/java/com/microsoft/codepush/react/CodePush.java +8 -4
  12. package/android/src/main/java/com/microsoft/codepush/react/CodePushConstants.java +11 -7
  13. package/android/src/main/java/com/microsoft/codepush/react/CodePushNativeModule.java +48 -41
  14. package/android/src/main/java/com/microsoft/codepush/react/CodePushUpdateManager.java +67 -175
  15. package/android/src/main/java/com/microsoft/codepush/react/CodePushUpdateUtils.java +7 -244
  16. package/android/src/main/java/com/microsoft/codepush/react/CodePushUtils.java +16 -51
  17. package/android/src/main/java/com/microsoft/codepush/react/FileUtils.java +1 -130
  18. package/ios/CodePush/CodePush.h +1 -30
  19. package/ios/CodePush/CodePush.m +23 -7
  20. package/ios/CodePush/CodePushPackage.m +363 -294
  21. package/ios/CodePush/CodePushUpdateUtils.m +77 -28
  22. package/ios/CodePush.xcodeproj/project.pbxproj +0 -18
  23. package/ios/Frameworks/DiffUpdates.xcframework/Info.plist +44 -0
  24. package/ios/Frameworks/DiffUpdates.xcframework/ios-arm64/DiffUpdates.framework/DiffUpdates +0 -0
  25. package/ios/Frameworks/DiffUpdates.xcframework/ios-arm64/DiffUpdates.framework/Headers/DiffUpdates.h +61 -0
  26. package/ios/Frameworks/DiffUpdates.xcframework/ios-arm64/DiffUpdates.framework/Info.plist +0 -0
  27. package/ios/Frameworks/DiffUpdates.xcframework/ios-arm64/DiffUpdates.framework/Modules/module.modulemap +6 -0
  28. package/ios/Frameworks/DiffUpdates.xcframework/ios-arm64_x86_64-simulator/DiffUpdates.framework/DiffUpdates +0 -0
  29. package/ios/Frameworks/DiffUpdates.xcframework/ios-arm64_x86_64-simulator/DiffUpdates.framework/Headers/DiffUpdates.h +61 -0
  30. package/ios/Frameworks/DiffUpdates.xcframework/ios-arm64_x86_64-simulator/DiffUpdates.framework/Info.plist +0 -0
  31. package/ios/Frameworks/DiffUpdates.xcframework/ios-arm64_x86_64-simulator/DiffUpdates.framework/Modules/module.modulemap +6 -0
  32. package/ios/Frameworks/DiffUpdates.xcframework/ios-arm64_x86_64-simulator/DiffUpdates.framework/_CodeSignature/CodeDirectory +0 -0
  33. package/ios/Frameworks/DiffUpdates.xcframework/ios-arm64_x86_64-simulator/DiffUpdates.framework/_CodeSignature/CodeRequirements +0 -0
  34. package/ios/Frameworks/DiffUpdates.xcframework/ios-arm64_x86_64-simulator/DiffUpdates.framework/_CodeSignature/CodeRequirements-1 +0 -0
  35. package/ios/Frameworks/DiffUpdates.xcframework/ios-arm64_x86_64-simulator/DiffUpdates.framework/_CodeSignature/CodeResources +132 -0
  36. package/ios/Frameworks/DiffUpdates.xcframework/ios-arm64_x86_64-simulator/DiffUpdates.framework/_CodeSignature/CodeSignature +0 -0
  37. package/package.json +2 -2
  38. package/request-fetch-adapter.js +2 -2
  39. package/scripts/generateBundledResourcesHash.js +102 -65
  40. package/android/src/main/java/com/microsoft/codepush/react/TLSSocketFactory.java +0 -72
  41. package/ios/CodePush/CodePushDownloadHandler.m +0 -130
  42. package/ios/CodePush/CodePushErrorUtils.m +0 -20
  43. package/ios/CodePush/CodePushUtils.m +0 -9
  44. package/revopush-react-native-code-push-1.4.0-rc1.tgz +0 -0
@@ -1,8 +1,21 @@
1
1
  package com.microsoft.codepush.react;
2
2
 
3
- import android.os.Build;
3
+ import static com.microsoft.codepush.react.CodePushConstants.ASSET_DOWNLOAD_URL_KEY;
4
+ import static com.microsoft.codepush.react.CodePushConstants.BUNDLE_DIFF_DOWNLOAD_URL_KEY;
5
+ import static com.microsoft.codepush.react.CodePushConstants.BUNDLE_DOWNLOAD_URL_KEY;
6
+ import static com.microsoft.codepush.react.CodePushConstants.CODE_PUSH_ASSET_HASH;
7
+ import static com.microsoft.codepush.react.CodePushConstants.CODE_PUSH_BUNDLE_HASH;
8
+ import static com.microsoft.codepush.react.CodePushConstants.CODE_PUSH_PACKAGE_HASH;
9
+ import static com.microsoft.codepush.react.CodePushConstants.DOWNLOAD_URL_KEY;
10
+ import static com.microsoft.codepush.react.CodePushConstants.PACKAGE_HASH_KEY;
11
+ import static com.microsoft.codepush.react.CodePushUtils.appendPathComponent;
12
+
13
+ import android.content.Context;
4
14
 
5
15
  import org.json.JSONObject;
16
+ import org.revopush.ota.model.CodePushContext;
17
+ import org.revopush.ota.BundleManager;
18
+ import org.revopush.ota.model.UpdatePackage;
6
19
 
7
20
  import java.io.BufferedInputStream;
8
21
  import java.io.BufferedOutputStream;
@@ -12,24 +25,22 @@ import java.io.IOException;
12
25
  import java.net.HttpURLConnection;
13
26
  import java.net.MalformedURLException;
14
27
  import java.net.URL;
15
- import java.nio.ByteBuffer;
16
-
17
- import javax.net.ssl.HttpsURLConnection;
18
28
 
19
29
  public class CodePushUpdateManager {
20
30
 
21
31
  private String mDocumentsDirectory;
22
-
23
- public CodePushUpdateManager(String documentsDirectory) {
24
- mDocumentsDirectory = documentsDirectory;
25
- }
26
-
27
- private String getDownloadFilePath() {
28
- return CodePushUtils.appendPathComponent(getCodePushPath(), CodePushConstants.DOWNLOAD_FILE_NAME);
32
+ private BundleManager bundleManager;
33
+
34
+ public CodePushUpdateManager(Context context, boolean isDebugMode) {
35
+ this.mDocumentsDirectory = context.getFilesDir().getAbsolutePath();
36
+ this.bundleManager = new BundleManager(
37
+ context.getApplicationContext(),
38
+ getCodePushPath(),
39
+ getUnzippedFolderPath());
29
40
  }
30
41
 
31
42
  private String getUnzippedFolderPath() {
32
- return CodePushUtils.appendPathComponent(getCodePushPath(), CodePushConstants.UNZIPPED_FOLDER_NAME);
43
+ return appendPathComponent(getCodePushPath(), CodePushConstants.UNZIPPED_FOLDER_NAME);
33
44
  }
34
45
 
35
46
  private String getDocumentsDirectory() {
@@ -37,16 +48,16 @@ public class CodePushUpdateManager {
37
48
  }
38
49
 
39
50
  private String getCodePushPath() {
40
- String codePushPath = CodePushUtils.appendPathComponent(getDocumentsDirectory(), CodePushConstants.CODE_PUSH_FOLDER_PREFIX);
51
+ String codePushPath = appendPathComponent(getDocumentsDirectory(), CodePushConstants.CODE_PUSH_FOLDER_PREFIX);
41
52
  if (CodePush.isUsingTestConfiguration()) {
42
- codePushPath = CodePushUtils.appendPathComponent(codePushPath, "TestPackages");
53
+ codePushPath = appendPathComponent(codePushPath, "TestPackages");
43
54
  }
44
55
 
45
56
  return codePushPath;
46
57
  }
47
58
 
48
59
  private String getStatusFilePath() {
49
- return CodePushUtils.appendPathComponent(getCodePushPath(), CodePushConstants.STATUS_FILE);
60
+ return appendPathComponent(getCodePushPath(), CodePushConstants.STATUS_FILE);
50
61
  }
51
62
 
52
63
  public JSONObject getCurrentPackageInfo() {
@@ -95,14 +106,14 @@ public class CodePushUpdateManager {
95
106
 
96
107
  String relativeBundlePath = currentPackage.optString(CodePushConstants.RELATIVE_BUNDLE_PATH_KEY, null);
97
108
  if (relativeBundlePath == null) {
98
- return CodePushUtils.appendPathComponent(packageFolder, bundleFileName);
109
+ return appendPathComponent(packageFolder, bundleFileName);
99
110
  } else {
100
- return CodePushUtils.appendPathComponent(packageFolder, relativeBundlePath);
111
+ return appendPathComponent(packageFolder, relativeBundlePath);
101
112
  }
102
113
  }
103
114
 
104
115
  public String getPackageFolderPath(String packageHash) {
105
- return CodePushUtils.appendPathComponent(getCodePushPath(), packageHash);
116
+ return appendPathComponent(getCodePushPath(), packageHash);
106
117
  }
107
118
 
108
119
  public String getCurrentPackageHash() {
@@ -135,7 +146,7 @@ public class CodePushUpdateManager {
135
146
 
136
147
  public JSONObject getPackage(String packageHash) {
137
148
  String folderPath = getPackageFolderPath(packageHash);
138
- String packageFilePath = CodePushUtils.appendPathComponent(folderPath, CodePushConstants.PACKAGE_FILE_NAME);
149
+ String packageFilePath = appendPathComponent(folderPath, CodePushConstants.PACKAGE_FILE_NAME);
139
150
  try {
140
151
  return CodePushUtils.getJsonObjectFromFile(packageFilePath);
141
152
  } catch (IOException e) {
@@ -143,172 +154,35 @@ public class CodePushUpdateManager {
143
154
  }
144
155
  }
145
156
 
146
- public void downloadPackage(JSONObject updatePackage, String expectedBundleFileName,
147
- DownloadProgressCallback progressCallback,
148
- String stringPublicKey) throws IOException {
149
- String newUpdateHash = updatePackage.optString(CodePushConstants.PACKAGE_HASH_KEY, null);
150
- String newUpdateFolderPath = getPackageFolderPath(newUpdateHash);
151
- String newUpdateMetadataPath = CodePushUtils.appendPathComponent(newUpdateFolderPath, CodePushConstants.PACKAGE_FILE_NAME);
152
- if (FileUtils.fileAtPathExists(newUpdateFolderPath)) {
153
- // This removes any stale data in newPackageFolderPath that could have been left
154
- // uncleared due to a crash or error during the download or install process.
155
- FileUtils.deleteDirectoryAtPath(newUpdateFolderPath);
156
- }
157
-
158
- String downloadUrlString = updatePackage.optString(CodePushConstants.DOWNLOAD_URL_KEY, null);
159
- HttpURLConnection connection = null;
160
- BufferedInputStream bin = null;
161
- FileOutputStream fos = null;
162
- BufferedOutputStream bout = null;
163
- File downloadFile = null;
164
- boolean isZip = false;
165
-
166
- // Download the file while checking if it is a zip and notifying client of progress.
167
- try {
168
- URL downloadUrl = new URL(downloadUrlString);
169
- connection = (HttpURLConnection) (downloadUrl.openConnection());
170
-
171
- if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP &&
172
- downloadUrl.toString().startsWith("https")) {
173
- try {
174
- ((HttpsURLConnection)connection).setSSLSocketFactory(new TLSSocketFactory());
175
- } catch (Exception e) {
176
- throw new CodePushUnknownException("Error set SSLSocketFactory. ", e);
177
- }
178
- }
179
-
180
- connection.setRequestProperty("Accept-Encoding", "identity");
181
- bin = new BufferedInputStream(connection.getInputStream());
182
-
183
- long totalBytes = connection.getContentLength();
184
- long receivedBytes = 0;
185
-
186
- File downloadFolder = new File(getCodePushPath());
187
- downloadFolder.mkdirs();
188
- downloadFile = new File(downloadFolder, CodePushConstants.DOWNLOAD_FILE_NAME);
189
- fos = new FileOutputStream(downloadFile);
190
- bout = new BufferedOutputStream(fos, CodePushConstants.DOWNLOAD_BUFFER_SIZE);
191
- byte[] data = new byte[CodePushConstants.DOWNLOAD_BUFFER_SIZE];
192
- byte[] header = new byte[4];
193
-
194
- int numBytesRead = 0;
195
- while ((numBytesRead = bin.read(data, 0, CodePushConstants.DOWNLOAD_BUFFER_SIZE)) >= 0) {
196
- if (receivedBytes < 4) {
197
- for (int i = 0; i < numBytesRead; i++) {
198
- int headerOffset = (int) (receivedBytes) + i;
199
- if (headerOffset >= 4) {
200
- break;
201
- }
202
-
203
- header[headerOffset] = data[i];
204
- }
205
- }
206
-
207
- receivedBytes += numBytesRead;
208
- bout.write(data, 0, numBytesRead);
209
- progressCallback.call(new DownloadProgress(totalBytes, receivedBytes));
210
- }
157
+ public void downloadPackage(JSONObject updatePackage, String expectedBundleFileName, DownloadProgressCallback progressCallback, String stringPublicKey) throws IOException {
158
+ UpdatePackage updatePack = fromJson(updatePackage);
211
159
 
212
- if (totalBytes != receivedBytes) {
213
- throw new CodePushUnknownException("Received " + receivedBytes + " bytes, expected " + totalBytes);
214
- }
160
+ String newUpdateFolderPath = getPackageFolderPath(updatePack.getNewUpdateHash());
161
+ CodePushContext codePushContext = new CodePushContext(
162
+ newUpdateFolderPath, // new update
163
+ getCurrentPackageFolderPath(), // curr package
164
+ expectedBundleFileName, // exp bundle filename
165
+ stringPublicKey, // pub key
166
+ updatePack.getNewUpdateHash(), // new update hash
167
+ getCurrentPackage() // current package
168
+ );
215
169
 
216
- isZip = ByteBuffer.wrap(header).getInt() == 0x504b0304;
217
- } catch (MalformedURLException e) {
218
- throw new CodePushMalformedDataException(downloadUrlString, e);
219
- } finally {
220
- try {
221
- if (bout != null) bout.close();
222
- if (fos != null) fos.close();
223
- if (bin != null) bin.close();
224
- if (connection != null) connection.disconnect();
225
- } catch (IOException e) {
226
- throw new CodePushUnknownException("Error closing IO resources.", e);
227
- }
228
- }
229
170
 
230
- if (isZip) {
231
- // Unzip the downloaded file and then delete the zip
232
- String unzippedFolderPath = getUnzippedFolderPath();
233
- FileUtils.unzipFile(downloadFile, unzippedFolderPath);
234
- FileUtils.deleteFileOrFolderSilently(downloadFile);
235
-
236
- // Merge contents with current update based on the manifest
237
- String diffManifestFilePath = CodePushUtils.appendPathComponent(unzippedFolderPath,
238
- CodePushConstants.DIFF_MANIFEST_FILE_NAME);
239
- boolean isDiffUpdate = FileUtils.fileAtPathExists(diffManifestFilePath);
240
- if (isDiffUpdate) {
241
- String currentPackageFolderPath = getCurrentPackageFolderPath();
242
- CodePushUpdateUtils.copyNecessaryFilesFromCurrentPackage(diffManifestFilePath, currentPackageFolderPath, newUpdateFolderPath);
243
- File diffManifestFile = new File(diffManifestFilePath);
244
- diffManifestFile.delete();
245
- }
171
+ bundleManager.downloadPackage(updatePack, codePushContext, (total, received) -> {
172
+ progressCallback.call(new DownloadProgress(total, received));
173
+ return null;
174
+ });
246
175
 
247
- FileUtils.copyDirectoryContents(unzippedFolderPath, newUpdateFolderPath);
248
- FileUtils.deleteFileAtPathSilently(unzippedFolderPath);
249
-
250
- // For zip updates, we need to find the relative path to the jsBundle and save it in the
251
- // metadata so that we can find and run it easily the next time.
252
- String relativeBundlePath = CodePushUpdateUtils.findJSBundleInUpdateContents(newUpdateFolderPath, expectedBundleFileName);
253
-
254
- if (relativeBundlePath == null) {
255
- throw new CodePushInvalidUpdateException("Update is invalid - A JS bundle file named \"" + expectedBundleFileName + "\" could not be found within the downloaded contents. Please check that you are releasing your CodePush updates using the exact same JS bundle file name that was shipped with your app's binary.");
256
- } else {
257
- if (FileUtils.fileAtPathExists(newUpdateMetadataPath)) {
258
- File metadataFileFromOldUpdate = new File(newUpdateMetadataPath);
259
- metadataFileFromOldUpdate.delete();
260
- }
261
-
262
- if (isDiffUpdate) {
263
- CodePushUtils.log("Applying diff update.");
264
- } else {
265
- CodePushUtils.log("Applying full update.");
266
- }
267
-
268
- boolean isSignatureVerificationEnabled = (stringPublicKey != null);
269
-
270
- String signaturePath = CodePushUpdateUtils.getSignatureFilePath(newUpdateFolderPath);
271
- boolean isSignatureAppearedInBundle = FileUtils.fileAtPathExists(signaturePath);
272
-
273
- if (isSignatureVerificationEnabled) {
274
- if (isSignatureAppearedInBundle) {
275
- CodePushUpdateUtils.verifyFolderHash(newUpdateFolderPath, newUpdateHash);
276
- CodePushUpdateUtils.verifyUpdateSignature(newUpdateFolderPath, newUpdateHash, stringPublicKey);
277
- } else {
278
- throw new CodePushInvalidUpdateException(
279
- "Error! Public key was provided but there is no JWT signature within app bundle to verify. " +
280
- "Possible reasons, why that might happen: \n" +
281
- "1. You've been released CodePush bundle update using version of CodePush CLI that is not support code signing.\n" +
282
- "2. You've been released CodePush bundle update without providing --privateKeyPath option."
283
- );
284
- }
285
- } else {
286
- if (isSignatureAppearedInBundle) {
287
- CodePushUtils.log(
288
- "Warning! JWT signature exists in codepush update but code integrity check couldn't be performed because there is no public key configured. " +
289
- "Please ensure that public key is properly configured within your application."
290
- );
291
- CodePushUpdateUtils.verifyFolderHash(newUpdateFolderPath, newUpdateHash);
292
- } else {
293
- if (isDiffUpdate) {
294
- CodePushUpdateUtils.verifyFolderHash(newUpdateFolderPath, newUpdateHash);
295
- }
296
- }
297
- }
298
-
299
- CodePushUtils.setJSONValueForKey(updatePackage, CodePushConstants.RELATIVE_BUNDLE_PATH_KEY, relativeBundlePath);
300
- }
301
- } else {
302
- // File is a jsbundle, move it to a folder with the packageHash as its name
303
- FileUtils.moveFile(downloadFile, newUpdateFolderPath, expectedBundleFileName);
304
- }
176
+ CodePushUtils.setJSONValueForKey(updatePackage, CodePushConstants.RELATIVE_BUNDLE_PATH_KEY, CodePushUpdateUtils.findJSBundleInUpdateContents(newUpdateFolderPath, expectedBundleFileName));
305
177
 
306
178
  // Save metadata to the folder.
179
+ String newUpdateMetadataPath = appendPathComponent(newUpdateFolderPath, CodePushConstants.PACKAGE_FILE_NAME);
180
+ FileUtils.deleteFileAtPathSilently(newUpdateMetadataPath); // delete metadata from previous update if exists
307
181
  CodePushUtils.writeJsonToFile(updatePackage, newUpdateMetadataPath);
308
182
  }
309
183
 
310
184
  public void installPackage(JSONObject updatePackage, boolean removePendingUpdate) {
311
- String packageHash = updatePackage.optString(CodePushConstants.PACKAGE_HASH_KEY, null);
185
+ String packageHash = updatePackage.optString(PACKAGE_HASH_KEY, null);
312
186
  JSONObject info = getCurrentPackageInfo();
313
187
 
314
188
  String currentPackageHash = info.optString(CodePushConstants.CURRENT_PACKAGE_KEY, null);
@@ -380,4 +254,22 @@ public class CodePushUpdateManager {
380
254
  public void clearUpdates() {
381
255
  FileUtils.deleteDirectoryAtPath(getCodePushPath());
382
256
  }
257
+
258
+ private static UpdatePackage fromJson(JSONObject jsonObject) {
259
+ String downloadUrl = jsonObject.optString(DOWNLOAD_URL_KEY, null);
260
+ String assetDownloadUrl = jsonObject.optString(ASSET_DOWNLOAD_URL_KEY, null);
261
+ String bundleDownloadUrl = jsonObject.optString(BUNDLE_DOWNLOAD_URL_KEY, null);
262
+ String bundleDiffDownloadUrl = jsonObject.optString(BUNDLE_DIFF_DOWNLOAD_URL_KEY, null);
263
+ String packageHash = jsonObject.optString(CODE_PUSH_PACKAGE_HASH, null);
264
+ String bundleHash = jsonObject.optString(CODE_PUSH_BUNDLE_HASH, null);
265
+ String assetHash = jsonObject.optString(CODE_PUSH_ASSET_HASH, null);
266
+ String newUpdateHash = jsonObject.optString(PACKAGE_HASH_KEY, null);
267
+ return new UpdatePackage(
268
+ downloadUrl,
269
+ assetDownloadUrl,
270
+ bundleDownloadUrl,
271
+ bundleDiffDownloadUrl,
272
+ packageHash, bundleHash, assetHash,
273
+ newUpdateHash);
274
+ }
383
275
  }
@@ -1,133 +1,27 @@
1
1
  package com.microsoft.codepush.react;
2
2
 
3
- import android.content.Context;
4
- import android.util.Base64;
5
-
6
- import com.nimbusds.jose.JWSVerifier;
7
- import com.nimbusds.jose.crypto.RSASSAVerifier;
8
- import com.nimbusds.jwt.SignedJWT;
9
-
10
3
  import java.security.interfaces.*;
11
-
12
- import org.json.JSONArray;
13
- import org.json.JSONException;
14
- import org.json.JSONObject;
15
-
16
- import java.io.ByteArrayInputStream;
17
4
  import java.io.File;
18
- import java.io.FileInputStream;
19
- import java.io.FileNotFoundException;
20
- import java.io.IOException;
21
- import java.io.InputStream;
22
- import java.security.DigestInputStream;
23
- import java.security.KeyFactory;
24
- import java.security.MessageDigest;
25
- import java.security.NoSuchAlgorithmException;
26
- import java.security.PublicKey;
27
- import java.security.spec.X509EncodedKeySpec;
28
- import java.util.ArrayList;
29
- import java.util.Collections;
30
- import java.util.Map;
31
5
 
32
6
  public class CodePushUpdateUtils {
33
7
 
34
- public static final String NEW_LINE = System.getProperty("line.separator");
35
-
36
- // Note: The hashing logic here must mirror the hashing logic in other native SDK's, as well as in the
37
- // CLI. Ensure that any changes here are propagated to these other locations.
38
- public static boolean isHashIgnored(String relativeFilePath) {
39
- final String __MACOSX = "__MACOSX/";
40
- final String DS_STORE = ".DS_Store";
41
- final String CODEPUSH_METADATA = ".codepushrelease";
42
-
43
- return relativeFilePath.startsWith(__MACOSX)
44
- || relativeFilePath.equals(DS_STORE)
45
- || relativeFilePath.endsWith("/" + DS_STORE)
46
- || relativeFilePath.equals(CODEPUSH_METADATA)
47
- || relativeFilePath.endsWith("/" + CODEPUSH_METADATA);
48
- }
49
-
50
- private static void addContentsOfFolderToManifest(String folderPath, String pathPrefix, ArrayList<String> manifest) {
51
- File folder = new File(folderPath);
52
- File[] folderFiles = folder.listFiles();
53
- for (File file : folderFiles) {
54
- String fileName = file.getName();
55
- String fullFilePath = file.getAbsolutePath();
56
- String relativePath = (pathPrefix.isEmpty() ? "" : (pathPrefix + "/")) + fileName;
57
-
58
- if (CodePushUpdateUtils.isHashIgnored(relativePath)) {
59
- continue;
60
- }
61
-
62
- if (file.isDirectory()) {
63
- addContentsOfFolderToManifest(fullFilePath, relativePath, manifest);
64
- } else {
65
- try {
66
- manifest.add(relativePath + ":" + computeHash(new FileInputStream(file)));
67
- } catch (FileNotFoundException e) {
68
- // Should not happen.
69
- throw new CodePushUnknownException("Unable to compute hash of update contents.", e);
70
- }
71
- }
72
- }
73
- }
8
+ public static String findJSBundleInUpdateContents(String folderPath, String expectedFileName) {
9
+ String result = doFindJSBundleInUpdateContents(folderPath, expectedFileName);
74
10
 
75
- private static String computeHash(InputStream dataStream) {
76
- MessageDigest messageDigest = null;
77
- DigestInputStream digestInputStream = null;
78
- try {
79
- messageDigest = MessageDigest.getInstance("SHA-256");
80
- digestInputStream = new DigestInputStream(dataStream, messageDigest);
81
- byte[] byteBuffer = new byte[1024 * 8];
82
- while (digestInputStream.read(byteBuffer) != -1) ;
83
- } catch (NoSuchAlgorithmException | IOException e) {
84
- // Should not happen.
85
- throw new CodePushUnknownException("Unable to compute hash of update contents.", e);
86
- } finally {
87
- try {
88
- if (digestInputStream != null) {
89
- digestInputStream.close();
90
- }
91
- if (dataStream != null) {
92
- dataStream.close();
93
- }
94
- } catch (IOException e) {
95
- e.printStackTrace();
96
- }
11
+ if (result == null) {
12
+ throw new CodePushInvalidUpdateException("Update is invalid - A JS bundle file named \"" + expectedFileName + "\" could not be found within the downloaded contents. Please check that you are releasing your CodePush updates using the exact same JS bundle file name that was shipped with your app's binary.");
97
13
  }
98
14
 
99
- byte[] hash = messageDigest.digest();
100
- return String.format("%064x", new java.math.BigInteger(1, hash));
101
- }
102
-
103
- public static void copyNecessaryFilesFromCurrentPackage(String diffManifestFilePath, String currentPackageFolderPath, String newPackageFolderPath) throws IOException {
104
- if (currentPackageFolderPath == null || !new File(currentPackageFolderPath).exists()) {
105
- CodePushUtils.log("Unable to copy files from current package during diff update, because currentPackageFolderPath is invalid.");
106
- return;
107
- }
108
- FileUtils.copyDirectoryContents(currentPackageFolderPath, newPackageFolderPath);
109
- JSONObject diffManifest = CodePushUtils.getJsonObjectFromFile(diffManifestFilePath);
110
- try {
111
- JSONArray deletedFiles = diffManifest.getJSONArray("deletedFiles");
112
- for (int i = 0; i < deletedFiles.length(); i++) {
113
- String fileNameToDelete = deletedFiles.getString(i);
114
- File fileToDelete = new File(newPackageFolderPath, fileNameToDelete);
115
- if (fileToDelete.exists()) {
116
- fileToDelete.delete();
117
- }
118
- }
119
- } catch (JSONException e) {
120
- throw new CodePushUnknownException("Unable to copy files from current package during diff update", e);
121
- }
15
+ return result;
122
16
  }
123
17
 
124
- public static String findJSBundleInUpdateContents(String folderPath, String expectedFileName) {
18
+ private static String doFindJSBundleInUpdateContents(String folderPath, String expectedFileName) {
125
19
  File folder = new File(folderPath);
126
20
  File[] folderFiles = folder.listFiles();
127
21
  for (File file : folderFiles) {
128
22
  String fullFilePath = CodePushUtils.appendPathComponent(folderPath, file.getName());
129
23
  if (file.isDirectory()) {
130
- String mainBundlePathInSubFolder = findJSBundleInUpdateContents(fullFilePath, expectedFileName);
24
+ String mainBundlePathInSubFolder = doFindJSBundleInUpdateContents(fullFilePath, expectedFileName);
131
25
  if (mainBundlePathInSubFolder != null) {
132
26
  return CodePushUtils.appendPathComponent(file.getName(), mainBundlePathInSubFolder);
133
27
  }
@@ -141,135 +35,4 @@ public class CodePushUpdateUtils {
141
35
 
142
36
  return null;
143
37
  }
144
-
145
- public static String getHashForBinaryContents(Context context, boolean isDebugMode) {
146
- try {
147
- return CodePushUtils.getStringFromInputStream(context.getAssets().open(CodePushConstants.CODE_PUSH_HASH_FILE_NAME));
148
- } catch (IOException e) {
149
- try {
150
- return CodePushUtils.getStringFromInputStream(context.getAssets().open(CodePushConstants.CODE_PUSH_OLD_HASH_FILE_NAME));
151
- } catch (IOException ex) {
152
- if (!isDebugMode) {
153
- // Only print this message in "Release" mode. In "Debug", we may not have the
154
- // hash if the build skips bundling the files.
155
- CodePushUtils.log("Unable to get the hash of the binary's bundled resources - \"codepush.gradle\" may have not been added to the build definition.");
156
- }
157
- }
158
- return null;
159
- }
160
- }
161
-
162
- // Hashing algorithm:
163
- // 1. Recursively generate a sorted array of format <relativeFilePath>: <sha256FileHash>
164
- // 2. JSON stringify the array
165
- // 3. SHA256-hash the result
166
- public static void verifyFolderHash(String folderPath, String expectedHash) {
167
- CodePushUtils.log("Verifying hash for folder path: " + folderPath);
168
- ArrayList<String> updateContentsManifest = new ArrayList<>();
169
- addContentsOfFolderToManifest(folderPath, "", updateContentsManifest);
170
- //sort manifest strings to make sure, that they are completely equal with manifest strings has been generated in cli!
171
- Collections.sort(updateContentsManifest);
172
- JSONArray updateContentsJSONArray = new JSONArray();
173
- for (String manifestEntry : updateContentsManifest) {
174
- updateContentsJSONArray.put(manifestEntry);
175
- }
176
-
177
- // The JSON serialization turns path separators into "\/", e.g. "CodePush\/assets\/image.png"
178
- String updateContentsManifestString = updateContentsJSONArray.toString().replace("\\/", "/");
179
- CodePushUtils.log("Manifest string: " + updateContentsManifestString);
180
-
181
- String updateContentsManifestHash = computeHash(new ByteArrayInputStream(updateContentsManifestString.getBytes()));
182
-
183
- CodePushUtils.log("Expected hash: " + expectedHash + ", actual hash: " + updateContentsManifestHash);
184
- if (!expectedHash.equals(updateContentsManifestHash)) {
185
- throw new CodePushInvalidUpdateException("The update contents failed the data integrity check.");
186
- }
187
-
188
- CodePushUtils.log("The update contents succeeded the data integrity check.");
189
- }
190
-
191
- public static Map<String, Object> verifyAndDecodeJWT(String jwt, PublicKey publicKey) {
192
- try {
193
- SignedJWT signedJWT = SignedJWT.parse(jwt);
194
- JWSVerifier verifier = new RSASSAVerifier((RSAPublicKey) publicKey);
195
- if (signedJWT.verify(verifier)) {
196
- Map<String, Object> claims = signedJWT.getJWTClaimsSet().getClaims();
197
- CodePushUtils.log("JWT verification succeeded, payload content: " + claims.toString());
198
- return claims;
199
- }
200
- return null;
201
- } catch (Exception ex) {
202
- CodePushUtils.log(ex.getMessage());
203
- CodePushUtils.log(ex.getStackTrace().toString());
204
- return null;
205
- }
206
- }
207
-
208
- public static PublicKey parsePublicKey(String stringPublicKey) {
209
- try {
210
- //remove unnecessary "begin/end public key" entries from string
211
- stringPublicKey = stringPublicKey
212
- .replace("-----BEGIN PUBLIC KEY-----", "")
213
- .replace("-----END PUBLIC KEY-----", "")
214
- .replace(NEW_LINE, "");
215
- byte[] byteKey = Base64.decode(stringPublicKey.getBytes(), Base64.DEFAULT);
216
- X509EncodedKeySpec X509Key = new X509EncodedKeySpec(byteKey);
217
- KeyFactory kf = KeyFactory.getInstance("RSA");
218
-
219
- return kf.generatePublic(X509Key);
220
- } catch (Exception e) {
221
- CodePushUtils.log(e.getMessage());
222
- CodePushUtils.log(e.getStackTrace().toString());
223
- return null;
224
- }
225
- }
226
-
227
- public static String getSignatureFilePath(String updateFolderPath) {
228
- return CodePushUtils.appendPathComponent(
229
- CodePushUtils.appendPathComponent(updateFolderPath, CodePushConstants.CODE_PUSH_FOLDER_PREFIX),
230
- CodePushConstants.BUNDLE_JWT_FILE
231
- );
232
- }
233
-
234
- public static String getSignature(String folderPath) {
235
- final String signatureFilePath = getSignatureFilePath(folderPath);
236
-
237
- try {
238
- return FileUtils.readFileToString(signatureFilePath);
239
- } catch (IOException e) {
240
- CodePushUtils.log(e.getMessage());
241
- CodePushUtils.log(e.getStackTrace().toString());
242
- return null;
243
- }
244
- }
245
-
246
- public static void verifyUpdateSignature(String folderPath, String packageHash, String stringPublicKey) throws CodePushInvalidUpdateException {
247
- CodePushUtils.log("Verifying signature for folder path: " + folderPath);
248
-
249
- final PublicKey publicKey = parsePublicKey(stringPublicKey);
250
- if (publicKey == null) {
251
- throw new CodePushInvalidUpdateException("The update could not be verified because no public key was found.");
252
- }
253
-
254
- final String signature = getSignature(folderPath);
255
- if (signature == null) {
256
- throw new CodePushInvalidUpdateException("The update could not be verified because no signature was found.");
257
- }
258
-
259
- final Map<String, Object> claims = verifyAndDecodeJWT(signature, publicKey);
260
- if (claims == null) {
261
- throw new CodePushInvalidUpdateException("The update could not be verified because it was not signed by a trusted party.");
262
- }
263
-
264
- final String contentHash = (String) claims.get("contentHash");
265
- if (contentHash == null) {
266
- throw new CodePushInvalidUpdateException("The update could not be verified because the signature did not specify a content hash.");
267
- }
268
-
269
- if (!contentHash.equals(packageHash)) {
270
- throw new CodePushInvalidUpdateException("The update contents failed the code signing check.");
271
- }
272
-
273
- CodePushUtils.log("The update contents succeeded the code signing check.");
274
- }
275
38
  }