1use crate::error::PdfError;
18use crate::objects::{Dict, Object, ObjectId, Stream};
19use crate::reader::lex::{Lexer, Token, TokenKind};
20
21pub trait LengthResolver {
44 fn resolve_length(&mut self, length_ref: ObjectId) -> Result<i64, PdfError>;
48}
49
50pub struct NoLengthResolver;
55
56impl LengthResolver for NoLengthResolver {
57 fn resolve_length(&mut self, _length_ref: ObjectId) -> Result<i64, PdfError> {
58 Err(PdfError::other(
59 "PDF parser: stream /Length is an indirect reference but no resolver \
60 was supplied (call parse_indirect_with_length_resolver)",
61 ))
62 }
63}
64
65impl<F> LengthResolver for F
68where
69 F: FnMut(ObjectId) -> Result<i64, PdfError>,
70{
71 fn resolve_length(&mut self, length_ref: ObjectId) -> Result<i64, PdfError> {
72 (self)(length_ref)
73 }
74}
75
76const MAX_PARSE_DEPTH: u32 = 256;
88
89pub struct Parser<'a> {
91 lex: Lexer<'a>,
92 peeked: Option<Token<'a>>,
97 depth: u32,
104}
105
106impl<'a> Parser<'a> {
107 pub fn new(input: &'a [u8]) -> Self {
108 Self {
109 lex: Lexer::new(input),
110 peeked: None,
111 depth: 0,
112 }
113 }
114
115 pub fn from_lexer(lex: Lexer<'a>) -> Self {
118 Self {
119 lex,
120 peeked: None,
121 depth: 0,
122 }
123 }
124
125 pub fn lexer_mut(&mut self) -> &mut Lexer<'a> {
127 self.peeked = None;
130 &mut self.lex
131 }
132
133 pub fn position(&self) -> usize {
137 if let Some(t) = &self.peeked {
138 t.start
139 } else {
140 self.lex.position()
141 }
142 }
143
144 fn next(&mut self) -> Result<Option<Token<'a>>, PdfError> {
145 if let Some(t) = self.peeked.take() {
146 return Ok(Some(t));
147 }
148 self.lex.next_token()
149 }
150
151 fn peek(&mut self) -> Result<Option<&Token<'a>>, PdfError> {
152 if self.peeked.is_none() {
153 self.peeked = self.lex.next_token()?;
154 }
155 Ok(self.peeked.as_ref())
156 }
157
158 pub fn parse_object(&mut self) -> Result<Option<Object>, PdfError> {
166 self.parse_object_with_length_resolver(&mut NoLengthResolver)
167 }
168
169 pub fn parse_object_with_length_resolver(
173 &mut self,
174 resolver: &mut dyn LengthResolver,
175 ) -> Result<Option<Object>, PdfError> {
176 let Some(tok) = self.next()? else {
177 return Ok(None);
178 };
179 Ok(Some(self.object_from_token(tok, resolver)?))
180 }
181
182 fn object_from_token(
183 &mut self,
184 tok: Token<'a>,
185 resolver: &mut dyn LengthResolver,
186 ) -> Result<Object, PdfError> {
187 match tok.kind {
188 TokenKind::Integer(n) => self.maybe_indirect_ref(n, tok.end),
189 TokenKind::Real(f) => Ok(Object::Real(f)),
190 TokenKind::Name(bytes) => Ok(Object::Name(String::from_utf8(bytes).map_err(|_| {
191 PdfError::other(format!("PDF parser: non-UTF-8 name at byte {}", tok.start))
192 })?)),
193 TokenKind::LiteralString(bytes) => Ok(Object::LiteralString(bytes)),
194 TokenKind::HexString(bytes) => Ok(Object::HexString(bytes)),
195 TokenKind::ArrayStart => self.parse_array(resolver),
196 TokenKind::DictStart => self.parse_dict_or_stream(tok.start, resolver),
197 TokenKind::Keyword(kw) => match kw {
198 b"true" => Ok(Object::Bool(true)),
199 b"false" => Ok(Object::Bool(false)),
200 b"null" => Ok(Object::Null),
201 other => Err(PdfError::other(format!(
202 "PDF parser: unexpected keyword `{}` at byte {}",
203 String::from_utf8_lossy(other),
204 tok.start
205 ))),
206 },
207 TokenKind::ArrayEnd => Err(PdfError::other(format!(
208 "PDF parser: unexpected `]` at byte {}",
209 tok.start
210 ))),
211 TokenKind::DictEnd => Err(PdfError::other(format!(
212 "PDF parser: unexpected `>>` at byte {}",
213 tok.start
214 ))),
215 }
216 }
217
218 fn maybe_indirect_ref(&mut self, n: i64, _start_end: usize) -> Result<Object, PdfError> {
223 let saved_pos = self.lex.position();
227 let saved_peeked = self.peeked.clone();
228
229 let t2 = self.next()?;
230 let Some(t2) = t2 else {
231 return Ok(Object::Integer(n));
233 };
234 let TokenKind::Integer(gen) = t2.kind else {
235 self.peeked = Some(t2);
237 let _ = saved_pos;
240 let _ = saved_peeked;
241 return Ok(Object::Integer(n));
242 };
243 let t3 = self.next()?;
244 let Some(t3) = t3 else {
245 self.lex.seek(saved_pos);
248 self.peeked = saved_peeked;
249 return Ok(Object::Integer(n));
250 };
251 let TokenKind::Keyword(b"R") = t3.kind else {
252 self.lex.seek(saved_pos);
255 self.peeked = saved_peeked;
256 return Ok(Object::Integer(n));
257 };
258 if n < 1 || n > u32::MAX as i64 || gen < 0 || gen > u16::MAX as i64 {
259 return Err(PdfError::other(format!(
260 "PDF parser: indirect ref out of range `{n} {gen} R`"
261 )));
262 }
263 Ok(Object::Reference(ObjectId {
264 number: n as u32,
265 generation: gen as u16,
266 }))
267 }
268
269 fn parse_array(&mut self, resolver: &mut dyn LengthResolver) -> Result<Object, PdfError> {
270 if self.depth >= MAX_PARSE_DEPTH {
271 return Err(PdfError::other(format!(
272 "PDF parser: array nesting exceeds maximum depth ({MAX_PARSE_DEPTH})"
273 )));
274 }
275 self.depth += 1;
276 let result = (|| -> Result<Object, PdfError> {
277 let mut items = Vec::new();
278 loop {
279 let tok = self.next()?.ok_or_else(|| {
280 PdfError::other("PDF parser: unterminated array (EOF before `]`)")
281 })?;
282 if let TokenKind::ArrayEnd = tok.kind {
283 return Ok(Object::Array(items));
284 }
285 items.push(self.object_from_token(tok, resolver)?);
286 }
287 })();
288 self.depth -= 1;
289 result
290 }
291
292 fn parse_dict_or_stream(
293 &mut self,
294 start: usize,
295 resolver: &mut dyn LengthResolver,
296 ) -> Result<Object, PdfError> {
297 if self.depth >= MAX_PARSE_DEPTH {
298 return Err(PdfError::other(format!(
299 "PDF parser: dict nesting exceeds maximum depth ({MAX_PARSE_DEPTH})"
300 )));
301 }
302 self.depth += 1;
303 let result = self.parse_dict_or_stream_inner(start, resolver);
304 self.depth -= 1;
305 result
306 }
307
308 fn parse_dict_or_stream_inner(
309 &mut self,
310 start: usize,
311 resolver: &mut dyn LengthResolver,
312 ) -> Result<Object, PdfError> {
313 let mut dict = Dict::new();
314 loop {
315 let tok = self.next()?.ok_or_else(|| {
316 PdfError::other(format!(
317 "PDF parser: unterminated dict starting at byte {start} (EOF before `>>`)"
318 ))
319 })?;
320 if let TokenKind::DictEnd = tok.kind {
321 break;
322 }
323 let TokenKind::Name(key_bytes) = tok.kind else {
325 return Err(PdfError::other(format!(
326 "PDF parser: dict key must be a Name at byte {} (got {:?})",
327 tok.start, tok.kind
328 )));
329 };
330 let key = String::from_utf8(key_bytes).map_err(|_| {
331 PdfError::other(format!(
332 "PDF parser: non-UTF-8 dict key at byte {}",
333 tok.start
334 ))
335 })?;
336 let val = self.parse_object()?.ok_or_else(|| {
342 PdfError::other(format!(
343 "PDF parser: dict key `{key}` at byte {} has no value",
344 tok.start
345 ))
346 })?;
347 dict.set(&key, val);
348 }
349 let after = self.peek()?.cloned();
352 if let Some(t) = after {
353 if let TokenKind::Keyword(b"stream") = t.kind {
354 let _ = self.next()?;
356 let raw = self.lex.input();
360 let mut data_start = t.end;
361 if data_start < raw.len() && raw[data_start] == b'\r' {
362 data_start += 1;
363 }
364 if data_start < raw.len() && raw[data_start] == b'\n' {
365 data_start += 1;
366 }
367 let length_obj = dict.entries().iter().find_map(|(k, v)| {
377 if k == "Length" {
378 Some(v.clone())
379 } else {
380 None
381 }
382 });
383 let len: usize = match length_obj {
384 Some(Object::Integer(n)) if n >= 0 => n as usize,
385 Some(Object::Reference(id)) => {
386 let resolved = resolver.resolve_length(id)?;
387 if resolved < 0 {
388 return Err(PdfError::other(format!(
389 "PDF parser: indirect /Length {id:?} resolved to \
390 negative integer {resolved}"
391 )));
392 }
393 dict.set("Length", Object::Integer(resolved));
398 resolved as usize
399 }
400 Some(other) => {
401 return Err(PdfError::other(format!(
402 "PDF parser: stream /Length must be a non-negative integer (got {other:?})"
403 )));
404 }
405 None => {
406 return Err(PdfError::other(
407 "PDF parser: stream object missing required /Length entry",
408 ));
409 }
410 };
411 let data_end = data_start.saturating_add(len).min(raw.len());
412 let data = self.lex.slice(data_start, data_end).to_vec();
413 self.lex.seek(data_end);
416 self.peeked = None;
417 let endstream = self
420 .next()?
421 .ok_or_else(|| PdfError::other("PDF parser: stream missing `endstream`"))?;
422 let TokenKind::Keyword(b"endstream") = endstream.kind else {
423 return Err(PdfError::other(format!(
424 "PDF parser: expected `endstream` after stream body at byte {} (got {:?})",
425 endstream.start, endstream.kind
426 )));
427 };
428 return Ok(Object::Stream(Stream::new(dict, data)));
429 }
430 }
431 Ok(Object::Dict(dict))
432 }
433
434 pub fn parse_indirect(&mut self) -> Result<(ObjectId, Object), PdfError> {
442 self.parse_indirect_with_length_resolver(&mut NoLengthResolver)
443 }
444
445 pub fn parse_indirect_with_length_resolver(
449 &mut self,
450 resolver: &mut dyn LengthResolver,
451 ) -> Result<(ObjectId, Object), PdfError> {
452 let n = self.expect_integer("indirect-object number")?;
453 let gen = self.expect_integer("indirect-object generation")?;
454 let obj_kw = self
455 .next()?
456 .ok_or_else(|| PdfError::other("PDF parser: unexpected EOF before `obj` keyword"))?;
457 let TokenKind::Keyword(b"obj") = obj_kw.kind else {
458 return Err(PdfError::other(format!(
459 "PDF parser: expected `obj` keyword at byte {} (got {:?})",
460 obj_kw.start, obj_kw.kind
461 )));
462 };
463 let body = self
464 .parse_object_with_length_resolver(resolver)?
465 .ok_or_else(|| PdfError::other("PDF parser: indirect object missing body"))?;
466 let endobj = self
467 .next()?
468 .ok_or_else(|| PdfError::other("PDF parser: indirect object missing `endobj`"))?;
469 let TokenKind::Keyword(b"endobj") = endobj.kind else {
470 return Err(PdfError::other(format!(
471 "PDF parser: expected `endobj` at byte {} (got {:?})",
472 endobj.start, endobj.kind
473 )));
474 };
475 if n < 1 || n > u32::MAX as i64 || !(0..=u16::MAX as i64).contains(&gen) {
476 return Err(PdfError::other(format!(
477 "PDF parser: indirect-object id `{n} {gen}` out of range"
478 )));
479 }
480 Ok((
481 ObjectId {
482 number: n as u32,
483 generation: gen as u16,
484 },
485 body,
486 ))
487 }
488
489 fn expect_integer(&mut self, what: &str) -> Result<i64, PdfError> {
490 let tok = self
491 .next()?
492 .ok_or_else(|| PdfError::other(format!("PDF parser: expected {what}, got EOF")))?;
493 match tok.kind {
494 TokenKind::Integer(n) => Ok(n),
495 other => Err(PdfError::other(format!(
496 "PDF parser: expected {what} (integer), got {other:?} at byte {}",
497 tok.start
498 ))),
499 }
500 }
501}
502
503#[cfg(test)]
504mod tests {
505 use super::*;
506
507 fn parse_one(input: &[u8]) -> Object {
508 Parser::new(input)
509 .parse_object()
510 .unwrap()
511 .expect("parsed object")
512 }
513
514 #[test]
515 fn primitives_parse_to_objects() {
516 assert!(matches!(parse_one(b"true"), Object::Bool(true)));
517 assert!(matches!(parse_one(b"false"), Object::Bool(false)));
518 assert!(matches!(parse_one(b"null"), Object::Null));
519 assert!(matches!(parse_one(b"42"), Object::Integer(42)));
520 match parse_one(b"2.5") {
521 Object::Real(f) => assert!((f - 2.5).abs() < 1e-9),
522 other => panic!("expected real, got {other:?}"),
523 }
524 match parse_one(b"(hello)") {
525 Object::LiteralString(b) => assert_eq!(b, b"hello".to_vec()),
526 other => panic!("expected literal string, got {other:?}"),
527 }
528 match parse_one(b"<48656C6C6F>") {
529 Object::HexString(b) => assert_eq!(b, b"Hello".to_vec()),
530 other => panic!("expected hex string, got {other:?}"),
531 }
532 match parse_one(b"/Pages") {
533 Object::Name(s) => assert_eq!(s, "Pages"),
534 other => panic!("expected name, got {other:?}"),
535 }
536 }
537
538 #[test]
539 fn array_parses_recursively() {
540 let arr = parse_one(b"[1 2.5 (a) /b [3 4]]");
541 let Object::Array(items) = arr else {
542 panic!("expected array")
543 };
544 assert_eq!(items.len(), 5);
545 assert!(matches!(items[0], Object::Integer(1)));
546 assert!(matches!(items[1], Object::Real(_)));
547 assert!(matches!(items[2], Object::LiteralString(_)));
548 assert!(matches!(items[3], Object::Name(_)));
549 assert!(matches!(items[4], Object::Array(_)));
550 }
551
552 #[test]
553 fn dict_parses_with_named_keys() {
554 let d = parse_one(b"<< /Type /Page /Count 3 /Kids [1 2 3] >>");
555 let Object::Dict(d) = d else {
556 panic!("expected dict")
557 };
558 let entries = d.entries();
559 assert_eq!(entries.len(), 3);
560 assert_eq!(entries[0].0, "Type");
561 assert_eq!(entries[1].0, "Count");
562 assert_eq!(entries[2].0, "Kids");
563 }
564
565 #[test]
566 fn indirect_reference_recognised() {
567 let r = parse_one(b"5 0 R");
568 let Object::Reference(id) = r else {
569 panic!("expected ref")
570 };
571 assert_eq!(id.number, 5);
572 assert_eq!(id.generation, 0);
573 }
574
575 #[test]
576 fn integer_followed_by_non_ref_does_not_become_ref() {
577 let arr = parse_one(b"[5 0]");
579 let Object::Array(items) = arr else {
580 panic!("expected array")
581 };
582 assert_eq!(items.len(), 2);
583 assert!(matches!(items[0], Object::Integer(5)));
584 assert!(matches!(items[1], Object::Integer(0)));
585 }
586
587 #[test]
588 fn indirect_object_round_trip() {
589 let input = b"3 0 obj\n<< /Type /Page >>\nendobj\n";
590 let mut p = Parser::new(input);
591 let (id, body) = p.parse_indirect().unwrap();
592 assert_eq!(id.number, 3);
593 assert!(matches!(body, Object::Dict(_)));
594 }
595
596 #[test]
597 fn stream_object_extracts_body_per_length() {
598 let input = b"4 0 obj\n<< /Length 5 >>\nstream\nABCDE\nendstream\nendobj\n";
599 let mut p = Parser::new(input);
600 let (_, body) = p.parse_indirect().unwrap();
601 let Object::Stream(s) = body else {
602 panic!("expected stream")
603 };
604 assert_eq!(s.data, b"ABCDE".to_vec());
605 }
606
607 #[test]
608 fn stream_with_crlf_eol_marker() {
609 let mut input = Vec::new();
612 input.extend_from_slice(b"4 0 obj\n<< /Length 3 >>\nstream\r\nXYZ\nendstream\nendobj\n");
613 let mut p = Parser::new(&input);
614 let (_, body) = p.parse_indirect().unwrap();
615 let Object::Stream(s) = body else {
616 panic!("expected stream")
617 };
618 assert_eq!(s.data, b"XYZ".to_vec());
619 }
620
621 #[test]
622 fn stream_indirect_length_without_resolver_is_rejected() {
623 let input = b"4 0 obj\n<< /Length 7 0 R >>\nstream\nXYZ\nendstream\nendobj\n";
628 let mut p = Parser::new(input);
629 let err = p.parse_indirect().unwrap_err();
630 assert!(format!("{err}").contains("indirect reference"));
631 }
632
633 #[test]
634 fn stream_indirect_length_with_resolver_resolves() {
635 let input = b"4 0 obj\n<< /Length 7 0 R >>\nstream\nXYZ\nendstream\nendobj\n";
639 let mut p = Parser::new(input);
640 let mut resolver = |id: ObjectId| -> Result<i64, PdfError> {
641 assert_eq!(id, ObjectId::new(7));
642 Ok(3)
643 };
644 let (id, body) = p
645 .parse_indirect_with_length_resolver(&mut resolver)
646 .unwrap();
647 assert_eq!(id, ObjectId::new(4));
648 let Object::Stream(s) = body else {
649 panic!("expected stream, got {body:?}")
650 };
651 assert_eq!(s.data, b"XYZ".to_vec());
652 let length = s
656 .dict
657 .entries()
658 .iter()
659 .find(|(k, _)| k == "Length")
660 .map(|(_, v)| v.clone());
661 assert!(matches!(length, Some(Object::Integer(3))));
662 }
663
664 #[test]
665 fn stream_indirect_length_negative_resolution_errors() {
666 let input = b"4 0 obj\n<< /Length 7 0 R >>\nstream\nXYZ\nendstream\nendobj\n";
669 let mut p = Parser::new(input);
670 let mut resolver = |_id: ObjectId| -> Result<i64, PdfError> { Ok(-1) };
671 let err = p
672 .parse_indirect_with_length_resolver(&mut resolver)
673 .unwrap_err();
674 assert!(format!("{err}").contains("negative"));
675 }
676
677 #[test]
678 fn stream_indirect_length_resolver_error_propagates() {
679 let input = b"4 0 obj\n<< /Length 7 0 R >>\nstream\nXYZ\nendstream\nendobj\n";
682 let mut p = Parser::new(input);
683 let mut resolver =
684 |_id: ObjectId| -> Result<i64, PdfError> { Err(PdfError::other("test: not found")) };
685 let err = p
686 .parse_indirect_with_length_resolver(&mut resolver)
687 .unwrap_err();
688 assert!(format!("{err}").contains("not found"));
689 }
690
691 #[test]
692 fn stream_missing_length_is_rejected() {
693 let input = b"4 0 obj\n<< /Type /XObject >>\nstream\nXYZ\nendstream\nendobj\n";
694 let mut p = Parser::new(input);
695 let err = p.parse_indirect().unwrap_err();
696 assert!(format!("{err}").contains("/Length"));
697 }
698
699 #[test]
700 fn nested_dict_in_array() {
701 let arr = parse_one(b"[<< /A 1 >> << /B 2 >>]");
704 let Object::Array(items) = arr else {
705 panic!("expected array")
706 };
707 assert_eq!(items.len(), 2);
708 assert!(matches!(items[0], Object::Dict(_)));
709 assert!(matches!(items[1], Object::Dict(_)));
710 }
711
712 #[test]
713 fn dict_with_indirect_reference_value() {
714 let d = parse_one(b"<< /Pages 2 0 R >>");
715 let Object::Dict(d) = d else {
716 panic!("expected dict")
717 };
718 let entries = d.entries();
719 assert_eq!(entries.len(), 1);
720 assert_eq!(entries[0].0, "Pages");
721 assert!(matches!(entries[0].1, Object::Reference(_)));
722 }
723
724 #[test]
729 fn deeply_nested_array_rejected_below_stack_overflow() {
730 let mut body = vec![b'['; MAX_PARSE_DEPTH as usize + 8];
731 body.extend(std::iter::repeat_n(b']', MAX_PARSE_DEPTH as usize + 8));
732 let err = Parser::new(&body)
733 .parse_object()
734 .expect_err("over-deep array must error");
735 let msg = format!("{err}");
736 assert!(
737 msg.contains("nesting") || msg.contains("depth"),
738 "unexpected error message: {msg}"
739 );
740 }
741
742 #[test]
746 fn deeply_nested_dict_rejected_below_stack_overflow() {
747 let levels = (MAX_PARSE_DEPTH as usize) + 8;
749 let mut body = Vec::new();
750 for _ in 0..levels {
751 body.extend_from_slice(b"<< /k ");
752 }
753 body.extend_from_slice(b"null");
754 for _ in 0..levels {
755 body.extend_from_slice(b" >>");
756 }
757 let err = Parser::new(&body)
758 .parse_object()
759 .expect_err("over-deep dict must error");
760 let msg = format!("{err}");
761 assert!(
762 msg.contains("nesting") || msg.contains("depth"),
763 "unexpected error message: {msg}"
764 );
765 }
766}