Skip to main content

pdfboss_core/
parser.rs

1//! Object parser built on the [`Lexer`], including indirect objects and
2//! streams (ISO 32000 §7.3.8/§7.3.10).
3
4use crate::error::{Error, Result};
5use crate::lexer::{Lexer, Token};
6use crate::object::{Dict, ObjRef, Object, Stream};
7
8/// Maximum container (array/dictionary) nesting depth. Deeper input is
9/// rejected with a syntax error: the parser recurses per nesting level, so
10/// without a bound a tiny crafted file (e.g. 100k `[`s) overflows the
11/// stack and aborts the whole process. Real documents nest a handful of
12/// levels; 128 is far beyond anything legitimate.
13const MAX_NESTING_DEPTH: usize = 128;
14
15/// Resolves indirect references while parsing (e.g. an indirect `/Length`).
16pub trait Resolve {
17    /// Returns the referenced object, or `None` if it cannot be resolved.
18    fn resolve_ref(&self, r: ObjRef) -> Option<Object>;
19}
20
21/// A [`Resolve`] implementation that never resolves anything.
22pub struct NoResolve;
23
24impl Resolve for NoResolve {
25    fn resolve_ref(&self, _r: ObjRef) -> Option<Object> {
26        None
27    }
28}
29
30/// Recursive-descent parser over a byte slice.
31pub struct Parser<'a> {
32    lexer: Lexer<'a>,
33}
34
35impl<'a> Parser<'a> {
36    /// Creates a parser at the start of `data`.
37    pub fn new(data: &'a [u8]) -> Self {
38        Parser {
39            lexer: Lexer::new(data),
40        }
41    }
42
43    /// Creates a parser positioned at byte offset `pos`.
44    pub fn at(data: &'a [u8], pos: usize) -> Self {
45        Parser {
46            lexer: Lexer::at(data, pos),
47        }
48    }
49
50    /// Current byte offset.
51    pub fn pos(&self) -> usize {
52        self.lexer.pos()
53    }
54
55    /// Moves the cursor to byte offset `pos`.
56    pub fn seek(&mut self, pos: usize) {
57        self.lexer.seek(pos);
58    }
59
60    /// Parses one object at the current position. `int int R` becomes
61    /// [`Object::Ref`] via lookahead with backtracking; dictionaries
62    /// followed by `stream` become [`Object::Stream`] (using `resolver`
63    /// for an indirect `/Length`, with `endstream` scan recovery).
64    pub fn parse_object(&mut self, resolver: &dyn Resolve) -> Result<Object> {
65        let token = self.lexer.next_token()?;
66        self.parse_from_token(token, resolver, 0)
67    }
68
69    /// Builds a [`Error::Syntax`] at the current position.
70    fn syntax(&self, msg: impl Into<String>) -> Error {
71        Error::Syntax {
72            offset: self.lexer.pos(),
73            msg: msg.into(),
74        }
75    }
76
77    /// Parses the object that `token` begins. `depth` counts enclosing
78    /// containers (bounded by `MAX_NESTING_DEPTH`).
79    fn parse_from_token(
80        &mut self,
81        token: Token,
82        resolver: &dyn Resolve,
83        depth: usize,
84    ) -> Result<Object> {
85        match token {
86            Token::Int(i) => Ok(self.try_reference(i)),
87            Token::Real(r) => Ok(Object::Real(r)),
88            Token::Name(n) => Ok(Object::Name(n)),
89            Token::LitString(s) | Token::HexString(s) => Ok(Object::String(s)),
90            Token::ArrayOpen => self.parse_array(resolver, depth),
91            Token::DictOpen => self.parse_dict_or_stream(resolver, depth),
92            Token::Keyword(k) => match k.as_slice() {
93                b"true" => Ok(Object::Bool(true)),
94                b"false" => Ok(Object::Bool(false)),
95                b"null" => Ok(Object::Null),
96                _ => Err(self.syntax(format!(
97                    "unexpected keyword `{}`",
98                    String::from_utf8_lossy(&k)
99                ))),
100            },
101            Token::ArrayClose => Err(self.syntax("unexpected `]`")),
102            Token::DictClose => Err(self.syntax("unexpected `>>`")),
103            Token::Eof => Err(self.syntax("unexpected end of input")),
104        }
105    }
106
107    /// After an integer has been read: if `int R` follows, the three tokens
108    /// form an indirect reference; otherwise the lexer is rewound so the
109    /// integer stands alone.
110    fn try_reference(&mut self, num: i64) -> Object {
111        let save = self.lexer.pos();
112        if (0..=i64::from(u32::MAX)).contains(&num) {
113            if let Ok(Token::Int(gen)) = self.lexer.next_token() {
114                if (0..=i64::from(u16::MAX)).contains(&gen)
115                    && matches!(self.lexer.next_token(),
116                                Ok(Token::Keyword(ref k)) if k.as_slice() == b"R")
117                {
118                    return Object::Ref(ObjRef {
119                        num: num as u32,
120                        gen: gen as u16,
121                    });
122                }
123            }
124        }
125        self.lexer.seek(save);
126        Object::Int(num)
127    }
128
129    /// Parses array elements up to `]` (leniently also up to end of input).
130    fn parse_array(&mut self, resolver: &dyn Resolve, depth: usize) -> Result<Object> {
131        if depth >= MAX_NESTING_DEPTH {
132            return Err(self.syntax("container nesting too deep"));
133        }
134        let mut items = Vec::new();
135        loop {
136            let token = self.lexer.next_token()?;
137            match token {
138                Token::ArrayClose | Token::Eof => break,
139                other => items.push(self.parse_from_token(other, resolver, depth + 1)?),
140            }
141        }
142        Ok(Object::Array(items))
143    }
144
145    /// Parses dictionary entries up to `>>`; if the `stream` keyword follows,
146    /// the dictionary becomes a stream's dictionary and the stream data is
147    /// read as well.
148    fn parse_dict_or_stream(&mut self, resolver: &dyn Resolve, depth: usize) -> Result<Object> {
149        if depth >= MAX_NESTING_DEPTH {
150            return Err(self.syntax("container nesting too deep"));
151        }
152        let mut dict = Dict::new();
153        loop {
154            match self.lexer.next_token()? {
155                Token::DictClose | Token::Eof => break,
156                Token::Name(key) => {
157                    let token = self.lexer.next_token()?;
158                    if token == Token::DictClose {
159                        // Lenient: a key with no value maps to null.
160                        dict.insert(key, Object::Null);
161                        break;
162                    }
163                    let value = self.parse_from_token(token, resolver, depth + 1)?;
164                    dict.insert(key, value);
165                }
166                // Lenient: a non-name key is consumed as an object and dropped.
167                other => {
168                    self.parse_from_token(other, resolver, depth + 1)?;
169                }
170            }
171        }
172        if matches!(self.lexer.peek_token(),
173                    Ok(Token::Keyword(ref k)) if k.as_slice() == b"stream")
174        {
175            self.lexer.next_token()?;
176            return self.parse_stream_body(dict, resolver);
177        }
178        Ok(Object::Dict(dict))
179    }
180
181    /// Reads stream data after the `stream` keyword (ISO 32000 §7.3.8):
182    /// skip the EOL after the keyword, take `/Length` bytes (resolving an
183    /// indirect length via `resolver`), and verify `endstream` follows. When
184    /// `/Length` is missing or wrong, recover by scanning for the nearest
185    /// `endstream` and trimming one trailing EOL from the data.
186    fn parse_stream_body(&mut self, dict: Dict, resolver: &dyn Resolve) -> Result<Object> {
187        let data = self.lexer.data();
188        let mut start = self.lexer.pos();
189        // The keyword should be followed by CRLF or LF; tolerate a lone CR.
190        if data.get(start) == Some(&b'\r') {
191            start += 1;
192        }
193        if data.get(start) == Some(&b'\n') {
194            start += 1;
195        }
196        let declared = match dict.get("Length") {
197            Some(Object::Int(n)) => Some(*n),
198            Some(Object::Ref(r)) => resolver.resolve_ref(*r).and_then(|o| o.as_int()),
199            _ => None,
200        };
201        if let Some(len) = declared.filter(|&l| l >= 0) {
202            if let Some(end) = start.checked_add(len as usize).filter(|&e| e <= data.len()) {
203                let mut probe = Lexer::at(data, end);
204                if matches!(probe.next_token(),
205                            Ok(Token::Keyword(ref k)) if k.as_slice() == b"endstream")
206                {
207                    self.lexer.seek(probe.pos());
208                    return Ok(Object::Stream(Stream {
209                        dict,
210                        data: data[start..end].to_vec(),
211                    }));
212                }
213            }
214        }
215        match memchr::memmem::find(&data[start..], b"endstream") {
216            Some(idx) => {
217                let mut end = start + idx;
218                if end > start && data[end - 1] == b'\n' {
219                    end -= 1;
220                }
221                if end > start && data[end - 1] == b'\r' {
222                    end -= 1;
223                }
224                self.lexer.seek(start + idx + b"endstream".len());
225                Ok(Object::Stream(Stream {
226                    dict,
227                    data: data[start..end].to_vec(),
228                }))
229            }
230            None => {
231                // Lenient: an unterminated stream takes the rest of the input.
232                self.lexer.seek(data.len());
233                Ok(Object::Stream(Stream {
234                    dict,
235                    data: data[start..].to_vec(),
236                }))
237            }
238        }
239    }
240
241    /// Expects `N G obj ... endobj` at the current position and returns the
242    /// reference plus the contained object. Lenient: a missing `endobj` is
243    /// accepted at the next `obj` or end of input.
244    pub fn parse_indirect(&mut self, resolver: &dyn Resolve) -> Result<(ObjRef, Object)> {
245        let num = match self.lexer.next_token()? {
246            Token::Int(n) if (0..=i64::from(u32::MAX)).contains(&n) => n as u32,
247            _ => return Err(self.syntax("expected object number")),
248        };
249        let gen = match self.lexer.next_token()? {
250            Token::Int(g) if (0..=i64::from(u16::MAX)).contains(&g) => g as u16,
251            _ => return Err(self.syntax("expected generation number")),
252        };
253        match self.lexer.next_token()? {
254            Token::Keyword(ref k) if k.as_slice() == b"obj" => {}
255            _ => return Err(self.syntax("expected `obj`")),
256        }
257        // Lenient: an empty body (`N G obj endobj`) yields null.
258        let object = match self.lexer.peek_token() {
259            Ok(Token::Keyword(ref k)) if k.as_slice() == b"endobj" => Object::Null,
260            _ => self.parse_object(resolver)?,
261        };
262        // Lenient: consume `endobj` when present; a missing `endobj` is
263        // accepted as-is (at the next `N G obj` header or end of input).
264        let save = self.lexer.pos();
265        match self.lexer.next_token() {
266            Ok(Token::Keyword(ref k)) if k.as_slice() == b"endobj" => {}
267            _ => self.lexer.seek(save),
268        }
269        Ok((ObjRef { num, gen }, object))
270    }
271}
272
273#[cfg(test)]
274mod tests {
275    use super::*;
276    use crate::object::Name;
277
278    fn parse(data: &[u8]) -> Object {
279        Parser::new(data).parse_object(&NoResolve).unwrap()
280    }
281
282    #[test]
283    fn atom_null_and_bools() {
284        assert_eq!(parse(b"null"), Object::Null);
285        assert_eq!(parse(b"true"), Object::Bool(true));
286        assert_eq!(parse(b"false"), Object::Bool(false));
287    }
288
289    #[test]
290    fn atom_numbers() {
291        assert_eq!(parse(b"42"), Object::Int(42));
292        assert_eq!(parse(b"-17"), Object::Int(-17));
293        assert_eq!(parse(b"+5"), Object::Int(5));
294        assert_eq!(parse(b"3.5"), Object::Real(3.5));
295        assert_eq!(parse(b"-.25"), Object::Real(-0.25));
296    }
297
298    #[test]
299    fn atom_strings() {
300        assert_eq!(parse(b"(hello)"), Object::String(b"hello".to_vec()));
301        assert_eq!(parse(b"<48690A>"), Object::String(b"Hi\n".to_vec()));
302    }
303
304    #[test]
305    fn atom_name() {
306        assert_eq!(parse(b"/Type"), Object::Name(Name("Type".into())));
307    }
308
309    #[test]
310    fn nested_arrays_and_dicts() {
311        let obj = parse(b"<< /A [1 2 [3]] /B << /C /D >> >>");
312        let dict = obj.as_dict().unwrap();
313        let a = dict.get_array("A").unwrap();
314        assert_eq!(a[0], Object::Int(1));
315        assert_eq!(a[1], Object::Int(2));
316        assert_eq!(a[2], Object::Array(vec![Object::Int(3)]));
317        let b = dict.get_dict("B").unwrap();
318        assert_eq!(b.get_name("C"), Some(&Name("D".into())));
319    }
320
321    #[test]
322    fn reference_from_lookahead() {
323        assert_eq!(parse(b"12 0 R"), Object::Ref(ObjRef { num: 12, gen: 0 }));
324        assert_eq!(
325            parse(b"[12 0 R 7 3 R]"),
326            Object::Array(vec![
327                Object::Ref(ObjRef { num: 12, gen: 0 }),
328                Object::Ref(ObjRef { num: 7, gen: 3 }),
329            ])
330        );
331    }
332
333    #[test]
334    fn two_ints_without_r_stay_ints() {
335        let mut p = Parser::new(b"12 0");
336        assert_eq!(p.parse_object(&NoResolve).unwrap(), Object::Int(12));
337        assert_eq!(p.parse_object(&NoResolve).unwrap(), Object::Int(0));
338        assert_eq!(
339            parse(b"[12 0]"),
340            Object::Array(vec![Object::Int(12), Object::Int(0)])
341        );
342        // `RG` is a different keyword, not `R`: no reference.
343        let mut p = Parser::new(b"12 0 RG");
344        assert_eq!(p.parse_object(&NoResolve).unwrap(), Object::Int(12));
345        assert_eq!(p.parse_object(&NoResolve).unwrap(), Object::Int(0));
346    }
347
348    #[test]
349    fn indirect_round_trip() {
350        let mut p = Parser::new(b"1 0 obj << /Type /Test /N 3 >> endobj");
351        let (r, obj) = p.parse_indirect(&NoResolve).unwrap();
352        assert_eq!(r, ObjRef { num: 1, gen: 0 });
353        let dict = obj.as_dict().unwrap();
354        assert_eq!(dict.get_name("Type"), Some(&Name("Test".into())));
355        assert_eq!(dict.get_int("N"), Some(3));
356        assert_eq!(p.lexer.next_token().unwrap(), Token::Eof);
357    }
358
359    #[test]
360    fn stream_with_direct_length() {
361        let mut p = Parser::new(b"<< /Length 5 >>\nstream\nhello\nendstream");
362        let obj = p.parse_object(&NoResolve).unwrap();
363        let s = obj.as_stream().unwrap();
364        assert_eq!(s.data, b"hello");
365        assert_eq!(p.lexer.next_token().unwrap(), Token::Eof);
366    }
367
368    /// Resolves exactly one reference, for indirect `/Length` tests.
369    struct OneResolve(ObjRef, Object);
370
371    impl Resolve for OneResolve {
372        fn resolve_ref(&self, r: ObjRef) -> Option<Object> {
373            (r == self.0).then(|| self.1.clone())
374        }
375    }
376
377    #[test]
378    fn stream_with_indirect_length() {
379        let resolver = OneResolve(ObjRef { num: 9, gen: 0 }, Object::Int(7));
380        let mut p = Parser::new(b"<< /Length 9 0 R >>stream\r\nhello!!\r\nendstream");
381        let obj = p.parse_object(&resolver).unwrap();
382        assert_eq!(obj.as_stream().unwrap().data, b"hello!!");
383    }
384
385    #[test]
386    fn stream_with_wrong_length_recovers() {
387        let mut p = Parser::new(b"<< /Length 3 >>stream\nhello world\nendstream endobj");
388        let obj = p.parse_object(&NoResolve).unwrap();
389        assert_eq!(obj.as_stream().unwrap().data, b"hello world");
390        // Recovery leaves the cursor right after `endstream`.
391        assert_eq!(
392            p.lexer.next_token().unwrap(),
393            Token::Keyword(b"endobj".to_vec())
394        );
395    }
396
397    #[test]
398    fn stream_with_overlong_length_recovers() {
399        let mut p = Parser::new(b"<< /Length 999 >>stream\nxy\r\nendstream");
400        let obj = p.parse_object(&NoResolve).unwrap();
401        assert_eq!(obj.as_stream().unwrap().data, b"xy");
402    }
403
404    #[test]
405    fn stream_without_length_recovers() {
406        let mut p = Parser::new(b"<< /Type /XObject >>stream\nabc\nendstream");
407        let obj = p.parse_object(&NoResolve).unwrap();
408        let s = obj.as_stream().unwrap();
409        assert_eq!(s.data, b"abc");
410        assert_eq!(s.dict.get_name("Type"), Some(&Name("XObject".into())));
411    }
412
413    #[test]
414    fn stream_with_unresolvable_length_recovers() {
415        // The resolver knows nothing, so the indirect length falls through
416        // to the `endstream` scan.
417        let mut p = Parser::new(b"<< /Length 8 0 R >>stream\ndata\nendstream");
418        let obj = p.parse_object(&NoResolve).unwrap();
419        assert_eq!(obj.as_stream().unwrap().data, b"data");
420    }
421
422    #[test]
423    fn missing_endobj_accepted_at_next_obj() {
424        let mut p = Parser::new(b"1 0 obj 42 2 0 obj (next) endobj");
425        let (r1, o1) = p.parse_indirect(&NoResolve).unwrap();
426        assert_eq!(r1, ObjRef { num: 1, gen: 0 });
427        assert_eq!(o1, Object::Int(42));
428        let (r2, o2) = p.parse_indirect(&NoResolve).unwrap();
429        assert_eq!(r2, ObjRef { num: 2, gen: 0 });
430        assert_eq!(o2, Object::String(b"next".to_vec()));
431    }
432
433    #[test]
434    fn missing_endobj_accepted_at_eof() {
435        let mut p = Parser::new(b"5 0 obj << /A 1 >>");
436        let (r, obj) = p.parse_indirect(&NoResolve).unwrap();
437        assert_eq!(r, ObjRef { num: 5, gen: 0 });
438        assert_eq!(obj.as_dict().unwrap().get_int("A"), Some(1));
439    }
440
441    #[test]
442    fn empty_indirect_body_is_null() {
443        let mut p = Parser::new(b"3 1 obj endobj");
444        let (r, obj) = p.parse_indirect(&NoResolve).unwrap();
445        assert_eq!(r, ObjRef { num: 3, gen: 1 });
446        assert_eq!(obj, Object::Null);
447        assert_eq!(p.lexer.next_token().unwrap(), Token::Eof);
448    }
449
450    #[test]
451    fn indirect_stream_object() {
452        let data = b"4 0 obj << /Length 3 >> stream\nxyz\nendstream endobj";
453        let mut p = Parser::new(data);
454        let (r, obj) = p.parse_indirect(&NoResolve).unwrap();
455        assert_eq!(r, ObjRef { num: 4, gen: 0 });
456        assert_eq!(obj.as_stream().unwrap().data, b"xyz");
457        assert_eq!(p.lexer.next_token().unwrap(), Token::Eof);
458    }
459
460    #[test]
461    fn dict_value_reference() {
462        let obj = parse(b"<< /Parent 6 0 R /Count 2 >>");
463        let dict = obj.as_dict().unwrap();
464        assert_eq!(dict.get_ref("Parent"), Some(ObjRef { num: 6, gen: 0 }));
465        assert_eq!(dict.get_int("Count"), Some(2));
466    }
467
468    /// Runs `f` on a deliberately small stack so that unbounded recursion
469    /// would abort the test binary instead of silently passing on a large
470    /// main-thread stack.
471    fn on_small_stack<T: Send + 'static>(f: impl FnOnce() -> T + Send + 'static) -> T {
472        std::thread::Builder::new()
473            .stack_size(512 * 1024)
474            .spawn(f)
475            .expect("spawn test thread")
476            .join()
477            .expect("parser must not overflow the stack")
478    }
479
480    #[test]
481    fn deeply_nested_array_is_rejected_not_stack_overflow() {
482        let mut data = vec![b'['; 200_000];
483        data.extend(std::iter::repeat_n(b']', 200_000));
484        let result = on_small_stack(move || Parser::new(&data).parse_object(&NoResolve));
485        assert!(matches!(result, Err(Error::Syntax { .. })));
486    }
487
488    #[test]
489    fn deeply_nested_dict_is_rejected_not_stack_overflow() {
490        let mut data = Vec::new();
491        for _ in 0..100_000 {
492            data.extend_from_slice(b"<</K");
493        }
494        let result = on_small_stack(move || Parser::new(&data).parse_object(&NoResolve));
495        assert!(matches!(result, Err(Error::Syntax { .. })));
496    }
497
498    #[test]
499    fn nesting_within_the_limit_still_parses() {
500        let mut data = vec![b'['; 100];
501        data.extend_from_slice(b" 7 ");
502        data.extend(std::iter::repeat_n(b']', 100));
503        let mut obj = parse(&data);
504        for _ in 0..100 {
505            let items = obj.as_array().expect("nested array").to_vec();
506            assert_eq!(items.len(), 1);
507            obj = items.into_iter().next().unwrap();
508        }
509        assert_eq!(obj, Object::Int(7));
510    }
511}