1use super::{Finder, Memo};
16use std::ops::Range;
17
18const MAX_DEPTH: usize = 256;
19
20#[derive(Clone, Copy, Debug, PartialEq, Eq)]
22enum ParseError {
23 Syntax,
25 TooDeep,
27}
28
29type ParseResult = Result<usize, ParseError>;
30
31#[inline]
32fn memchr2(n1: u8, n2: u8, haystack: &[u8]) -> Option<usize> {
33 haystack.iter().position(|&b| b == n1 || b == n2)
34}
35
36#[inline]
37fn skip_ws(input: &[u8], mut pos: usize) -> usize {
38 while pos < input.len() && matches!(input[pos], b' ' | b'\t' | b'\n' | b'\r') {
39 pos += 1;
40 }
41 pos
42}
43
44#[inline]
47fn is_open_or_ws(b: u8) -> bool {
48 matches!(b, b'{' | b'[' | b' ' | b'\t' | b'\n' | b'\r')
49}
50
51fn skip_open_run(input: &[u8], pos: usize) -> usize {
53 let mut p = pos;
54 while p < input.len() && is_open_or_ws(input[p]) {
55 p += 1;
56 }
57 p
58}
59
60fn inside_too_deep_run(input: &[u8], pos: usize) -> bool {
65 let mut start = pos;
66 while start > 0 && is_open_or_ws(input[start - 1]) {
67 start -= 1;
68 }
69 match (start..pos).find(|&i| matches!(input[i], b'{' | b'[')) {
70 Some(first) => parse_value(input, first, 0) == Err(ParseError::TooDeep),
71 None => false,
72 }
73}
74
75fn parse_value(input: &[u8], pos: usize, depth: usize) -> ParseResult {
76 if depth > MAX_DEPTH {
77 return Err(ParseError::TooDeep);
78 }
79 let pos = skip_ws(input, pos);
80 if pos >= input.len() {
81 return Err(ParseError::Syntax);
82 }
83 match input[pos] {
84 b'{' => parse_object(input, pos, depth + 1),
85 b'[' => parse_array(input, pos, depth + 1),
86 b'"' => parse_string(input, pos).ok_or(ParseError::Syntax),
87 b't' => parse_literal(input, pos, b"true").ok_or(ParseError::Syntax),
88 b'f' => parse_literal(input, pos, b"false").ok_or(ParseError::Syntax),
89 b'n' => parse_literal(input, pos, b"null").ok_or(ParseError::Syntax),
90 b'-' | b'0'..=b'9' => parse_number(input, pos).ok_or(ParseError::Syntax),
91 _ => Err(ParseError::Syntax),
92 }
93}
94
95fn parse_object(input: &[u8], start: usize, depth: usize) -> ParseResult {
96 debug_assert_eq!(input[start], b'{');
97 let mut pos = start + 1;
98 pos = skip_ws(input, pos);
99 if pos < input.len() && input[pos] == b'}' {
100 return Ok(pos + 1);
101 }
102 loop {
103 pos = skip_ws(input, pos);
104 if pos >= input.len() || input[pos] != b'"' {
105 return Err(ParseError::Syntax);
106 }
107 pos = parse_string(input, pos).ok_or(ParseError::Syntax)?;
108 pos = skip_ws(input, pos);
109 if pos >= input.len() || input[pos] != b':' {
110 return Err(ParseError::Syntax);
111 }
112 pos += 1;
113 pos = parse_value(input, pos, depth)?;
114 pos = skip_ws(input, pos);
115 if pos >= input.len() {
116 return Err(ParseError::Syntax);
117 }
118 match input[pos] {
119 b',' => pos += 1,
120 b'}' => return Ok(pos + 1),
121 _ => return Err(ParseError::Syntax),
122 }
123 }
124}
125
126fn parse_array(input: &[u8], start: usize, depth: usize) -> ParseResult {
127 debug_assert_eq!(input[start], b'[');
128 let mut pos = start + 1;
129 pos = skip_ws(input, pos);
130 if pos < input.len() && input[pos] == b']' {
131 return Ok(pos + 1);
132 }
133 loop {
134 pos = parse_value(input, pos, depth)?;
135 pos = skip_ws(input, pos);
136 if pos >= input.len() {
137 return Err(ParseError::Syntax);
138 }
139 match input[pos] {
140 b',' => pos += 1,
141 b']' => return Ok(pos + 1),
142 _ => return Err(ParseError::Syntax),
143 }
144 }
145}
146
147fn parse_string(input: &[u8], start: usize) -> Option<usize> {
148 debug_assert_eq!(input[start], b'"');
149 let mut pos = start + 1;
150 while pos < input.len() {
151 let b = input[pos];
152 if b == b'"' {
153 return Some(pos + 1);
154 }
155 if b == b'\\' {
156 pos += 1;
157 if pos >= input.len() {
158 return None;
159 }
160 match input[pos] {
161 b'"' | b'\\' | b'/' | b'b' | b'f' | b'n' | b'r' | b't' => pos += 1,
162 b'u' => {
163 let unit = parse_hex4(input, pos + 1)?;
164 pos += 5;
165 if (0xD800..=0xDBFF).contains(&unit) {
166 if pos + 1 >= input.len() || input[pos] != b'\\' || input[pos + 1] != b'u' {
170 return None;
171 }
172 let low = parse_hex4(input, pos + 2)?;
173 if !(0xDC00..=0xDFFF).contains(&low) {
174 return None;
175 }
176 pos += 6;
177 } else if (0xDC00..=0xDFFF).contains(&unit) {
178 return None;
180 }
181 }
182 _ => return None,
183 }
184 } else if b < 0x20 {
185 return None;
187 } else {
188 pos += 1;
189 }
190 }
191 None
192}
193
194fn parse_hex4(input: &[u8], start: usize) -> Option<u16> {
197 if start + 4 > input.len() {
198 return None;
199 }
200 let mut value: u16 = 0;
201 for &b in &input[start..start + 4] {
202 let digit = (b as char).to_digit(16)?;
203 value = (value << 4) | digit as u16;
204 }
205 Some(value)
206}
207
208fn parse_number(input: &[u8], start: usize) -> Option<usize> {
209 let mut pos = start;
210 if input[pos] == b'-' {
211 pos += 1;
212 if pos >= input.len() {
213 return None;
214 }
215 }
216 match input.get(pos)? {
218 b'0' => pos += 1,
219 b'1'..=b'9' => {
220 pos += 1;
221 while pos < input.len() && input[pos].is_ascii_digit() {
222 pos += 1;
223 }
224 }
225 _ => return None,
226 }
227 if pos < input.len() && input[pos] == b'.' {
229 pos += 1;
230 let frac_start = pos;
231 while pos < input.len() && input[pos].is_ascii_digit() {
232 pos += 1;
233 }
234 if pos == frac_start {
235 return None;
236 }
237 }
238 if pos < input.len() && (input[pos] == b'e' || input[pos] == b'E') {
240 pos += 1;
241 if pos < input.len() && (input[pos] == b'+' || input[pos] == b'-') {
242 pos += 1;
243 }
244 let exp_start = pos;
245 while pos < input.len() && input[pos].is_ascii_digit() {
246 pos += 1;
247 }
248 if pos == exp_start {
249 return None;
250 }
251 }
252 Some(pos)
253}
254
255fn parse_literal(input: &[u8], start: usize, lit: &[u8]) -> Option<usize> {
256 let end = start + lit.len();
257 if end <= input.len() && &input[start..end] == lit {
258 Some(end)
259 } else {
260 None
261 }
262}
263
264#[derive(Default)]
265pub struct Json {}
266
267impl Finder for Json {
268 fn id(&self) -> &'static str {
269 "json"
270 }
271
272 fn dispatchable(&self) -> bool {
273 true
274 }
275
276 fn could_start_at(&self, byte: u8) -> bool {
277 matches!(byte, b'{' | b'[')
278 }
279
280 fn try_at(&self, input: &[u8], pos: usize) -> Option<Range<usize>> {
281 self.try_at_memo(input, pos, &mut Memo::default())
282 }
283
284 fn try_at_memo(&self, input: &[u8], pos: usize, memo: &mut Memo) -> Option<Range<usize>> {
285 if !matches!(input[pos], b'{' | b'[') {
286 return None;
287 }
288 if memo.covers(pos) {
292 return None;
293 }
294 let too_deep = match parse_value(input, pos, 0) {
295 Ok(end) => {
296 if !inside_too_deep_run(input, pos) {
299 return Some(pos..end);
300 }
301 true
302 }
303 Err(ParseError::TooDeep) => true,
304 Err(ParseError::Syntax) => false,
305 };
306 if too_deep {
307 let mut start = pos;
308 while start > 0 && is_open_or_ws(input[start - 1]) {
309 start -= 1;
310 }
311 *memo = Memo {
312 start,
313 end: skip_open_run(input, pos),
314 aux: 0,
315 };
316 }
317 None
318 }
319
320 fn find(&self, s: &str) -> Option<Range<usize>> {
321 let input = s.as_bytes();
322 let mut idx = 0;
323 while let Some(offset) = memchr2(b'{', b'[', &input[idx..]) {
324 idx += offset;
325 match parse_value(input, idx, 0) {
326 Ok(end) => return Some(idx..end),
327 Err(ParseError::TooDeep) => idx = skip_open_run(input, idx),
333 Err(ParseError::Syntax) => idx += 1,
334 }
335 }
336 None
337 }
338}
339
340#[cfg(test)]
341mod tests {
342 use super::*;
343
344 #[test]
345 fn id_should_return_json() {
346 let finder = Json::default();
347 assert_eq!("json", finder.id());
348 }
349
350 #[test]
351 fn try_at_extracts_json_at_exact_position() {
352 let finder = Json::default();
353 let input = br#"x {"key": "value"} y"#;
354 assert_eq!(Some(2..18), finder.try_at(input, 2));
355 assert_eq!(None, finder.try_at(input, 0));
356 }
357
358 #[test]
360 fn find_should_extract_simple_object() {
361 let finder = Json::default();
362 let input = r#"data: {"key": "value"}"#;
363 let range = finder.find(input).unwrap();
364 assert_eq!(r#"{"key": "value"}"#, &input[range]);
365 }
366
367 #[test]
368 fn find_should_extract_nested_object() {
369 let finder = Json::default();
370 let input = r#"{"a": {"b": "c"}}"#;
371 let range = finder.find(input).unwrap();
372 assert_eq!(r#"{"a": {"b": "c"}}"#, &input[range]);
373 }
374
375 #[test]
376 fn find_should_extract_object_with_array() {
377 let finder = Json::default();
378 let input = r#"{"items": [1, 2, 3]}"#;
379 let range = finder.find(input).unwrap();
380 assert_eq!(r#"{"items": [1, 2, 3]}"#, &input[range]);
381 }
382
383 #[test]
384 fn find_should_extract_empty_object() {
385 let finder = Json::default();
386 let input = "result: {}";
387 let range = finder.find(input).unwrap();
388 assert_eq!("{}", &input[range]);
389 }
390
391 #[test]
393 fn find_should_extract_simple_array() {
394 let finder = Json::default();
395 let input = "data: [1, 2, 3]";
396 let range = finder.find(input).unwrap();
397 assert_eq!("[1, 2, 3]", &input[range]);
398 }
399
400 #[test]
401 fn find_should_extract_nested_array() {
402 let finder = Json::default();
403 let input = "[[1, 2], [3, 4]]";
404 let range = finder.find(input).unwrap();
405 assert_eq!("[[1, 2], [3, 4]]", &input[range]);
406 }
407
408 #[test]
409 fn find_should_extract_empty_array() {
410 let finder = Json::default();
411 let input = "items: []";
412 let range = finder.find(input).unwrap();
413 assert_eq!("[]", &input[range]);
414 }
415
416 #[test]
418 fn find_should_handle_escaped_quotes() {
419 let finder = Json::default();
420 let input = r#"{"msg": "say \"hello\""}"#;
421 let range = finder.find(input).unwrap();
422 assert_eq!(r#"{"msg": "say \"hello\""}"#, &input[range]);
423 }
424
425 #[test]
426 fn find_should_handle_braces_in_strings() {
427 let finder = Json::default();
428 let input = r#"{"template": "{name}"}"#;
429 let range = finder.find(input).unwrap();
430 assert_eq!(r#"{"template": "{name}"}"#, &input[range]);
431 }
432
433 #[test]
434 fn find_should_handle_brackets_in_strings() {
435 let finder = Json::default();
436 let input = r#"{"pattern": "[a-z]"}"#;
437 let range = finder.find(input).unwrap();
438 assert_eq!(r#"{"pattern": "[a-z]"}"#, &input[range]);
439 }
440
441 #[test]
442 fn find_should_handle_escaped_backslash() {
443 let finder = Json::default();
444 let input = r#"{"path": "C:\\Users\\foo"}"#;
445 let range = finder.find(input).unwrap();
446 assert_eq!(r#"{"path": "C:\\Users\\foo"}"#, &input[range]);
447 }
448
449 #[test]
451 fn find_should_reject_unclosed_object() {
452 let finder = Json::default();
453 assert!(finder.find(r#"{"key": "value""#).is_none());
454 }
455
456 #[test]
457 fn find_should_reject_unclosed_array() {
458 let finder = Json::default();
459 assert!(finder.find("[1, 2, 3").is_none());
460 }
461
462 #[test]
463 fn find_should_handle_empty_input() {
464 let finder = Json::default();
465 assert!(finder.find("").is_none());
466 }
467
468 #[test]
469 fn find_should_extract_json_from_log_line() {
470 let finder = Json::default();
471 let input = r#"2024-01-15 INFO: {"event": "login", "user": "alice"} processed"#;
472 let range = finder.find(input).unwrap();
473 assert_eq!(r#"{"event": "login", "user": "alice"}"#, &input[range]);
474 }
475
476 #[test]
477 fn find_should_extract_first_json_object() {
478 let finder = Json::default();
479 let input = r#"{"a": 1} and {"b": 2}"#;
480 let range = finder.find(input).unwrap();
481 assert_eq!(r#"{"a": 1}"#, &input[range]);
482 }
483
484 #[test]
486 fn find_should_extract_multiple_json_iteratively() {
487 let finder = Json::default();
488 let input = r#"{"a": 1} and [2, 3]"#;
489
490 let mut results = Vec::new();
491 let mut idx = 0;
492 while idx < input.len() {
493 if let Some(range) = finder.find(&input[idx..]) {
494 results.push(&input[idx + range.start..idx + range.end]);
495 idx += range.end;
496 } else {
497 break;
498 }
499 }
500
501 assert_eq!(vec![r#"{"a": 1}"#, "[2, 3]"], results);
502 }
503
504 #[test]
505 fn find_should_handle_deeply_nested() {
506 let finder = Json::default();
507 let input = r#"{"a": {"b": {"c": {"d": "deep"}}}}"#;
508 let range = finder.find(input).unwrap();
509 assert_eq!(input, &input[range]);
510 }
511
512 #[test]
513 fn find_should_skip_unclosed_and_find_next() {
514 let finder = Json::default();
515 let input = r#"{unclosed and {"valid": true}"#;
516 let range = finder.find(input).unwrap();
517 assert_eq!(r#"{"valid": true}"#, &input[range]);
518 }
519
520 #[test]
523 fn find_should_handle_long_string_value() {
524 let finder = Json::default();
525 let long_val = "a".repeat(10000);
526 let input = format!(r#"{{"key": "{}"}}"#, long_val);
527 let range = finder.find(&input).unwrap();
528 assert_eq!(&input, &input[range]);
529 }
530
531 #[test]
532 fn find_should_handle_string_with_many_escapes() {
533 let finder = Json::default();
534 let escapes = r#"\\\\\\\\\\\\\\\\\\\\\"end"#;
535 let input = format!(r#"{{"key": "{}"}}"#, escapes);
536 let range = finder.find(&input).unwrap();
537 assert_eq!(&input, &input[range]);
538 }
539
540 #[test]
541 fn find_should_handle_string_with_braces_inside() {
542 let finder = Json::default();
543 let input = r#"{"template": "{{not nested}}"}"#;
544 let range = finder.find(input).unwrap();
545 assert_eq!(input, &input[range]);
546 }
547
548 #[test]
549 fn find_should_handle_unclosed_string_in_object() {
550 let finder = Json::default();
551 let input = r#"{"key": "unterminated"#;
552 assert!(finder.find(input).is_none());
553 }
554
555 #[test]
556 fn find_should_handle_empty_strings() {
557 let finder = Json::default();
558 let input = r#"{"": ""}"#;
559 let range = finder.find(input).unwrap();
560 assert_eq!(r#"{"": ""}"#, &input[range]);
561 }
562
563 #[test]
564 fn find_should_handle_escape_at_string_end() {
565 let finder = Json::default();
566 let input = r#"{"key": "val\\"}"#;
567 let range = finder.find(input).unwrap();
568 assert_eq!(input, &input[range]);
569 }
570
571 #[test]
574 fn find_should_reject_balanced_garbage() {
575 let finder = Json::default();
576 assert!(finder.find("{not really json}").is_none());
577 }
578
579 #[test]
580 fn find_should_reject_object_without_colon() {
581 let finder = Json::default();
582 assert!(finder.find(r#"{"key" "value"}"#).is_none());
583 }
584
585 #[test]
586 fn find_should_reject_object_unquoted_key() {
587 let finder = Json::default();
588 assert!(finder.find(r#"{key: "value"}"#).is_none());
589 }
590
591 #[test]
592 fn find_should_reject_trailing_comma_in_object() {
593 let finder = Json::default();
594 assert!(finder.find(r#"{"a": 1,}"#).is_none());
595 }
596
597 #[test]
598 fn find_should_reject_trailing_comma_in_array() {
599 let finder = Json::default();
600 assert!(finder.find("[1, 2,]").is_none());
601 }
602
603 #[test]
604 fn find_should_reject_single_quoted_strings() {
605 let finder = Json::default();
606 assert!(finder.find(r#"{'key': 'value'}"#).is_none());
607 }
608
609 #[test]
610 fn find_should_reject_javascript_undefined() {
611 let finder = Json::default();
612 assert!(finder.find(r#"{"k": undefined}"#).is_none());
613 }
614
615 #[test]
616 fn find_should_reject_bare_number_with_leading_zero() {
617 let finder = Json::default();
618 assert!(finder.find("[01]").is_none());
619 }
620
621 #[test]
622 fn find_should_reject_bare_dot_number() {
623 let finder = Json::default();
624 assert!(finder.find("[.5]").is_none());
626 }
627
628 #[test]
629 fn find_should_reject_trailing_dot_number() {
630 let finder = Json::default();
631 assert!(finder.find("[1.]").is_none());
633 }
634
635 #[test]
636 fn find_should_reject_invalid_escape_sequence() {
637 let finder = Json::default();
638 assert!(finder.find(r#"{"k": "bad \q escape"}"#).is_none());
639 }
640
641 #[test]
642 fn find_should_reject_short_unicode_escape() {
643 let finder = Json::default();
644 assert!(finder.find(r#"{"k": "\u12"}"#).is_none());
645 }
646
647 #[test]
648 fn find_should_reject_non_hex_unicode_escape() {
649 let finder = Json::default();
650 assert!(finder.find(r#"{"k": "\uZZZZ"}"#).is_none());
651 }
652
653 #[test]
654 fn find_should_reject_unescaped_control_char_in_string() {
655 let finder = Json::default();
656 let input = "{\"k\": \"line1\nline2\"}";
657 assert!(finder.find(input).is_none());
658 }
659
660 #[test]
661 fn find_should_accept_number_with_exponent() {
662 let finder = Json::default();
663 let input = "[1.5e10, -2.0E-3]";
664 let range = finder.find(input).unwrap();
665 assert_eq!(input, &input[range]);
666 }
667
668 #[test]
669 fn find_should_accept_literals() {
670 let finder = Json::default();
671 let input = "[true, false, null]";
672 let range = finder.find(input).unwrap();
673 assert_eq!(input, &input[range]);
674 }
675
676 #[test]
677 fn find_should_reject_truncated_literal() {
678 let finder = Json::default();
679 assert!(finder.find("[tru]").is_none());
680 }
681
682 #[test]
683 fn find_should_reject_uppercase_literal() {
684 let finder = Json::default();
685 assert!(finder.find("[True]").is_none());
686 }
687
688 #[test]
689 fn find_should_accept_whitespace_around_values() {
690 let finder = Json::default();
691 let input = "{ \"k\" : \"v\" , \"n\" : 42 }";
692 let range = finder.find(input).unwrap();
693 assert_eq!(input, &input[range]);
694 }
695
696 #[test]
697 fn find_should_accept_unicode_escapes() {
698 let finder = Json::default();
699 let input = r#"{"k": "éclair"}"#;
700 let range = finder.find(input).unwrap();
701 assert_eq!(input, &input[range]);
702 }
703
704 #[test]
705 fn find_should_reject_array_with_two_consecutive_commas() {
706 let finder = Json::default();
707 assert!(finder.find("[1,,2]").is_none());
708 }
709
710 #[test]
711 fn find_should_reject_extra_close_brace() {
712 let finder = Json::default();
713 let input = r#"{"a":1}}"#;
716 let r = finder.find(input).unwrap();
717 assert_eq!(r#"{"a":1}"#, &input[r]);
718 }
719}