@revopush/react-native-code-push 1.5.0 → 2.5.0-rc.1
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.
- package/CodePush.js +8 -5
- package/CodePush.podspec +9 -0
- package/android/build.gradle +53 -40
- package/android/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/android/gradle/wrapper/gradle-wrapper.properties +3 -1
- package/android/gradle.properties +1 -1
- package/android/settings.gradle +31 -0
- package/android/src/main/java/com/microsoft/codepush/react/CodePush.java +8 -4
- package/android/src/main/java/com/microsoft/codepush/react/CodePushConstants.java +11 -7
- package/android/src/main/java/com/microsoft/codepush/react/CodePushNativeModule.java +48 -41
- package/android/src/main/java/com/microsoft/codepush/react/CodePushUpdateManager.java +67 -175
- package/android/src/main/java/com/microsoft/codepush/react/CodePushUpdateUtils.java +7 -244
- package/android/src/main/java/com/microsoft/codepush/react/CodePushUtils.java +16 -51
- package/android/src/main/java/com/microsoft/codepush/react/FileUtils.java +1 -130
- package/ios/CodePush/CodePush.h +1 -30
- package/ios/CodePush/CodePush.m +23 -7
- package/ios/CodePush/CodePushPackage.m +363 -294
- package/ios/CodePush/CodePushUpdateUtils.m +77 -28
- package/ios/CodePush.xcodeproj/project.pbxproj +0 -18
- package/ios/Frameworks/DiffUpdates.xcframework/Info.plist +44 -0
- package/ios/Frameworks/DiffUpdates.xcframework/ios-arm64/DiffUpdates.framework/DiffUpdates +0 -0
- package/ios/Frameworks/DiffUpdates.xcframework/ios-arm64/DiffUpdates.framework/Headers/DiffUpdates.h +61 -0
- package/ios/Frameworks/DiffUpdates.xcframework/ios-arm64/DiffUpdates.framework/Info.plist +0 -0
- package/ios/Frameworks/DiffUpdates.xcframework/ios-arm64/DiffUpdates.framework/Modules/module.modulemap +6 -0
- package/ios/Frameworks/DiffUpdates.xcframework/ios-arm64_x86_64-simulator/DiffUpdates.framework/DiffUpdates +0 -0
- package/ios/Frameworks/DiffUpdates.xcframework/ios-arm64_x86_64-simulator/DiffUpdates.framework/Headers/DiffUpdates.h +61 -0
- package/ios/Frameworks/DiffUpdates.xcframework/ios-arm64_x86_64-simulator/DiffUpdates.framework/Info.plist +0 -0
- package/ios/Frameworks/DiffUpdates.xcframework/ios-arm64_x86_64-simulator/DiffUpdates.framework/Modules/module.modulemap +6 -0
- package/ios/Frameworks/DiffUpdates.xcframework/ios-arm64_x86_64-simulator/DiffUpdates.framework/_CodeSignature/CodeDirectory +0 -0
- package/ios/Frameworks/DiffUpdates.xcframework/ios-arm64_x86_64-simulator/DiffUpdates.framework/_CodeSignature/CodeRequirements +0 -0
- package/ios/Frameworks/DiffUpdates.xcframework/ios-arm64_x86_64-simulator/DiffUpdates.framework/_CodeSignature/CodeRequirements-1 +0 -0
- package/ios/Frameworks/DiffUpdates.xcframework/ios-arm64_x86_64-simulator/DiffUpdates.framework/_CodeSignature/CodeResources +132 -0
- package/ios/Frameworks/DiffUpdates.xcframework/ios-arm64_x86_64-simulator/DiffUpdates.framework/_CodeSignature/CodeSignature +0 -0
- package/package.json +2 -2
- package/request-fetch-adapter.js +2 -2
- package/scripts/generateBundledResourcesHash.js +102 -65
- package/android/src/main/java/com/microsoft/codepush/react/TLSSocketFactory.java +0 -72
- package/ios/CodePush/CodePushDownloadHandler.m +0 -130
- package/ios/CodePush/CodePushErrorUtils.m +0 -20
- package/ios/CodePush/CodePushUtils.m +0 -9
|
@@ -1,8 +1,21 @@
|
|
|
1
1
|
package com.microsoft.codepush.react;
|
|
2
2
|
|
|
3
|
-
import
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
|
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 =
|
|
51
|
+
String codePushPath = appendPathComponent(getDocumentsDirectory(), CodePushConstants.CODE_PUSH_FOLDER_PREFIX);
|
|
41
52
|
if (CodePush.isUsingTestConfiguration()) {
|
|
42
|
-
codePushPath =
|
|
53
|
+
codePushPath = appendPathComponent(codePushPath, "TestPackages");
|
|
43
54
|
}
|
|
44
55
|
|
|
45
56
|
return codePushPath;
|
|
46
57
|
}
|
|
47
58
|
|
|
48
59
|
private String getStatusFilePath() {
|
|
49
|
-
return
|
|
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
|
|
109
|
+
return appendPathComponent(packageFolder, bundleFileName);
|
|
99
110
|
} else {
|
|
100
|
-
return
|
|
111
|
+
return appendPathComponent(packageFolder, relativeBundlePath);
|
|
101
112
|
}
|
|
102
113
|
}
|
|
103
114
|
|
|
104
115
|
public String getPackageFolderPath(String packageHash) {
|
|
105
|
-
return
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
213
|
-
|
|
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
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
|
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
|
-
|
|
76
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
}
|