react-native-update 10.49.0 → 10.51.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/BUNDLEHASH_MIGRATION.md +22 -2
- package/NATIVE_CHECKUPDATE_DESIGN.md +251 -21
- package/NATIVE_CHECK_FOLLOWUPS.md +365 -0
- package/README-CN.md +14 -1
- package/README.md +14 -1
- package/android/jni/Android.mk +5 -1
- 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/src/main/java/cn/reactnative/modules/update/BundledResourceCopier.java +42 -9
- package/android/src/main/java/cn/reactnative/modules/update/DownloadTask.java +98 -22
- package/android/src/main/java/cn/reactnative/modules/update/DownloadTaskParams.java +3 -0
- package/android/src/main/java/cn/reactnative/modules/update/NativeCheckOrchestrator.java +455 -0
- package/android/src/main/java/cn/reactnative/modules/update/NativeUpdateFlow.java +23 -0
- package/android/src/main/java/cn/reactnative/modules/update/PatchFailedException.java +16 -0
- package/android/src/main/java/cn/reactnative/modules/update/UpdateContext.java +196 -42
- package/android/src/main/java/cn/reactnative/modules/update/UpdateModuleImpl.java +56 -3
- package/android/src/newarch/cn/reactnative/modules/update/UpdateModule.java +10 -0
- package/android/src/oldarch/cn/reactnative/modules/update/UpdateModule.java +10 -0
- package/cpp/patch_core/digest.cpp +53 -0
- package/cpp/patch_core/digest.h +18 -0
- package/cpp/patch_core/patch_core.cpp +17 -1
- package/cpp/patch_core/patch_core.h +8 -0
- package/cpp/patch_core/tests/patch_core_test.cpp +99 -0
- package/cpp/update_flow_core/flow_json.cpp +522 -0
- package/cpp/update_flow_core/flow_json.h +103 -0
- package/cpp/update_flow_core/tests/flow_vectors.json +1401 -0
- package/cpp/update_flow_core/tests/update_flow_core_test.cpp +317 -0
- package/cpp/update_flow_core/update_flow_core.cpp +301 -0
- package/cpp/update_flow_core/update_flow_core.h +77 -0
- package/cpp/update_flow_core/update_flow_jni.cpp +121 -0
- package/harmony/pushy/src/main/cpp/CMakeLists.txt +7 -0
- package/harmony/pushy/src/main/cpp/pushy.cpp +150 -1
- package/harmony/pushy/src/main/ets/DownloadTask.ts +133 -9
- package/harmony/pushy/src/main/ets/DownloadTaskParams.ts +3 -0
- package/harmony/pushy/src/main/ets/MonotonicClock.ts +13 -0
- package/harmony/pushy/src/main/ets/NativeCheckOrchestrator.ts +500 -0
- package/harmony/pushy/src/main/ets/NativePatchCore.ts +16 -0
- package/harmony/pushy/src/main/ets/PathUtils.ts +21 -0
- package/harmony/pushy/src/main/ets/PushyTurboModule.ts +22 -0
- package/harmony/pushy/src/main/ets/UpdateContext.ts +152 -52
- package/harmony/pushy.har +0 -0
- package/ios/RCTPushy/RCTPushy.mm +840 -68
- package/ios/RCTPushy/RCTPushyDownloader.h +1 -0
- package/ios/RCTPushy/RCTPushyDownloader.mm +4 -3
- package/package.json +3 -1
- package/react-native-update.podspec +2 -0
- package/src/NativePushy.ts +14 -0
- package/src/client.ts +439 -122
- package/src/endpoint.ts +105 -80
- package/src/error.ts +4 -0
- package/src/locales/en.ts +4 -0
- package/src/locales/zh.ts +4 -0
- package/src/provider.tsx +74 -9
- package/src/telemetry.ts +8 -0
- package/src/type.ts +31 -0
- package/src/updateFlowCore.ts +320 -0
- package/src/utils.ts +14 -12
- package/src/isInRollout.ts +0 -86
- package/src/resolveCheckResult.ts +0 -30
|
@@ -814,6 +814,102 @@ void TestSha256File() {
|
|
|
814
814
|
"Sha256File over a missing file returns empty");
|
|
815
815
|
}
|
|
816
816
|
|
|
817
|
+
void TestCrc32KnownVectors() {
|
|
818
|
+
{
|
|
819
|
+
pushy::digest::Crc32 crc;
|
|
820
|
+
Expect(crc.Value() == 0x00000000u, "crc32 of empty input");
|
|
821
|
+
}
|
|
822
|
+
{
|
|
823
|
+
// The classic check value: crc32("123456789") == 0xCBF43926.
|
|
824
|
+
pushy::digest::Crc32 crc;
|
|
825
|
+
const char* input = "123456789";
|
|
826
|
+
crc.Update(reinterpret_cast<const uint8_t*>(input), 9);
|
|
827
|
+
Expect(crc.Value() == 0xCBF43926u, "crc32 check value of \"123456789\"");
|
|
828
|
+
}
|
|
829
|
+
{
|
|
830
|
+
// Streaming in awkward chunks must match the one-shot value.
|
|
831
|
+
const std::string input(100000, 'a');
|
|
832
|
+
pushy::digest::Crc32 one_shot;
|
|
833
|
+
one_shot.Update(
|
|
834
|
+
reinterpret_cast<const uint8_t*>(input.data()), input.size());
|
|
835
|
+
pushy::digest::Crc32 streamed;
|
|
836
|
+
size_t offset = 0;
|
|
837
|
+
size_t chunk = 1;
|
|
838
|
+
while (offset < input.size()) {
|
|
839
|
+
const size_t take =
|
|
840
|
+
offset + chunk > input.size() ? input.size() - offset : chunk;
|
|
841
|
+
streamed.Update(
|
|
842
|
+
reinterpret_cast<const uint8_t*>(input.data()) + offset, take);
|
|
843
|
+
offset += take;
|
|
844
|
+
chunk = chunk * 2 + 1;
|
|
845
|
+
}
|
|
846
|
+
Expect(
|
|
847
|
+
streamed.Value() == one_shot.Value(),
|
|
848
|
+
"crc32 streaming matches one-shot");
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
void TestCrc32File() {
|
|
853
|
+
TempDir temp;
|
|
854
|
+
const std::string dir = temp.path;
|
|
855
|
+
const std::string path = dir + "/input.bin";
|
|
856
|
+
WriteFile(path, "123456789");
|
|
857
|
+
uint32_t value = 0;
|
|
858
|
+
Expect(pushy::digest::Crc32File(path, &value), "Crc32File should read");
|
|
859
|
+
Expect(value == 0xCBF43926u, "Crc32File check value");
|
|
860
|
+
Expect(
|
|
861
|
+
!pushy::digest::Crc32File(dir + "/missing.bin", &value),
|
|
862
|
+
"Crc32File over a missing file returns false");
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
void TestApplyPatchCopiesVerifyExpectedCrc() {
|
|
866
|
+
TempDir temp;
|
|
867
|
+
const std::string source = JoinPath(temp.path, "origin");
|
|
868
|
+
const std::string target = JoinPath(temp.path, "target");
|
|
869
|
+
const std::string patch = JoinPath(temp.path, "bundle.patch");
|
|
870
|
+
|
|
871
|
+
WriteFile(JoinPath(source, "index.bundlejs"), "old bundle");
|
|
872
|
+
WriteFile(JoinPath(source, "assets/keep.txt"), "123456789");
|
|
873
|
+
WriteFile(patch, "unused patch");
|
|
874
|
+
|
|
875
|
+
FakeBundlePatcher patcher("patched bundle");
|
|
876
|
+
FileSourcePatchOptions options;
|
|
877
|
+
options.source_root = source;
|
|
878
|
+
options.target_root = target;
|
|
879
|
+
options.origin_bundle_path = JoinPath(source, "index.bundlejs");
|
|
880
|
+
options.bundle_patch_path = patch;
|
|
881
|
+
options.bundle_output_path = JoinPath(target, "index.bundlejs");
|
|
882
|
+
options.enable_merge = false;
|
|
883
|
+
CopyOperation copy;
|
|
884
|
+
copy.from = "assets/keep.txt";
|
|
885
|
+
copy.to = "assets/keep.txt";
|
|
886
|
+
copy.has_expected_crc = true;
|
|
887
|
+
copy.expected_crc = 0xCBF43926u; // crc32("123456789")
|
|
888
|
+
options.manifest.copies.push_back(copy);
|
|
889
|
+
|
|
890
|
+
Status status = ApplyPatchFromFileSource(options, patcher);
|
|
891
|
+
Expect(status.ok, status.message);
|
|
892
|
+
ExpectEq(
|
|
893
|
+
ReadFile(JoinPath(target, "assets/keep.txt")),
|
|
894
|
+
"123456789",
|
|
895
|
+
"verified copy content");
|
|
896
|
+
|
|
897
|
+
// Same manifest applied on a "rebuilt" source whose file drifted: the copy
|
|
898
|
+
// must fail the whole patch (caller falls back to full), not copy silently.
|
|
899
|
+
WriteFile(JoinPath(source, "assets/keep.txt"), "drifted-bytes");
|
|
900
|
+
const std::string target2 = JoinPath(temp.path, "target2");
|
|
901
|
+
options.target_root = target2;
|
|
902
|
+
options.bundle_output_path = JoinPath(target2, "index.bundlejs");
|
|
903
|
+
Status mismatch = ApplyPatchFromFileSource(options, patcher);
|
|
904
|
+
Expect(!mismatch.ok, "drifted copy source must fail the patch");
|
|
905
|
+
Expect(
|
|
906
|
+
mismatch.message.find("crc32") != std::string::npos,
|
|
907
|
+
"mismatch error should mention crc32");
|
|
908
|
+
Expect(
|
|
909
|
+
!Exists(JoinPath(target2, "assets/keep.txt")),
|
|
910
|
+
"drifted content must not be copied");
|
|
911
|
+
}
|
|
912
|
+
|
|
817
913
|
} // namespace
|
|
818
914
|
|
|
819
915
|
int main(int argc, char** argv) {
|
|
@@ -848,6 +944,9 @@ int main(int argc, char** argv) {
|
|
|
848
944
|
{"Sha256KnownVectors", TestSha256KnownVectors},
|
|
849
945
|
{"Sha256StreamingMatchesOneShot", TestSha256StreamingMatchesOneShot},
|
|
850
946
|
{"Sha256File", TestSha256File},
|
|
947
|
+
{"Crc32KnownVectors", TestCrc32KnownVectors},
|
|
948
|
+
{"Crc32File", TestCrc32File},
|
|
949
|
+
{"ApplyPatchCopiesVerifyExpectedCrc", TestApplyPatchCopiesVerifyExpectedCrc},
|
|
851
950
|
};
|
|
852
951
|
|
|
853
952
|
int failures = 0;
|
|
@@ -0,0 +1,522 @@
|
|
|
1
|
+
#include "flow_json.h"
|
|
2
|
+
|
|
3
|
+
#include <cmath>
|
|
4
|
+
#include <cstdio>
|
|
5
|
+
#include <cstdlib>
|
|
6
|
+
|
|
7
|
+
namespace flowjson {
|
|
8
|
+
|
|
9
|
+
namespace {
|
|
10
|
+
const Value kUndefined;
|
|
11
|
+
} // namespace
|
|
12
|
+
|
|
13
|
+
bool Value::Truthy() const {
|
|
14
|
+
switch (kind_) {
|
|
15
|
+
case Kind::Undefined:
|
|
16
|
+
case Kind::Null:
|
|
17
|
+
return false;
|
|
18
|
+
case Kind::Bool:
|
|
19
|
+
return bool_;
|
|
20
|
+
case Kind::Number:
|
|
21
|
+
return number_ != 0 && !std::isnan(number_);
|
|
22
|
+
case Kind::String:
|
|
23
|
+
return !string_.empty();
|
|
24
|
+
case Kind::Array:
|
|
25
|
+
case Kind::Object:
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const Value& Value::At(size_t i) const {
|
|
32
|
+
if (kind_ != Kind::Array || i >= elements_.size()) {
|
|
33
|
+
return kUndefined;
|
|
34
|
+
}
|
|
35
|
+
return elements_[i];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const Value& Value::Get(const std::string& key) const {
|
|
39
|
+
if (kind_ == Kind::Object) {
|
|
40
|
+
for (const auto& member : members_) {
|
|
41
|
+
if (member.first == key) {
|
|
42
|
+
return member.second;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return kUndefined;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
void Value::Set(const std::string& key, Value v) {
|
|
50
|
+
for (auto& member : members_) {
|
|
51
|
+
if (member.first == key) {
|
|
52
|
+
member.second = std::move(v);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
members_.emplace_back(key, std::move(v));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
void Value::Remove(const std::string& key) {
|
|
60
|
+
for (auto it = members_.begin(); it != members_.end(); ++it) {
|
|
61
|
+
if (it->first == key) {
|
|
62
|
+
members_.erase(it);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
bool Value::StrictEquals(const Value& a, const Value& b) {
|
|
69
|
+
if (a.kind_ != b.kind_) {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
switch (a.kind_) {
|
|
73
|
+
case Kind::Undefined:
|
|
74
|
+
case Kind::Null:
|
|
75
|
+
return true;
|
|
76
|
+
case Kind::Bool:
|
|
77
|
+
return a.bool_ == b.bool_;
|
|
78
|
+
case Kind::Number:
|
|
79
|
+
return a.number_ == b.number_;
|
|
80
|
+
case Kind::String:
|
|
81
|
+
return a.string_ == b.string_;
|
|
82
|
+
case Kind::Array:
|
|
83
|
+
case Kind::Object:
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
namespace {
|
|
90
|
+
|
|
91
|
+
void AppendEscaped(const std::string& s, std::string* out) {
|
|
92
|
+
out->push_back('"');
|
|
93
|
+
for (unsigned char c : s) {
|
|
94
|
+
switch (c) {
|
|
95
|
+
case '"':
|
|
96
|
+
out->append("\\\"");
|
|
97
|
+
break;
|
|
98
|
+
case '\\':
|
|
99
|
+
out->append("\\\\");
|
|
100
|
+
break;
|
|
101
|
+
case '\b':
|
|
102
|
+
out->append("\\b");
|
|
103
|
+
break;
|
|
104
|
+
case '\f':
|
|
105
|
+
out->append("\\f");
|
|
106
|
+
break;
|
|
107
|
+
case '\n':
|
|
108
|
+
out->append("\\n");
|
|
109
|
+
break;
|
|
110
|
+
case '\r':
|
|
111
|
+
out->append("\\r");
|
|
112
|
+
break;
|
|
113
|
+
case '\t':
|
|
114
|
+
out->append("\\t");
|
|
115
|
+
break;
|
|
116
|
+
default:
|
|
117
|
+
if (c < 0x20) {
|
|
118
|
+
char buf[8];
|
|
119
|
+
std::snprintf(buf, sizeof(buf), "\\u%04x", c);
|
|
120
|
+
out->append(buf);
|
|
121
|
+
} else {
|
|
122
|
+
out->push_back(static_cast<char>(c));
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
out->push_back('"');
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
void AppendNumber(double n, std::string* out) {
|
|
130
|
+
// The decision layer only ever emits integral numbers (hashes, counters,
|
|
131
|
+
// rollout percentages); print them the way JSON.stringify does. The %.17g
|
|
132
|
+
// fallback exists so an unexpected fractional value surfaces as a vector
|
|
133
|
+
// mismatch instead of silent truncation.
|
|
134
|
+
if (std::isfinite(n) && n == std::floor(n) && std::fabs(n) <= 9007199254740992.0) {
|
|
135
|
+
char buf[32];
|
|
136
|
+
std::snprintf(buf, sizeof(buf), "%lld", static_cast<long long>(n));
|
|
137
|
+
out->append(buf);
|
|
138
|
+
} else {
|
|
139
|
+
char buf[40];
|
|
140
|
+
std::snprintf(buf, sizeof(buf), "%.17g", n);
|
|
141
|
+
out->append(buf);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
void AppendValue(const Value& v, std::string* out) {
|
|
146
|
+
switch (v.kind()) {
|
|
147
|
+
case Value::Kind::Undefined: // only reachable inside arrays
|
|
148
|
+
case Value::Kind::Null:
|
|
149
|
+
out->append("null");
|
|
150
|
+
break;
|
|
151
|
+
case Value::Kind::Bool:
|
|
152
|
+
out->append(v.AsBool() ? "true" : "false");
|
|
153
|
+
break;
|
|
154
|
+
case Value::Kind::Number:
|
|
155
|
+
AppendNumber(v.AsNumber(), out);
|
|
156
|
+
break;
|
|
157
|
+
case Value::Kind::String:
|
|
158
|
+
AppendEscaped(v.AsString(), out);
|
|
159
|
+
break;
|
|
160
|
+
case Value::Kind::Array: {
|
|
161
|
+
out->push_back('[');
|
|
162
|
+
bool first = true;
|
|
163
|
+
for (const auto& element : v.elements()) {
|
|
164
|
+
if (!first) {
|
|
165
|
+
out->push_back(',');
|
|
166
|
+
}
|
|
167
|
+
first = false;
|
|
168
|
+
AppendValue(element, out);
|
|
169
|
+
}
|
|
170
|
+
out->push_back(']');
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
case Value::Kind::Object: {
|
|
174
|
+
out->push_back('{');
|
|
175
|
+
bool first = true;
|
|
176
|
+
for (const auto& member : v.members()) {
|
|
177
|
+
if (member.second.IsUndefined()) {
|
|
178
|
+
continue; // JSON.stringify drops undefined-valued members
|
|
179
|
+
}
|
|
180
|
+
if (!first) {
|
|
181
|
+
out->push_back(',');
|
|
182
|
+
}
|
|
183
|
+
first = false;
|
|
184
|
+
AppendEscaped(member.first, out);
|
|
185
|
+
out->push_back(':');
|
|
186
|
+
AppendValue(member.second, out);
|
|
187
|
+
}
|
|
188
|
+
out->push_back('}');
|
|
189
|
+
break;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
} // namespace
|
|
195
|
+
|
|
196
|
+
std::string Stringify(const Value& v) {
|
|
197
|
+
if (v.IsUndefined()) {
|
|
198
|
+
return "undefined";
|
|
199
|
+
}
|
|
200
|
+
std::string out;
|
|
201
|
+
AppendValue(v, &out);
|
|
202
|
+
return out;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
namespace {
|
|
206
|
+
|
|
207
|
+
// The parser also consumes server checkUpdate responses, so hostile input
|
|
208
|
+
// must fail cleanly: nesting is capped to keep recursive descent off the
|
|
209
|
+
// stack limit. Real responses nest ~4 levels.
|
|
210
|
+
constexpr int kMaxDepth = 64;
|
|
211
|
+
|
|
212
|
+
class Parser {
|
|
213
|
+
public:
|
|
214
|
+
Parser(const std::string& text) : text_(text) {}
|
|
215
|
+
|
|
216
|
+
Value Run(bool* ok) {
|
|
217
|
+
Value v = ParseValue();
|
|
218
|
+
SkipWs();
|
|
219
|
+
*ok = ok_ && pos_ == text_.size();
|
|
220
|
+
return *ok ? v : Value::Undefined();
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
private:
|
|
224
|
+
void SkipWs() {
|
|
225
|
+
while (pos_ < text_.size()) {
|
|
226
|
+
char c = text_[pos_];
|
|
227
|
+
if (c == ' ' || c == '\t' || c == '\n' || c == '\r') {
|
|
228
|
+
pos_++;
|
|
229
|
+
} else {
|
|
230
|
+
break;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
bool Consume(char expected) {
|
|
236
|
+
if (pos_ < text_.size() && text_[pos_] == expected) {
|
|
237
|
+
pos_++;
|
|
238
|
+
return true;
|
|
239
|
+
}
|
|
240
|
+
ok_ = false;
|
|
241
|
+
return false;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
bool ConsumeLiteral(const char* literal) {
|
|
245
|
+
size_t len = 0;
|
|
246
|
+
while (literal[len]) {
|
|
247
|
+
len++;
|
|
248
|
+
}
|
|
249
|
+
if (text_.compare(pos_, len, literal) == 0) {
|
|
250
|
+
pos_ += len;
|
|
251
|
+
return true;
|
|
252
|
+
}
|
|
253
|
+
ok_ = false;
|
|
254
|
+
return false;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
Value ParseValue() {
|
|
258
|
+
SkipWs();
|
|
259
|
+
if (pos_ >= text_.size()) {
|
|
260
|
+
ok_ = false;
|
|
261
|
+
return Value::Undefined();
|
|
262
|
+
}
|
|
263
|
+
char c = text_[pos_];
|
|
264
|
+
switch (c) {
|
|
265
|
+
case '{':
|
|
266
|
+
case '[': {
|
|
267
|
+
if (depth_ >= kMaxDepth) {
|
|
268
|
+
ok_ = false;
|
|
269
|
+
return Value::Undefined();
|
|
270
|
+
}
|
|
271
|
+
depth_++;
|
|
272
|
+
Value v = c == '{' ? ParseObject() : ParseArray();
|
|
273
|
+
depth_--;
|
|
274
|
+
return v;
|
|
275
|
+
}
|
|
276
|
+
case '"':
|
|
277
|
+
return Value::String(ParseString());
|
|
278
|
+
case 't':
|
|
279
|
+
ConsumeLiteral("true");
|
|
280
|
+
return Value::Bool(true);
|
|
281
|
+
case 'f':
|
|
282
|
+
ConsumeLiteral("false");
|
|
283
|
+
return Value::Bool(false);
|
|
284
|
+
case 'n':
|
|
285
|
+
ConsumeLiteral("null");
|
|
286
|
+
return Value::Null();
|
|
287
|
+
default:
|
|
288
|
+
return ParseNumber();
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
Value ParseObject() {
|
|
293
|
+
Value obj = Value::Object();
|
|
294
|
+
Consume('{');
|
|
295
|
+
SkipWs();
|
|
296
|
+
if (pos_ < text_.size() && text_[pos_] == '}') {
|
|
297
|
+
pos_++;
|
|
298
|
+
return obj;
|
|
299
|
+
}
|
|
300
|
+
while (ok_) {
|
|
301
|
+
SkipWs();
|
|
302
|
+
std::string key = ParseString();
|
|
303
|
+
SkipWs();
|
|
304
|
+
Consume(':');
|
|
305
|
+
obj.Set(key, ParseValue());
|
|
306
|
+
SkipWs();
|
|
307
|
+
if (pos_ < text_.size() && text_[pos_] == ',') {
|
|
308
|
+
pos_++;
|
|
309
|
+
continue;
|
|
310
|
+
}
|
|
311
|
+
Consume('}');
|
|
312
|
+
break;
|
|
313
|
+
}
|
|
314
|
+
return obj;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
Value ParseArray() {
|
|
318
|
+
Value arr = Value::Array();
|
|
319
|
+
Consume('[');
|
|
320
|
+
SkipWs();
|
|
321
|
+
if (pos_ < text_.size() && text_[pos_] == ']') {
|
|
322
|
+
pos_++;
|
|
323
|
+
return arr;
|
|
324
|
+
}
|
|
325
|
+
while (ok_) {
|
|
326
|
+
arr.Push(ParseValue());
|
|
327
|
+
SkipWs();
|
|
328
|
+
if (pos_ < text_.size() && text_[pos_] == ',') {
|
|
329
|
+
pos_++;
|
|
330
|
+
continue;
|
|
331
|
+
}
|
|
332
|
+
Consume(']');
|
|
333
|
+
break;
|
|
334
|
+
}
|
|
335
|
+
return arr;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
std::string ParseString() {
|
|
339
|
+
std::string out;
|
|
340
|
+
if (!Consume('"')) {
|
|
341
|
+
return out;
|
|
342
|
+
}
|
|
343
|
+
while (pos_ < text_.size()) {
|
|
344
|
+
char c = text_[pos_++];
|
|
345
|
+
if (c == '"') {
|
|
346
|
+
return out;
|
|
347
|
+
}
|
|
348
|
+
if (c != '\\') {
|
|
349
|
+
out.push_back(c);
|
|
350
|
+
continue;
|
|
351
|
+
}
|
|
352
|
+
if (pos_ >= text_.size()) {
|
|
353
|
+
break;
|
|
354
|
+
}
|
|
355
|
+
char esc = text_[pos_++];
|
|
356
|
+
switch (esc) {
|
|
357
|
+
case '"':
|
|
358
|
+
case '\\':
|
|
359
|
+
case '/':
|
|
360
|
+
out.push_back(esc);
|
|
361
|
+
break;
|
|
362
|
+
case 'b':
|
|
363
|
+
out.push_back('\b');
|
|
364
|
+
break;
|
|
365
|
+
case 'f':
|
|
366
|
+
out.push_back('\f');
|
|
367
|
+
break;
|
|
368
|
+
case 'n':
|
|
369
|
+
out.push_back('\n');
|
|
370
|
+
break;
|
|
371
|
+
case 'r':
|
|
372
|
+
out.push_back('\r');
|
|
373
|
+
break;
|
|
374
|
+
case 't':
|
|
375
|
+
out.push_back('\t');
|
|
376
|
+
break;
|
|
377
|
+
case 'u': {
|
|
378
|
+
unsigned code = ParseHex4();
|
|
379
|
+
// BMP code point to UTF-8 (surrogate pairs are combined).
|
|
380
|
+
if (code >= 0xd800 && code <= 0xdbff &&
|
|
381
|
+
text_.compare(pos_, 2, "\\u") == 0) {
|
|
382
|
+
pos_ += 2;
|
|
383
|
+
unsigned low = ParseHex4();
|
|
384
|
+
if (low >= 0xdc00 && low <= 0xdfff) {
|
|
385
|
+
code = 0x10000 + ((code - 0xd800) << 10) + (low - 0xdc00);
|
|
386
|
+
} else {
|
|
387
|
+
ok_ = false;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
AppendUtf8(code, &out);
|
|
391
|
+
break;
|
|
392
|
+
}
|
|
393
|
+
default:
|
|
394
|
+
ok_ = false;
|
|
395
|
+
return out;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
ok_ = false;
|
|
399
|
+
return out;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
unsigned ParseHex4() {
|
|
403
|
+
unsigned code = 0;
|
|
404
|
+
for (int i = 0; i < 4; i++) {
|
|
405
|
+
if (pos_ >= text_.size()) {
|
|
406
|
+
ok_ = false;
|
|
407
|
+
return 0;
|
|
408
|
+
}
|
|
409
|
+
char c = text_[pos_++];
|
|
410
|
+
code <<= 4;
|
|
411
|
+
if (c >= '0' && c <= '9') {
|
|
412
|
+
code |= static_cast<unsigned>(c - '0');
|
|
413
|
+
} else if (c >= 'a' && c <= 'f') {
|
|
414
|
+
code |= static_cast<unsigned>(c - 'a' + 10);
|
|
415
|
+
} else if (c >= 'A' && c <= 'F') {
|
|
416
|
+
code |= static_cast<unsigned>(c - 'A' + 10);
|
|
417
|
+
} else {
|
|
418
|
+
ok_ = false;
|
|
419
|
+
return 0;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
return code;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
static void AppendUtf8(unsigned code, std::string* out) {
|
|
426
|
+
if (code < 0x80) {
|
|
427
|
+
out->push_back(static_cast<char>(code));
|
|
428
|
+
} else if (code < 0x800) {
|
|
429
|
+
out->push_back(static_cast<char>(0xc0 | (code >> 6)));
|
|
430
|
+
out->push_back(static_cast<char>(0x80 | (code & 0x3f)));
|
|
431
|
+
} else if (code < 0x10000) {
|
|
432
|
+
out->push_back(static_cast<char>(0xe0 | (code >> 12)));
|
|
433
|
+
out->push_back(static_cast<char>(0x80 | ((code >> 6) & 0x3f)));
|
|
434
|
+
out->push_back(static_cast<char>(0x80 | (code & 0x3f)));
|
|
435
|
+
} else {
|
|
436
|
+
out->push_back(static_cast<char>(0xf0 | (code >> 18)));
|
|
437
|
+
out->push_back(static_cast<char>(0x80 | ((code >> 12) & 0x3f)));
|
|
438
|
+
out->push_back(static_cast<char>(0x80 | ((code >> 6) & 0x3f)));
|
|
439
|
+
out->push_back(static_cast<char>(0x80 | (code & 0x3f)));
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
Value ParseNumber() {
|
|
444
|
+
size_t start = pos_;
|
|
445
|
+
if (pos_ < text_.size() && text_[pos_] == '-') {
|
|
446
|
+
pos_++;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
if (pos_ >= text_.size()) {
|
|
450
|
+
ok_ = false;
|
|
451
|
+
return Value::Undefined();
|
|
452
|
+
}
|
|
453
|
+
if (text_[pos_] == '0') {
|
|
454
|
+
pos_++;
|
|
455
|
+
// RFC 8259 forbids leading zeroes (except the number zero itself).
|
|
456
|
+
if (pos_ < text_.size() && text_[pos_] >= '0' && text_[pos_] <= '9') {
|
|
457
|
+
ok_ = false;
|
|
458
|
+
return Value::Undefined();
|
|
459
|
+
}
|
|
460
|
+
} else if (text_[pos_] >= '1' && text_[pos_] <= '9') {
|
|
461
|
+
while (pos_ < text_.size() && text_[pos_] >= '0' &&
|
|
462
|
+
text_[pos_] <= '9') {
|
|
463
|
+
pos_++;
|
|
464
|
+
}
|
|
465
|
+
} else {
|
|
466
|
+
ok_ = false;
|
|
467
|
+
return Value::Undefined();
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
if (pos_ < text_.size() && text_[pos_] == '.') {
|
|
471
|
+
pos_++;
|
|
472
|
+
if (pos_ >= text_.size() || text_[pos_] < '0' || text_[pos_] > '9') {
|
|
473
|
+
ok_ = false;
|
|
474
|
+
return Value::Undefined();
|
|
475
|
+
}
|
|
476
|
+
while (pos_ < text_.size() && text_[pos_] >= '0' &&
|
|
477
|
+
text_[pos_] <= '9') {
|
|
478
|
+
pos_++;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
if (pos_ < text_.size() &&
|
|
483
|
+
(text_[pos_] == 'e' || text_[pos_] == 'E')) {
|
|
484
|
+
pos_++;
|
|
485
|
+
if (pos_ < text_.size() &&
|
|
486
|
+
(text_[pos_] == '+' || text_[pos_] == '-')) {
|
|
487
|
+
pos_++;
|
|
488
|
+
}
|
|
489
|
+
if (pos_ >= text_.size() || text_[pos_] < '0' || text_[pos_] > '9') {
|
|
490
|
+
ok_ = false;
|
|
491
|
+
return Value::Undefined();
|
|
492
|
+
}
|
|
493
|
+
while (pos_ < text_.size() && text_[pos_] >= '0' &&
|
|
494
|
+
text_[pos_] <= '9') {
|
|
495
|
+
pos_++;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
char* end = nullptr;
|
|
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);
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
const std::string& text_;
|
|
510
|
+
size_t pos_ = 0;
|
|
511
|
+
int depth_ = 0;
|
|
512
|
+
bool ok_ = true;
|
|
513
|
+
};
|
|
514
|
+
|
|
515
|
+
} // namespace
|
|
516
|
+
|
|
517
|
+
Value Parse(const std::string& text, bool* ok) {
|
|
518
|
+
Parser parser(text);
|
|
519
|
+
return parser.Run(ok);
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
} // namespace flowjson
|