Oregami
Repositories/oxedyne/fe2o3

oxedyne/fe2o3/fe2o3_jdat/src/string/json.rs

13.9 KiB, 1 run

created by r1870400018:61146, which is this file's identity for as long as the history lasts, whatever it is later renamed to

download · who wrote it · its history

1//! Why a second reader: the text decoder with `DecoderConfig::json` still reads JDAT's own forms,
2//! so a kind annotation `(u64|5)`, a hex integer, an unquoted word, a single-quoted string, a
3//! trailing comma, digits run together across a space and anything after the first value all
4//! decode there. Found by the presentation audit of 2026-09-23. Input that must mean exactly one
5//! thing comes through `Dat::decode_json_strict` instead.
6
7use crate::{
8 prelude::*,
9 bdat::limits::DecodeLimits,
10};
11
12use oxedyne_fe2o3_core::prelude::*;
13use oxedyne_fe2o3_num::string::NumberString;
14
15use std::str;
16
17
18impl Dat {
19
20 /// Decodes JSON text as RFC 8259 defines it and refuses everything else.
21 ///
22 /// An object becomes a `Dat::Map` with string keys, an array a `Dat::List`, a string a
23 /// `Dat::Str`, `true` and `false` a `Dat::Bool` and `null` a `Dat::Opt` holding nothing. A
24 /// number becomes the daticle the JDAT text decoder makes of the same digits. The input may
25 /// hold one value, with only JSON's four whitespace characters around it.
26 ///
27 /// Refused as well, where RFC 8259 leaves the reader a choice: an object naming a member twice,
28 /// and a `\u` escape that leaves half a surrogate pair, since neither has one meaning.
29 /// `limits` bounds the length of the text and the depth of nesting, the root at depth 1.
30 pub fn decode_json_strict(s: &str, limits: &DecodeLimits) -> Outcome<Self> {
31 res!(limits.check_len(s.len()));
32 let mut r = Reader {
33 src: s.as_bytes(),
34 pos: 0,
35 limits: *limits,
36 };
37 r.skip_ws();
38 let value = res!(r.value(1));
39 r.skip_ws();
40 if r.pos < r.src.len() {
41 return Err(err!(
42 "JSON text holds one value, and more follows it at byte {} of {}.",
43 r.pos, r.src.len();
44 Invalid, Input, Decode));
45 }
46 Ok(value)
47 }
48}
49
50struct Reader<'a> {
51 src: &'a [u8],
52 pos: usize,
53 limits: DecodeLimits,
54}
55
56impl<'a> Reader<'a> {
57
58 fn peek(&self) -> Option<u8> {
59 self.src.get(self.pos).copied()
60 }
61
62 fn skip_ws(&mut self) {
63 while let Some(b' ' | b'\t' | b'\n' | b'\r') = self.peek() {
64 self.pos += 1;
65 }
66 }
67
68 fn digit_run(&mut self) {
69 while let Some(b'0'..=b'9') = self.peek() {
70 self.pos += 1;
71 }
72 }
73
74 /// What the byte at the cursor is, for an error.
75 fn found(&self) -> String {
76 match self.peek() {
77 Some(b) if b.is_ascii_graphic() => fmt!("'{}'", b as char),
78 Some(b) => fmt!("byte 0x{:02x}", b),
79 None => fmt!("the end of the text"),
80 }
81 }
82
83 fn value(&mut self, depth: usize) -> Outcome<Dat> {
84 res!(self.limits.check_depth(depth, self.pos));
85 match self.peek() {
86 Some(b'{') => self.object(depth),
87 Some(b'[') => self.array(depth),
88 Some(b'"') => Ok(Dat::Str(res!(self.string()))),
89 Some(b't') => self.literal("true", Dat::Bool(true)),
90 Some(b'f') => self.literal("false", Dat::Bool(false)),
91 Some(b'n') => self.literal("null", Dat::Opt(Box::new(None))),
92 Some(b'-' | b'0'..=b'9') => self.number(),
93 _ => Err(err!(
94 "A JSON value begins with '{{', '[', '\"', a digit, '-', true, false or null, \
95 and byte {} holds {}.", self.pos, self.found();
96 Invalid, Input, Decode)),
97 }
98 }
99
100 fn object(&mut self, depth: usize) -> Outcome<Dat> {
101 let open = self.pos;
102 self.pos += 1;
103 let mut map = DaticleMap::new();
104 self.skip_ws();
105 if self.peek() == Some(b'}') {
106 self.pos += 1;
107 return Ok(Dat::Map(map));
108 }
109 loop {
110 self.skip_ws();
111 if self.peek() != Some(b'"') {
112 return Err(err!(
113 "A member of the JSON object opened at byte {} is named by a string, and \
114 byte {} holds {}.", open, self.pos, self.found();
115 Invalid, Input, Decode));
116 }
117 let at = self.pos;
118 let key = Dat::Str(res!(self.string()));
119 self.skip_ws();
120 if self.peek() != Some(b':') {
121 return Err(err!(
122 "A member name in the JSON object opened at byte {} is followed by ':', and \
123 byte {} holds {}.", open, self.pos, self.found();
124 Invalid, Input, Decode));
125 }
126 self.pos += 1;
127 self.skip_ws();
128 let value = res!(self.value(depth + 1));
129 if map.contains_key(&key) {
130 return Err(err!(
131 "The JSON object opened at byte {} names the member {:?} a second time, at \
132 byte {}.", open, key, at;
133 Invalid, Input, Duplicate));
134 }
135 map.insert(key, value);
136 self.skip_ws();
137 match self.peek() {
138 Some(b',') => self.pos += 1,
139 Some(b'}') => {
140 self.pos += 1;
141 return Ok(Dat::Map(map));
142 },
143 _ => return Err(err!(
144 "A member of the JSON object opened at byte {} is followed by ',' or '}}', \
145 and byte {} holds {}.", open, self.pos, self.found();
146 Invalid, Input, Decode)),
147 }
148 }
149 }
150
151 fn array(&mut self, depth: usize) -> Outcome<Dat> {
152 let open = self.pos;
153 self.pos += 1;
154 let mut list = Vec::new();
155 self.skip_ws();
156 if self.peek() == Some(b']') {
157 self.pos += 1;
158 return Ok(Dat::List(list));
159 }
160 loop {
161 self.skip_ws();
162 list.push(res!(self.value(depth + 1)));
163 self.skip_ws();
164 match self.peek() {
165 Some(b',') => self.pos += 1,
166 Some(b']') => {
167 self.pos += 1;
168 return Ok(Dat::List(list));
169 },
170 _ => return Err(err!(
171 "An element of the JSON array opened at byte {} is followed by ',' or ']', \
172 and byte {} holds {}.", open, self.pos, self.found();
173 Invalid, Input, Decode)),
174 }
175 }
176 }
177
178 fn literal(&mut self, word: &str, dat: Dat) -> Outcome<Dat> {
179 if !self.src[self.pos..].starts_with(word.as_bytes()) {
180 return Err(err!(
181 "The JSON value at byte {} begins like {} and is not it.", self.pos, word;
182 Invalid, Input, Decode));
183 }
184 self.pos += word.len();
185 Ok(dat)
186 }
187
188 /// A number: `-? (0 | [1-9][0-9]*) (. [0-9]+)? ([eE] [+-]? [0-9]+)?` and nothing else, so no
189 /// plus sign, leading zero, bare point, radix prefix or digit separator.
190 fn number(&mut self) -> Outcome<Dat> {
191 let start = self.pos;
192 if self.peek() == Some(b'-') {
193 self.pos += 1;
194 }
195 match self.peek() {
196 Some(b'0') => {
197 self.pos += 1;
198 if let Some(b'0'..=b'9') = self.peek() {
199 return Err(err!(
200 "The JSON number at byte {} has a leading zero.", start;
201 Invalid, Input, Decode));
202 }
203 },
204 Some(b'1'..=b'9') => self.digit_run(),
205 _ => return Err(err!(
206 "The JSON number at byte {} has no digit after its sign; byte {} holds {}.",
207 start, self.pos, self.found();
208 Invalid, Input, Decode)),
209 }
210 if self.peek() == Some(b'.') {
211 self.pos += 1;
212 if !matches!(self.peek(), Some(b'0'..=b'9')) {
213 return Err(err!(
214 "The JSON number at byte {} has no digit after its point.", start;
215 Invalid, Input, Decode));
216 }
217 self.digit_run();
218 }
219 if let Some(b'e' | b'E') = self.peek() {
220 self.pos += 1;
221 if let Some(b'+' | b'-') = self.peek() {
222 self.pos += 1;
223 }
224 if !matches!(self.peek(), Some(b'0'..=b'9')) {
225 return Err(err!(
226 "The JSON number at byte {} has no digit in its exponent.", start;
227 Invalid, Input, Decode));
228 }
229 self.digit_run();
230 }
231 let text = res!(str::from_utf8(&self.src[start..self.pos]), Decode, UTF8);
232 Dat::from_untyped_number(&res!(NumberString::new(text)))
233 }
234
235 /// A string, cursor on its opening quote. Characters below U+0020 must be escaped, and the
236 /// only escapes are RFC 8259's eight and `\u` with four hex digits.
237 fn string(&mut self) -> Outcome<String> {
238 let open = self.pos;
239 self.pos += 1;
240 let mut out = String::new();
241 let mut run = self.pos; // start of the unescaped run being read
242 loop {
243 match self.peek() {
244 Some(b'"') => {
245 out.push_str(res!(str::from_utf8(&self.src[run..self.pos]), Decode, UTF8));
246 self.pos += 1;
247 return Ok(out);
248 },
249 Some(b'\\') => {
250 out.push_str(res!(str::from_utf8(&self.src[run..self.pos]), Decode, UTF8));
251 self.pos += 1;
252 out.push(res!(self.escape()));
253 run = self.pos;
254 },
255 Some(b) if b < 0x20 => return Err(err!(
256 "The JSON string opened at byte {} holds the control byte 0x{:02x} at byte \
257 {}, which must be escaped.", open, b, self.pos;
258 Invalid, Input, Decode)),
259 Some(_) => self.pos += 1,
260 None => return Err(err!(
261 "The JSON string opened at byte {} is never closed.", open;
262 Invalid, Input, Decode)),
263 }
264 }
265 }
266
267 /// The character an escape stands for, cursor just past its backslash.
268 fn escape(&mut self) -> Outcome<char> {
269 let at = self.pos - 1;
270 let c = match self.peek() {
271 Some(c) => c,
272 None => return Err(err!(
273 "The JSON text ends inside the escape at byte {}.", at;
274 Invalid, Input, Decode)),
275 };
276 self.pos += 1;
277 let ch = match c {
278 b'"' => '"',
279 b'\\' => '\\',
280 b'/' => '/',
281 b'b' => '\u{0008}',
282 b'f' => '\u{000C}',
283 b'n' => '\n',
284 b'r' => '\r',
285 b't' => '\t',
286 b'u' => {
287 let unit = res!(self.hex4());
288 let cp = match unit {
289 0xD800..=0xDBFF => {
290 if !self.src[self.pos..].starts_with(b"\\u") {
291 return Err(err!(
292 "The escape at byte {} is a high surrogate with no low \
293 surrogate escape after it.", at;
294 Invalid, Input, Decode));
295 }
296 self.pos += 2;
297 let low = res!(self.hex4());
298 if !(0xDC00..=0xDFFF).contains(&low) {
299 return Err(err!(
300 "The escape at byte {} is a high surrogate followed by \
301 \\u{:04X}, which is not a low surrogate.", at, low;
302 Invalid, Input, Decode));
303 }
304 0x10000 + ((unit - 0xD800) << 10) + (low - 0xDC00)
305 },
306 0xDC00..=0xDFFF => return Err(err!(
307 "The escape at byte {} is a low surrogate with no high surrogate \
308 before it.", at;
309 Invalid, Input, Decode)),
310 _ => unit,
311 };
312 match char::from_u32(cp) {
313 Some(ch) => ch,
314 None => return Err(err!(
315 "The escape at byte {} names U+{:X}, which is not a character.", at, cp;
316 Invalid, Input, Decode)),
317 }
318 },
319 _ => return Err(err!(
320 "The escape at byte {} is '\\{}', which JSON does not have.", at,
321 (c as char).escape_default();
322 Invalid, Input, Decode)),
323 };
324 Ok(ch)
325 }
326
327 fn hex4(&mut self) -> Outcome<u32> {
328 let mut v = 0u32;
329 for _ in 0..4 {
330 let d = match self.peek() {
331 Some(b @ b'0'..=b'9') => b - b'0',
332 Some(b @ b'a'..=b'f') => b - b'a' + 10,
333 Some(b @ b'A'..=b'F') => b - b'A' + 10,
334 _ => return Err(err!(
335 "A \\u escape takes four hex digits, and byte {} holds {}.",
336 self.pos, self.found();
337 Invalid, Input, Decode)),
338 };
339 v = (v << 4) | d as u32;
340 self.pos += 1;
341 }
342 Ok(v)
343 }
344}
345
346
347#[cfg(test)]
348mod tests {
349 use super::*;
350
351 fn strict(s: &str) -> Outcome<Dat> {
352 Dat::decode_json_strict(s, &DecodeLimits::text())
353 }
354
355 /// Nesting past the limit is refused before it is descended into.
356 #[test]
357 fn test_depth_limit_00() -> Outcome<()> {
358 let limits = DecodeLimits::new(3, 1024);
359 res!(Dat::decode_json_strict("[[1]]", &limits));
360 assert!(Dat::decode_json_strict("[[[1]]]", &limits).is_err(), "depth 4 is past 3");
361 let deep = fmt!("{}{}", "[".repeat(100_000), "]".repeat(100_000));
362 assert!(strict(&deep).is_err(), "a hundred thousand levels must be refused, not recursed");
363 Ok(())
364 }
365
366 /// The length limit is checked before any byte is read.
367 #[test]
368 fn test_length_limit_00() {
369 let limits = DecodeLimits::new(8, 4);
370 assert!(Dat::decode_json_strict("12345", &limits).is_err(), "five bytes is past four");
371 }
372}