react-native-update 10.59.1 → 10.60.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 +7 -7
- package/README.md +7 -6
- package/android/build.gradle +25 -33
- package/android/jni/Android.mk +16 -24
- package/android/lib/arm64-v8a/librnpushy.so +0 -0
- package/android/lib/armeabi-v7a/librnpushy.so +0 -0
- package/android/lib/x86/librnpushy.so +0 -0
- package/android/lib/x86_64/librnpushy.so +0 -0
- package/android/proguard.pro +9 -7
- package/android/src/main/java/cn/reactnative/modules/update/ApkInstaller.java +11 -12
- package/android/src/main/java/cn/reactnative/modules/update/ArchiveLimits.java +23 -6
- package/android/src/main/java/cn/reactnative/modules/update/BundledResourceCopier.java +50 -51
- package/android/src/main/java/cn/reactnative/modules/update/CleanupKeepNames.java +12 -13
- package/android/src/main/java/cn/reactnative/modules/update/CopyGroupResult.java +3 -0
- package/android/src/main/java/cn/reactnative/modules/update/CrashHold.java +22 -30
- package/android/src/main/java/cn/reactnative/modules/update/DownloadTask.java +181 -332
- package/android/src/main/java/cn/reactnative/modules/update/DownloadTaskParams.java +3 -1
- package/android/src/main/java/cn/reactnative/modules/update/ErrorCodes.java +27 -0
- package/android/src/main/java/cn/reactnative/modules/update/FileOperationException.java +1 -1
- package/android/src/main/java/cn/reactnative/modules/update/FlowBridge.java +32 -8
- package/android/src/main/java/cn/reactnative/modules/update/HttpClients.java +63 -0
- package/android/src/main/java/cn/reactnative/modules/update/HttpUtils.java +0 -11
- package/android/src/main/java/cn/reactnative/modules/update/InstallRecord.java +66 -116
- package/android/src/main/java/cn/reactnative/modules/update/NativeCore.java +9 -1
- package/android/src/main/java/cn/reactnative/modules/update/PatchFailedException.java +1 -1
- package/android/src/main/java/cn/reactnative/modules/update/PushyRuntime.java +32 -60
- package/android/src/main/java/cn/reactnative/modules/update/ReactReloadManager.java +8 -23
- package/android/src/main/java/cn/reactnative/modules/update/SafeZipFile.java +42 -80
- package/android/src/main/java/cn/reactnative/modules/update/StateSerialRunner.java +22 -39
- package/android/src/main/java/cn/reactnative/modules/update/SyncCoordinator.java +176 -299
- package/android/src/main/java/cn/reactnative/modules/update/Threads.java +35 -0
- package/android/src/main/java/cn/reactnative/modules/update/UpdateContext.java +242 -174
- package/android/src/main/java/cn/reactnative/modules/update/UpdateEventEmitter.java +1 -1
- package/android/src/main/java/cn/reactnative/modules/update/UpdateFileUtils.java +75 -17
- package/android/src/main/java/cn/reactnative/modules/update/{UpdateModuleImpl.java → UpdateModule.java} +113 -95
- package/android/src/main/java/cn/reactnative/modules/update/UpdatePackage.java +16 -19
- package/android/src/oldarch/cn/reactnative/modules/update/NativePushySpec.java +69 -0
- package/cpp/android_jni/archive_limits_jni.cpp +83 -0
- package/cpp/android_jni/install_record_jni.cpp +89 -0
- package/cpp/android_jni/jni_log.h +31 -0
- package/cpp/android_jni/jni_natives.h +40 -0
- package/cpp/android_jni/jni_registration.cpp +133 -0
- package/cpp/android_jni/jni_util.h +160 -0
- package/cpp/android_jni/update_core_android.cpp +450 -0
- package/cpp/android_jni/update_flow_jni.cpp +133 -0
- package/{android/jni → cpp/hpatch}/hpatch.c +5 -5
- package/{android/jni → cpp/hpatch}/hpatch.h +4 -1
- package/cpp/patch_core/archive_limits.cpp +194 -0
- package/cpp/patch_core/archive_limits.h +99 -10
- package/cpp/patch_core/archive_patch_core.cpp +96 -12
- package/cpp/patch_core/archive_patch_core.h +49 -0
- package/cpp/patch_core/digest.h +5 -4
- package/cpp/patch_core/error_codes.h +5 -3
- package/cpp/patch_core/install_record.cpp +430 -0
- package/cpp/patch_core/install_record.h +163 -9
- package/cpp/patch_core/patch_core.cpp +62 -16
- package/cpp/patch_core/patch_core.h +27 -7
- package/cpp/patch_core/patch_manifest.cpp +201 -0
- package/cpp/patch_core/patch_manifest.h +74 -0
- package/cpp/patch_core/state_ops.h +3 -3
- package/cpp/update_flow_core/flow_json.cpp +82 -8
- package/cpp/update_flow_core/flow_json.h +21 -0
- package/cpp/update_flow_core/native_config.cpp +456 -0
- package/cpp/update_flow_core/native_config.h +84 -0
- package/cpp/update_flow_core/native_round.cpp +326 -0
- package/cpp/update_flow_core/native_round.h +90 -0
- package/cpp/update_flow_core/update_flow_core.h +2 -2
- package/harmony/hvigor-plugin.ts +11 -19
- package/harmony/pushy/src/main/cpp/CMakeLists.txt +19 -26
- package/harmony/pushy/src/main/cpp/PushyPackage.h +2 -2
- package/harmony/pushy/src/main/cpp/PushyTurboModule.cpp +55 -94
- package/harmony/pushy/src/main/cpp/pushy.cpp +694 -517
- package/harmony/pushy/src/main/ets/ArchiveLimits.ts +48 -72
- package/harmony/pushy/src/main/ets/DownloadTask.ts +164 -404
- package/harmony/pushy/src/main/ets/DownloadTaskParams.ts +1 -3
- package/harmony/pushy/src/main/ets/ErrorCodes.ts +1 -14
- package/harmony/pushy/src/main/ets/EventHub.ts +1 -1
- package/harmony/pushy/src/main/ets/InstallRecord.ts +52 -151
- package/harmony/pushy/src/main/ets/Logger.ts +20 -34
- package/harmony/pushy/src/main/ets/NativePatchCore.ts +116 -42
- package/harmony/pushy/src/main/ets/PushyConfiguration.ts +8 -85
- package/harmony/pushy/src/main/ets/PushyTurboModule.ts +11 -25
- package/harmony/pushy/src/main/ets/SaveFile.ts +99 -18
- package/harmony/pushy/src/main/ets/SyncCoordinator.ts +136 -335
- package/harmony/pushy/src/main/ets/Texts.ts +0 -4
- package/harmony/pushy/src/main/ets/UpdateContext.ts +106 -119
- package/harmony/pushy.har +0 -0
- package/ios/RCTPushy/RCTPushy+Launch.mm +185 -0
- package/ios/RCTPushy/RCTPushy.h +12 -0
- package/ios/RCTPushy/RCTPushy.mm +87 -2628
- package/ios/RCTPushy/RCTPushyCommon.h +34 -0
- package/ios/RCTPushy/RCTPushyConfiguration.h +0 -8
- package/ios/RCTPushy/RCTPushyConfiguration.mm +40 -121
- package/ios/RCTPushy/RCTPushyDownloader.mm +40 -37
- package/ios/RCTPushy/RCTPushyInstallRecord.mm +61 -0
- package/ios/RCTPushy/RCTPushyInstaller.mm +689 -0
- package/ios/RCTPushy/RCTPushyInternal.h +144 -0
- package/ios/RCTPushy/RCTPushyOrchestrator.mm +1001 -0
- package/ios/RCTPushy/RCTPushyState.mm +314 -0
- package/package.json +25 -23
- package/react-native-update.podspec +74 -83
- package/src/client.ts +361 -451
- package/src/context.ts +1 -1
- package/src/core.ts +35 -8
- package/src/endpoint.ts +0 -2
- package/src/error.ts +13 -32
- package/src/i18n.ts +18 -83
- package/src/locales/en.ts +0 -4
- package/src/locales/zh.ts +0 -4
- package/src/provider.tsx +60 -53
- package/src/telemetry.ts +0 -13
- package/src/type.ts +0 -15
- package/src/updateFlowCore.ts +116 -10
- package/src/utils.ts +49 -42
- package/android/src/main/java/cn/reactnative/modules/update/ArchivePatchPlanResult.java +0 -6
- package/android/src/main/java/cn/reactnative/modules/update/PushyConfiguration.java +0 -104
- package/android/src/main/java/cn/reactnative/modules/update/UiThreadRunner.java +0 -37
- package/android/src/newarch/cn/reactnative/modules/update/UpdateModule.java +0 -121
- package/android/src/oldarch/cn/reactnative/modules/update/UpdateModule.java +0 -125
- package/cpp/patch_core/jni_natives.h +0 -27
- package/cpp/patch_core/jni_registration.cpp +0 -103
- package/cpp/patch_core/jni_util.h +0 -60
- package/cpp/patch_core/patch_core_android.cpp +0 -97
- package/cpp/patch_core/update_core_android.cpp +0 -485
- package/cpp/update_flow_core/update_flow_jni.cpp +0 -150
|
@@ -21,7 +21,10 @@
|
|
|
21
21
|
#include "hbc_transform_wire.h"
|
|
22
22
|
|
|
23
23
|
extern "C" {
|
|
24
|
-
|
|
24
|
+
// Relative to this file: under use_frameworks! a bare "hpatch.h" resolves
|
|
25
|
+
// to the flattened framework PrivateHeaders copy, whose own HDiffPatch/
|
|
26
|
+
// includes then fail.
|
|
27
|
+
#include "../hpatch/hpatch.h"
|
|
25
28
|
}
|
|
26
29
|
|
|
27
30
|
namespace pushy {
|
|
@@ -487,14 +490,6 @@ Status MergeDirectoryRecursively(
|
|
|
487
490
|
|
|
488
491
|
} // namespace
|
|
489
492
|
|
|
490
|
-
Status Status::Ok() {
|
|
491
|
-
return Status{true, std::string()};
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
Status Status::Error(const std::string& message) {
|
|
495
|
-
return Status{false, message};
|
|
496
|
-
}
|
|
497
|
-
|
|
498
493
|
Status ValidateManifest(const PatchManifest& manifest) {
|
|
499
494
|
return ValidateManifestImpl(manifest);
|
|
500
495
|
}
|
|
@@ -580,7 +575,7 @@ Status ApplyBundlePatchWithHbcTransform(
|
|
|
580
575
|
const FileSourcePatchOptions& options,
|
|
581
576
|
const BundleRebuilder& bundle_patcher) {
|
|
582
577
|
hbc::HbcTransformMeta meta;
|
|
583
|
-
if (!hbc::ParseHbcTransformMeta(options.bundle_hbc_transform_meta, &meta)) {
|
|
578
|
+
if (!hbc::ParseHbcTransformMeta(options.manifest.bundle_hbc_transform_meta, &meta)) {
|
|
584
579
|
return Status::Error("Invalid hbcTransform metadata");
|
|
585
580
|
}
|
|
586
581
|
if (meta.v != hbc::kHbcTransformSupportedVersion) {
|
|
@@ -654,7 +649,7 @@ Status ApplyPatchFromFileSource(
|
|
|
654
649
|
}
|
|
655
650
|
|
|
656
651
|
Status bundle_status =
|
|
657
|
-
options.bundle_hbc_transform_meta.empty()
|
|
652
|
+
options.manifest.bundle_hbc_transform_meta.empty()
|
|
658
653
|
? bundle_patcher.Apply(
|
|
659
654
|
options.origin_bundle_path,
|
|
660
655
|
options.bundle_patch_path,
|
|
@@ -727,11 +722,16 @@ Status CleanupOldEntries(
|
|
|
727
722
|
max_age_days, now);
|
|
728
723
|
}
|
|
729
724
|
|
|
730
|
-
|
|
725
|
+
namespace {
|
|
726
|
+
|
|
727
|
+
// Lists the non-dot entries of root_dir that are not kept and are at least
|
|
728
|
+
// max_age_days old: the one selection rule shared by delete and trash.
|
|
729
|
+
Status SelectOldEntries(
|
|
731
730
|
const std::string& root_dir,
|
|
732
731
|
const std::vector<std::string>& keep_names,
|
|
733
732
|
int max_age_days,
|
|
734
|
-
std::time_t now
|
|
733
|
+
std::time_t now,
|
|
734
|
+
std::vector<std::string>* out) {
|
|
735
735
|
DIR* dir = opendir(root_dir.c_str());
|
|
736
736
|
if (!dir) {
|
|
737
737
|
if (errno == ENOENT) {
|
|
@@ -765,15 +765,61 @@ Status CleanupOldEntries(
|
|
|
765
765
|
if (effective_now - st.st_mtime < max_age_seconds) {
|
|
766
766
|
continue;
|
|
767
767
|
}
|
|
768
|
+
out->push_back(name);
|
|
769
|
+
}
|
|
768
770
|
|
|
769
|
-
|
|
771
|
+
closedir(dir);
|
|
772
|
+
return Status::Ok();
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
} // namespace
|
|
776
|
+
|
|
777
|
+
Status CleanupOldEntries(
|
|
778
|
+
const std::string& root_dir,
|
|
779
|
+
const std::vector<std::string>& keep_names,
|
|
780
|
+
int max_age_days,
|
|
781
|
+
std::time_t now) {
|
|
782
|
+
std::vector<std::string> doomed;
|
|
783
|
+
Status select_status =
|
|
784
|
+
SelectOldEntries(root_dir, keep_names, max_age_days, now, &doomed);
|
|
785
|
+
if (!select_status) {
|
|
786
|
+
return select_status;
|
|
787
|
+
}
|
|
788
|
+
for (const std::string& name : doomed) {
|
|
789
|
+
Status remove_status = RemovePathRecursively(JoinPath(root_dir, name));
|
|
770
790
|
if (!remove_status) {
|
|
771
|
-
closedir(dir);
|
|
772
791
|
return remove_status;
|
|
773
792
|
}
|
|
774
793
|
}
|
|
794
|
+
return Status::Ok();
|
|
795
|
+
}
|
|
775
796
|
|
|
776
|
-
|
|
797
|
+
Status MoveOldEntriesToTrash(
|
|
798
|
+
const std::string& root_dir,
|
|
799
|
+
const std::vector<std::string>& keep_names,
|
|
800
|
+
int max_age_days,
|
|
801
|
+
const std::string& trash_dir,
|
|
802
|
+
std::time_t now) {
|
|
803
|
+
std::vector<std::string> doomed;
|
|
804
|
+
Status select_status =
|
|
805
|
+
SelectOldEntries(root_dir, keep_names, max_age_days, now, &doomed);
|
|
806
|
+
if (!select_status || doomed.empty()) {
|
|
807
|
+
return select_status;
|
|
808
|
+
}
|
|
809
|
+
Status dir_status = EnsureDirectory(trash_dir);
|
|
810
|
+
if (!dir_status) {
|
|
811
|
+
return dir_status;
|
|
812
|
+
}
|
|
813
|
+
for (const std::string& name : doomed) {
|
|
814
|
+
std::string target = JoinPath(trash_dir, name);
|
|
815
|
+
for (int suffix = 1; PathExists(target); ++suffix) {
|
|
816
|
+
target = JoinPath(trash_dir, name + "." + IntToString(suffix));
|
|
817
|
+
}
|
|
818
|
+
const std::string source = JoinPath(root_dir, name);
|
|
819
|
+
if (rename(source.c_str(), target.c_str()) != 0) {
|
|
820
|
+
return MakeErrnoStatus("Failed to move " + source + " to trash");
|
|
821
|
+
}
|
|
822
|
+
}
|
|
777
823
|
return Status::Ok();
|
|
778
824
|
}
|
|
779
825
|
|
|
@@ -17,8 +17,12 @@ struct Status {
|
|
|
17
17
|
|
|
18
18
|
explicit operator bool() const { return ok; }
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
// Inline so a core that only reports a Status (install_record.cpp) links
|
|
21
|
+
// without patch_core.cpp and its hpatch dependencies.
|
|
22
|
+
static Status Ok() { return Status{true, std::string()}; }
|
|
23
|
+
static Status Error(const std::string& message) {
|
|
24
|
+
return Status{false, message};
|
|
25
|
+
}
|
|
22
26
|
};
|
|
23
27
|
|
|
24
28
|
struct CopyOperation {
|
|
@@ -33,9 +37,16 @@ struct CopyOperation {
|
|
|
33
37
|
uint32_t expected_crc = 0;
|
|
34
38
|
};
|
|
35
39
|
|
|
40
|
+
// What a patch archive's __diff.json declares (ParsePatchManifest in
|
|
41
|
+
// patch_manifest.h is the one parser every platform uses).
|
|
36
42
|
struct PatchManifest {
|
|
37
43
|
std::vector<CopyOperation> copies;
|
|
38
44
|
std::vector<std::string> deletes;
|
|
45
|
+
// __diff.json 中该 bundle patch 条目的 hbcTransform 元数据(JSON 文本)。
|
|
46
|
+
// 非空时 patch 走变换域:T(origin) → hpatch → T⁻¹。元数据不可解析或
|
|
47
|
+
// 变换规范版本不受支持时返回错误(调用方回退整包),绝不忽略元数据
|
|
48
|
+
// 直接应用——那会产出损坏的 bundle。
|
|
49
|
+
std::string bundle_hbc_transform_meta;
|
|
39
50
|
};
|
|
40
51
|
|
|
41
52
|
struct FileSourcePatchOptions {
|
|
@@ -47,11 +58,6 @@ struct FileSourcePatchOptions {
|
|
|
47
58
|
std::string bundle_output_path;
|
|
48
59
|
std::string merge_source_subdir;
|
|
49
60
|
bool enable_merge = true;
|
|
50
|
-
// __diff.json 中该 bundle patch 条目的 hbcTransform 元数据(原始 JSON)。
|
|
51
|
-
// 非空时 patch 走变换域:T(origin) → hpatch → T⁻¹。元数据不可解析或
|
|
52
|
-
// 变换规范版本不受支持时返回错误(调用方回退整包),绝不忽略元数据
|
|
53
|
-
// 直接应用——那会产出损坏的 bundle。
|
|
54
|
-
std::string bundle_hbc_transform_meta;
|
|
55
61
|
};
|
|
56
62
|
|
|
57
63
|
class BundleRebuilder {
|
|
@@ -89,6 +95,20 @@ Status CleanupOldEntries(
|
|
|
89
95
|
int max_age_days,
|
|
90
96
|
std::time_t now = 0);
|
|
91
97
|
|
|
98
|
+
// Same selection as CleanupOldEntries, but each doomed entry is renamed into
|
|
99
|
+
// trash_dir (created if needed; must be on the same file system, e.g. a
|
|
100
|
+
// dot-named child of root_dir so the selection itself skips it) instead of
|
|
101
|
+
// being deleted. Renames are cheap, so a caller can hold its version-files
|
|
102
|
+
// lock across this call and delete trash_dir's contents after releasing it
|
|
103
|
+
// (CleanupOldEntries(trash_dir, {}, 0)). A name already present in trash_dir
|
|
104
|
+
// (left over by an interrupted earlier delete) gets a numeric suffix.
|
|
105
|
+
Status MoveOldEntriesToTrash(
|
|
106
|
+
const std::string& root_dir,
|
|
107
|
+
const std::vector<std::string>& keep_names,
|
|
108
|
+
int max_age_days,
|
|
109
|
+
const std::string& trash_dir,
|
|
110
|
+
std::time_t now = 0);
|
|
111
|
+
|
|
92
112
|
bool IsSafeRelativePath(const std::string& path);
|
|
93
113
|
|
|
94
114
|
namespace internal {
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
#include "patch_manifest.h"
|
|
2
|
+
|
|
3
|
+
#include <cmath>
|
|
4
|
+
#include <cstdint>
|
|
5
|
+
#include <unordered_map>
|
|
6
|
+
#include <utility>
|
|
7
|
+
|
|
8
|
+
// Relative like every other cross-core include (see patch_core.cpp's hpatch.h
|
|
9
|
+
// include): use_frameworks! flattens the pod's headers.
|
|
10
|
+
#include "../update_flow_core/flow_json.h"
|
|
11
|
+
|
|
12
|
+
namespace pushy {
|
|
13
|
+
namespace delta {
|
|
14
|
+
namespace {
|
|
15
|
+
|
|
16
|
+
constexpr const char* kErrorPrefix = "delta manifest: ";
|
|
17
|
+
|
|
18
|
+
Status ManifestError(const std::string& message) {
|
|
19
|
+
return Status::Error(kErrorPrefix + message);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// null counts as absent everywhere (JSON writers emit it for empty maps).
|
|
23
|
+
bool IsAbsent(const flowjson::Value& value) {
|
|
24
|
+
return value.IsUndefined() ||
|
|
25
|
+
value.kind() == flowjson::Value::Kind::Null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// An integer in [0, 2^32): the only CRC value a client can compare.
|
|
29
|
+
bool ReadCrc32(const flowjson::Value& value, uint32_t* out) {
|
|
30
|
+
if (!value.IsNumber()) {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
const double number = value.AsNumber();
|
|
34
|
+
if (!std::isfinite(number) || number < 0 || number > 4294967295.0 ||
|
|
35
|
+
std::floor(number) != number) {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
*out = static_cast<uint32_t>(number);
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
Status ParseCopies(
|
|
43
|
+
const flowjson::Value& copies,
|
|
44
|
+
const flowjson::Value& copies_crc,
|
|
45
|
+
const PatchManifestOptions& options,
|
|
46
|
+
PatchManifest* manifest) {
|
|
47
|
+
if (IsAbsent(copies)) {
|
|
48
|
+
return Status::Ok();
|
|
49
|
+
}
|
|
50
|
+
if (!copies.IsObject()) {
|
|
51
|
+
return ManifestError("copies is not an object");
|
|
52
|
+
}
|
|
53
|
+
if (!IsAbsent(copies_crc) && !copies_crc.IsObject()) {
|
|
54
|
+
return ManifestError("copiesCrc is not an object");
|
|
55
|
+
}
|
|
56
|
+
// Target -> declared CRC value; one pass, not a member scan per copy.
|
|
57
|
+
std::unordered_map<std::string, const flowjson::Value*> crc_by_target;
|
|
58
|
+
if (copies_crc.IsObject()) {
|
|
59
|
+
crc_by_target.reserve(copies_crc.members().size());
|
|
60
|
+
for (const auto& member : copies_crc.members()) {
|
|
61
|
+
crc_by_target.emplace(member.first, &member.second);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
const std::string& prefix = options.copy_source_prefix;
|
|
65
|
+
// Source -> the CRC some copy of it declared, to catch contradictions.
|
|
66
|
+
std::unordered_map<std::string, uint32_t> crc_by_source;
|
|
67
|
+
manifest->copies.reserve(copies.members().size());
|
|
68
|
+
for (const auto& member : copies.members()) {
|
|
69
|
+
const std::string& to = member.first;
|
|
70
|
+
const flowjson::Value& from_value = member.second;
|
|
71
|
+
std::string from;
|
|
72
|
+
if (from_value.IsString()) {
|
|
73
|
+
from = from_value.AsString();
|
|
74
|
+
} else if (!IsAbsent(from_value)) {
|
|
75
|
+
return ManifestError("copies entry is not a string: " + to);
|
|
76
|
+
}
|
|
77
|
+
if (!prefix.empty() && from.compare(0, prefix.size(), prefix) == 0) {
|
|
78
|
+
from.erase(0, prefix.size());
|
|
79
|
+
}
|
|
80
|
+
if (from.empty()) {
|
|
81
|
+
from = to;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
CopyOperation copy;
|
|
85
|
+
copy.from = std::move(from);
|
|
86
|
+
copy.to = to;
|
|
87
|
+
const auto declared = crc_by_target.find(to);
|
|
88
|
+
if (declared != crc_by_target.end()) {
|
|
89
|
+
const flowjson::Value& crc = *declared->second;
|
|
90
|
+
if (!IsAbsent(crc)) {
|
|
91
|
+
if (!ReadCrc32(crc, ©.expected_crc)) {
|
|
92
|
+
return ManifestError("copiesCrc entry is not a CRC32: " + to);
|
|
93
|
+
}
|
|
94
|
+
copy.has_expected_crc = true;
|
|
95
|
+
const auto inserted =
|
|
96
|
+
crc_by_source.emplace(copy.from, copy.expected_crc);
|
|
97
|
+
if (!inserted.second &&
|
|
98
|
+
inserted.first->second != copy.expected_crc) {
|
|
99
|
+
return ManifestError("conflicting copiesCrc for source " + copy.from);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
manifest->copies.push_back(std::move(copy));
|
|
104
|
+
}
|
|
105
|
+
return Status::Ok();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
Status ParseDeletes(const flowjson::Value& deletes, PatchManifest* manifest) {
|
|
109
|
+
if (IsAbsent(deletes)) {
|
|
110
|
+
return Status::Ok();
|
|
111
|
+
}
|
|
112
|
+
if (deletes.IsObject()) {
|
|
113
|
+
manifest->deletes.reserve(deletes.members().size());
|
|
114
|
+
for (const auto& member : deletes.members()) {
|
|
115
|
+
manifest->deletes.push_back(member.first);
|
|
116
|
+
}
|
|
117
|
+
return Status::Ok();
|
|
118
|
+
}
|
|
119
|
+
if (deletes.IsArray()) {
|
|
120
|
+
manifest->deletes.reserve(deletes.Size());
|
|
121
|
+
for (const flowjson::Value& path : deletes.elements()) {
|
|
122
|
+
if (!path.IsString()) {
|
|
123
|
+
return ManifestError("deletes entry is not a string");
|
|
124
|
+
}
|
|
125
|
+
manifest->deletes.push_back(path.AsString());
|
|
126
|
+
}
|
|
127
|
+
return Status::Ok();
|
|
128
|
+
}
|
|
129
|
+
return ManifestError("deletes is not an object or array");
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
Status ParseHbcTransform(
|
|
133
|
+
const flowjson::Value& hbc_transform,
|
|
134
|
+
const PatchManifestOptions& options,
|
|
135
|
+
PatchManifest* manifest) {
|
|
136
|
+
if (IsAbsent(hbc_transform)) {
|
|
137
|
+
return Status::Ok();
|
|
138
|
+
}
|
|
139
|
+
if (!hbc_transform.IsObject()) {
|
|
140
|
+
return ManifestError("hbcTransform is not an object");
|
|
141
|
+
}
|
|
142
|
+
const flowjson::Value& meta =
|
|
143
|
+
hbc_transform.Get(options.bundle_patch_entry_name);
|
|
144
|
+
if (IsAbsent(meta)) {
|
|
145
|
+
return Status::Ok();
|
|
146
|
+
}
|
|
147
|
+
if (!meta.IsObject()) {
|
|
148
|
+
return ManifestError(
|
|
149
|
+
"hbcTransform entry is not an object: " +
|
|
150
|
+
options.bundle_patch_entry_name);
|
|
151
|
+
}
|
|
152
|
+
manifest->bundle_hbc_transform_meta = flowjson::Stringify(meta);
|
|
153
|
+
return Status::Ok();
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
} // namespace
|
|
157
|
+
|
|
158
|
+
Status ParsePatchManifest(
|
|
159
|
+
const std::string& json_text,
|
|
160
|
+
const PatchManifestOptions& options,
|
|
161
|
+
PatchManifest* out) {
|
|
162
|
+
if (out == nullptr) {
|
|
163
|
+
return Status::Error("Manifest output is required");
|
|
164
|
+
}
|
|
165
|
+
flowjson::ParseLimits limits;
|
|
166
|
+
limits.max_input_bytes =
|
|
167
|
+
static_cast<size_t>(archive_limits::kMaxManifestBytes);
|
|
168
|
+
limits.max_nodes = kMaxManifestNodes;
|
|
169
|
+
bool ok = false;
|
|
170
|
+
const flowjson::Value root = flowjson::Parse(json_text, limits, &ok);
|
|
171
|
+
if (!ok) {
|
|
172
|
+
return ManifestError("invalid JSON or over the size limits");
|
|
173
|
+
}
|
|
174
|
+
if (!root.IsObject()) {
|
|
175
|
+
return ManifestError("not a JSON object");
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
PatchManifest manifest;
|
|
179
|
+
Status status =
|
|
180
|
+
ParseCopies(root.Get("copies"), root.Get("copiesCrc"), options, &manifest);
|
|
181
|
+
if (!status) {
|
|
182
|
+
return status;
|
|
183
|
+
}
|
|
184
|
+
status = ParseDeletes(root.Get("deletes"), &manifest);
|
|
185
|
+
if (!status) {
|
|
186
|
+
return status;
|
|
187
|
+
}
|
|
188
|
+
status = ParseHbcTransform(root.Get("hbcTransform"), options, &manifest);
|
|
189
|
+
if (!status) {
|
|
190
|
+
return status;
|
|
191
|
+
}
|
|
192
|
+
status = ValidateManifest(manifest);
|
|
193
|
+
if (!status) {
|
|
194
|
+
return status;
|
|
195
|
+
}
|
|
196
|
+
*out = std::move(manifest);
|
|
197
|
+
return Status::Ok();
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
} // namespace delta
|
|
201
|
+
} // namespace pushy
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <cstddef>
|
|
4
|
+
#include <string>
|
|
5
|
+
|
|
6
|
+
#include "archive_limits.h"
|
|
7
|
+
#include "patch_core.h"
|
|
8
|
+
|
|
9
|
+
// Keep the core internal to whichever binary links it (see podspec Core).
|
|
10
|
+
#pragma GCC visibility push(hidden)
|
|
11
|
+
|
|
12
|
+
namespace pushy {
|
|
13
|
+
namespace delta {
|
|
14
|
+
|
|
15
|
+
// __diff.json, the manifest inside every patch archive, as the generators
|
|
16
|
+
// emit it (pushy-go internal/taskexecutor/bundle_diff.go and
|
|
17
|
+
// react-native-update-cli src/diff.ts):
|
|
18
|
+
//
|
|
19
|
+
// {"copies": {"<to>": "<from>" | "", ...},
|
|
20
|
+
// "copiesCrc": {"<to>": <crc32>, ...}, package diffs only
|
|
21
|
+
// "deletes": {"<path>": 1, ...}, ppk diffs only
|
|
22
|
+
// "hbcTransform": {"<bundle patch entry>": {...}}} hdiffv2 track only
|
|
23
|
+
//
|
|
24
|
+
// The one parser of that file on Android, iOS and HarmonyOS. The rules are
|
|
25
|
+
// the most permissive form that is still safe; anything else fails the
|
|
26
|
+
// patch, and the JS strategy chain falls back to the full package:
|
|
27
|
+
// - the text is a JSON object (at most archive_limits::kMaxManifestBytes,
|
|
28
|
+
// at most kMaxManifestNodes values); unknown members are ignored, and a
|
|
29
|
+
// member that is null counts as absent;
|
|
30
|
+
// - copies: an object of target -> source strings. An empty (or null)
|
|
31
|
+
// source means "same path as the target". Order follows the document;
|
|
32
|
+
// - copiesCrc: an object keyed by copy *target* (not source). Each value is
|
|
33
|
+
// an integer in [0, 2^32) or null; any other value is an error (a CRC the
|
|
34
|
+
// client cannot honour must not silently skip the content check). Keys
|
|
35
|
+
// naming no copy are ignored. Two copies of one source that declare
|
|
36
|
+
// different CRCs are a self-contradictory manifest and an error;
|
|
37
|
+
// - deletes: an object (its keys) or an array of strings;
|
|
38
|
+
// - hbcTransform: an object; its member named after the bundle patch entry,
|
|
39
|
+
// when present, must be an object and is handed on re-serialized
|
|
40
|
+
// (PatchManifest::bundle_hbc_transform_meta). Non-object metadata is an
|
|
41
|
+
// error, never ignored: applying a transform-domain patch without the
|
|
42
|
+
// transform produces a corrupt bundle;
|
|
43
|
+
// - every path passes ValidateManifest.
|
|
44
|
+
// Values of the wrong type (a numeric source, a non-string delete, copies as
|
|
45
|
+
// an array, ...) are errors on every platform.
|
|
46
|
+
struct PatchManifestOptions {
|
|
47
|
+
// The archive entry whose hbcTransform metadata is extracted
|
|
48
|
+
// (archive_patch::DefaultBundleDeltaEntryName() unless the platform names
|
|
49
|
+
// its bundle differently, e.g. HarmonyOS).
|
|
50
|
+
std::string bundle_patch_entry_name;
|
|
51
|
+
// Stripped once from the start of every copy source that begins with it,
|
|
52
|
+
// before the empty-source rule applies. HarmonyOS package diffs name the
|
|
53
|
+
// origin entries by their HAP path ("resources/rawfile/assets/a.png"),
|
|
54
|
+
// which the resource manager reads back as "assets/a.png".
|
|
55
|
+
std::string copy_source_prefix;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// A package holds at most archive_limits::kMaxEntries entries, and the
|
|
59
|
+
// generators emit at most one copy source, one CRC and one delete per entry
|
|
60
|
+
// (plus a few hundred values of hbcTransform metadata). The cap bounds the
|
|
61
|
+
// parsed tree of a hostile 16 MB manifest to about ten MB of heap.
|
|
62
|
+
constexpr size_t kMaxManifestNodes =
|
|
63
|
+
4 * static_cast<size_t>(archive_limits::kMaxEntries);
|
|
64
|
+
|
|
65
|
+
// Fails without touching *out.
|
|
66
|
+
Status ParsePatchManifest(
|
|
67
|
+
const std::string& json_text,
|
|
68
|
+
const PatchManifestOptions& options,
|
|
69
|
+
PatchManifest* out);
|
|
70
|
+
|
|
71
|
+
} // namespace delta
|
|
72
|
+
} // namespace pushy
|
|
73
|
+
|
|
74
|
+
#pragma GCC visibility pop
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#pragma once
|
|
2
2
|
|
|
3
3
|
// Single source of truth for the state-machine operation codes shared across
|
|
4
|
-
// the platform glue layers (Android JNI in
|
|
5
|
-
// HarmonyOS NAPI in pushy.cpp).
|
|
6
|
-
// callers on each platform:
|
|
4
|
+
// the platform glue layers (Android JNI in
|
|
5
|
+
// cpp/android_jni/update_core_android.cpp and HarmonyOS NAPI in pushy.cpp).
|
|
6
|
+
// The integer values MUST stay in sync with the callers on each platform:
|
|
7
7
|
// - Android: UpdateContext.java (STATE_OP_* constants)
|
|
8
8
|
// - HarmonyOS: UpdateContext.ts (StateOperation usage)
|
|
9
9
|
// Do not renumber existing entries; only append new ones.
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
#include <cmath>
|
|
4
4
|
#include <cstdint>
|
|
5
5
|
#include <cstdio>
|
|
6
|
+
#include <unordered_map>
|
|
6
7
|
|
|
7
8
|
namespace flowjson {
|
|
8
9
|
|
|
@@ -86,6 +87,40 @@ bool Value::StrictEquals(const Value& a, const Value& b) {
|
|
|
86
87
|
return false;
|
|
87
88
|
}
|
|
88
89
|
|
|
90
|
+
bool Value::DeepEquals(const Value& a, const Value& b) {
|
|
91
|
+
if (a.kind_ == Kind::Array && b.kind_ == Kind::Array) {
|
|
92
|
+
if (a.elements_.size() != b.elements_.size()) {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
for (size_t i = 0; i < a.elements_.size(); i++) {
|
|
96
|
+
if (!DeepEquals(a.elements_[i], b.elements_[i])) {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
if (a.kind_ == Kind::Object && b.kind_ == Kind::Object) {
|
|
103
|
+
size_t defined = 0;
|
|
104
|
+
for (const auto& member : a.members_) {
|
|
105
|
+
if (member.second.IsUndefined()) {
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
defined++;
|
|
109
|
+
const Value& other = b.Get(member.first);
|
|
110
|
+
if (other.IsUndefined() || !DeepEquals(member.second, other)) {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
for (const auto& member : b.members_) {
|
|
115
|
+
if (!member.second.IsUndefined()) {
|
|
116
|
+
defined--;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return defined == 0;
|
|
120
|
+
}
|
|
121
|
+
return StrictEquals(a, b);
|
|
122
|
+
}
|
|
123
|
+
|
|
89
124
|
namespace {
|
|
90
125
|
|
|
91
126
|
void AppendEscaped(const std::string& s, std::string* out) {
|
|
@@ -232,6 +267,8 @@ namespace {
|
|
|
232
267
|
// stack limit (size and node count are capped in flow_json.h). Real
|
|
233
268
|
// responses nest ~4 levels.
|
|
234
269
|
constexpr int kMaxDepth = 64;
|
|
270
|
+
// Objects with more members than this get a hash index while parsing.
|
|
271
|
+
constexpr size_t kIndexedObjectMembers = 16;
|
|
235
272
|
|
|
236
273
|
// Locale-independent decimal -> double. strtod() honours LC_NUMERIC, so a
|
|
237
274
|
// host process that called setlocale() into a comma-decimal locale would stop
|
|
@@ -327,7 +364,8 @@ double DecimalToDouble(const std::string& token) {
|
|
|
327
364
|
|
|
328
365
|
class Parser {
|
|
329
366
|
public:
|
|
330
|
-
Parser(const std::string& text
|
|
367
|
+
Parser(const std::string& text, size_t max_nodes)
|
|
368
|
+
: text_(text), max_nodes_(max_nodes) {}
|
|
331
369
|
|
|
332
370
|
Value Run(bool* ok) {
|
|
333
371
|
Value v = ParseValue();
|
|
@@ -376,7 +414,7 @@ class Parser {
|
|
|
376
414
|
ok_ = false;
|
|
377
415
|
return Value::Undefined();
|
|
378
416
|
}
|
|
379
|
-
if (++nodes_ >
|
|
417
|
+
if (++nodes_ > max_nodes_) {
|
|
380
418
|
ok_ = false;
|
|
381
419
|
return Value::Undefined();
|
|
382
420
|
}
|
|
@@ -409,20 +447,51 @@ class Parser {
|
|
|
409
447
|
}
|
|
410
448
|
}
|
|
411
449
|
|
|
450
|
+
// Duplicate keys keep the first position and the last value (Value::Set
|
|
451
|
+
// semantics). Large objects (a patch manifest's copies map has thousands
|
|
452
|
+
// of members) look keys up through a hash index instead of Set's linear
|
|
453
|
+
// scan, which would make parsing quadratic in the member count.
|
|
412
454
|
Value ParseObject() {
|
|
413
|
-
Value obj = Value::Object();
|
|
414
455
|
Consume('{');
|
|
415
456
|
SkipWs();
|
|
416
457
|
if (pos_ < text_.size() && text_[pos_] == '}') {
|
|
417
458
|
pos_++;
|
|
418
|
-
return
|
|
459
|
+
return Value::Object();
|
|
419
460
|
}
|
|
461
|
+
Members members;
|
|
462
|
+
std::unordered_map<std::string, size_t> index;
|
|
420
463
|
while (ok_) {
|
|
421
464
|
SkipWs();
|
|
422
465
|
std::string key = ParseString();
|
|
423
466
|
SkipWs();
|
|
424
467
|
Consume(':');
|
|
425
|
-
|
|
468
|
+
Value value = ParseValue();
|
|
469
|
+
if (members.size() == kIndexedObjectMembers && index.empty()) {
|
|
470
|
+
for (size_t i = 0; i < members.size(); i++) {
|
|
471
|
+
index.emplace(members[i].first, i);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
size_t existing = members.size();
|
|
475
|
+
if (index.empty()) {
|
|
476
|
+
for (size_t i = 0; i < members.size(); i++) {
|
|
477
|
+
if (members[i].first == key) {
|
|
478
|
+
existing = i;
|
|
479
|
+
break;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
} else {
|
|
483
|
+
auto found = index.find(key);
|
|
484
|
+
if (found != index.end()) {
|
|
485
|
+
existing = found->second;
|
|
486
|
+
} else {
|
|
487
|
+
index.emplace(key, members.size());
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
if (existing < members.size()) {
|
|
491
|
+
members[existing].second = std::move(value);
|
|
492
|
+
} else {
|
|
493
|
+
members.emplace_back(std::move(key), std::move(value));
|
|
494
|
+
}
|
|
426
495
|
SkipWs();
|
|
427
496
|
if (pos_ < text_.size() && text_[pos_] == ',') {
|
|
428
497
|
pos_++;
|
|
@@ -431,7 +500,7 @@ class Parser {
|
|
|
431
500
|
Consume('}');
|
|
432
501
|
break;
|
|
433
502
|
}
|
|
434
|
-
return
|
|
503
|
+
return Value::Object(std::move(members));
|
|
435
504
|
}
|
|
436
505
|
|
|
437
506
|
Value ParseArray() {
|
|
@@ -635,6 +704,7 @@ class Parser {
|
|
|
635
704
|
}
|
|
636
705
|
|
|
637
706
|
const std::string& text_;
|
|
707
|
+
const size_t max_nodes_;
|
|
638
708
|
size_t pos_ = 0;
|
|
639
709
|
int depth_ = 0;
|
|
640
710
|
size_t nodes_ = 0;
|
|
@@ -644,11 +714,15 @@ class Parser {
|
|
|
644
714
|
} // namespace
|
|
645
715
|
|
|
646
716
|
Value Parse(const std::string& text, bool* ok) {
|
|
647
|
-
|
|
717
|
+
return Parse(text, ParseLimits(), ok);
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
Value Parse(const std::string& text, const ParseLimits& limits, bool* ok) {
|
|
721
|
+
if (text.size() > limits.max_input_bytes) {
|
|
648
722
|
*ok = false;
|
|
649
723
|
return Value::Undefined();
|
|
650
724
|
}
|
|
651
|
-
Parser parser(text);
|
|
725
|
+
Parser parser(text, limits.max_nodes);
|
|
652
726
|
return parser.Run(ok);
|
|
653
727
|
}
|
|
654
728
|
|
|
@@ -51,6 +51,12 @@ class Value {
|
|
|
51
51
|
}
|
|
52
52
|
static Value Array() { return Value(Kind::Array); }
|
|
53
53
|
static Value Object() { return Value(Kind::Object); }
|
|
54
|
+
// The members must have distinct keys (Set's invariant).
|
|
55
|
+
static Value Object(Members members) {
|
|
56
|
+
Value v(Kind::Object);
|
|
57
|
+
v.members_ = std::move(members);
|
|
58
|
+
return v;
|
|
59
|
+
}
|
|
54
60
|
|
|
55
61
|
Kind kind() const { return kind_; }
|
|
56
62
|
bool IsUndefined() const { return kind_ == Kind::Undefined; }
|
|
@@ -81,6 +87,12 @@ class Value {
|
|
|
81
87
|
|
|
82
88
|
static bool StrictEquals(const Value& a, const Value& b);
|
|
83
89
|
|
|
90
|
+
// Structural equality of JSON values (src/updateFlowCore.ts
|
|
91
|
+
// jsonDeepEqual): object key order is ignored and undefined members count
|
|
92
|
+
// as absent; arrays compare element-wise in order; primitives use ===
|
|
93
|
+
// (so 1 equals 1.0, false is not 0, null equals only null).
|
|
94
|
+
static bool DeepEquals(const Value& a, const Value& b);
|
|
95
|
+
|
|
84
96
|
private:
|
|
85
97
|
explicit Value(Kind kind) : kind_(kind) {}
|
|
86
98
|
|
|
@@ -116,6 +128,15 @@ constexpr size_t kMaxNodes = 65536;
|
|
|
116
128
|
// Lone surrogate escapes decode to U+FFFD so the output is always valid UTF-8.
|
|
117
129
|
Value Parse(const std::string& text, bool* ok);
|
|
118
130
|
|
|
131
|
+
// Same parser under caller-chosen caps, for documents whose size is bounded
|
|
132
|
+
// by something other than a checkUpdate response (the patch core's
|
|
133
|
+
// __diff.json manifest, cpp/patch_core/patch_manifest.h).
|
|
134
|
+
struct ParseLimits {
|
|
135
|
+
size_t max_input_bytes = kMaxInputBytes;
|
|
136
|
+
size_t max_nodes = kMaxNodes;
|
|
137
|
+
};
|
|
138
|
+
Value Parse(const std::string& text, const ParseLimits& limits, bool* ok);
|
|
139
|
+
|
|
119
140
|
} // namespace flowjson
|
|
120
141
|
|
|
121
142
|
#pragma GCC visibility pop
|