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
165impl ParseFrame {
166 const fn object_key_slot(&mut self) -> Option<&mut Option<String>> {
170 match self {
171 Self::Object { current_key, .. } => Some(current_key),
172 Self::Array(_) => None,
173 }
174 }
175}
176
177enum Flow {
179 Next(ParseState),
180 Done(JsonNode),
181}
182
183fn upsert_object(keys: &mut Vec<String>, values: &mut Vec<JsonNode>, key: String, node: JsonNode) {
186 if let Some(at) = keys.iter().position(|existing| *existing == key) {
187 values[at] = node;
188 } else {
189 keys.push(key);
190 values.push(node);
191 }
192}
193
194#[derive(Debug)]
198enum ParseFrame {
199 Array(Vec<JsonNode>),
200 Object {
201 keys: Vec<String>,
202 values: Vec<JsonNode>,
203 current_key: Option<String>,
204 },
205}
206
207enum ParseState {
208 Value,
210 Key,
212 Attach,
214}
215
216fn frame_into_node(frame: ParseFrame) -> JsonNode {
217 match frame {
218 ParseFrame::Array(items) => JsonNode::array(items),
219 ParseFrame::Object { keys, values, .. } => JsonNode::object(keys, values),
220 }
221}
222
223impl Parser<'_> {
224 const fn peek(&self) -> Option<u8> {
225 if self.pos < self.bytes.len() {
226 Some(self.bytes[self.pos])
227 } else {
228 None
229 }
230 }
231
232 const fn bump(&mut self) {
233 self.pos += 1;
234 }
235
236 const fn skip_ws(&mut self) {
237 while matches!(self.peek(), Some(b' ' | b'\t' | b'\n' | b'\r')) {
238 self.pos += 1;
239 }
240 }
241
242 fn literal(&mut self, word: &str) -> bool {
243 if self.bytes[self.pos..].starts_with(word.as_bytes()) {
244 self.pos += word.len();
245 return true;
246 }
247 false
248 }
249
250 fn parse_root(&mut self) -> Result<JsonNode, ParseError> {
251 let mut flow = Flow::Next(ParseState::Value);
252 loop {
253 flow = match flow {
254 Flow::Next(ParseState::Value) => Flow::Next(self.step_value()?),
255 Flow::Next(ParseState::Key) => Flow::Next(self.step_key()?),
256 Flow::Next(ParseState::Attach) => self.step_attach()?,
257 Flow::Done(node) => return Ok(node),
258 };
259 }
260 }
261
262 fn step_value(&mut self) -> Result<ParseState, ParseError> {
264 self.skip_ws();
265 match self.peek() {
266 Some(b'[') => {
267 self.bump();
268 self.open_frame()?;
269 self.skip_ws();
270 if self.peek() == Some(b']') {
271 self.bump();
272 self.close_frame();
273 Ok(ParseState::Attach)
274 } else {
275 Ok(ParseState::Value)
276 }
277 }
278 Some(b'{') => {
279 self.bump();
280 self.open_frame()?;
281 self.skip_ws();
282 if self.peek() == Some(b'}') {
283 self.bump();
284 self.close_frame();
285 Ok(ParseState::Attach)
286 } else {
287 Ok(ParseState::Key)
288 }
289 }
290 _ => {
291 self.pending = Some(self.parse_leaf()?);
292 Ok(ParseState::Attach)
293 }
294 }
295 }
296
297 fn open_frame(&mut self) -> Result<(), ParseError> {
299 self.depth += 1;
300 if self.depth > PARSE_DEPTH_LIMIT {
301 return Err(ParseError::Depth);
302 }
303 if self.bytes[self.pos - 1] == b'[' {
304 self.stack.push(ParseFrame::Array(Vec::new()));
305 } else {
306 self.stack.push(ParseFrame::Object {
307 keys: Vec::new(),
308 values: Vec::new(),
309 current_key: None,
310 });
311 }
312 Ok(())
313 }
314
315 fn close_frame(&mut self) {
318 self.depth -= 1;
319 let frame = self.stack.pop().expect("frame stack not empty");
320 self.pending = Some(frame_into_node(frame));
321 }
322
323 fn step_key(&mut self) -> Result<ParseState, ParseError> {
325 self.skip_ws();
326 if self.peek() != Some(b'"') {
327 return Err(ParseError::Invalid);
328 }
329 let key = self.parse_string()?;
330 self.skip_ws();
331 if self.peek() != Some(b':') {
332 return Err(ParseError::Invalid);
333 }
334 self.bump();
335 let mut frame = self.stack.last_mut();
338 #[cfg(not(coverage))] if let Some(slot) = frame.as_mut().and_then(|frame| frame.object_key_slot()) {
342 *slot = Some(key);
343 }
344 #[cfg(coverage)]
345 {
346 let slot = frame
347 .as_mut()
348 .and_then(|frame| frame.object_key_slot())
349 .expect("a Key step always follows an object frame");
350 *slot = Some(key);
351 }
352 Ok(ParseState::Value)
353 }
354
355 fn step_attach(&mut self) -> Result<Flow, ParseError> {
358 #[cfg(not(coverage))] let Some(node) = self.pending.take() else {
360 return Err(ParseError::Invalid);
361 };
362 #[cfg(coverage)]
363 let node = self
364 .pending
365 .take()
366 .expect("every Attach entry queues a node");
367 self.skip_ws();
368 let delimiter = self.peek();
369 let Some(frame) = self.stack.pop() else {
370 if self.pos != self.bytes.len() {
371 return Err(ParseError::Invalid);
373 }
374 return Ok(Flow::Done(node));
375 };
376 match frame {
377 ParseFrame::Array(mut items) => match delimiter {
378 Some(b',') => {
379 self.bump();
380 items.push(node);
381 self.stack.push(ParseFrame::Array(items));
382 Ok(Flow::Next(ParseState::Value))
383 }
384 Some(b']') => {
385 self.bump();
386 items.push(node);
387 self.depth -= 1;
388 self.pending = Some(JsonNode::array(items));
389 Ok(Flow::Next(ParseState::Attach))
390 }
391 _ => Err(ParseError::Invalid),
392 },
393 ParseFrame::Object {
394 mut keys,
395 mut values,
396 mut current_key,
397 } => {
398 #[cfg(not(coverage))] let key = current_key.take().ok_or(ParseError::Invalid)?;
402 #[cfg(coverage)]
403 let key = current_key
404 .take()
405 .expect("the key slot is re-armed before every Attach");
406 match delimiter {
407 Some(b',') => {
408 self.bump();
409 upsert_object(&mut keys, &mut values, key, node);
410 self.stack.push(ParseFrame::Object {
411 keys,
412 values,
413 current_key,
414 });
415 Ok(Flow::Next(ParseState::Key))
416 }
417 Some(b'}') => {
418 self.bump();
419 upsert_object(&mut keys, &mut values, key, node);
420 self.depth -= 1;
421 self.pending = Some(JsonNode::object(keys, values));
422 Ok(Flow::Next(ParseState::Attach))
423 }
424 _ => Err(ParseError::Invalid),
425 }
426 }
427 }
428 }
429
430 fn parse_leaf(&mut self) -> Result<JsonNode, ParseError> {
433 match self.peek() {
434 Some(b'"') => Ok(JsonNode::leaf(Scalar::Str(self.parse_string()?))),
435 Some(b't') if self.literal("true") => Ok(JsonNode::leaf(Scalar::Bool(true))),
436 Some(b'f') if self.literal("false") => Ok(JsonNode::leaf(Scalar::Bool(false))),
437 Some(b'n') if self.literal("null") => Ok(JsonNode::leaf(Scalar::Null)),
438 Some(b'N') if self.literal("NaN") => Ok(JsonNode::leaf(Scalar::Nan)),
439 Some(b'I') if self.literal("Infinity") => Ok(JsonNode::leaf(Scalar::Infinity)),
440 Some(b'-') => {
441 if self.literal("-Infinity") {
442 return Ok(JsonNode::leaf(Scalar::NegInfinity));
443 }
444 Ok(JsonNode::leaf(Scalar::Number(self.parse_number_literal()?)))
445 }
446 Some(c) if c.is_ascii_digit() => {
447 Ok(JsonNode::leaf(Scalar::Number(self.parse_number_literal()?)))
448 }
449 _ => Err(ParseError::Invalid),
450 }
451 }
452
453 fn parse_string(&mut self) -> Result<String, ParseError> {
456 self.bump(); let mut out = String::new();
458 loop {
459 match self.peek() {
460 None => return Err(ParseError::Invalid),
461 Some(b'"') => {
462 self.bump();
463 return Ok(out);
464 }
465 Some(b'\\') => {
466 self.bump();
467 let escaped = self.peek().ok_or(ParseError::Invalid)?;
468 self.bump();
469 match escaped {
470 b'"' => out.push('"'),
471 b'\\' => out.push('\\'),
472 b'/' => out.push('/'),
473 b'b' => out.push('\u{8}'),
474 b'f' => out.push('\u{c}'),
475 b'n' => out.push('\n'),
476 b'r' => out.push('\r'),
477 b't' => out.push('\t'),
478 b'u' => {
479 let first = self.parse_hex4()?;
480 let ch = match first {
481 (0xD800..=0xDBFF) => {
482 if self.peek() == Some(b'\\')
487 && self.bytes.get(self.pos + 1) == Some(&b'u')
488 {
489 self.pos += 2;
490 let second = self.parse_hex4()?;
491 if (0xDC00..=0xDFFF).contains(&second) {
492 let combined = 0x1_0000
493 + ((u32::from(first) - 0xD800) << 10)
494 + (u32::from(second) - 0xDC00);
495 #[cfg(not(coverage))] let ch = char::from_u32(combined)
499 .ok_or(ParseError::Invalid)?;
500 #[cfg(coverage)]
501 let ch = char::from_u32(combined).expect(
502 "the pair arithmetic yields a valid scalar",
503 );
504 ch
505 } else {
506 self.pos -= 2;
507 '\u{FFFD}'
508 }
509 } else {
510 '\u{FFFD}'
511 }
512 }
513 (0xDC00..=0xDFFF) => '\u{FFFD}',
514 #[cfg(not(coverage))] _ => char::from_u32(u32::from(first)).ok_or(ParseError::Invalid)?,
517 #[cfg(coverage)]
518 _ => char::from_u32(u32::from(first))
519 .expect("a non-surrogate u16 is a valid scalar"),
520 };
521 out.push(ch);
522 }
523 _ => return Err(ParseError::Invalid),
524 }
525 }
526 Some(c) if c < 0x20 => return Err(ParseError::Invalid),
527 Some(_) => {
528 let rest = &self.bytes[self.pos..];
530 #[cfg(not(coverage))] let s = std::str::from_utf8(rest).map_err(|_| ParseError::Invalid)?;
533 #[cfg(coverage)]
534 let s = std::str::from_utf8(rest).expect("a &str slice is valid UTF-8");
535 #[cfg(not(coverage))] let ch = s.chars().next().ok_or(ParseError::Invalid)?;
538 #[cfg(coverage)]
539 let ch = s.chars().next().expect("the remainder is non-empty");
540 out.push(ch);
541 self.pos += ch.len_utf8();
542 }
543 }
544 }
545 }
546
547 fn parse_hex4(&mut self) -> Result<u16, ParseError> {
548 if self.pos + 4 > self.bytes.len() {
549 return Err(ParseError::Invalid);
550 }
551 let slice = &self.bytes[self.pos..self.pos + 4];
552 let text = std::str::from_utf8(slice).map_err(|_| ParseError::Invalid)?;
556 let value = u16::from_str_radix(text, 16).map_err(|_| ParseError::Invalid)?;
557 self.pos += 4;
558 Ok(value)
559 }
560
561 fn parse_number_literal(&mut self) -> Result<String, ParseError> {
564 let start = self.pos;
565 if self.peek() == Some(b'-') {
566 self.bump();
567 }
568 match self.peek() {
569 Some(b'0') => self.bump(),
570 Some(c) if c.is_ascii_digit() => {
571 while matches!(self.peek(), Some(c) if c.is_ascii_digit()) {
572 self.bump();
573 }
574 }
575 _ => return Err(ParseError::Invalid),
576 }
577 if self.peek() == Some(b'.') {
578 self.bump();
579 if !matches!(self.peek(), Some(c) if c.is_ascii_digit()) {
580 return Err(ParseError::Invalid);
581 }
582 while matches!(self.peek(), Some(c) if c.is_ascii_digit()) {
583 self.bump();
584 }
585 }
586 if matches!(self.peek(), Some(b'e' | b'E')) {
587 self.bump();
588 if matches!(self.peek(), Some(b'+' | b'-')) {
589 self.bump();
590 }
591 if !matches!(self.peek(), Some(c) if c.is_ascii_digit()) {
592 return Err(ParseError::Invalid);
593 }
594 while matches!(self.peek(), Some(c) if c.is_ascii_digit()) {
595 self.bump();
596 }
597 }
598 #[cfg(not(coverage))] let text =
601 std::str::from_utf8(&self.bytes[start..self.pos]).map_err(|_| ParseError::Invalid)?;
602 #[cfg(coverage)]
603 let text = std::str::from_utf8(&self.bytes[start..self.pos])
604 .expect("the number span is valid UTF-8");
605 Ok(text.to_owned())
606 }
607}
608
609fn mongo_operator_key_re() -> &'static Regex {
611 static RE: LazyLock<Regex> = LazyLock::new(|| {
612 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)$")
613 .expect("mongo operator key regex")
614 });
615 &RE
616}
617
618struct Frame<'a> {
619 is_entry: bool,
620 key: String,
621 node: &'a JsonNode,
622 depth: usize,
623}
624
625pub(crate) fn append_json_walk_entries(
635 mut values: Vec<BodyScanValue>,
636 root: &JsonNode,
637 context: &str,
638 excluded: crate::body_scan::ExcludedBodyFields<'_>,
639 depth_cap: usize,
640) -> Vec<BodyScanValue> {
641 let allow_leaf_reparse = context != REQUEST_BODY_CONTEXT;
642 let mut stack = vec![Frame {
645 is_entry: false,
646 key: String::new(),
647 node: root,
648 depth: 1,
649 }];
650 while let Some(frame) = stack.pop() {
651 if frame.is_entry {
652 if excluded.contains(&frame.key) {
653 continue;
654 }
655 if mongo_operator_key_re().is_match(&frame.key) {
656 values.push(BodyScanValue::forced(
659 frame.key,
660 crate::body_scan::MONGO_OPERATOR_CATEGORY,
661 ));
662 continue;
663 }
664 values.push(BodyScanValue::plain(
665 frame.key.as_str(),
666 REQUEST_BODY_CONTEXT,
667 ));
668 stack.push(Frame {
669 is_entry: false,
670 key: frame.key.clone(),
671 node: frame.node,
672 depth: frame.depth + 1,
673 });
674 continue;
675 }
676 let node = frame.node;
677 if node.is_object {
678 if frame.depth >= depth_cap {
679 values.push(BodyScanValue::plain(serialize_compact_json(node), context));
680 continue;
681 }
682 for index in (0..node.keys.len()).rev() {
683 stack.push(Frame {
684 is_entry: true,
685 key: node.keys[index].clone(),
686 node: &node.values[index],
687 depth: frame.depth,
688 });
689 }
690 } else if node.is_array {
691 if frame.depth >= depth_cap {
692 values.push(BodyScanValue::plain(serialize_compact_json(node), context));
693 continue;
694 }
695 for item in node.items.iter().rev() {
696 stack.push(Frame {
698 is_entry: false,
699 key: frame.key.clone(),
700 node: item,
701 depth: frame.depth + 1,
702 });
703 }
704 } else {
705 let scalar = node.scalar.as_ref().expect("leaf node carries a scalar");
706 let text = scalar.scan_text();
707 if allow_leaf_reparse && let Some(inner) = parse_ordered_json(&text) {
708 values = append_json_walk_entries(
709 values,
710 &inner,
711 &format!("{context}{EMBEDDED_JSON_LEAF_CONTEXT_SUFFIX}"),
712 excluded,
713 depth_cap,
714 );
715 continue;
716 }
717 values.push(BodyScanValue::plain(text, context));
718 }
719 }
720 values
721}
722
723#[must_use]
730pub fn serialize_compact_json(node: &JsonNode) -> String {
731 let mut out = String::new();
732 write_compact_json(&mut out, node);
733 out
734}
735
736fn write_compact_json(out: &mut String, node: &JsonNode) {
737 if node.is_object {
738 out.push('{');
739 for (index, key) in node.keys.iter().enumerate() {
740 if index > 0 {
741 out.push(',');
742 }
743 out.push('"');
744 write_json_string_body(out, key);
745 out.push_str("\":");
746 write_compact_json(out, &node.values[index]);
747 }
748 out.push('}');
749 return;
750 }
751 if node.is_array {
752 out.push('[');
753 for (index, item) in node.items.iter().enumerate() {
754 if index > 0 {
755 out.push(',');
756 }
757 write_compact_json(out, item);
758 }
759 out.push(']');
760 return;
761 }
762 match node.scalar.as_ref().expect("leaf node carries a scalar") {
763 Scalar::Str(s) => {
764 out.push('"');
765 write_json_string_body(out, s);
766 out.push('"');
767 }
768 Scalar::Number(literal) => out.push_str(literal),
769 Scalar::Bool(true) => out.push_str("true"),
770 Scalar::Bool(false) => out.push_str("false"),
771 Scalar::Null => out.push_str("null"),
772 Scalar::Nan => out.push_str("NaN"),
773 Scalar::Infinity => out.push_str("Infinity"),
774 Scalar::NegInfinity => out.push_str("-Infinity"),
775 }
776}
777
778fn write_json_string_body(out: &mut String, s: &str) {
781 for r in s.chars() {
782 match r {
783 '"' => out.push_str("\\\""),
784 '\\' => out.push_str("\\\\"),
785 '\n' => out.push_str("\\n"),
786 '\r' => out.push_str("\\r"),
787 '\t' => out.push_str("\\t"),
788 '\u{8}' => out.push_str("\\b"),
789 '\u{c}' => out.push_str("\\f"),
790 _ => {
791 if r < ' ' {
792 const HEX_DIGITS: &[u8; 16] = b"0123456789abcdef";
793 let cp = r as u32;
794 out.push_str("\\u00");
795 out.push(HEX_DIGITS[((cp >> 4) & 0xf) as usize] as char);
796 out.push(HEX_DIGITS[(cp & 0xf) as usize] as char);
797 } else {
798 out.push(r);
799 }
800 }
801 }
802 }
803}
804
805#[cfg(test)]
806mod tests {
807 use super::*;
808
809 fn walk(context: &str, body: &str) -> Vec<(String, String, Option<&'static str>)> {
810 parse_ordered_json(body)
811 .map(|root| {
812 append_json_walk_entries(
813 Vec::new(),
814 &root,
815 context,
816 crate::body_scan::ExcludedBodyFields::default(),
817 JSON_WALK_DEPTH_CAP,
818 )
819 .into_iter()
820 .map(|v| (v.context, v.content, v.forced_category))
821 .collect()
822 })
823 .unwrap_or_default()
824 }
825
826 #[test]
827 fn root_must_be_object_or_array() {
828 assert!(parse_ordered_json("123").is_none());
829 assert!(parse_ordered_json("\"str\"").is_none());
830 assert!(parse_ordered_json("true").is_none());
831 assert!(parse_ordered_json("null").is_none());
832 assert!(parse_ordered_json("").is_none());
833 assert!(parse_ordered_json("{\"a\":1}").is_some());
834 assert!(parse_ordered_json("[1,2]").is_some());
835 }
836
837 #[test]
838 fn object_key_slot_answers_none_for_array_frames() {
839 let mut array = ParseFrame::Array(Vec::new());
840 assert!(array.object_key_slot().is_none());
841 let mut object = ParseFrame::Object {
842 keys: Vec::new(),
843 values: Vec::new(),
844 current_key: None,
845 };
846 assert!(object.object_key_slot().is_some());
847 }
848
849 #[test]
850 fn parse_error_arms_reject_the_reference_failures() {
851 let deep = format!("{}1{}", "[".repeat(1001), "]".repeat(1001));
853 assert!(parse_ordered_json(&deep).is_none(), "depth bound fires");
854 let deep_objects = format!("{}1{}", "{\"a\":".repeat(1001), "1".repeat(1001));
856 assert!(parse_ordered_json(&deep_objects).is_none(), "object depth");
857 assert!(parse_ordered_json("{\"a\":1,2}").is_none());
860 assert!(parse_ordered_json("{\"a\":1,}").is_none());
862 assert!(parse_ordered_json("{\"k\":\"\\ud83d\\uZZZZ\"}").is_none());
864 assert!(parse_ordered_json("{\"\\q\":1}").is_none());
866 assert!(parse_ordered_json("{\"a\":1,}").is_none());
868 assert!(parse_ordered_json("{\"k\":\"a\u{1}b\"}").is_none());
870 assert!(parse_ordered_json("{\"k\":\"\\uZZZZ\"}").is_none());
872 assert!(parse_ordered_json("{\"k\":\"\\u\u{e9}\u{20ac}\"}").is_none());
874 }
875
876 #[test]
877 fn surrogate_pair_arms_follow_the_reference() {
878 let entries = walk("body", "{\"k\":\"\\ud83d\\ude00\"}");
880 let joined: String = entries.iter().map(|e| e.1.as_str()).collect();
881 assert!(
882 joined.contains('\u{1F600}'),
883 "the surrogate pair combines: {entries:?}"
884 );
885 let entries = walk("body", "{\"k\":\"\\ud83dx\"}");
888 let joined: String = entries.iter().map(|e| e.1.as_str()).collect();
889 assert!(
890 joined.contains('\u{FFFD}'),
891 "the lone high surrogate degrades: {entries:?}"
892 );
893 let entries = walk("body", "{\"k\":\"\\udc00\"}");
895 let joined: String = entries.iter().map(|e| e.1.as_str()).collect();
896 assert!(
897 joined.contains('\u{FFFD}'),
898 "the lone low surrogate degrades: {entries:?}"
899 );
900 }
901
902 #[test]
903 fn trailing_data_rejected_like_json_loads() {
904 assert!(parse_ordered_json("{\"a\":1} trailing").is_none());
905 assert!(parse_ordered_json("[1] [2]").is_none());
906 }
907
908 #[test]
909 fn duplicate_keys_keep_first_position_and_last_value() {
910 let values = walk(REQUEST_BODY_CONTEXT, "{\"a\":1,\"b\":2,\"a\":3}");
911 let contents: Vec<&str> = values.iter().map(|(_, c, _)| c.as_str()).collect();
912 assert_eq!(contents, vec!["a", "3", "b", "2"]);
914 }
915
916 #[test]
917 fn number_and_boolean_leaves_scan_python_str_renderings() {
918 let values = walk(REQUEST_BODY_CONTEXT, "[1, -2.5, 1e2, true, false, null]");
919 let contents: Vec<&str> = values.iter().map(|(_, c, _)| c.as_str()).collect();
920 assert_eq!(contents, vec!["1", "-2.5", "1e2", "True", "False", "None"]);
923 }
924
925 #[test]
926 fn nan_and_infinity_accepted_like_json_loads() {
927 let values = walk(REQUEST_BODY_CONTEXT, "[NaN, Infinity, -Infinity]");
928 let contents: Vec<&str> = values.iter().map(|(_, c, _)| c.as_str()).collect();
929 assert_eq!(contents, vec!["nan", "inf", "-inf"]);
930 }
931
932 #[test]
933 fn mongo_operator_keys_force_nosql_hits() {
934 for key in ["$where", "$ne", "$regex", "$elemMatch"] {
935 let body = format!("{{\"{key}\": 1}}");
936 let values = walk(REQUEST_BODY_CONTEXT, &body);
937 assert_eq!(values.len(), 1, "{key}");
938 assert_eq!(values[0].0, REQUEST_BODY_CONTEXT);
939 assert_eq!(values[0].1, key);
940 assert_eq!(values[0].2, Some(crate::body_scan::MONGO_OPERATOR_CATEGORY));
941 }
942 }
943
944 #[test]
945 fn dollar_word_that_is_not_an_operator_scans_plain() {
946 let values = walk(REQUEST_BODY_CONTEXT, "{\"$money\": 1}");
947 assert_eq!(values.len(), 2);
948 assert_eq!(values[0].2, None);
949 assert_eq!(values[0].1, "$money");
950 }
951
952 #[test]
953 fn embedded_walk_appends_the_suffix_per_level() {
954 let single = r#"{"outer":"{\"a\":\"1 OR 1=1\"}"}"#;
957 let values = walk("request_body:form_field", single);
958 assert_eq!(
959 values,
960 vec![
961 (REQUEST_BODY_CONTEXT.to_owned(), "outer".to_owned(), None),
962 (REQUEST_BODY_CONTEXT.to_owned(), "a".to_owned(), None),
963 (
964 "request_body:form_field:embedded_json".to_owned(),
965 "1 OR 1=1".to_owned(),
966 None
967 ),
968 ]
969 );
970
971 let double = r#"{"outer":"{\"a\":\"{\\\"b\\\":\\\"1 OR 1=1\\\"}\"}"}"#;
975 let values = walk("request_body:form_field", double);
976 assert_eq!(
977 values,
978 vec![
979 (REQUEST_BODY_CONTEXT.to_owned(), "outer".to_owned(), None),
980 (REQUEST_BODY_CONTEXT.to_owned(), "a".to_owned(), None),
981 (REQUEST_BODY_CONTEXT.to_owned(), "b".to_owned(), None),
982 (
983 "request_body:form_field:embedded_json:embedded_json".to_owned(),
984 "1 OR 1=1".to_owned(),
985 None
986 ),
987 ]
988 );
989 }
990
991 #[test]
992 fn top_level_walk_never_reparses_leaf_strings() {
993 let body = "{\"a\":\"{\\\"b\\\":\\\"c\\\"}\"}";
994 let values = walk(REQUEST_BODY_CONTEXT, body);
995 assert_eq!(
996 values,
997 vec![
998 (REQUEST_BODY_CONTEXT.to_owned(), "a".to_owned(), None),
999 (
1000 REQUEST_BODY_CONTEXT.to_owned(),
1001 "{\"b\":\"c\"}".to_owned(),
1002 None
1003 ),
1004 ]
1005 );
1006 }
1007
1008 #[test]
1009 fn depth_cap_serializes_the_subtree_as_compact_json() {
1010 let mut deep = String::new();
1011 for _ in 0..40 {
1012 deep.insert(0, '[');
1013 deep.push(']');
1014 }
1015 let values = walk(REQUEST_BODY_CONTEXT, &format!("{{\"a\":{deep}}}"));
1016 assert_eq!(values[0].1, "a");
1017 let last = values.last().expect("capped subtree value");
1018 assert_eq!(last.1.matches('[').count(), 10);
1021 assert_eq!(last.1.matches(']').count(), 10);
1022 }
1023
1024 #[test]
1025 fn compact_serialization_escapes_like_json_dumps() {
1026 let values = walk(REQUEST_BODY_CONTEXT, "{\"a\":\"quote\\\" back\\\\ nl\\n\"}");
1027 let last = values.last().expect("leaf");
1028 assert_eq!(last.1, "quote\" back\\ nl\n");
1029 let node = parse_ordered_json("{\"a\":\"x\\u0001y\"}").expect("parses");
1031 let text = serialize_compact_json(&node);
1032 assert_eq!(text, "{\"a\":\"x\\u0001y\"}");
1033 }
1034
1035 #[test]
1036 fn parse_depth_limit_beyond_python_recursion_fails_to_blob() {
1037 let mut deep = String::new();
1038 for _ in 0..1500 {
1039 deep.insert(0, '[');
1040 deep.push(']');
1041 }
1042 assert!(parse_ordered_json(&deep).is_none());
1043 let mut mid = String::new();
1046 for _ in 0..40 {
1047 mid.insert(0, '[');
1048 mid.push(']');
1049 }
1050 assert!(parse_ordered_json(&mid).is_some());
1051 }
1052
1053 #[test]
1054 fn strict_grammar_rejects_loose_json() {
1055 for body in [
1056 "{\"a\":01}",
1057 "[1,]",
1058 "{\"a\":}",
1059 "{\"a\" 1}",
1060 "[unclosed",
1061 "\"\\x41\"",
1062 "[1 2]",
1063 ] {
1064 assert!(parse_ordered_json(body).is_none(), "{body:?} must fail");
1065 }
1066 }
1067
1068 #[test]
1069 fn unicode_escapes_decode() {
1070 let values = walk(
1071 REQUEST_BODY_CONTEXT,
1072 "[\"\\u00e9\\ud83d\\ude00\", \"\\ud800\"]",
1073 );
1074 assert_eq!(values[0].1, "\u{e9}\u{1F600}");
1075 assert_eq!(values[1].1, "\u{FFFD}", "lone surrogate -> replacement");
1076 }
1077}
1078
1079#[cfg(test)]
1080mod coverage_tests {
1081 use super::*;
1082
1083 #[test]
1084 fn strict_parser_rejects_malformed_documents() {
1085 for bad in [
1086 "{'1':2}", "[\"k\":1]", "\"abc", "\"abc\\", "{\"a\":01}", "{\"a\":-}", "{\"a\":tru}", "+1", "{\"a\":1,}", "{} extra", ] {
1097 assert!(parse_ordered_json(bad).is_none(), "accepted {bad:?}");
1098 }
1099 }
1100
1101 #[test]
1102 fn string_escapes_decode_including_surrogate_pairs() {
1103 let node = parse_ordered_json(r#"{"e":"\/\b\f\r\tA"}"#).expect("escapes");
1106 assert_eq!(serialize_compact_json(&node), r#"{"e":"/\b\f\r\tA"}"#);
1107
1108 let node = parse_ordered_json(r#"{"p":"\ud83d\ude00"}"#).expect("pair");
1110 assert_eq!(serialize_compact_json(&node), "{\"p\":\"\u{1F600}\"}");
1111
1112 let node = parse_ordered_json(r#"{"h":"\ud83d"}"#).expect("lone high");
1114 assert_eq!(serialize_compact_json(&node), "{\"h\":\"\u{FFFD}\"}");
1115 let node = parse_ordered_json(r#"{"l":"\udc00"}"#).expect("lone low");
1116 assert_eq!(serialize_compact_json(&node), "{\"l\":\"\u{FFFD}\"}");
1117 }
1118
1119 #[test]
1120 fn serializer_renders_every_scalar_and_separator() {
1121 let cases = [
1122 (
1123 r#"{"n":1,"t":true,"f":false,"z":null}"#,
1124 "{\"n\":1,\"t\":true,\"f\":false,\"z\":null}",
1125 ),
1126 ("[1,2]", "[1,2]"),
1127 (
1128 r#"{"x":NaN,"y":Infinity,"z":-Infinity}"#,
1129 "{\"x\":NaN,\"y\":Infinity,\"z\":-Infinity}",
1130 ),
1131 (
1132 r#"{"s":"quote\" back\\ nl\ncr\rtab\bbc\ffc"}"#,
1133 "{\"s\":\"quote\\\" back\\\\ nl\\ncr\\rtab\\bbc\\ffc\"}",
1134 ),
1135 (r#"{"o":{"k":[{},[]]}}"#, "{\"o\":{\"k\":[{},[]]}}"),
1136 ];
1137 for (src, want) in cases {
1138 let node = parse_ordered_json(src).expect(src);
1139 assert_eq!(serialize_compact_json(&node), want, "serializing {src}");
1140 }
1141 }
1142}
1143
1144#[cfg(test)]
1145mod gap_tests {
1146 use super::*;
1147 use crate::body_scan::ExcludedBodyFields;
1148
1149 #[test]
1150 fn a_high_surrogate_followed_by_a_non_low_escape_becomes_fffd() {
1151 let node = parse_ordered_json(r#"{"k":"\ud800\u0041"}"#).expect("parses");
1154 let values = append_json_walk_entries(
1155 Vec::new(),
1156 &node,
1157 REQUEST_BODY_CONTEXT,
1158 ExcludedBodyFields::default(),
1159 JSON_WALK_DEPTH_CAP,
1160 );
1161 assert!(values.iter().any(|value| value.content == "\u{FFFD}41"));
1164 }
1165
1166 #[test]
1167 fn a_truncated_unicode_escape_is_malformed() {
1168 assert!(parse_ordered_json("{\"k\":\"\\u1\"}").is_none());
1170 }
1171
1172 #[test]
1173 fn malformed_number_grammars_are_rejected() {
1174 assert!(parse_ordered_json("{\"k\":1.}").is_none());
1176 assert!(parse_ordered_json("{\"k\":1e+}").is_none());
1178 let node = parse_ordered_json("{\"k\":1e+5}").expect("parses");
1180 let values = append_json_walk_entries(
1181 Vec::new(),
1182 &node,
1183 REQUEST_BODY_CONTEXT,
1184 ExcludedBodyFields::default(),
1185 JSON_WALK_DEPTH_CAP,
1186 );
1187 assert!(values.iter().any(|value| value.content == "1e+5"));
1188 }
1189
1190 #[test]
1191 fn an_excluded_entry_key_skips_its_subtree() {
1192 let excluded = std::iter::once("password".to_owned()).collect();
1193 let node = parse_ordered_json(r#"{"password":"secret","keep":"v"}"#).expect("parses");
1194 let values = append_json_walk_entries(
1195 Vec::new(),
1196 &node,
1197 REQUEST_BODY_CONTEXT,
1198 ExcludedBodyFields::new(&excluded),
1199 JSON_WALK_DEPTH_CAP,
1200 );
1201 let keys: Vec<&str> = values.iter().map(|value| value.content.as_str()).collect();
1202 assert!(!keys.contains(&"password"));
1203 assert!(!keys.contains(&"secret"));
1204 assert!(keys.contains(&"keep"));
1205 }
1206
1207 #[test]
1208 fn containers_at_the_depth_cap_serialize_back_to_json() {
1209 let body = format!("{}{{\"k\":1}}{}", "[".repeat(31), "]".repeat(31));
1211 let node = parse_ordered_json(&body).expect("parses");
1212 let values = append_json_walk_entries(
1213 Vec::new(),
1214 &node,
1215 REQUEST_BODY_CONTEXT,
1216 ExcludedBodyFields::default(),
1217 JSON_WALK_DEPTH_CAP,
1218 );
1219 assert!(values.iter().any(|value| value.content == "{\"k\":1}"));
1220 }
1221}