Skip to main content

pith_pdf/
document.rs

1//! Document assembly: xref -> trailer -> page tree -> per-page extraction.
2//!
3//! `Document` is lazy: objects parse on first reference and are cached, so
4//! a corrupt object errors on the page that references it instead of
5//! refusing the whole file. Object streams (`/ObjStm`) are decoded once and
6//! their members cached under their real object numbers.
7//!
8//! An unresolvable xref is rebuilt by scanning the file for `N G obj`
9//! headers (see `xref::scan_xref`); [`Document::xref_was_rebuilt`] reports
10//! that the recovery path ran.
11
12use alloc::boxed::Box;
13use alloc::collections::BTreeMap;
14use alloc::string::String;
15use alloc::vec::Vec;
16use core::cell::RefCell;
17
18use pith_digest::{Error as KErr, Result as KResult};
19
20use crate::content::{Resources, extract_page_text};
21use crate::font::Resolve;
22use crate::object::{Obj, Ref, decode_stream, dict_get, parse_obj};
23use crate::xref::{Loc, Xref, read_xref, scan_xref};
24
25/// The crate's public error: wraps the suite [`pith_digest::Error`]
26/// with the page or object where the failure happened.
27#[derive(Debug)]
28pub enum Error {
29    /// A document-level failure (header, xref, trailer, page tree).
30    Kit(KErr),
31    /// Failure attributed to one indirect object.
32    Object {
33        /// Object number.
34        object: u32,
35        /// Generation number.
36        generation: u16,
37        /// Underlying cause.
38        cause: Box<Error>,
39    },
40    /// Failure attributed to one page.
41    Page {
42        /// Zero-based page index in document order.
43        page: usize,
44        /// Underlying cause.
45        cause: Box<Error>,
46    },
47}
48
49/// The crate's result type.
50pub type Result<T, E = Error> = core::result::Result<T, E>;
51
52impl From<KErr> for Error {
53    fn from(e: KErr) -> Error {
54        Error::Kit(e)
55    }
56}
57
58impl core::fmt::Display for Error {
59    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
60        match self {
61            Error::Kit(e) => e.fmt(f),
62            Error::Object {
63                object,
64                generation,
65                cause,
66            } => {
67                f.write_str("object ")?;
68                write_u32(f, *object)?;
69                f.write_str(" ")?;
70                write_u32(f, u32::from(*generation))?;
71                f.write_str(": ")?;
72                cause.fmt(f)
73            }
74            Error::Page { page, cause } => {
75                f.write_str("page ")?;
76                write_usize(f, *page)?;
77                f.write_str(": ")?;
78                cause.fmt(f)
79            }
80        }
81    }
82}
83
84fn write_u32(f: &mut core::fmt::Formatter<'_>, v: u32) -> core::fmt::Result {
85    // decimal without allocations
86    let mut buf = [0u8; 10];
87    let mut i = buf.len();
88    let mut v = v;
89    if v == 0 {
90        return f.write_str("0");
91    }
92    while v > 0 {
93        i -= 1;
94        buf[i] = b'0' + (v % 10) as u8;
95        v /= 10;
96    }
97    let s = core::str::from_utf8(&buf[i..]).unwrap_or("?");
98    f.write_str(s)
99}
100
101fn write_usize(f: &mut core::fmt::Formatter<'_>, v: usize) -> core::fmt::Result {
102    write_u32(f, v as u32)
103}
104
105/// A page in document order: its dictionary ref and the *effective*
106/// `/Resources` dictionary after inheriting down the page tree.
107struct Page {
108    /// Reference to the `/Page` dictionary object.
109    r: Ref,
110    /// Merged resources (ancestors' `/Resources` overridden by the page's).
111    resources: Vec<(Vec<u8>, Obj)>,
112}
113
114/// An open PDF document.
115pub struct Document<'a> {
116    data: &'a [u8],
117    xref: Xref,
118    limits: pith_inflate::Limits,
119    pages: Vec<Page>,
120    encrypt: Option<Ref>,
121    cache: RefCell<BTreeMap<u32, Obj>>,
122    /// decoded member lists of object streams, keyed by stream object num
123    objstms: RefCell<BTreeMap<u32, Vec<(u32, Obj)>>>,
124}
125
126impl Resolve for Document<'_> {
127    fn deref(&self, r: Ref) -> KResult<Obj> {
128        self.resolve_obj(r).map_err(|e| match e {
129            Error::Kit(k) => k,
130            _ => KErr::BadValue("indirect object"),
131        })
132    }
133}
134
135/// Worklist item for `collect_pages`.
136type PageStackItem = (Ref, Vec<(Vec<u8>, Obj)>);
137
138impl<'a> Document<'a> {
139    /// Open with default [`pith_inflate::Limits`].
140    pub fn open(data: &'a [u8]) -> Result<Document<'a>> {
141        Self::open_with_limits(data, pith_inflate::Limits::default())
142    }
143
144    /// Open with explicit decompression limits.
145    pub fn open_with_limits(data: &'a [u8], limits: pith_inflate::Limits) -> Result<Document<'a>> {
146        if data.len() < 8 {
147            return Err(Error::Kit(KErr::Truncated {
148                what: "PDF header",
149                needed: 8,
150                found: data.len(),
151            }));
152        }
153        // %PDF- may sit within the first KiB of a damaged file; binary junk
154        // before the signature is tolerated by every reader
155        let sig = data[..1024.min(data.len())]
156            .windows(5)
157            .position(|w| w == b"%PDF-")
158            .ok_or(KErr::InvalidMagic {
159                what: "PDF signature",
160            })?;
161        let _ = sig;
162
163        let xref = match read_xref(data, &limits) {
164            Ok(x) => x,
165            Err(_) => scan_xref(data, &limits)?,
166        };
167        let trailer = &xref.trailer;
168
169        let encrypt = dict_get(trailer, b"Encrypt").and_then(Obj::as_ref);
170
171        let root = dict_get(trailer, b"Root")
172            .and_then(Obj::as_ref)
173            .ok_or(KErr::BadValue("trailer /Root"))?;
174
175        let mut doc = Document {
176            data,
177            xref,
178            limits,
179            pages: Vec::new(),
180            encrypt,
181            cache: RefCell::new(BTreeMap::new()),
182            objstms: RefCell::new(BTreeMap::new()),
183        };
184        let catalog = doc.resolve_obj(root)?;
185        let catalog_dict = catalog.dict().ok_or(KErr::BadValue("/Root"))?;
186        match dict_get(catalog_dict, b"Type") {
187            Some(Obj::Name(n)) if n.as_slice() == b"Catalog" => {}
188            _ => return Err(KErr::BadValue("/Root is not a Catalog").into()),
189        }
190        let pages_ref = dict_get(catalog_dict, b"Pages")
191            .and_then(Obj::as_ref)
192            .ok_or(KErr::BadValue("catalog /Pages"))?;
193        doc.collect_pages(pages_ref, &[])?;
194        if doc.pages.is_empty() {
195            return Err(KErr::BadValue("empty page tree").into());
196        }
197        Ok(doc)
198    }
199
200    /// `true` when the xref table could not be parsed and was rebuilt by
201    /// scanning for object headers.
202    pub fn xref_was_rebuilt(&self) -> bool {
203        self.xref.rebuilt
204    }
205
206    /// `true` when the trailer declares `/Encrypt`. Text extraction refuses
207    /// encrypted documents with `Error::Unsupported`-bearing errors.
208    pub fn is_encrypted(&self) -> bool {
209        self.encrypt.is_some()
210    }
211
212    /// The `/Encrypt` object reference, if present.
213    pub fn encryption(&self) -> Option<Ref> {
214        self.encrypt
215    }
216
217    /// Page count.
218    pub fn pages(&self) -> usize {
219        self.pages.len()
220    }
221
222    /// Resolve and parse one indirect object (public for inspection; the
223    /// returned object is owned).
224    pub fn object(&self, r: Ref) -> Result<Obj> {
225        self.resolve_obj(r).map_err(|e| Error::Object {
226            object: r.num,
227            generation: r.generation,
228            cause: Box::new(match e {
229                Error::Kit(k) => Error::Kit(k),
230                other => other,
231            }),
232        })
233    }
234
235    /// Extract one page's text.
236    pub fn page_text(&self, page: usize) -> Result<String> {
237        let p = self
238            .pages
239            .get(page)
240            .ok_or(KErr::BadValue("page index"))
241            .map_err(Error::from)?;
242        if let Some(er) = self.encrypt {
243            return Err(Error::Page {
244                page,
245                cause: Box::new(Error::Object {
246                    object: er.num,
247                    generation: er.generation,
248                    cause: Box::new(Error::Kit(KErr::Unsupported(
249                        "encrypted document (/Encrypt in trailer)",
250                    ))),
251                }),
252            });
253        }
254        self.page_text_inner(p).map_err(|e| Error::Page {
255            page,
256            cause: Box::new(e),
257        })
258    }
259
260    /// Extract the whole document's text, pages joined by `\x0c` (form
261    /// feed). The first failing page aborts with [`Error::Page`].
262    pub fn text(&self) -> Result<String> {
263        let mut out = String::new();
264        for i in 0..self.pages.len() {
265            if i > 0 {
266                out.push('\x0c');
267            }
268            out.push_str(&self.page_text(i)?);
269        }
270        Ok(out)
271    }
272
273    // -- internals -----------------------------------------------------------
274
275    fn resolve_obj(&self, r: Ref) -> Result<Obj> {
276        if let Some(o) = self.cache.borrow().get(&r.num) {
277            return Ok(o.clone());
278        }
279        let loc = self
280            .xref
281            .map
282            .get(&r.num)
283            .copied()
284            .ok_or(KErr::BadValue("reference to missing object"))?;
285        let obj = match loc {
286            Loc::Plain { offset, generation } => {
287                if generation != r.generation {
288                    // generation mismatch: the xref points at a different
289                    // revision of this object. Readers tolerate it; we do
290                    // too, since the location is still authoritative.
291                }
292                let p = parse_obj(self.data, offset)?;
293                let mut obj = p.obj;
294                if let Some((len_ref, start)) = p.pending {
295                    // re-slice stream data using the resolved length
296                    let len = self
297                        .resolve_obj(len_ref)?
298                        .as_i64()
299                        .ok_or(KErr::BadValue("indirect /Length value"))?;
300                    if len < 0 {
301                        return Err(KErr::BadValue("negative /Length").into());
302                    }
303                    if let Obj::Stream { data: d, .. } = &mut obj {
304                        let end = start
305                            .checked_add(len as usize)
306                            .and_then(|e| self.data.get(..e).map(|_| e))
307                            .ok_or(KErr::Truncated {
308                                what: "stream data",
309                                needed: len as usize,
310                                found: self.data.len().saturating_sub(start),
311                            })?;
312                        *d = self.data[start..end].to_vec();
313                    }
314                }
315                obj
316            }
317            Loc::InStm { stm, idx } => {
318                self.expand_objstm(stm)?;
319                let members = self.objstms.borrow();
320                let list = members
321                    .get(&stm)
322                    .ok_or(KErr::BadValue("object stream missing"))?;
323                let (n, o) = list
324                    .get(idx as usize)
325                    .ok_or(KErr::BadValue("object stream index"))?;
326                if *n != r.num {
327                    return Err(KErr::BadValue("object stream member number").into());
328                }
329                o.clone()
330            }
331        };
332        self.cache.borrow_mut().insert(r.num, obj.clone());
333        Ok(obj)
334    }
335
336    /// Decode an `/ObjStm` stream and cache its members.
337    fn expand_objstm(&self, stm: u32) -> Result<()> {
338        if self.objstms.borrow().contains_key(&stm) {
339            return Ok(());
340        }
341        let sobj = self.resolve_obj(Ref {
342            num: stm,
343            generation: 0,
344        })?;
345        let dict = sobj.dict().ok_or(KErr::BadValue("ObjStm dict"))?;
346        let n = dict_get(dict, b"N")
347            .and_then(Obj::as_usize)
348            .ok_or(KErr::BadValue("ObjStm /N"))?;
349        let first = dict_get(dict, b"First")
350            .and_then(Obj::as_usize)
351            .ok_or(KErr::BadValue("ObjStm /First"))?;
352        let raw = decode_stream(&sobj, &self.limits)?;
353        if first > raw.len() {
354            return Err(KErr::BadValue("ObjStm /First").into());
355        }
356        // header: N pairs of `objnum offset` integers
357        let mut lx = crate::lex::Lexer::new(&raw);
358        let mut hdr: Vec<(u32, usize)> = Vec::with_capacity(n);
359        for _ in 0..n {
360            let num = lx.expect_int("ObjStm member number")?;
361            let off = lx.expect_int("ObjStm member offset")?;
362            if num < 0 || num > i64::from(u32::MAX) || off < 0 {
363                return Err(KErr::BadValue("ObjStm member header").into());
364            }
365            let off = off as usize;
366            if off >= raw.len() - first && off != 0 {
367                return Err(KErr::BadValue("ObjStm member offset").into());
368            }
369            hdr.push((num as u32, off));
370        }
371        let mut members: Vec<(u32, Obj)> = Vec::with_capacity(n);
372        for (i, &(num, off)) in hdr.iter().enumerate() {
373            let start = first + off;
374            let end = hdr.get(i + 1).map(|&(_, o)| first + o).unwrap_or(raw.len());
375            if start > raw.len() || end > raw.len() || end < start {
376                return Err(KErr::BadValue("ObjStm member span").into());
377            }
378            let slice = &raw[start..end];
379            let p = crate::object::parse_standalone(slice, 0)?;
380            members.push((num, p.obj));
381        }
382        self.objstms.borrow_mut().insert(stm, members);
383        Ok(())
384    }
385
386    /// Walk the page tree collecting `/Page` nodes in document order with
387    /// inherited `/Resources`.
388    fn collect_pages(&mut self, node: Ref, inherited: &[(Vec<u8>, Obj)]) -> Result<()> {
389        let mut stack: Vec<PageStackItem> = alloc::vec![(node, inherited.to_vec())];
390        let mut visited = 0usize;
391        while let Some((r, inh)) = stack.pop() {
392            visited += 1;
393            if visited > 1 << 16 {
394                return Err(KErr::TooLarge {
395                    what: "page tree nodes",
396                    limit: 1 << 16,
397                }
398                .into());
399            }
400            let obj = self.resolve_obj(r)?;
401            let dict = obj.dict().ok_or(KErr::BadValue("page tree node"))?;
402            // resources inherit: merge inherited with node's own (node wins)
403            let mut res = inh.clone();
404            if let Some(rd) = dict_get(dict, b"Resources").and_then(Obj::dict) {
405                for (k, v) in rd {
406                    res.retain(|(ek, _)| ek != k);
407                    res.push((k.clone(), v.clone()));
408                }
409            }
410            match dict_get(dict, b"Type") {
411                Some(Obj::Name(t)) if t.as_slice() == b"Page" => {
412                    self.pages.push(Page { r, resources: res });
413                }
414                Some(Obj::Name(t)) if t.as_slice() == b"Pages" => {
415                    match dict_get(dict, b"Kids").and_then(Obj::as_array) {
416                        Some(kids) => {
417                            // push reversed so document order pops first
418                            for k in kids.iter().rev() {
419                                let kr =
420                                    k.as_ref().ok_or(KErr::BadValue("page tree /Kids entry"))?;
421                                stack.push((kr, res.clone()));
422                            }
423                        }
424                        None => return Err(KErr::BadValue("page tree /Kids").into()),
425                    }
426                }
427                // a node without a recognizable type is still walked via
428                // /Kids for tolerance
429                _ => {
430                    if let Some(kids) = dict_get(dict, b"Kids").and_then(Obj::as_array) {
431                        for k in kids.iter().rev() {
432                            let kr = k.as_ref().ok_or(KErr::BadValue("page tree /Kids entry"))?;
433                            stack.push((kr, res.clone()));
434                        }
435                    } else {
436                        self.pages.push(Page { r, resources: res });
437                    }
438                }
439            }
440        }
441        Ok(())
442    }
443
444    fn page_text_inner(&self, p: &Page) -> Result<String> {
445        let page_obj = self.resolve_obj(p.r)?;
446        let dict = page_obj.dict().ok_or(KErr::BadValue("page dictionary"))?;
447        // /Contents: absent -> empty text; stream ref -> one stream;
448        // array of refs -> concatenated in array order
449        let contents = match dict_get(dict, b"Contents") {
450            None | Some(Obj::Null) => return Ok(String::new()),
451            Some(o) => o.clone(),
452        };
453        let mut streams: Vec<Vec<u8>> = Vec::new();
454        let collect = |o: &Obj, streams: &mut Vec<Vec<u8>>| -> Result<()> {
455            let o = match o {
456                Obj::Ref(r) => self.resolve_obj(*r)?,
457                other => other.clone(),
458            };
459            if let Obj::Stream { .. } = o {
460                let raw = decode_stream(&o, &self.limits)?;
461                streams.push(raw);
462                Ok(())
463            } else {
464                Err(KErr::BadValue("page /Contents entry").into())
465            }
466        };
467        match &contents {
468            Obj::Arr(a) => {
469                for o in a {
470                    collect(o, &mut streams)?;
471                }
472            }
473            other => collect(other, &mut streams)?,
474        }
475        let res = Resources::from_dict(Some(&p.resources));
476        extract_page_text(&streams, &res, self, &self.limits).map_err(Error::Kit)
477    }
478}