1use crate::error::{Error, Result};
23use crate::limits::Limits;
24
25use super::span::{Span, SpanKind, SpanSet};
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29pub enum LexIssue {
30 UnterminatedLiteralString { at: u64 },
32 UnterminatedHexString { at: u64 },
34 UnterminatedComment { at: u64 },
36}
37
38#[derive(Debug, Clone, PartialEq, Eq)]
40pub struct LexResult {
41 pub spans: SpanSet,
43 pub issues: Vec<LexIssue>,
45}
46
47const fn is_whitespace(b: u8) -> bool {
49 matches!(b, 0x00 | 0x09 | 0x0A | 0x0C | 0x0D | 0x20)
50}
51
52const fn is_delimiter(b: u8) -> bool {
54 matches!(
55 b,
56 b'(' | b')' | b'<' | b'>' | b'[' | b']' | b'{' | b'}' | b'/' | b'%'
57 )
58}
59
60const fn is_regular(b: u8) -> bool {
62 !is_whitespace(b) && !is_delimiter(b)
63}
64
65pub fn lex(input: &[u8], limits: Limits) -> Result<LexResult> {
73 let n = input.len();
74 let max = limits.max_pdf_spans;
75 let mut spans: Vec<Span> = Vec::new();
76 let mut issues: Vec<LexIssue> = Vec::new();
77 let mut pos: usize = 0;
78
79 while pos < n {
80 let b = input[pos];
81 if is_whitespace(b) {
82 let start = pos;
83 while pos < n && is_whitespace(input[pos]) {
84 pos += 1;
85 }
86 push(&mut spans, start, pos - start, SpanKind::Whitespace, max)?;
87 } else if b == b'%' {
88 let start = pos;
89 pos += 1;
90 while pos < n && input[pos] != b'\r' && input[pos] != b'\n' {
91 pos += 1;
92 }
93 if pos >= n {
94 issues.push(LexIssue::UnterminatedComment { at: start as u64 });
95 }
96 push(&mut spans, start, pos - start, SpanKind::Comment, max)?;
97 } else if b == b'(' {
98 let start = pos;
99 pos = scan_literal_string(input, start, &mut issues);
100 push(&mut spans, start, pos - start, SpanKind::LiteralString, max)?;
101 } else if b == b'<' {
102 if pos + 1 < n && input[pos + 1] == b'<' {
103 push(&mut spans, pos, 2, SpanKind::DictOpen, max)?;
104 pos += 2;
105 } else {
106 let start = pos;
107 pos += 1;
108 while pos < n && input[pos] != b'>' {
109 pos += 1;
110 }
111 if pos < n {
112 pos += 1; } else {
114 issues.push(LexIssue::UnterminatedHexString { at: start as u64 });
115 }
116 push(&mut spans, start, pos - start, SpanKind::HexString, max)?;
117 }
118 } else if b == b'>' {
119 if pos + 1 < n && input[pos + 1] == b'>' {
120 push(&mut spans, pos, 2, SpanKind::DictClose, max)?;
121 pos += 2;
122 } else {
123 push(&mut spans, pos, 1, SpanKind::Regular, max)?;
126 pos += 1;
127 }
128 } else if b == b'[' {
129 push(&mut spans, pos, 1, SpanKind::ArrayOpen, max)?;
130 pos += 1;
131 } else if b == b']' {
132 push(&mut spans, pos, 1, SpanKind::ArrayClose, max)?;
133 pos += 1;
134 } else if b == b'{' {
135 push(&mut spans, pos, 1, SpanKind::BraceOpen, max)?;
136 pos += 1;
137 } else if b == b'}' {
138 push(&mut spans, pos, 1, SpanKind::BraceClose, max)?;
139 pos += 1;
140 } else if b == b'/' {
141 let start = pos;
142 pos += 1;
143 while pos < n && is_regular(input[pos]) {
144 pos += 1;
145 }
146 push(&mut spans, start, pos - start, SpanKind::Name, max)?;
147 } else if b == b')' {
148 push(&mut spans, pos, 1, SpanKind::Regular, max)?;
151 pos += 1;
152 } else {
153 let start = pos;
154 while pos < n && is_regular(input[pos]) {
155 pos += 1;
156 }
157 push(&mut spans, start, pos - start, SpanKind::Regular, max)?;
158 if input[start..pos] == *b"stream"
160 && let Some(eol_len) = post_stream_eol_len(input, pos)
161 {
162 let eol_start = pos;
163 pos += eol_len;
164 push(&mut spans, eol_start, eol_len, SpanKind::Whitespace, max)?;
165 let data_start = pos;
166 match find_endstream(input, data_start) {
167 Some(es) => {
168 if es > data_start {
169 push(
170 &mut spans,
171 data_start,
172 es - data_start,
173 SpanKind::Regular,
174 max,
175 )?;
176 }
177 push(&mut spans, es, b"endstream".len(), SpanKind::Regular, max)?;
178 pos = es + b"endstream".len();
179 }
180 None => {
181 if data_start < n {
183 push(
184 &mut spans,
185 data_start,
186 n - data_start,
187 SpanKind::Regular,
188 max,
189 )?;
190 }
191 pos = n;
192 }
193 }
194 }
195 }
196 }
197
198 let spans = SpanSet { spans };
199 spans.validate(n as u64)?;
200 Ok(LexResult { spans, issues })
201}
202
203fn post_stream_eol_len(input: &[u8], pos: usize) -> Option<usize> {
206 match input.get(pos) {
207 Some(b'\n') => Some(1),
208 Some(b'\r') if input.get(pos + 1) == Some(&b'\n') => Some(2),
209 _ => None,
210 }
211}
212
213#[cfg(feature = "memmem-scan")]
239fn find_endstream(input: &[u8], from: usize) -> Option<usize> {
240 use std::sync::OnceLock;
241
242 const NEEDLE: &[u8] = b"endstream";
243 static FINDER: OnceLock<memchr::memmem::Finder<'static>> = OnceLock::new();
246 let finder = FINDER.get_or_init(|| memchr::memmem::Finder::new(NEEDLE));
247
248 let start = from.min(input.len());
249 finder.find_iter(&input[start..]).find_map(|rel| {
250 let i = start + rel;
251 match input.get(i + NEEDLE.len()) {
252 None => Some(i),
253 Some(&b) if is_whitespace(b) || is_delimiter(b) => Some(i),
254 _ => None,
255 }
256 })
257}
258
259#[cfg(any(test, not(feature = "memmem-scan")))]
263fn find_endstream_scalar(input: &[u8], from: usize) -> Option<usize> {
264 let needle = b"endstream";
265 let mut i = from;
266 while i + needle.len() <= input.len() {
267 if &input[i..i + needle.len()] == needle {
268 let terminated = match input.get(i + needle.len()) {
269 None => true,
270 Some(&b) => is_whitespace(b) || is_delimiter(b),
271 };
272 if terminated {
273 return Some(i);
274 }
275 }
276 i += 1;
277 }
278 None
279}
280
281#[cfg(not(feature = "memmem-scan"))]
283fn find_endstream(input: &[u8], from: usize) -> Option<usize> {
284 find_endstream_scalar(input, from)
285}
286
287fn scan_literal_string(input: &[u8], start: usize, issues: &mut Vec<LexIssue>) -> usize {
291 let n = input.len();
292 let mut pos = start;
293 let mut depth: u64 = 0;
294 loop {
295 if pos >= n {
296 issues.push(LexIssue::UnterminatedLiteralString { at: start as u64 });
297 return pos;
298 }
299 let c = input[pos];
300 if c == b'\\' {
301 pos += 1;
302 if pos < n {
303 let escaped = input[pos];
304 pos += 1;
305 if escaped == b'\r' && pos < n && input[pos] == b'\n' {
306 pos += 1;
307 }
308 }
309 } else if c == b'(' {
310 depth = depth.saturating_add(1);
311 pos += 1;
312 } else if c == b')' {
313 depth = depth.saturating_sub(1);
314 pos += 1;
315 if depth == 0 {
316 return pos;
317 }
318 } else {
319 pos += 1;
320 }
321 }
322}
323
324fn push(spans: &mut Vec<Span>, start: usize, len: usize, kind: SpanKind, max: u32) -> Result<()> {
326 if spans.len() as u64 >= max as u64 {
327 return Err(Error::resource_limit(format!(
328 "pdf span count exceeds limit {max}"
329 )));
330 }
331 spans.push(Span {
332 start: start as u64,
333 len: len as u64,
334 kind,
335 });
336 Ok(())
337}
338
339#[cfg(test)]
340mod tests {
341 use super::*;
342 use crate::error::ErrorClass;
343
344 fn run(input: &[u8]) -> LexResult {
345 lex(input, Limits::DEFAULT).expect("lex must succeed")
346 }
347
348 fn kinds(r: &LexResult) -> Vec<SpanKind> {
349 r.spans.spans.iter().map(|s| s.kind).collect()
350 }
351
352 #[test]
353 fn empty_input() {
354 let r = run(b"");
355 assert!(r.spans.spans.is_empty());
356 assert!(r.issues.is_empty());
357 assert!(r.spans.validate(0).is_ok());
358 }
359
360 #[test]
361 fn single_whitespace() {
362 let r = run(b" ");
363 assert_eq!(r.spans.spans.len(), 1);
364 assert_eq!(r.spans.spans[0].kind, SpanKind::Whitespace);
365 assert_eq!(r.spans.spans[0].len, 1);
366 }
367
368 #[test]
369 fn whitespace_run_coalesces() {
370 let input = b" \t\r\n\x0c\x00 ";
371 let r = run(input);
372 assert_eq!(r.spans.spans.len(), 1);
373 assert_eq!(r.spans.spans[0].kind, SpanKind::Whitespace);
374 assert_eq!(r.spans.spans[0].len, input.len() as u64);
375 }
376
377 #[test]
378 fn comment_to_eol_excludes_eol() {
379 let r = run(b"%hello\n");
380 assert_eq!(kinds(&r), vec![SpanKind::Comment, SpanKind::Whitespace]);
381 assert_eq!(r.spans.spans[0].start, 0);
382 assert_eq!(r.spans.spans[0].len, 6);
383 assert_eq!(r.spans.spans[1].start, 6);
384 assert_eq!(r.spans.spans[1].len, 1);
385 assert!(r.issues.is_empty());
386 }
387
388 #[test]
389 fn comment_stops_before_cr() {
390 let r = run(b"%x\r\n");
391 assert_eq!(kinds(&r), vec![SpanKind::Comment, SpanKind::Whitespace]);
392 assert_eq!(r.spans.spans[0].len, 2);
393 assert_eq!(r.spans.spans[1].len, 2);
394 assert!(r.issues.is_empty());
395 }
396
397 #[test]
398 fn comment_to_eof_is_issue() {
399 let r = run(b"%abc");
400 assert_eq!(kinds(&r), vec![SpanKind::Comment]);
401 assert_eq!(r.spans.spans[0].len, 4);
402 assert_eq!(r.issues, vec![LexIssue::UnterminatedComment { at: 0 }]);
403 }
404
405 #[test]
406 fn literal_string_with_escape() {
407 let r = run(b"(a\\)b)");
409 assert_eq!(kinds(&r), vec![SpanKind::LiteralString]);
410 assert_eq!(r.spans.spans[0].len, 6);
411 assert!(r.issues.is_empty());
412 }
413
414 #[test]
415 fn literal_string_with_line_continuation() {
416 let r = run(b"(a\\\r\nb)");
418 assert_eq!(kinds(&r), vec![SpanKind::LiteralString]);
419 assert_eq!(r.spans.spans[0].len, 7);
420 assert!(r.issues.is_empty());
421 }
422
423 #[test]
424 fn literal_string_with_nested_parens() {
425 let r = run(b"(a(b)c)");
426 assert_eq!(kinds(&r), vec![SpanKind::LiteralString]);
427 assert_eq!(r.spans.spans[0].len, 7);
428 assert!(r.issues.is_empty());
429 }
430
431 #[test]
432 fn unterminated_literal_string_is_issue() {
433 let r = run(b"(abc");
434 assert_eq!(kinds(&r), vec![SpanKind::LiteralString]);
435 assert_eq!(r.spans.spans[0].len, 4);
436 assert_eq!(
437 r.issues,
438 vec![LexIssue::UnterminatedLiteralString { at: 0 }]
439 );
440 }
441
442 #[test]
443 fn percent_inside_literal_string_is_not_a_comment() {
444 let r = run(b"( % )");
445 assert_eq!(kinds(&r), vec![SpanKind::LiteralString]);
446 assert_eq!(r.spans.spans[0].len, 5);
447 assert!(r.issues.is_empty());
448 }
449
450 #[test]
451 fn paren_inside_hex_string_is_not_special() {
452 let r = run(b"<4(2>");
453 assert_eq!(kinds(&r), vec![SpanKind::HexString]);
454 assert_eq!(r.spans.spans[0].len, 5);
455 assert!(r.issues.is_empty());
456 }
457
458 #[test]
459 fn hex_string_basic() {
460 let r = run(b"<4142>");
461 assert_eq!(kinds(&r), vec![SpanKind::HexString]);
462 assert_eq!(r.spans.spans[0].len, 6);
463 }
464
465 #[test]
466 fn unterminated_hex_string_is_issue() {
467 let r = run(b"<41");
468 assert_eq!(kinds(&r), vec![SpanKind::HexString]);
469 assert_eq!(r.spans.spans[0].len, 3);
470 assert_eq!(r.issues, vec![LexIssue::UnterminatedHexString { at: 0 }]);
471 }
472
473 #[test]
474 fn dict_delimiters() {
475 let r = run(b"<<>>");
476 assert_eq!(kinds(&r), vec![SpanKind::DictOpen, SpanKind::DictClose]);
477 assert_eq!(r.spans.spans[0].len, 2);
478 assert_eq!(r.spans.spans[1].len, 2);
479 }
480
481 #[test]
482 fn lone_closers_are_regular_tokens() {
483 let r = run(b")>");
484 assert_eq!(kinds(&r), vec![SpanKind::Regular, SpanKind::Regular]);
485 let r = run(b">>>");
486 assert_eq!(kinds(&r), vec![SpanKind::DictClose, SpanKind::Regular]);
487 }
488
489 #[test]
490 fn name_and_empty_name() {
491 let r = run(b"/Name");
492 assert_eq!(kinds(&r), vec![SpanKind::Name]);
493 assert_eq!(r.spans.spans[0].len, 5);
494 let r = run(b"/");
495 assert_eq!(kinds(&r), vec![SpanKind::Name]);
496 assert_eq!(r.spans.spans[0].len, 1);
497 }
498
499 #[test]
500 fn arrays_and_braces() {
501 let r = run(b"[]{}");
502 assert_eq!(
503 kinds(&r),
504 vec![
505 SpanKind::ArrayOpen,
506 SpanKind::ArrayClose,
507 SpanKind::BraceOpen,
508 SpanKind::BraceClose,
509 ]
510 );
511 }
512
513 #[test]
514 fn numbers_and_reference_are_regular_tokens() {
515 let r = run(b"12 0 R");
516 assert_eq!(
517 kinds(&r),
518 vec![
519 SpanKind::Regular,
520 SpanKind::Whitespace,
521 SpanKind::Regular,
522 SpanKind::Whitespace,
523 SpanKind::Regular,
524 ]
525 );
526 let text: Vec<&[u8]> = r
527 .spans
528 .spans
529 .iter()
530 .map(|s| &b"12 0 R"[s.start as usize..(s.start + s.len) as usize])
531 .collect();
532 assert_eq!(text, vec![&b"12"[..], b" ", b"0", b" ", b"R"]);
533 }
534
535 #[test]
536 fn endobj_inside_literal_string_stays_one_span() {
537 let r = run(b"(1 0 obj endobj)5");
538 assert_eq!(kinds(&r), vec![SpanKind::LiteralString, SpanKind::Regular]);
539 assert_eq!(r.spans.spans[0].start, 0);
540 assert_eq!(r.spans.spans[0].len, 16);
541 assert_eq!(r.spans.spans[1].start, 16);
542 assert_eq!(r.spans.spans[1].len, 1);
543 }
544
545 #[test]
546 fn stream_payload_without_trailing_eol_is_one_opaque_span() {
547 let payload = b"(unbalanced ( with endstreamZ and streamY bytes";
552 let mut input = Vec::new();
553 input.extend_from_slice(b"stream\n");
554 input.extend_from_slice(payload);
555 input.extend_from_slice(b"endstream\n");
556
557 let r = run(&input);
558 assert_eq!(
559 kinds(&r),
560 vec![
561 SpanKind::Regular, SpanKind::Whitespace, SpanKind::Regular, SpanKind::Regular, SpanKind::Whitespace, ]
567 );
568 let p = &r.spans.spans[2];
569 assert_eq!(
570 &input[p.start as usize..(p.start + p.len) as usize],
571 payload
572 );
573 let es = &r.spans.spans[3];
574 assert_eq!(
575 &input[es.start as usize..(es.start + es.len) as usize],
576 b"endstream"
577 );
578 assert!(r.issues.is_empty());
579 r.spans.validate(input.len() as u64).unwrap();
580 }
581
582 #[test]
583 fn stream_payload_followed_by_eol_then_endstream_still_works() {
584 let input = b"stream\nhello\nendstream\n";
585 let r = run(input);
586 assert_eq!(
587 kinds(&r),
588 vec![
589 SpanKind::Regular, SpanKind::Whitespace, SpanKind::Regular, SpanKind::Regular, SpanKind::Whitespace, ]
595 );
596 let p = &r.spans.spans[2];
597 assert_eq!(
598 &input[p.start as usize..(p.start + p.len) as usize],
599 b"hello\n"
600 );
601 let es = &r.spans.spans[3];
602 assert_eq!(
603 &input[es.start as usize..(es.start + es.len) as usize],
604 b"endstream"
605 );
606 assert!(r.issues.is_empty());
607 r.spans.validate(input.len() as u64).unwrap();
608 }
609
610 #[test]
611 fn endstream_at_eof_terminates_payload() {
612 let input = b"stream\npayloadendstream";
613 let r = run(input);
614 assert_eq!(
615 kinds(&r),
616 vec![
617 SpanKind::Regular, SpanKind::Whitespace, SpanKind::Regular, SpanKind::Regular, ]
622 );
623 let es = &r.spans.spans[3];
624 assert_eq!(
625 &input[es.start as usize..(es.start + es.len) as usize],
626 b"endstream"
627 );
628 assert_eq!(es.start + es.len, input.len() as u64);
629 assert!(r.issues.is_empty());
630 r.spans.validate(input.len() as u64).unwrap();
631 }
632
633 #[test]
634 fn endstream_like_run_without_right_terminator_is_not_a_keyword() {
635 let input = b"stream\nxx endstreamZ yyendstream\n";
638 let r = run(input);
639 assert_eq!(
640 kinds(&r),
641 vec![
642 SpanKind::Regular, SpanKind::Whitespace, SpanKind::Regular, SpanKind::Regular, SpanKind::Whitespace, ]
648 );
649 let p = &r.spans.spans[2];
650 assert_eq!(
651 &input[p.start as usize..(p.start + p.len) as usize],
652 b"xx endstreamZ yy"
653 );
654 r.spans.validate(input.len() as u64).unwrap();
655 }
656
657 #[test]
658 fn span_at_over_lexed_input() {
659 let r = run(b"12 0 R");
660 assert_eq!(r.spans.span_at(0).map(|s| s.kind), Some(SpanKind::Regular));
661 assert_eq!(r.spans.span_at(1).map(|s| s.kind), Some(SpanKind::Regular));
662 assert_eq!(
663 r.spans.span_at(2).map(|s| s.kind),
664 Some(SpanKind::Whitespace)
665 );
666 assert_eq!(r.spans.span_at(3).map(|s| s.kind), Some(SpanKind::Regular));
667 assert_eq!(
668 r.spans.span_at(4).map(|s| s.kind),
669 Some(SpanKind::Whitespace)
670 );
671 assert_eq!(r.spans.span_at(5).map(|s| s.kind), Some(SpanKind::Regular));
672 assert_eq!(r.spans.span_at(6), None);
673 }
674
675 #[test]
676 fn span_limit_triggers_resource_limit() {
677 let limits = Limits {
678 max_pdf_spans: 2,
679 ..Limits::DEFAULT
680 };
681 let e = lex(b"a b c", limits).unwrap_err();
682 assert_eq!(e.class(), ErrorClass::ResourceLimit);
683 }
684
685 #[test]
686 fn cover_invariant_holds_for_a_battery() {
687 let inputs: [&[u8]; 15] = [
688 b"",
689 b" ",
690 b"%%EOF",
691 b"<< /Type /Catalog >>",
692 b"[1 2.5 -3 (str) <4142> /Name]",
693 b"(unterminated",
694 b"<414243",
695 b"%comment with ( and < and >>",
696 b"()<>[]{}",
697 b"\x00\x09\x0a\x0c\x0d\x20mixed",
698 b"trailing>",
699 b")))",
700 b"<<<<<<",
701 b"(nested (deep (deeper)) end)",
702 b"1 0 obj\n<< /A (x) >>\nendobj",
703 ];
704 for input in inputs {
705 let r = lex(input, Limits::DEFAULT).expect("lex must succeed");
706 r.spans
707 .validate(input.len() as u64)
708 .expect("cover must validate");
709 }
710 }
711
712 fn xorshift64(state: &mut u64) -> u64 {
713 let mut x = *state;
714 x ^= x << 13;
715 x ^= x >> 7;
716 x ^= x << 17;
717 *state = x;
718 x
719 }
720
721 #[test]
722 fn cover_invariant_holds_for_random_bytes() {
723 let mut state: u64 = 0x9E37_79B9_7F4A_7C15;
724 for _ in 0..500 {
725 let len = (xorshift64(&mut state) % 300) as usize;
726 let mut buf = Vec::with_capacity(len);
727 for _ in 0..len {
728 buf.push((xorshift64(&mut state) & 0xFF) as u8);
729 }
730 let r = lex(&buf, Limits::STRICT).expect("lex must not fail on bounded input");
731 r.spans
732 .validate(buf.len() as u64)
733 .expect("random cover must validate");
734 assert!(r.spans.spans.len() <= buf.len());
735 }
736 }
737
738 #[cfg(feature = "memmem-scan")]
743 #[test]
744 fn memmem_find_endstream_matches_scalar_over_a_battery() {
745 let inputs: [&[u8]; 13] = [
746 b"",
747 b"endstream",
748 b"endstreamX",
749 b"endstream ",
750 b"endstream\r\n",
751 b"xendstream",
752 b"payload endstream)) ",
753 b"endstreamendstream",
754 b"endstreamen",
755 b"end\x00stream",
756 b"aaaaendstream/",
757 b"endstream\nendstream\r\n",
758 b"stream\nendstream\nendobj",
759 ];
760 for input in inputs {
761 for from in 0..=input.len() + 2 {
762 assert_eq!(
763 find_endstream(input, from),
764 find_endstream_scalar(input, from),
765 "mismatch for from={from} input={input:?}"
766 );
767 }
768 }
769 }
770
771 #[cfg(feature = "memmem-scan")]
774 #[test]
775 fn memmem_find_endstream_matches_scalar_random() {
776 let toks: [&[u8]; 10] = [
777 b"endstream",
778 b"end",
779 b"stream",
780 b"endstreamx",
781 b"x",
782 b" ",
783 b"/",
784 b"\n",
785 b"\r",
786 b"e",
787 ];
788 let mut state: u64 = 0xDEAD_BEEF_1234_5678;
789 for _ in 0..2000 {
790 let n = (xorshift64(&mut state) % 40) as usize;
791 let mut buf: Vec<u8> = Vec::new();
792 for _ in 0..n {
793 buf.extend_from_slice(toks[(xorshift64(&mut state) as usize) % toks.len()]);
794 }
795 for from in 0..=buf.len() {
796 assert_eq!(
797 find_endstream(&buf, from),
798 find_endstream_scalar(&buf, from),
799 "mismatch for from={from} buf={buf:?}"
800 );
801 }
802 }
803 }
804}