react-native-update 10.55.1 → 10.56.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.
- package/README-CN.md +35 -0
- package/README.md +37 -0
- package/android/build.gradle +53 -27
- package/android/jni/Application.mk +6 -1
- package/android/jni/hpatch.c +59 -4
- package/android/lib/arm64-v8a/librnupdate.so +0 -0
- package/android/lib/armeabi-v7a/librnupdate.so +0 -0
- package/android/lib/x86/librnupdate.so +0 -0
- package/android/lib/x86_64/librnupdate.so +0 -0
- package/android/proguard.pro +70 -14
- package/android/src/main/java/cn/reactnative/modules/update/ApkInstaller.java +15 -8
- package/android/src/main/java/cn/reactnative/modules/update/ArchiveLimits.java +1 -0
- package/android/src/main/java/cn/reactnative/modules/update/BundledResourceCopier.java +1 -2
- package/android/src/main/java/cn/reactnative/modules/update/CleanupKeepNames.java +25 -0
- package/android/src/main/java/cn/reactnative/modules/update/DownloadTask.java +98 -34
- package/android/src/main/java/cn/reactnative/modules/update/DownloadTaskParams.java +29 -0
- package/android/src/main/java/cn/reactnative/modules/update/FileOperationException.java +15 -0
- package/android/src/main/java/cn/reactnative/modules/update/HttpUtils.java +59 -0
- package/android/src/main/java/cn/reactnative/modules/update/InstallRecord.java +27 -1
- package/android/src/main/java/cn/reactnative/modules/update/NativeCheckOrchestrator.java +57 -16
- package/android/src/main/java/cn/reactnative/modules/update/ReactReloadManager.java +38 -46
- package/android/src/main/java/cn/reactnative/modules/update/UpdateContext.java +279 -183
- package/android/src/main/java/cn/reactnative/modules/update/UpdateFileUtils.java +14 -0
- package/android/src/main/java/cn/reactnative/modules/update/UpdateModuleImpl.java +222 -215
- package/android/src/main/java/cn/reactnative/modules/update/UpdatePackage.java +5 -0
- package/android/src/newarch/cn/reactnative/modules/update/UpdateModule.java +23 -25
- package/android/src/oldarch/cn/reactnative/modules/update/UpdateModule.java +29 -29
- package/cpp/patch_core/archive_limits.h +6 -0
- package/cpp/patch_core/jni_util.h +6 -2
- package/cpp/patch_core/patch_core.cpp +237 -84
- package/cpp/patch_core/patch_core.h +11 -0
- package/cpp/patch_core/patch_core_android.cpp +49 -33
- package/cpp/patch_core/update_core_android.cpp +134 -103
- package/cpp/update_flow_core/flow_json.cpp +150 -17
- package/cpp/update_flow_core/flow_json.h +13 -1
- package/cpp/update_flow_core/update_flow_core.cpp +78 -8
- package/cpp/update_flow_core/update_flow_core.h +9 -0
- package/cpp/update_flow_core/update_flow_jni.cpp +44 -25
- package/harmony/hvigor-plugin.ts +168 -21
- package/harmony/pushy/src/README.md +74 -0
- package/harmony/pushy/src/main/cpp/CMakeLists.txt +13 -3
- package/harmony/pushy/src/main/cpp/PushyTurboModule.cpp +11 -5
- package/harmony/pushy/src/main/cpp/pushy.cpp +103 -18
- package/harmony/pushy/src/main/ets/ArchiveLimits.ts +61 -5
- package/harmony/pushy/src/main/ets/DownloadTask.ts +255 -166
- package/harmony/pushy/src/main/ets/ErrorCodes.ts +75 -0
- package/harmony/pushy/src/main/ets/EventHub.ts +22 -20
- package/harmony/pushy/src/main/ets/InstallRecord.ts +79 -21
- package/harmony/pushy/src/main/ets/Logger.ts +42 -16
- package/harmony/pushy/src/main/ets/NativeCheckOrchestrator.ts +98 -17
- package/harmony/pushy/src/main/ets/NativePatchCore.ts +10 -3
- package/harmony/pushy/src/main/ets/PathUtils.ts +9 -3
- package/harmony/pushy/src/main/ets/PushyTurboModule.ts +139 -53
- package/harmony/pushy/src/main/ets/UpdateContext.ts +269 -159
- package/harmony/pushy/src/main/module.json5 +5 -1
- package/harmony/pushy.har +0 -0
- package/ios/PrivacyInfo.xcprivacy +79 -0
- package/ios/RCTPushy/RCTPushy.mm +399 -170
- package/ios/RCTPushy/RCTPushyDownloader.h +14 -0
- package/ios/RCTPushy/RCTPushyDownloader.mm +136 -11
- package/package.json +14 -24
- package/react-native-update.podspec +36 -3
- package/src/client.ts +236 -82
- package/src/context.ts +40 -27
- package/src/core.ts +41 -16
- package/src/endpoint.ts +2 -1
- package/src/error.ts +49 -11
- package/src/index.ts +14 -0
- package/src/locales/en.ts +1 -23
- package/src/locales/zh.ts +1 -22
- package/src/provider.tsx +159 -111
- package/src/type.ts +0 -2
- package/src/updateFlowCore.ts +10 -2
- package/src/utils.ts +61 -4
- package/android/src/main/java/cn/reactnative/modules/update/ReactNativeHostHandler.java +0 -13
- package/android/src/main/java/cn/reactnative/modules/update/UpdateModuleSupport.java +0 -45
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#include <jni.h>
|
|
2
2
|
|
|
3
|
+
#include <exception>
|
|
3
4
|
#include <string>
|
|
4
5
|
#include <vector>
|
|
5
6
|
|
|
@@ -306,12 +307,22 @@ Java_cn_reactnative_modules_update_UpdateContext_syncStateWithBinaryVersion(
|
|
|
306
307
|
jstring package_version,
|
|
307
308
|
jstring build_time,
|
|
308
309
|
jobject state_result) {
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
310
|
+
// C++ exceptions (bad_alloc/length_error from input-proportional STL
|
|
311
|
+
// allocations) must not unwind through the JNI boundary: convert them to a
|
|
312
|
+
// Java RuntimeException like every other failure of these entry points.
|
|
313
|
+
try {
|
|
314
|
+
pushy::state::State state = ReadStateFromResult(env, state_result);
|
|
315
|
+
pushy::state::BinaryVersionSyncResult result = pushy::state::SyncBinaryVersion(
|
|
316
|
+
state,
|
|
317
|
+
JStringToString(env, package_version),
|
|
318
|
+
JStringToString(env, build_time));
|
|
319
|
+
return MakeStateResult(env, result.state, result.changed);
|
|
320
|
+
} catch (const std::exception& error) {
|
|
321
|
+
ThrowRuntimeException(env, error.what());
|
|
322
|
+
} catch (...) {
|
|
323
|
+
ThrowRuntimeException(env, "Unexpected native exception in syncStateWithBinaryVersion");
|
|
324
|
+
}
|
|
325
|
+
return nullptr;
|
|
315
326
|
}
|
|
316
327
|
|
|
317
328
|
extern "C" JNIEXPORT jobject JNICALL
|
|
@@ -323,46 +334,52 @@ Java_cn_reactnative_modules_update_UpdateContext_runStateCore(
|
|
|
323
334
|
jstring string_arg,
|
|
324
335
|
jboolean flag_a,
|
|
325
336
|
jboolean flag_b) {
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
337
|
+
try {
|
|
338
|
+
const pushy::state::State state = ReadStateFromResult(env, state_result);
|
|
339
|
+
switch (static_cast<StateOperation>(operation)) {
|
|
340
|
+
case StateOperation::kSwitchVersion:
|
|
341
|
+
return MakeStateResult(
|
|
342
|
+
env,
|
|
343
|
+
pushy::state::SwitchVersion(state, JStringToString(env, string_arg)));
|
|
344
|
+
case StateOperation::kMarkSuccess: {
|
|
345
|
+
const pushy::state::MarkSuccessResult result =
|
|
346
|
+
pushy::state::MarkSuccess(state);
|
|
347
|
+
return MakeStateResult(
|
|
348
|
+
env,
|
|
349
|
+
result.state,
|
|
350
|
+
false,
|
|
351
|
+
result.stale_version_to_delete);
|
|
352
|
+
}
|
|
353
|
+
case StateOperation::kRollback: {
|
|
354
|
+
const pushy::state::State next = pushy::state::Rollback(state);
|
|
355
|
+
return MakeStateResult(
|
|
356
|
+
env, next, false, std::string(), next.current_version, true);
|
|
357
|
+
}
|
|
358
|
+
case StateOperation::kClearFirstTime:
|
|
359
|
+
return MakeStateResult(env, pushy::state::ClearFirstTime(state));
|
|
360
|
+
case StateOperation::kClearRollbackMark:
|
|
361
|
+
return MakeStateResult(env, pushy::state::ClearRollbackMark(state));
|
|
362
|
+
case StateOperation::kResolveLaunch: {
|
|
363
|
+
const pushy::state::LaunchDecision decision =
|
|
364
|
+
pushy::state::ResolveLaunchState(
|
|
365
|
+
state, flag_a == JNI_TRUE, flag_b == JNI_TRUE);
|
|
366
|
+
return MakeStateResult(
|
|
367
|
+
env,
|
|
368
|
+
decision.state,
|
|
369
|
+
false,
|
|
370
|
+
std::string(),
|
|
371
|
+
decision.load_version,
|
|
372
|
+
decision.did_rollback,
|
|
373
|
+
decision.consumed_first_time);
|
|
374
|
+
}
|
|
362
375
|
}
|
|
363
|
-
}
|
|
364
376
|
|
|
365
|
-
|
|
377
|
+
ThrowRuntimeException(env, "Unknown state operation");
|
|
378
|
+
} catch (const std::exception& error) {
|
|
379
|
+
ThrowRuntimeException(env, error.what());
|
|
380
|
+
} catch (...) {
|
|
381
|
+
ThrowRuntimeException(env, "Unexpected native exception in runStateCore");
|
|
382
|
+
}
|
|
366
383
|
return nullptr;
|
|
367
384
|
}
|
|
368
385
|
|
|
@@ -375,32 +392,39 @@ Java_cn_reactnative_modules_update_DownloadTask_buildArchivePatchPlan(
|
|
|
375
392
|
jobjectArray copy_froms,
|
|
376
393
|
jobjectArray copy_tos,
|
|
377
394
|
jobjectArray deletes) {
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
395
|
+
try {
|
|
396
|
+
const std::vector<std::string> from_values = JArrayToVector(env, copy_froms);
|
|
397
|
+
const std::vector<std::string> to_values = JArrayToVector(env, copy_tos);
|
|
398
|
+
if (from_values.size() != to_values.size()) {
|
|
399
|
+
ThrowRuntimeException(env, "copy_froms and copy_tos length mismatch");
|
|
400
|
+
return nullptr;
|
|
401
|
+
}
|
|
384
402
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
403
|
+
pushy::patch::PatchManifest manifest =
|
|
404
|
+
BuildManifest(from_values, to_values, JArrayToVector(env, deletes));
|
|
405
|
+
pushy::archive_patch::ArchivePatchType archive_type;
|
|
406
|
+
if (!ToArchivePatchType(patch_type, &archive_type)) {
|
|
407
|
+
ThrowRuntimeException(env, "Unknown archive patch type");
|
|
408
|
+
return nullptr;
|
|
409
|
+
}
|
|
410
|
+
pushy::archive_patch::ArchivePatchPlan plan;
|
|
411
|
+
pushy::patch::Status status = pushy::archive_patch::BuildArchivePatchPlan(
|
|
412
|
+
archive_type,
|
|
413
|
+
manifest,
|
|
414
|
+
JArrayToVector(env, entry_names),
|
|
415
|
+
&plan);
|
|
416
|
+
if (!status.ok) {
|
|
417
|
+
ThrowRuntimeException(env, status.message);
|
|
418
|
+
return nullptr;
|
|
419
|
+
}
|
|
402
420
|
|
|
403
|
-
|
|
421
|
+
return NewArchivePatchPlanResult(env, plan);
|
|
422
|
+
} catch (const std::exception& error) {
|
|
423
|
+
ThrowRuntimeException(env, error.what());
|
|
424
|
+
} catch (...) {
|
|
425
|
+
ThrowRuntimeException(env, "Unexpected native exception in buildArchivePatchPlan");
|
|
426
|
+
}
|
|
427
|
+
return nullptr;
|
|
404
428
|
}
|
|
405
429
|
|
|
406
430
|
extern "C" JNIEXPORT jobjectArray JNICALL
|
|
@@ -409,45 +433,52 @@ Java_cn_reactnative_modules_update_DownloadTask_buildCopyGroups(
|
|
|
409
433
|
jclass,
|
|
410
434
|
jobjectArray copy_froms,
|
|
411
435
|
jobjectArray copy_tos) {
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
436
|
+
try {
|
|
437
|
+
const std::vector<std::string> from_values = JArrayToVector(env, copy_froms);
|
|
438
|
+
const std::vector<std::string> to_values = JArrayToVector(env, copy_tos);
|
|
439
|
+
if (from_values.size() != to_values.size()) {
|
|
440
|
+
ThrowRuntimeException(env, "copy_froms and copy_tos length mismatch");
|
|
441
|
+
return nullptr;
|
|
442
|
+
}
|
|
418
443
|
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
444
|
+
pushy::patch::PatchManifest manifest = BuildManifest(
|
|
445
|
+
from_values, to_values, std::vector<std::string>());
|
|
446
|
+
std::vector<pushy::archive_patch::CopyGroup> groups;
|
|
447
|
+
pushy::patch::Status status =
|
|
448
|
+
pushy::archive_patch::BuildCopyGroups(manifest, &groups);
|
|
449
|
+
if (!status.ok) {
|
|
450
|
+
ThrowRuntimeException(env, status.message);
|
|
451
|
+
return nullptr;
|
|
452
|
+
}
|
|
428
453
|
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
454
|
+
jclass result_class =
|
|
455
|
+
env->FindClass("cn/reactnative/modules/update/CopyGroupResult");
|
|
456
|
+
if (result_class == nullptr) {
|
|
457
|
+
return nullptr;
|
|
458
|
+
}
|
|
434
459
|
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
460
|
+
jclass string_class = env->FindClass("java/lang/String");
|
|
461
|
+
if (string_class == nullptr) {
|
|
462
|
+
return nullptr;
|
|
463
|
+
}
|
|
439
464
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
465
|
+
jobjectArray result = env->NewObjectArray(
|
|
466
|
+
static_cast<jsize>(groups.size()), result_class, nullptr);
|
|
467
|
+
if (result == nullptr) {
|
|
468
|
+
return nullptr;
|
|
469
|
+
}
|
|
445
470
|
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
471
|
+
for (jsize index = 0; index < static_cast<jsize>(groups.size()); ++index) {
|
|
472
|
+
jobject group = NewCopyGroupResult(env, result_class, string_class, groups[index]);
|
|
473
|
+
env->SetObjectArrayElement(result, index, group);
|
|
474
|
+
env->DeleteLocalRef(group);
|
|
475
|
+
}
|
|
476
|
+
env->DeleteLocalRef(string_class);
|
|
477
|
+
return result;
|
|
478
|
+
} catch (const std::exception& error) {
|
|
479
|
+
ThrowRuntimeException(env, error.what());
|
|
480
|
+
} catch (...) {
|
|
481
|
+
ThrowRuntimeException(env, "Unexpected native exception in buildCopyGroups");
|
|
450
482
|
}
|
|
451
|
-
|
|
452
|
-
return result;
|
|
483
|
+
return nullptr;
|
|
453
484
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#include "flow_json.h"
|
|
2
2
|
|
|
3
3
|
#include <cmath>
|
|
4
|
+
#include <cstdint>
|
|
4
5
|
#include <cstdio>
|
|
5
|
-
#include <cstdlib>
|
|
6
6
|
|
|
7
7
|
namespace flowjson {
|
|
8
8
|
|
|
@@ -127,18 +127,41 @@ void AppendEscaped(const std::string& s, std::string* out) {
|
|
|
127
127
|
}
|
|
128
128
|
|
|
129
129
|
void AppendNumber(double n, std::string* out) {
|
|
130
|
+
// JSON.stringify prints non-finite numbers as null.
|
|
131
|
+
if (!std::isfinite(n)) {
|
|
132
|
+
out->append("null");
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
130
135
|
// The decision layer only ever emits integral numbers (hashes, counters,
|
|
131
136
|
// rollout percentages); print them the way JSON.stringify does. The %.17g
|
|
132
137
|
// fallback exists so an unexpected fractional value surfaces as a vector
|
|
133
138
|
// mismatch instead of silent truncation.
|
|
134
|
-
if (
|
|
139
|
+
if (n == std::floor(n) && std::fabs(n) <= 9007199254740992.0) {
|
|
135
140
|
char buf[32];
|
|
136
141
|
std::snprintf(buf, sizeof(buf), "%lld", static_cast<long long>(n));
|
|
137
142
|
out->append(buf);
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
char buf[40];
|
|
146
|
+
const int len = std::snprintf(buf, sizeof(buf), "%.17g", n);
|
|
147
|
+
if (len <= 0) {
|
|
148
|
+
out->append("null");
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
// %g writes the LC_NUMERIC decimal point ("," — or a multibyte sequence in
|
|
152
|
+
// some locales). Everything else it can produce for a finite value is
|
|
153
|
+
// [0-9eE+-], so normalize whatever else appears back to '.' instead of
|
|
154
|
+
// depending on the process locale.
|
|
155
|
+
bool point_written = false;
|
|
156
|
+
for (int i = 0; i < len && i < static_cast<int>(sizeof(buf)); i++) {
|
|
157
|
+
const char c = buf[i];
|
|
158
|
+
if ((c >= '0' && c <= '9') || c == '-' || c == '+' || c == 'e' ||
|
|
159
|
+
c == 'E') {
|
|
160
|
+
out->push_back(c);
|
|
161
|
+
} else if (!point_written) {
|
|
162
|
+
out->push_back('.');
|
|
163
|
+
point_written = true;
|
|
164
|
+
}
|
|
142
165
|
}
|
|
143
166
|
}
|
|
144
167
|
|
|
@@ -206,9 +229,102 @@ namespace {
|
|
|
206
229
|
|
|
207
230
|
// The parser also consumes server checkUpdate responses, so hostile input
|
|
208
231
|
// must fail cleanly: nesting is capped to keep recursive descent off the
|
|
209
|
-
// stack limit
|
|
232
|
+
// stack limit (size and node count are capped in flow_json.h). Real
|
|
233
|
+
// responses nest ~4 levels.
|
|
210
234
|
constexpr int kMaxDepth = 64;
|
|
211
235
|
|
|
236
|
+
// Locale-independent decimal -> double. strtod() honours LC_NUMERIC, so a
|
|
237
|
+
// host process that called setlocale() into a comma-decimal locale would stop
|
|
238
|
+
// at the '.' of every fractional literal and reject the whole response. The
|
|
239
|
+
// token has already been validated against the JSON number grammar.
|
|
240
|
+
// Correctly rounded whenever the significand fits in 53 bits and the decimal
|
|
241
|
+
// exponent is within +-22 (exact double powers of ten) or the value is an
|
|
242
|
+
// integer below 2^53 x 10^15 — which covers everything the decision layer
|
|
243
|
+
// handles (hashes, counters, rollout percentages, timestamps). Anything more
|
|
244
|
+
// exotic is computed as significand x 10^exponent in double arithmetic and
|
|
245
|
+
// may differ from JSON.parse by an ulp; extreme exponents saturate to
|
|
246
|
+
// infinity / zero like JS.
|
|
247
|
+
double DecimalToDouble(const std::string& token) {
|
|
248
|
+
size_t i = 0;
|
|
249
|
+
bool negative = false;
|
|
250
|
+
if (i < token.size() && token[i] == '-') {
|
|
251
|
+
negative = true;
|
|
252
|
+
i++;
|
|
253
|
+
}
|
|
254
|
+
uint64_t significand = 0;
|
|
255
|
+
int digits = 0; // significant digits folded into `significand`
|
|
256
|
+
int exponent = 0; // decimal exponent applied to `significand`
|
|
257
|
+
bool fraction = false;
|
|
258
|
+
for (; i < token.size(); i++) {
|
|
259
|
+
const char c = token[i];
|
|
260
|
+
if (c == '.') {
|
|
261
|
+
fraction = true;
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
if (c == 'e' || c == 'E') {
|
|
265
|
+
break;
|
|
266
|
+
}
|
|
267
|
+
const int digit = c - '0';
|
|
268
|
+
if (significand == 0 && digit == 0) {
|
|
269
|
+
if (fraction) {
|
|
270
|
+
exponent--; // leading zeros after the point only shift the scale
|
|
271
|
+
}
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
if (digits < 19) {
|
|
275
|
+
significand = significand * 10 + static_cast<uint64_t>(digit);
|
|
276
|
+
digits++;
|
|
277
|
+
if (fraction) {
|
|
278
|
+
exponent--;
|
|
279
|
+
}
|
|
280
|
+
} else if (!fraction) {
|
|
281
|
+
exponent++; // beyond 19 digits: keep the magnitude, drop precision
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
if (i < token.size()) { // exponent part
|
|
285
|
+
i++;
|
|
286
|
+
bool exp_negative = false;
|
|
287
|
+
if (i < token.size() && (token[i] == '+' || token[i] == '-')) {
|
|
288
|
+
exp_negative = token[i] == '-';
|
|
289
|
+
i++;
|
|
290
|
+
}
|
|
291
|
+
int exp_value = 0;
|
|
292
|
+
for (; i < token.size(); i++) {
|
|
293
|
+
if (exp_value < 100000) { // saturate: the result is inf/0 anyway
|
|
294
|
+
exp_value = exp_value * 10 + (token[i] - '0');
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
exponent += exp_negative ? -exp_value : exp_value;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
double result = 0.0;
|
|
301
|
+
if (significand != 0) {
|
|
302
|
+
static const double kPow10[] = {
|
|
303
|
+
1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11,
|
|
304
|
+
1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22};
|
|
305
|
+
static const uint64_t kPow10Int[] = {
|
|
306
|
+
1ull, 10ull, 100ull, 1000ull,
|
|
307
|
+
10000ull, 100000ull, 1000000ull, 10000000ull,
|
|
308
|
+
100000000ull, 1000000000ull, 10000000000ull, 100000000000ull,
|
|
309
|
+
1000000000000ull, 10000000000000ull, 100000000000000ull,
|
|
310
|
+
1000000000000000ull};
|
|
311
|
+
const uint64_t kExact = 1ull << 53;
|
|
312
|
+
if (significand <= kExact && exponent >= -22 && exponent <= 22) {
|
|
313
|
+
result = exponent >= 0
|
|
314
|
+
? static_cast<double>(significand) * kPow10[exponent]
|
|
315
|
+
: static_cast<double>(significand) / kPow10[-exponent];
|
|
316
|
+
} else if (significand <= kExact && exponent > 22 && exponent <= 22 + 15 &&
|
|
317
|
+
significand <= kExact / kPow10Int[exponent - 22]) {
|
|
318
|
+
result = static_cast<double>(significand * kPow10Int[exponent - 22]) *
|
|
319
|
+
kPow10[22];
|
|
320
|
+
} else {
|
|
321
|
+
result = static_cast<double>(significand) *
|
|
322
|
+
std::pow(10.0, static_cast<double>(exponent));
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
return negative ? -result : result;
|
|
326
|
+
}
|
|
327
|
+
|
|
212
328
|
class Parser {
|
|
213
329
|
public:
|
|
214
330
|
Parser(const std::string& text) : text_(text) {}
|
|
@@ -260,6 +376,10 @@ class Parser {
|
|
|
260
376
|
ok_ = false;
|
|
261
377
|
return Value::Undefined();
|
|
262
378
|
}
|
|
379
|
+
if (++nodes_ > kMaxNodes) {
|
|
380
|
+
ok_ = false;
|
|
381
|
+
return Value::Undefined();
|
|
382
|
+
}
|
|
263
383
|
char c = text_[pos_];
|
|
264
384
|
switch (c) {
|
|
265
385
|
case '{':
|
|
@@ -376,17 +496,32 @@ class Parser {
|
|
|
376
496
|
break;
|
|
377
497
|
case 'u': {
|
|
378
498
|
unsigned code = ParseHex4();
|
|
379
|
-
|
|
499
|
+
if (!ok_) {
|
|
500
|
+
return out;
|
|
501
|
+
}
|
|
502
|
+
// A high surrogate combines with an immediately following low
|
|
503
|
+
// surrogate escape into one supplementary code point.
|
|
380
504
|
if (code >= 0xd800 && code <= 0xdbff &&
|
|
381
505
|
text_.compare(pos_, 2, "\\u") == 0) {
|
|
506
|
+
const size_t escape_start = pos_;
|
|
382
507
|
pos_ += 2;
|
|
383
508
|
unsigned low = ParseHex4();
|
|
509
|
+
if (!ok_) {
|
|
510
|
+
return out;
|
|
511
|
+
}
|
|
384
512
|
if (low >= 0xdc00 && low <= 0xdfff) {
|
|
385
513
|
code = 0x10000 + ((code - 0xd800) << 10) + (low - 0xdc00);
|
|
386
514
|
} else {
|
|
387
|
-
|
|
515
|
+
pos_ = escape_start; // the loop re-reads that escape on its own
|
|
388
516
|
}
|
|
389
517
|
}
|
|
518
|
+
// JSON.parse accepts a lone surrogate, but it has no UTF-8 form:
|
|
519
|
+
// emitting one byte-encoded (ED A0 80..) would push invalid UTF-8
|
|
520
|
+
// into napi_create_string_utf8 / NewString. Decode it to U+FFFD,
|
|
521
|
+
// the way TextEncoder serializes such a JS string.
|
|
522
|
+
if (code >= 0xd800 && code <= 0xdfff) {
|
|
523
|
+
code = 0xfffd;
|
|
524
|
+
}
|
|
390
525
|
AppendUtf8(code, &out);
|
|
391
526
|
break;
|
|
392
527
|
}
|
|
@@ -496,25 +631,23 @@ class Parser {
|
|
|
496
631
|
}
|
|
497
632
|
}
|
|
498
633
|
|
|
499
|
-
|
|
500
|
-
std::string token = text_.substr(start, pos_ - start);
|
|
501
|
-
double n = std::strtod(token.c_str(), &end);
|
|
502
|
-
if (end == nullptr || *end != '\0') {
|
|
503
|
-
ok_ = false;
|
|
504
|
-
return Value::Undefined();
|
|
505
|
-
}
|
|
506
|
-
return Value::Number(n);
|
|
634
|
+
return Value::Number(DecimalToDouble(text_.substr(start, pos_ - start)));
|
|
507
635
|
}
|
|
508
636
|
|
|
509
637
|
const std::string& text_;
|
|
510
638
|
size_t pos_ = 0;
|
|
511
639
|
int depth_ = 0;
|
|
640
|
+
size_t nodes_ = 0;
|
|
512
641
|
bool ok_ = true;
|
|
513
642
|
};
|
|
514
643
|
|
|
515
644
|
} // namespace
|
|
516
645
|
|
|
517
646
|
Value Parse(const std::string& text, bool* ok) {
|
|
647
|
+
if (text.size() > kMaxInputBytes) {
|
|
648
|
+
*ok = false;
|
|
649
|
+
return Value::Undefined();
|
|
650
|
+
}
|
|
518
651
|
Parser parser(text);
|
|
519
652
|
return parser.Run(ok);
|
|
520
653
|
}
|
|
@@ -96,9 +96,21 @@ class Value {
|
|
|
96
96
|
// equal.
|
|
97
97
|
std::string Stringify(const Value& v);
|
|
98
98
|
|
|
99
|
+
// Hard limits for Parse. The TS reference has none (JSON.parse is unbounded)
|
|
100
|
+
// and the divergence is deliberate: this parser runs inside the native
|
|
101
|
+
// orchestrators on the raw checkUpdate body, and with sizeof(Value) ~ 96 a
|
|
102
|
+
// 1 MB "[0,0,...]" balloons to ~100 MB of heap. A response over either cap is
|
|
103
|
+
// treated as malformed (HandleCheckResponse -> invalidResponse). Real
|
|
104
|
+
// responses are a few KB and a few hundred nodes.
|
|
105
|
+
constexpr size_t kMaxInputBytes = 1024 * 1024;
|
|
106
|
+
constexpr size_t kMaxNodes = 65536;
|
|
107
|
+
|
|
99
108
|
// Strict JSON parser (the vectors file and checkUpdate responses). Returns
|
|
100
109
|
// Undefined and sets *ok to false on malformed input; nesting beyond 64
|
|
101
|
-
// levels
|
|
110
|
+
// levels, more than kMaxInputBytes of text or more than kMaxNodes values are
|
|
111
|
+
// rejected so hostile server data cannot exhaust the stack or the heap.
|
|
112
|
+
// Number parsing/printing is locale-independent (never strtod/LC_NUMERIC).
|
|
113
|
+
// Lone surrogate escapes decode to U+FFFD so the output is always valid UTF-8.
|
|
102
114
|
Value Parse(const std::string& text, bool* ok);
|
|
103
115
|
|
|
104
116
|
} // namespace flowjson
|
|
@@ -6,19 +6,83 @@ namespace updateflow {
|
|
|
6
6
|
|
|
7
7
|
using flowjson::Value;
|
|
8
8
|
|
|
9
|
+
namespace {
|
|
10
|
+
|
|
11
|
+
// The low byte of every UTF-16 code unit of `utf8`, which is exactly what the
|
|
12
|
+
// TS reference hashes (`key.charCodeAt(i) & 0xff` over `key.length` units).
|
|
13
|
+
// Identity for ASCII; an astral code point contributes its two surrogate
|
|
14
|
+
// units. Accepts the JNI modified-UTF-8 forms too (C0 80 for U+0000 and
|
|
15
|
+
// byte-encoded surrogates count as one unit each, as charCodeAt would see
|
|
16
|
+
// them); malformed bytes decode to U+FFFD.
|
|
17
|
+
std::vector<unsigned char> Utf16LowBytes(const std::string& utf8) {
|
|
18
|
+
std::vector<unsigned char> units;
|
|
19
|
+
units.reserve(utf8.size());
|
|
20
|
+
size_t i = 0;
|
|
21
|
+
while (i < utf8.size()) {
|
|
22
|
+
const unsigned char first = static_cast<unsigned char>(utf8[i]);
|
|
23
|
+
uint32_t codePoint = 0xfffd;
|
|
24
|
+
size_t width = 1;
|
|
25
|
+
if (first < 0x80) {
|
|
26
|
+
codePoint = first;
|
|
27
|
+
} else if (first >= 0xc2 && first <= 0xdf && i + 1 < utf8.size()) {
|
|
28
|
+
const unsigned char b1 = static_cast<unsigned char>(utf8[i + 1]);
|
|
29
|
+
if ((b1 & 0xc0) == 0x80) {
|
|
30
|
+
codePoint = ((first & 0x1f) << 6) | (b1 & 0x3f);
|
|
31
|
+
width = 2;
|
|
32
|
+
}
|
|
33
|
+
} else if (first == 0xc0 && i + 1 < utf8.size() &&
|
|
34
|
+
static_cast<unsigned char>(utf8[i + 1]) == 0x80) {
|
|
35
|
+
codePoint = 0;
|
|
36
|
+
width = 2;
|
|
37
|
+
} else if (first >= 0xe0 && first <= 0xef && i + 2 < utf8.size()) {
|
|
38
|
+
const unsigned char b1 = static_cast<unsigned char>(utf8[i + 1]);
|
|
39
|
+
const unsigned char b2 = static_cast<unsigned char>(utf8[i + 2]);
|
|
40
|
+
if ((b1 & 0xc0) == 0x80 && (b2 & 0xc0) == 0x80 &&
|
|
41
|
+
!(first == 0xe0 && b1 < 0xa0)) {
|
|
42
|
+
codePoint = ((first & 0x0f) << 12) | ((b1 & 0x3f) << 6) | (b2 & 0x3f);
|
|
43
|
+
width = 3;
|
|
44
|
+
}
|
|
45
|
+
} else if (first >= 0xf0 && first <= 0xf4 && i + 3 < utf8.size()) {
|
|
46
|
+
const unsigned char b1 = static_cast<unsigned char>(utf8[i + 1]);
|
|
47
|
+
const unsigned char b2 = static_cast<unsigned char>(utf8[i + 2]);
|
|
48
|
+
const unsigned char b3 = static_cast<unsigned char>(utf8[i + 3]);
|
|
49
|
+
if ((b1 & 0xc0) == 0x80 && (b2 & 0xc0) == 0x80 &&
|
|
50
|
+
(b3 & 0xc0) == 0x80 && !(first == 0xf0 && b1 < 0x90) &&
|
|
51
|
+
!(first == 0xf4 && b1 > 0x8f)) {
|
|
52
|
+
codePoint = ((first & 0x07) << 18) | ((b1 & 0x3f) << 12) |
|
|
53
|
+
((b2 & 0x3f) << 6) | (b3 & 0x3f);
|
|
54
|
+
width = 4;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
i += width;
|
|
58
|
+
if (codePoint <= 0xffff) {
|
|
59
|
+
units.push_back(static_cast<unsigned char>(codePoint & 0xff));
|
|
60
|
+
} else {
|
|
61
|
+
codePoint -= 0x10000;
|
|
62
|
+
units.push_back(static_cast<unsigned char>((0xd800 + (codePoint >> 10)) & 0xff));
|
|
63
|
+
units.push_back(static_cast<unsigned char>((0xdc00 + (codePoint & 0x3ff)) & 0xff));
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return units;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
} // namespace
|
|
70
|
+
|
|
9
71
|
uint32_t Murmur3_32(const std::string& key, uint32_t seed) {
|
|
10
72
|
// The TS reference emulates 32-bit multiplication with 16-bit halves;
|
|
11
73
|
// native uint32_t arithmetic is exactly (x * y) mod 2^32, so the halved
|
|
12
|
-
// dance collapses to plain multiplies.
|
|
13
|
-
//
|
|
74
|
+
// dance collapses to plain multiplies. Input bytes are the low byte of each
|
|
75
|
+
// UTF-16 code unit (charCodeAt & 0xff), not the UTF-8 bytes: hashing UTF-8
|
|
76
|
+
// would bucket non-ASCII keys differently from the TS reference.
|
|
14
77
|
const uint32_t c1 = 0xcc9e2d51;
|
|
15
78
|
const uint32_t c2 = 0x1b873593;
|
|
16
|
-
const
|
|
79
|
+
const std::vector<unsigned char> units = Utf16LowBytes(key);
|
|
80
|
+
static const unsigned char kEmpty = 0;
|
|
81
|
+
const unsigned char* data = units.empty() ? &kEmpty : units.data();
|
|
82
|
+
const size_t len = units.size();
|
|
17
83
|
const size_t nblocks = len / 4;
|
|
18
84
|
uint32_t h1 = seed;
|
|
19
85
|
|
|
20
|
-
const unsigned char* data =
|
|
21
|
-
reinterpret_cast<const unsigned char*>(key.data());
|
|
22
86
|
for (size_t i = 0; i < nblocks; i++) {
|
|
23
87
|
uint32_t k1 = static_cast<uint32_t>(data[i * 4]) |
|
|
24
88
|
(static_cast<uint32_t>(data[i * 4 + 1]) << 8) |
|
|
@@ -62,6 +126,10 @@ bool IsInRollout(double rollout, const std::string& uuid) {
|
|
|
62
126
|
return static_cast<double>(Murmur3_32(uuid) % 100) < rollout;
|
|
63
127
|
}
|
|
64
128
|
|
|
129
|
+
bool IsMirrorRetryableCode(const std::string& code) {
|
|
130
|
+
return code != "PATCH_FAILED";
|
|
131
|
+
}
|
|
132
|
+
|
|
65
133
|
namespace {
|
|
66
134
|
|
|
67
135
|
// ^[a-z][a-z0-9+.-]*:\/\/ (case-insensitive)
|
|
@@ -114,7 +182,9 @@ Value OrderEndpointCandidates(const Value& endpoints, double randomSample) {
|
|
|
114
182
|
}
|
|
115
183
|
bool seen = false;
|
|
116
184
|
for (const auto& kept : deduped.elements()) {
|
|
117
|
-
|
|
185
|
+
// Set membership is ===: a stray non-string entry (a number, `true`)
|
|
186
|
+
// dedupes against equal primitives only, never against strings.
|
|
187
|
+
if (Value::StrictEquals(kept, endpoint)) {
|
|
118
188
|
seen = true;
|
|
119
189
|
break;
|
|
120
190
|
}
|
|
@@ -247,8 +317,8 @@ Value DeclineDownload(const char* reason) {
|
|
|
247
317
|
Value DecideDownload(const Value& info, const Value& identity, bool isDev) {
|
|
248
318
|
const Value& hash = info.Get("hash");
|
|
249
319
|
Value paths = info.Get("paths");
|
|
250
|
-
if (paths.IsUndefined()) {
|
|
251
|
-
paths = Value::Array(); //
|
|
320
|
+
if (paths.IsUndefined() || paths.kind() == Value::Kind::Null) {
|
|
321
|
+
paths = Value::Array(); // info.paths ?? [] — a server `null` too
|
|
252
322
|
}
|
|
253
323
|
if (!info.Get("update").Truthy() || !hash.Truthy()) {
|
|
254
324
|
return DeclineDownload("noUpdate");
|