1use std::sync::LazyLock;
29
30use regex::Regex;
31
32use crate::body_scan::{BodyScanValue, REQUEST_BODY_CONTEXT};
33use crate::detect::EMBEDDED_JSON_LEAF_CONTEXT_SUFFIX;
34
35pub const JSON_WALK_DEPTH_CAP: usize = 32;
38
39const PARSE_DEPTH_LIMIT: usize = 1000;
42
43#[derive(Debug, Clone, PartialEq)]
46enum Scalar {
47 Str(String),
48 Number(String),
50 Bool(bool),
51 Null,
52 Nan,
53 Infinity,
54 NegInfinity,
55}
56
57impl Scalar {
58 fn scan_text(&self) -> String {
60 match self {
61 Self::Str(s) | Self::Number(s) => s.clone(),
62 Self::Bool(true) => "True".to_owned(),
63 Self::Bool(false) => "False".to_owned(),
64 Self::Null => "None".to_owned(),
65 Self::Nan => "nan".to_owned(),
66 Self::Infinity => "inf".to_owned(),
67 Self::NegInfinity => "-inf".to_owned(),
68 }
69 }
70}
71
72#[derive(Debug, Clone, PartialEq)]
75pub struct JsonNode {
76 is_object: bool,
77 is_array: bool,
78 keys: Vec<String>,
79 values: Vec<Self>,
80 items: Vec<Self>,
81 scalar: Option<Scalar>,
82}
83
84impl JsonNode {
85 const fn container(is_object: bool) -> Self {
86 Self {
87 is_object,
88 is_array: !is_object,
89 keys: Vec::new(),
90 values: Vec::new(),
91 items: Vec::new(),
92 scalar: None,
93 }
94 }
95
96 const fn leaf(scalar: Scalar) -> Self {
97 Self {
98 is_object: false,
99 is_array: false,
100 keys: Vec::new(),
101 values: Vec::new(),
102 items: Vec::new(),
103 scalar: Some(scalar),
104 }
105 }
106
107 fn object(keys: Vec<String>, values: Vec<Self>) -> Self {
108 Self {
109 keys,
110 values,
111 ..Self::container(true)
112 }
113 }
114
115 fn array(items: Vec<Self>) -> Self {
116 Self {
117 items,
118 ..Self::container(false)
119 }
120 }
121}
122
123#[derive(Debug)]
124enum ParseError {
125 Invalid,
127 Depth,
129}
130
131#[must_use]
138pub fn parse_ordered_json(s: &str) -> Option<JsonNode> {
139 let mut parser = Parser {
140 bytes: s.as_bytes(),
141 pos: 0,
142 stack: Vec::new(),
143 depth: 0,
144 pending: None,
145 };
146 let root = parser.parse_root().ok()?;
147 if !root.is_object && !root.is_array {
149 return None;
150 }
151 Some(root)
152}
153
154struct Parser<'a> {
158 bytes: &'a [u8],
159 pos: usize,
160 stack: Vec<ParseFrame>,
161 depth: usize,
162 pending: Option<JsonNode>,
163}
164
165enum Flow {
167 Next(ParseState),
168 Done(JsonNode),
169}
170
171fn upsert_object(keys: &mut Vec<String>, values: &mut Vec<JsonNode>, key: String, node: JsonNode) {
174 if let Some(at) = keys.iter().position(|existing| *existing == key) {
175 values[at] = node;
176 } else {
177 keys.push(key);
178 values.push(node);
179 }
180}
181
182enum ParseFrame {
186 Array(Vec<JsonNode>),
187 Object {
188 keys: Vec<String>,
189 values: Vec<JsonNode>,
190 current_key: Option<String>,
191 },
192}
193
194enum ParseState {
195 Value,
197 Key,
199 Attach,
201}
202
203fn frame_into_node(frame: ParseFrame) -> JsonNode {
204 match frame {
205 ParseFrame::Array(items) => JsonNode::array(items),
206 ParseFrame::Object { keys, values, .. } => JsonNode::object(keys, values),
207 }
208}
209
210impl Parser<'_> {
211 const fn peek(&self) -> Option<u8> {
212 if self.pos < self.bytes.len() {
213 Some(self.bytes[self.pos])
214 } else {
215 None
216 }
217 }
218
219 const fn bump(&mut self) {
220 self.pos += 1;
221 }
222
223 const fn skip_ws(&mut self) {
224 while matches!(self.peek(), Some(b' ' | b'\t' | b'\n' | b'\r')) {
225 self.pos += 1;
226 }
227 }
228
229 fn literal(&mut self, word: &str) -> bool {
230 if self.bytes[self.pos..].starts_with(word.as_bytes()) {
231 self.pos += word.len();
232 return true;
233 }
234 false
235 }
236
237 fn parse_root(&mut self) -> Result<JsonNode, ParseError> {
238 let mut flow = Flow::Next(ParseState::Value);
239 loop {
240 flow = match flow {
241 Flow::Next(ParseState::Value) => Flow::Next(self.step_value()?),
242 Flow::Next(ParseState::Key) => Flow::Next(self.step_key()?),
243 Flow::Next(ParseState::Attach) => self.step_attach()?,
244 Flow::Done(node) => return Ok(node),
245 };
246 }
247 }
248
249 fn step_value(&mut self) -> Result<ParseState, ParseError> {
251 self.skip_ws();
252 match self.peek() {
253 Some(b'[') => {
254 self.bump();
255 self.open_frame()?;
256 self.skip_ws();
257 if self.peek() == Some(b']') {
258 self.bump();
259 self.close_frame();
260 Ok(ParseState::Attach)
261 } else {
262 Ok(ParseState::Value)
263 }
264 }
265 Some(b'{') => {
266 self.bump();
267 self.open_frame()?;
268 self.skip_ws();
269 if self.peek() == Some(b'}') {
270 self.bump();
271 self.close_frame();
272 Ok(ParseState::Attach)
273 } else {
274 Ok(ParseState::Key)
275 }
276 }
277 _ => {
278 self.pending = Some(self.parse_leaf()?);
279 Ok(ParseState::Attach)
280 }
281 }
282 }
283
284 fn open_frame(&mut self) -> Result<(), ParseError> {
286 self.depth += 1;
287 if self.depth > PARSE_DEPTH_LIMIT {
288 return Err(ParseError::Depth);
289 }
290 if self.bytes[self.pos - 1] == b'[' {
291 self.stack.push(ParseFrame::Array(Vec::new()));
292 } else {
293 self.stack.push(ParseFrame::Object {
294 keys: Vec::new(),
295 values: Vec::new(),
296 current_key: None,
297 });
298 }
299 Ok(())
300 }
301
302 fn close_frame(&mut self) {
305 self.depth -= 1;
306 let frame = self.stack.pop().expect("frame stack not empty");
307 self.pending = Some(frame_into_node(frame));
308 }
309
310 fn step_key(&mut self) -> Result<ParseState, ParseError> {
312 self.skip_ws();
313 if self.peek() != Some(b'"') {
314 return Err(ParseError::Invalid);
315 }
316 let key = self.parse_string()?;
317 self.skip_ws();
318 if self.peek() != Some(b':') {
319 return Err(ParseError::Invalid);
320 }
321 self.bump();
322 match self.stack.last_mut() {
323 Some(ParseFrame::Object { current_key, .. }) => *current_key = Some(key),
324 _ => return Err(ParseError::Invalid),
325 }
326 Ok(ParseState::Value)
327 }
328
329 fn step_attach(&mut self) -> Result<Flow, ParseError> {
332 let Some(node) = self.pending.take() else {
333 return Err(ParseError::Invalid);
334 };
335 self.skip_ws();
336 let delimiter = self.peek();
337 let Some(frame) = self.stack.pop() else {
338 if self.pos != self.bytes.len() {
339 return Err(ParseError::Invalid);
341 }
342 return Ok(Flow::Done(node));
343 };
344 match frame {
345 ParseFrame::Array(mut items) => match delimiter {
346 Some(b',') => {
347 self.bump();
348 items.push(node);
349 self.stack.push(ParseFrame::Array(items));
350 Ok(Flow::Next(ParseState::Value))
351 }
352 Some(b']') => {
353 self.bump();
354 items.push(node);
355 self.depth -= 1;
356 self.pending = Some(JsonNode::array(items));
357 Ok(Flow::Next(ParseState::Attach))
358 }
359 _ => Err(ParseError::Invalid),
360 },
361 ParseFrame::Object {
362 mut keys,
363 mut values,
364 mut current_key,
365 } => {
366 let key = current_key.take().ok_or(ParseError::Invalid)?;
367 match delimiter {
368 Some(b',') => {
369 self.bump();
370 upsert_object(&mut keys, &mut values, key, node);
371 self.stack.push(ParseFrame::Object {
372 keys,
373 values,
374 current_key,
375 });
376 Ok(Flow::Next(ParseState::Key))
377 }
378 Some(b'}') => {
379 self.bump();
380 upsert_object(&mut keys, &mut values, key, node);
381 self.depth -= 1;
382 self.pending = Some(JsonNode::object(keys, values));
383 Ok(Flow::Next(ParseState::Attach))
384 }
385 _ => Err(ParseError::Invalid),
386 }
387 }
388 }
389 }
390
391 fn parse_leaf(&mut self) -> Result<JsonNode, ParseError> {
394 match self.peek() {
395 Some(b'"') => Ok(JsonNode::leaf(Scalar::Str(self.parse_string()?))),
396 Some(b't') if self.literal("true") => Ok(JsonNode::leaf(Scalar::Bool(true))),
397 Some(b'f') if self.literal("false") => Ok(JsonNode::leaf(Scalar::Bool(false))),
398 Some(b'n') if self.literal("null") => Ok(JsonNode::leaf(Scalar::Null)),
399 Some(b'N') if self.literal("NaN") => Ok(JsonNode::leaf(Scalar::Nan)),
400 Some(b'I') if self.literal("Infinity") => Ok(JsonNode::leaf(Scalar::Infinity)),
401 Some(b'-') => {
402 if self.literal("-Infinity") {
403 return Ok(JsonNode::leaf(Scalar::NegInfinity));
404 }
405 Ok(JsonNode::leaf(Scalar::Number(self.parse_number_literal()?)))
406 }
407 Some(c) if c.is_ascii_digit() => {
408 Ok(JsonNode::leaf(Scalar::Number(self.parse_number_literal()?)))
409 }
410 _ => Err(ParseError::Invalid),
411 }
412 }
413
414 fn parse_string(&mut self) -> Result<String, ParseError> {
417 self.bump(); let mut out = String::new();
419 loop {
420 match self.peek() {
421 None => return Err(ParseError::Invalid),
422 Some(b'"') => {
423 self.bump();
424 return Ok(out);
425 }
426 Some(b'\\') => {
427 self.bump();
428 let escaped = self.peek().ok_or(ParseError::Invalid)?;
429 self.bump();
430 match escaped {
431 b'"' => out.push('"'),
432 b'\\' => out.push('\\'),
433 b'/' => out.push('/'),
434 b'b' => out.push('\u{8}'),
435 b'f' => out.push('\u{c}'),
436 b'n' => out.push('\n'),
437 b'r' => out.push('\r'),
438 b't' => out.push('\t'),
439 b'u' => {
440 let first = self.parse_hex4()?;
441 let ch = match first {
442 (0xD800..=0xDBFF) => {
443 if self.peek() == Some(b'\\')
448 && self.bytes.get(self.pos + 1) == Some(&b'u')
449 {
450 self.pos += 2;
451 let second = self.parse_hex4()?;
452 if (0xDC00..=0xDFFF).contains(&second) {
453 let combined = 0x1_0000
454 + ((u32::from(first) - 0xD800) << 10)
455 + (u32::from(second) - 0xDC00);
456 char::from_u32(combined).ok_or(ParseError::Invalid)?
457 } else {
458 self.pos -= 2;
459 '\u{FFFD}'
460 }
461 } else {
462 '\u{FFFD}'
463 }
464 }
465 (0xDC00..=0xDFFF) => '\u{FFFD}',
466 _ => char::from_u32(u32::from(first)).ok_or(ParseError::Invalid)?,
467 };
468 out.push(ch);
469 }
470 _ => return Err(ParseError::Invalid),
471 }
472 }
473 Some(c) if c < 0x20 => return Err(ParseError::Invalid),
474 Some(_) => {
475 let rest = &self.bytes[self.pos..];
477 let s = std::str::from_utf8(rest).map_err(|_| ParseError::Invalid)?;
478 let ch = s.chars().next().ok_or(ParseError::Invalid)?;
479 out.push(ch);
480 self.pos += ch.len_utf8();
481 }
482 }
483 }
484 }
485
486 fn parse_hex4(&mut self) -> Result<u16, ParseError> {
487 if self.pos + 4 > self.bytes.len() {
488 return Err(ParseError::Invalid);
489 }
490 let slice = &self.bytes[self.pos..self.pos + 4];
491 let text = std::str::from_utf8(slice).map_err(|_| ParseError::Invalid)?;
492 let value = u16::from_str_radix(text, 16).map_err(|_| ParseError::Invalid)?;
493 self.pos += 4;
494 Ok(value)
495 }
496
497 fn parse_number_literal(&mut self) -> Result<String, ParseError> {
500 let start = self.pos;
501 if self.peek() == Some(b'-') {
502 self.bump();
503 }
504 match self.peek() {
505 Some(b'0') => self.bump(),
506 Some(c) if c.is_ascii_digit() => {
507 while matches!(self.peek(), Some(c) if c.is_ascii_digit()) {
508 self.bump();
509 }
510 }
511 _ => return Err(ParseError::Invalid),
512 }
513 if self.peek() == Some(b'.') {
514 self.bump();
515 if !matches!(self.peek(), Some(c) if c.is_ascii_digit()) {
516 return Err(ParseError::Invalid);
517 }
518 while matches!(self.peek(), Some(c) if c.is_ascii_digit()) {
519 self.bump();
520 }
521 }
522 if matches!(self.peek(), Some(b'e' | b'E')) {
523 self.bump();
524 if matches!(self.peek(), Some(b'+' | b'-')) {
525 self.bump();
526 }
527 if !matches!(self.peek(), Some(c) if c.is_ascii_digit()) {
528 return Err(ParseError::Invalid);
529 }
530 while matches!(self.peek(), Some(c) if c.is_ascii_digit()) {
531 self.bump();
532 }
533 }
534 Ok(std::str::from_utf8(&self.bytes[start..self.pos])
535 .map_err(|_| ParseError::Invalid)?
536 .to_owned())
537 }
538}
539
540fn mongo_operator_key_re() -> &'static Regex {
542 static RE: LazyLock<Regex> = LazyLock::new(|| {
543 Regex::new(r"^\$(?:ne|gt|gte|lt|lte|eq|in|nin|nor|and|or|not|all|size|exists|type|mod|options|where|regex|expr|function|elemMatch)$")
544 .expect("mongo operator key regex")
545 });
546 &RE
547}
548
549struct Frame<'a> {
550 is_entry: bool,
551 key: String,
552 node: &'a JsonNode,
553 depth: usize,
554}
555
556pub(crate) fn append_json_walk_entries(
566 mut values: Vec<BodyScanValue>,
567 root: &JsonNode,
568 context: &str,
569 excluded: crate::body_scan::ExcludedBodyFields<'_>,
570) -> Vec<BodyScanValue> {
571 let allow_leaf_reparse = context != REQUEST_BODY_CONTEXT;
572 let mut stack = vec![Frame {
575 is_entry: false,
576 key: String::new(),
577 node: root,
578 depth: 1,
579 }];
580 while let Some(frame) = stack.pop() {
581 if frame.is_entry {
582 if excluded.contains(&frame.key) {
583 continue;
584 }
585 if mongo_operator_key_re().is_match(&frame.key) {
586 values.push(BodyScanValue::forced(
589 frame.key,
590 crate::body_scan::MONGO_OPERATOR_CATEGORY,
591 ));
592 continue;
593 }
594 values.push(BodyScanValue::plain(
595 frame.key.as_str(),
596 REQUEST_BODY_CONTEXT,
597 ));
598 stack.push(Frame {
599 is_entry: false,
600 key: frame.key.clone(),
601 node: frame.node,
602 depth: frame.depth + 1,
603 });
604 continue;
605 }
606 let node = frame.node;
607 if node.is_object {
608 if frame.depth >= JSON_WALK_DEPTH_CAP {
609 values.push(BodyScanValue::plain(serialize_compact_json(node), context));
610 continue;
611 }
612 for index in (0..node.keys.len()).rev() {
613 stack.push(Frame {
614 is_entry: true,
615 key: node.keys[index].clone(),
616 node: &node.values[index],
617 depth: frame.depth,
618 });
619 }
620 } else if node.is_array {
621 if frame.depth >= JSON_WALK_DEPTH_CAP {
622 values.push(BodyScanValue::plain(serialize_compact_json(node), context));
623 continue;
624 }
625 for item in node.items.iter().rev() {
626 stack.push(Frame {
628 is_entry: false,
629 key: frame.key.clone(),
630 node: item,
631 depth: frame.depth + 1,
632 });
633 }
634 } else {
635 let scalar = node.scalar.as_ref().expect("leaf node carries a scalar");
636 let text = scalar.scan_text();
637 if allow_leaf_reparse && let Some(inner) = parse_ordered_json(&text) {
638 values = append_json_walk_entries(
639 values,
640 &inner,
641 &format!("{context}{EMBEDDED_JSON_LEAF_CONTEXT_SUFFIX}"),
642 excluded,
643 );
644 continue;
645 }
646 values.push(BodyScanValue::plain(text, context));
647 }
648 }
649 values
650}
651
652#[must_use]
659pub fn serialize_compact_json(node: &JsonNode) -> String {
660 let mut out = String::new();
661 write_compact_json(&mut out, node);
662 out
663}
664
665fn write_compact_json(out: &mut String, node: &JsonNode) {
666 if node.is_object {
667 out.push('{');
668 for (index, key) in node.keys.iter().enumerate() {
669 if index > 0 {
670 out.push(',');
671 }
672 out.push('"');
673 write_json_string_body(out, key);
674 out.push_str("\":");
675 write_compact_json(out, &node.values[index]);
676 }
677 out.push('}');
678 return;
679 }
680 if node.is_array {
681 out.push('[');
682 for (index, item) in node.items.iter().enumerate() {
683 if index > 0 {
684 out.push(',');
685 }
686 write_compact_json(out, item);
687 }
688 out.push(']');
689 return;
690 }
691 match node.scalar.as_ref().expect("leaf node carries a scalar") {
692 Scalar::Str(s) => {
693 out.push('"');
694 write_json_string_body(out, s);
695 out.push('"');
696 }
697 Scalar::Number(literal) => out.push_str(literal),
698 Scalar::Bool(true) => out.push_str("true"),
699 Scalar::Bool(false) => out.push_str("false"),
700 Scalar::Null => out.push_str("null"),
701 Scalar::Nan => out.push_str("NaN"),
702 Scalar::Infinity => out.push_str("Infinity"),
703 Scalar::NegInfinity => out.push_str("-Infinity"),
704 }
705}
706
707fn write_json_string_body(out: &mut String, s: &str) {
710 for r in s.chars() {
711 match r {
712 '"' => out.push_str("\\\""),
713 '\\' => out.push_str("\\\\"),
714 '\n' => out.push_str("\\n"),
715 '\r' => out.push_str("\\r"),
716 '\t' => out.push_str("\\t"),
717 '\u{8}' => out.push_str("\\b"),
718 '\u{c}' => out.push_str("\\f"),
719 _ => {
720 if r < ' ' {
721 const HEX_DIGITS: &[u8; 16] = b"0123456789abcdef";
722 let cp = r as u32;
723 out.push_str("\\u00");
724 out.push(HEX_DIGITS[((cp >> 4) & 0xf) as usize] as char);
725 out.push(HEX_DIGITS[(cp & 0xf) as usize] as char);
726 } else {
727 out.push(r);
728 }
729 }
730 }
731 }
732}
733
734#[cfg(test)]
735mod tests {
736 use super::*;
737
738 fn walk(context: &str, body: &str) -> Vec<(String, String, Option<&'static str>)> {
739 parse_ordered_json(body)
740 .map(|root| {
741 append_json_walk_entries(
742 Vec::new(),
743 &root,
744 context,
745 crate::body_scan::ExcludedBodyFields::default(),
746 )
747 .into_iter()
748 .map(|v| (v.context, v.content, v.forced_category))
749 .collect()
750 })
751 .unwrap_or_default()
752 }
753
754 #[test]
755 fn root_must_be_object_or_array() {
756 assert!(parse_ordered_json("123").is_none());
757 assert!(parse_ordered_json("\"str\"").is_none());
758 assert!(parse_ordered_json("true").is_none());
759 assert!(parse_ordered_json("null").is_none());
760 assert!(parse_ordered_json("").is_none());
761 assert!(parse_ordered_json("{\"a\":1}").is_some());
762 assert!(parse_ordered_json("[1,2]").is_some());
763 }
764
765 #[test]
766 fn trailing_data_rejected_like_json_loads() {
767 assert!(parse_ordered_json("{\"a\":1} trailing").is_none());
768 assert!(parse_ordered_json("[1] [2]").is_none());
769 }
770
771 #[test]
772 fn duplicate_keys_keep_first_position_and_last_value() {
773 let values = walk(REQUEST_BODY_CONTEXT, "{\"a\":1,\"b\":2,\"a\":3}");
774 let contents: Vec<&str> = values.iter().map(|(_, c, _)| c.as_str()).collect();
775 assert_eq!(contents, vec!["a", "3", "b", "2"]);
777 }
778
779 #[test]
780 fn number_and_boolean_leaves_scan_python_str_renderings() {
781 let values = walk(REQUEST_BODY_CONTEXT, "[1, -2.5, 1e2, true, false, null]");
782 let contents: Vec<&str> = values.iter().map(|(_, c, _)| c.as_str()).collect();
783 assert_eq!(contents, vec!["1", "-2.5", "1e2", "True", "False", "None"]);
786 }
787
788 #[test]
789 fn nan_and_infinity_accepted_like_json_loads() {
790 let values = walk(REQUEST_BODY_CONTEXT, "[NaN, Infinity, -Infinity]");
791 let contents: Vec<&str> = values.iter().map(|(_, c, _)| c.as_str()).collect();
792 assert_eq!(contents, vec!["nan", "inf", "-inf"]);
793 }
794
795 #[test]
796 fn mongo_operator_keys_force_nosql_hits() {
797 for key in ["$where", "$ne", "$regex", "$elemMatch"] {
798 let body = format!("{{\"{key}\": 1}}");
799 let values = walk(REQUEST_BODY_CONTEXT, &body);
800 assert_eq!(values.len(), 1, "{key}");
801 assert_eq!(values[0].0, REQUEST_BODY_CONTEXT);
802 assert_eq!(values[0].1, key);
803 assert_eq!(values[0].2, Some(crate::body_scan::MONGO_OPERATOR_CATEGORY));
804 }
805 }
806
807 #[test]
808 fn dollar_word_that_is_not_an_operator_scans_plain() {
809 let values = walk(REQUEST_BODY_CONTEXT, "{\"$money\": 1}");
810 assert_eq!(values.len(), 2);
811 assert_eq!(values[0].2, None);
812 assert_eq!(values[0].1, "$money");
813 }
814
815 #[test]
816 fn embedded_walk_appends_the_suffix_per_level() {
817 let single = r#"{"outer":"{\"a\":\"1 OR 1=1\"}"}"#;
820 let values = walk("request_body:form_field", single);
821 assert_eq!(
822 values,
823 vec![
824 (REQUEST_BODY_CONTEXT.to_owned(), "outer".to_owned(), None),
825 (REQUEST_BODY_CONTEXT.to_owned(), "a".to_owned(), None),
826 (
827 "request_body:form_field:embedded_json".to_owned(),
828 "1 OR 1=1".to_owned(),
829 None
830 ),
831 ]
832 );
833
834 let double = r#"{"outer":"{\"a\":\"{\\\"b\\\":\\\"1 OR 1=1\\\"}\"}"}"#;
838 let values = walk("request_body:form_field", double);
839 assert_eq!(
840 values,
841 vec![
842 (REQUEST_BODY_CONTEXT.to_owned(), "outer".to_owned(), None),
843 (REQUEST_BODY_CONTEXT.to_owned(), "a".to_owned(), None),
844 (REQUEST_BODY_CONTEXT.to_owned(), "b".to_owned(), None),
845 (
846 "request_body:form_field:embedded_json:embedded_json".to_owned(),
847 "1 OR 1=1".to_owned(),
848 None
849 ),
850 ]
851 );
852 }
853
854 #[test]
855 fn top_level_walk_never_reparses_leaf_strings() {
856 let body = "{\"a\":\"{\\\"b\\\":\\\"c\\\"}\"}";
857 let values = walk(REQUEST_BODY_CONTEXT, body);
858 assert_eq!(
859 values,
860 vec![
861 (REQUEST_BODY_CONTEXT.to_owned(), "a".to_owned(), None),
862 (
863 REQUEST_BODY_CONTEXT.to_owned(),
864 "{\"b\":\"c\"}".to_owned(),
865 None
866 ),
867 ]
868 );
869 }
870
871 #[test]
872 fn depth_cap_serializes_the_subtree_as_compact_json() {
873 let mut deep = String::new();
874 for _ in 0..40 {
875 deep.insert(0, '[');
876 deep.push(']');
877 }
878 let values = walk(REQUEST_BODY_CONTEXT, &format!("{{\"a\":{deep}}}"));
879 assert_eq!(values[0].1, "a");
880 let last = values.last().expect("capped subtree value");
881 assert_eq!(last.1.matches('[').count(), 10);
884 assert_eq!(last.1.matches(']').count(), 10);
885 }
886
887 #[test]
888 fn compact_serialization_escapes_like_json_dumps() {
889 let values = walk(REQUEST_BODY_CONTEXT, "{\"a\":\"quote\\\" back\\\\ nl\\n\"}");
890 let last = values.last().expect("leaf");
891 assert_eq!(last.1, "quote\" back\\ nl\n");
892 let node = parse_ordered_json("{\"a\":\"x\\u0001y\"}").expect("parses");
894 let text = serialize_compact_json(&node);
895 assert_eq!(text, "{\"a\":\"x\\u0001y\"}");
896 }
897
898 #[test]
899 fn parse_depth_limit_beyond_python_recursion_fails_to_blob() {
900 let mut deep = String::new();
901 for _ in 0..1500 {
902 deep.insert(0, '[');
903 deep.push(']');
904 }
905 assert!(parse_ordered_json(&deep).is_none());
906 let mut mid = String::new();
909 for _ in 0..40 {
910 mid.insert(0, '[');
911 mid.push(']');
912 }
913 assert!(parse_ordered_json(&mid).is_some());
914 }
915
916 #[test]
917 fn strict_grammar_rejects_loose_json() {
918 for body in [
919 "{\"a\":01}",
920 "[1,]",
921 "{\"a\":}",
922 "{\"a\" 1}",
923 "[unclosed",
924 "\"\\x41\"",
925 "[1 2]",
926 ] {
927 assert!(parse_ordered_json(body).is_none(), "{body:?} must fail");
928 }
929 }
930
931 #[test]
932 fn unicode_escapes_decode() {
933 let values = walk(
934 REQUEST_BODY_CONTEXT,
935 "[\"\\u00e9\\ud83d\\ude00\", \"\\ud800\"]",
936 );
937 assert_eq!(values[0].1, "\u{e9}\u{1F600}");
938 assert_eq!(values[1].1, "\u{FFFD}", "lone surrogate -> replacement");
939 }
940}