Skip to main content

hayro_syntax/
xref.rs

1//! Reading and querying the xref table of a PDF file.
2
3use crate::crypto::{DecryptionError, DecryptionTarget, Decryptor, get};
4use crate::data::Data;
5use crate::metadata::Metadata;
6use crate::object::Name;
7use crate::object::ObjectIdentifier;
8use crate::object::Stream;
9use crate::object::dict::keys::{
10    AUTHOR, CREATION_DATE, CREATOR, ENCRYPT, FIRST, ID, INDEX, INFO, KEYWORDS, MOD_DATE, N,
11    OCPROPERTIES, PAGES, PREV, PRODUCER, ROOT, SIZE, SUBJECT, TITLE, TYPE, VERSION, W, XREF_STM,
12};
13use crate::object::dict::probe_dict;
14use crate::object::indirect::IndirectObject;
15use crate::object::{Array, MaybeRef};
16use crate::object::{DateTime, Dict};
17use crate::object::{Object, ObjectLike};
18use crate::pdf::PdfVersion;
19use crate::reader::Reader;
20use crate::reader::{Readable, ReaderContext, ReaderExt};
21use crate::sync::{Arc, FxHashMap, RwLock, RwLockExt};
22use crate::trivia::is_white_space_character;
23use crate::util::findr_needle;
24use crate::{PdfData, object};
25use alloc::boxed::Box;
26use alloc::collections::BTreeSet;
27use alloc::vec;
28use alloc::vec::Vec;
29use core::cmp::max;
30use core::iter;
31use core::ops::Deref;
32
33pub(crate) const XREF_ENTRY_LEN: usize = 20;
34
35#[derive(Debug, Copy, Clone)]
36pub(crate) enum XRefError {
37    Unknown,
38    Encryption(DecryptionError),
39}
40
41/// Parse the "root" xref from the PDF.
42pub(crate) fn root_xref(data: PdfData, password: &[u8]) -> Result<XRef, XRefError> {
43    let mut xref_map = FxHashMap::default();
44    let xref_pos = find_last_xref_pos(data.as_ref()).ok_or(XRefError::Unknown)?;
45    let trailer =
46        populate_xref_impl(data.as_ref(), xref_pos, &mut xref_map).ok_or(XRefError::Unknown)?;
47
48    XRef::new(
49        data.clone(),
50        xref_map,
51        XRefInput::TrailerDictData(trailer),
52        false,
53        password,
54    )
55}
56
57/// Try to manually parse the PDF to build an xref table and trailer dictionary.
58pub(crate) fn fallback(data: PdfData, password: &[u8]) -> Option<XRef> {
59    let (xref_map, xref_input) = fallback_xref_map(&data, password);
60
61    if let Some(xref_input) = xref_input {
62        XRef::new(data.clone(), xref_map, xref_input, true, password).ok()
63    } else {
64        None
65    }
66}
67
68fn fallback_xref_map<'a>(data: &'a PdfData, password: &[u8]) -> (XrefMap, Option<XRefInput<'a>>) {
69    fallback_xref_map_inner(data, ReaderContext::dummy(), true, password)
70}
71
72fn fallback_xref_map_inner<'a>(
73    data: &'a PdfData,
74    mut dummy_ctx: ReaderContext<'a>,
75    recurse: bool,
76    password: &[u8],
77) -> (XrefMap, Option<XRefInput<'a>>) {
78    let mut xref_map = FxHashMap::default();
79    let mut trailer_dicts = vec![];
80    let mut root_ref = None;
81
82    let mut r = Reader::new(data.as_ref());
83
84    let mut last_obj_num = None;
85
86    loop {
87        let cur_pos = r.offset();
88
89        let mut old_r = r.clone();
90
91        // First try to check if we have an object identifier.
92        if r.peek_byte().is_some_and(|b: u8| b.is_ascii_digit()) {
93            if let Some(obj_id) = r.read::<ObjectIdentifier>(&dummy_ctx) {
94                let mut cloned = r.clone();
95                // Check that the object following it is actually valid before inserting it.
96                cloned.skip_white_spaces_and_comments();
97                if cloned.skip::<Object<'_>>(false).is_some() {
98                    xref_map.insert(obj_id, EntryType::Normal(cur_pos));
99                    last_obj_num = Some(obj_id);
100                    dummy_ctx.set_obj_number(obj_id);
101                }
102            } else {
103                // There must be a white space before the next object number.
104                r.forward_while(|b| !is_white_space_character(b));
105            }
106        } else {
107            // Then, try to check whether we have a dictionary, in particular a trailer
108            // dictionary.
109            let mut probe_reader = r.clone();
110            if r.peek_bytes(2).is_some_and(|b| b == b"<<")
111                && let Some(probe) =
112                    { probe_dict(&mut probe_reader, &dummy_ctx, Some(b"<<"), b">>") }
113            {
114                r = probe_reader;
115                if probe.has_root || probe.has_type {
116                    let mut dict_reader = Reader::new(probe.data);
117                    if let Some(dict) = dict_reader.read_with_context::<Dict<'_>>(&dummy_ctx) {
118                        if probe.has_root && dict.contains_key(ROOT) {
119                            trailer_dicts.push(dict.clone());
120                        }
121
122                        if dict
123                            .get::<Name<'_>>(TYPE)
124                            .is_some_and(|n| n.as_str() == "Catalog")
125                        {
126                            root_ref = last_obj_num;
127                        }
128
129                        if dict.get::<Name<'_>>(TYPE).as_deref() == Some(b"ObjStm")
130                            && {
131                                let mut after_dict = r.clone();
132                                after_dict.skip_white_spaces_and_comments();
133                                after_dict.forward_tag(b"stream").is_some()
134                            }
135                            && let Some(stream) = old_r.read::<Stream<'_>>(&dummy_ctx)
136                            && let Some(last_obj_num) = last_obj_num
137                            && let Some(obj_stream) = ObjectStream::new(&stream)
138                        {
139                            for (idx, (obj_num, offset)) in obj_stream.offsets.iter().enumerate() {
140                                if offset.is_none() {
141                                    continue;
142                                }
143
144                                let id = ObjectIdentifier::new(*obj_num, 0);
145                                // If we already found an entry for that object number that was not
146                                // inside an object stream. Somewhat arbitrary and maybe
147                                // we can do better, but that seems to work for the current
148                                // set of tests.
149                                if xref_map
150                                    .get(&id)
151                                    .is_none_or(|e| !matches!(e, &EntryType::Normal(_)))
152                                {
153                                    xref_map.insert(
154                                        id,
155                                        EntryType::ObjStream(last_obj_num.obj_number, idx as u32),
156                                    );
157                                }
158                            }
159                        }
160                    }
161                }
162            } else {
163                // We can skip everything until the next white space character,
164                // as there cannot possibly be any new dictionary/object identifier
165                // until then.
166                let old_pos = r.offset;
167                r.forward_while(|b| !is_white_space_character(b));
168                if r.offset == old_pos {
169                    r.read_byte();
170                }
171            }
172        }
173
174        if r.at_end() {
175            break;
176        }
177    }
178
179    // Try to choose the right trailer dict by doing basic validation.
180    let mut trailer_dict = None;
181
182    for dict in trailer_dicts {
183        if let Some(root_id) = dict.get_raw::<Dict<'_>>(ROOT) {
184            let check = |dict: &Dict<'_>| -> bool { dict.contains_key(PAGES) };
185
186            match root_id {
187                MaybeRef::Ref(r) => match xref_map.get(&r.into()) {
188                    Some(EntryType::Normal(offset)) => {
189                        let mut reader = Reader::new(&data.as_ref()[*offset..]);
190
191                        if let Some(obj) =
192                            reader.read_with_context::<IndirectObject<Dict<'_>>>(&dummy_ctx)
193                            && {
194                                let obj = obj.get();
195                                check(&obj)
196                            }
197                        {
198                            trailer_dict = Some(dict);
199                        }
200                    }
201                    Some(EntryType::ObjStream(obj_num, idx)) => {
202                        if let Some(EntryType::Normal(offset)) =
203                            xref_map.get(&ObjectIdentifier::new(*obj_num, 0))
204                        {
205                            let mut reader = Reader::new(&data.as_ref()[*offset..]);
206
207                            if let Some(stream) =
208                                reader.read_with_context::<IndirectObject<Stream<'_>>>(&dummy_ctx)
209                                && {
210                                    if let Some(object_stream) = ObjectStream::new(&stream.get())
211                                        && let Some(obj) =
212                                            object_stream.get::<Dict<'_>>(*idx, &dummy_ctx)
213                                    {
214                                        check(&obj)
215                                    } else {
216                                        false
217                                    }
218                                }
219                            {
220                                trailer_dict = Some(dict);
221                            }
222                        }
223                    }
224                    _ => {}
225                },
226                MaybeRef::NotRef(d) => {
227                    if check(&d) {
228                        trailer_dict = Some(dict);
229                    }
230                }
231            }
232        }
233    }
234
235    let has_encryption = trailer_dict
236        .as_ref()
237        .is_some_and(|t| t.contains_key(ENCRYPT));
238
239    if has_encryption && recurse {
240        // The problem is that in this case, we have used a dummy reader context which does not have
241        // a decryptor. Therefore, we were unable to decrypt any of the object streams and missed
242        // all objects that are inside of such a stream. Therefore, we need to redo the process
243        // using a `ReaderContext` that does have the ability to decrypt.
244        if let Ok(xref) = XRef::new(
245            data.clone(),
246            xref_map.clone(),
247            XRefInput::TrailerDictData(trailer_dict.as_ref().map(|d| d.data()).unwrap()),
248            true,
249            password,
250        ) {
251            let ctx = ReaderContext::new(&xref, false);
252            let (patched_map, _) = fallback_xref_map_inner(data, ctx, false, password);
253            xref_map = patched_map;
254        }
255    }
256
257    if let Some(trailer_dict_data) = trailer_dict.map(|d| d.data()) {
258        (
259            xref_map,
260            Some(XRefInput::TrailerDictData(trailer_dict_data)),
261        )
262    } else if let Some(root_ref) = root_ref {
263        (xref_map, Some(XRefInput::RootRef(root_ref)))
264    } else {
265        (xref_map, None)
266    }
267}
268
269const DUMMY_XREF: XRef = XRef(Inner::Dummy);
270
271/// An xref table.
272#[derive(Debug, Clone)]
273pub struct XRef(Inner);
274
275impl XRef {
276    fn new(
277        data: PdfData,
278        xref_map: XrefMap,
279        input: XRefInput<'_>,
280        repaired: bool,
281        password: &[u8],
282    ) -> Result<Self, XRefError> {
283        // This is a bit hacky, but the problem is we can't read the resolved trailer dictionary
284        // before we actually created the xref struct. So we first create it using dummy data
285        // and then populate the data.
286        let trailer_data = TrailerData::dummy();
287
288        let mut xref = Self(Inner::Some(Arc::new(SomeRepr {
289            data: Arc::new(Data::new(data)),
290            map: Arc::new(RwLock::new(MapRepr { xref_map, repaired })),
291            decryptor: Arc::new(Decryptor::None),
292            has_ocgs: false,
293            metadata: Arc::new(Metadata::default()),
294            trailer_data,
295            password: password.to_vec(),
296        })));
297
298        // We read the trailer twice, once to determine the encryption used and then a second
299        // time to resolve the catalog dictionary, etc. This allows us to support catalog dictionaries
300        // that are stored in an encrypted object stream.
301
302        let decryptor = {
303            match input {
304                XRefInput::TrailerDictData(trailer_dict_data) => {
305                    let mut r = Reader::new(trailer_dict_data);
306
307                    let trailer_dict = r
308                        .read_with_context::<Dict<'_>>(&ReaderContext::new(&xref, false))
309                        .ok_or(XRefError::Unknown)?;
310
311                    get_decryptor(&trailer_dict, password)?
312                }
313                XRefInput::RootRef(_) => Decryptor::None,
314            }
315        };
316
317        match &mut xref.0 {
318            Inner::Dummy => unreachable!(),
319            Inner::Some(r) => {
320                let mutable = Arc::make_mut(r);
321                mutable.decryptor = Arc::new(decryptor.clone());
322            }
323        }
324
325        let (trailer_data, has_ocgs, metadata) = match input {
326            XRefInput::TrailerDictData(trailer_dict_data) => {
327                let mut r = Reader::new(trailer_dict_data);
328
329                let trailer_dict = r
330                    .read_with_context::<Dict<'_>>(&ReaderContext::new(&xref, false))
331                    .ok_or(XRefError::Unknown)?;
332
333                let root_ref = trailer_dict.get_ref(ROOT).ok_or(XRefError::Unknown)?;
334                let root = trailer_dict
335                    .get::<Dict<'_>>(ROOT)
336                    .ok_or(XRefError::Unknown)?;
337                let metadata = trailer_dict
338                    .get::<Dict<'_>>(INFO)
339                    .map(|d| parse_metadata(&d))
340                    .unwrap_or_default();
341                let pages_ref = root.get_ref(PAGES).ok_or(XRefError::Unknown)?;
342                let has_ocgs = root.get::<Dict<'_>>(OCPROPERTIES).is_some();
343                let version = root
344                    .get::<Name<'_>>(VERSION)
345                    .and_then(|v| PdfVersion::from_bytes(v.deref()));
346
347                let td = TrailerData {
348                    pages_ref: pages_ref.into(),
349                    root_ref: root_ref.into(),
350                    version,
351                };
352
353                (td, has_ocgs, metadata)
354            }
355            XRefInput::RootRef(root_ref) => {
356                let root = xref.get::<Dict<'_>>(root_ref).ok_or(XRefError::Unknown)?;
357                let pages_ref = root.get_ref(PAGES).ok_or(XRefError::Unknown)?;
358
359                let td = TrailerData {
360                    pages_ref: pages_ref.into(),
361                    root_ref,
362                    version: None,
363                };
364
365                (td, false, Metadata::default())
366            }
367        };
368
369        match &mut xref.0 {
370            Inner::Dummy => unreachable!(),
371            Inner::Some(r) => {
372                let mutable = Arc::make_mut(r);
373                mutable.trailer_data = trailer_data;
374                mutable.decryptor = Arc::new(decryptor);
375                mutable.has_ocgs = has_ocgs;
376                mutable.metadata = Arc::new(metadata);
377            }
378        }
379
380        Ok(xref)
381    }
382
383    pub(crate) fn dummy() -> &'static Self {
384        &DUMMY_XREF
385    }
386
387    pub(crate) fn len(&self) -> usize {
388        match &self.0 {
389            Inner::Dummy => 0,
390            Inner::Some(r) => r.map.get().xref_map.len(),
391        }
392    }
393
394    pub(crate) fn trailer_data(&self) -> &TrailerData {
395        match &self.0 {
396            Inner::Dummy => unreachable!(),
397            Inner::Some(r) => &r.trailer_data,
398        }
399    }
400
401    pub(crate) fn metadata(&self) -> &Metadata {
402        match &self.0 {
403            Inner::Dummy => unreachable!(),
404            Inner::Some(r) => &r.metadata,
405        }
406    }
407
408    /// Return the object ID of the root dictionary.
409    pub fn root_id(&self) -> ObjectIdentifier {
410        self.trailer_data().root_ref
411    }
412
413    /// Whether the PDF has optional content groups.
414    pub fn has_optional_content_groups(&self) -> bool {
415        match &self.0 {
416            Inner::Dummy => false,
417            Inner::Some(r) => r.has_ocgs,
418        }
419    }
420
421    pub(crate) fn objects(&self) -> impl IntoIterator<Item = Object<'_>> + '_ {
422        match &self.0 {
423            Inner::Dummy => unimplemented!(),
424            Inner::Some(r) => {
425                let locked = r.map.get();
426                let mut elements = locked
427                    .xref_map
428                    .iter()
429                    .map(|(id, e)| {
430                        let offset = match e {
431                            EntryType::Normal(o) => (*o, 0),
432                            EntryType::ObjStream(id, index) => {
433                                if let Some(EntryType::Normal(offset)) =
434                                    locked.xref_map.get(&ObjectIdentifier::new(*id, 0))
435                                {
436                                    (*offset, *index)
437                                } else {
438                                    (usize::MAX, 0)
439                                }
440                            }
441                        };
442
443                        (*id, offset)
444                    })
445                    .collect::<Vec<_>>();
446
447                // Try to yield in the order the objects appeared in the
448                // PDF.
449                elements.sort_by_key(|e1| e1.1);
450
451                let mut iter = elements.into_iter();
452
453                iter::from_fn(move || {
454                    for next in iter.by_ref() {
455                        if let Some(obj) = self.get_with(next.0, &ReaderContext::new(self, false)) {
456                            return Some(obj);
457                        } else {
458                            // Skip invalid objects.
459                            continue;
460                        }
461                    }
462
463                    None
464                })
465            }
466        }
467    }
468
469    pub(crate) fn repair(&self) {
470        let Inner::Some(r) = &self.0 else {
471            unreachable!();
472        };
473
474        let mut locked = r.map.put();
475        if locked.repaired {
476            return;
477        }
478
479        let (xref_map, _) = fallback_xref_map(r.data.get(), &r.password);
480        locked.xref_map = xref_map;
481        locked.repaired = true;
482    }
483
484    #[inline]
485    pub(crate) fn needs_decryption(&self, ctx: &ReaderContext<'_>) -> bool {
486        match &self.0 {
487            Inner::Dummy => false,
488            Inner::Some(r) => {
489                if matches!(r.decryptor.as_ref(), Decryptor::None) {
490                    false
491                } else {
492                    !ctx.in_content_stream() && !ctx.in_object_stream()
493                }
494            }
495        }
496    }
497
498    #[inline]
499    pub(crate) fn decrypt(
500        &self,
501        id: ObjectIdentifier,
502        data: &[u8],
503        target: DecryptionTarget<'_>,
504    ) -> Option<Vec<u8>> {
505        match &self.0 {
506            Inner::Dummy => Some(data.to_vec()),
507            Inner::Some(r) => r.decryptor.decrypt(id, data, target),
508        }
509    }
510
511    /// Return the object with the given identifier.
512    #[allow(private_bounds)]
513    pub fn get<'a, T>(&'a self, id: ObjectIdentifier) -> Option<T>
514    where
515        T: ObjectLike<'a>,
516    {
517        let ctx = ReaderContext::new(self, false);
518        self.get_with(id, &ctx)
519    }
520
521    /// Return the object with the given identifier.
522    #[allow(private_bounds)]
523    pub(crate) fn get_with<'a, T>(
524        &'a self,
525        id: ObjectIdentifier,
526        ctx: &ReaderContext<'a>,
527    ) -> Option<T>
528    where
529        T: ObjectLike<'a>,
530    {
531        let Inner::Some(repr) = &self.0 else {
532            return None;
533        };
534
535        // The flag must describe the map this entry came from.
536        let (entry, was_repaired) = {
537            let locked = repr.map.get();
538            // References to undefined objects are treated as null.
539            (*locked.xref_map.get(&id)?, locked.repaired)
540        };
541
542        let mut r = Reader::new(repr.data.get().as_ref());
543        let caller_ctx = ctx;
544        let mut ctx = ctx.clone();
545        ctx.set_obj_number(id);
546        ctx.set_in_content_stream(false);
547
548        match entry {
549            EntryType::Normal(offset) => {
550                ctx.set_in_object_stream(false);
551                r.jump(offset);
552
553                if let Some(object) = r.read_with_context::<IndirectObject<T>>(&ctx) {
554                    if object.id() == &id {
555                        return Some(object.get());
556                    }
557                } else {
558                    // There is a valid object at the offset, it's just not of the type the caller
559                    // expected, which is fine.
560                    if r.skip::<IndirectObject<Object<'_>>>(false).is_some() {
561                        return None;
562                    }
563                };
564
565                self.repair_and_get(id, caller_ctx, was_repaired)
566            }
567            EntryType::ObjStream(obj_stram_gen_num, index) => {
568                // Generation number is implicitly 0.
569                let obj_stream_id = ObjectIdentifier::new(obj_stram_gen_num, 0);
570
571                if obj_stream_id == id || ctx.parent_chain_contains(&id) {
572                    return None;
573                }
574
575                // An object stream is a stream, so it can't itself be stored in an object
576                // stream (ISO 32000-2, 7.5.7).
577                if !matches!(
578                    repr.map.get().xref_map.get(&obj_stream_id),
579                    Some(EntryType::Normal(_))
580                ) {
581                    return None;
582                }
583
584                ctx.parent_chain_push(id);
585
586                let object_stream = repr.data.object_stream(obj_stream_id, &ctx)?;
587
588                if object_stream.obj_number(index) == Some(id.obj_number) {
589                    return object_stream.get(index, &ctx);
590                }
591
592                self.repair_and_get(id, caller_ctx, was_repaired)
593            }
594        }
595    }
596
597    fn repair_and_get<'a, T>(
598        &'a self,
599        id: ObjectIdentifier,
600        ctx: &ReaderContext<'a>,
601        was_repaired: bool,
602    ) -> Option<T>
603    where
604        T: ObjectLike<'a>,
605    {
606        // The xref table is broken, try to repair if not already repaired.
607        if was_repaired {
608            None
609        } else {
610            self.repair();
611
612            // Now try reading again.
613            self.get_with::<T>(id, ctx)
614        }
615    }
616}
617
618/// An input that is passed to the xref constructor so that we can fully resolve
619/// the PDF.
620#[derive(Debug, Copy, Clone)]
621pub(crate) enum XRefInput<'a> {
622    /// This option is going to be uesd in 99.999% of the case. It contains the
623    /// raw data of the trailer dictionary which is then going to be processed.
624    TrailerDictData(&'a [u8]),
625    /// In case the trailer dictionary could not be read (for example because
626    /// it is cut-off), we just pass the object ID of the root dictionary
627    /// in case we have found one, and try our best to build the PDF just
628    /// with the information we have there.
629    ///
630    /// Note that this won't work if the document is encrypted, as we
631    /// can't access the crypto dictionary.
632    RootRef(ObjectIdentifier),
633}
634
635pub(crate) fn find_last_xref_pos(data: &[u8]) -> Option<usize> {
636    let needle = b"startxref";
637    let pos = findr_needle(data, needle)?;
638    let mut finder = Reader::new(data);
639    finder.jump(pos);
640    finder.forward_tag(needle)?;
641    finder.skip_white_spaces_and_comments();
642    finder.read_without_context::<i32>()?.try_into().ok()
643}
644
645/// A type of xref entry.
646#[derive(Debug, PartialEq, Eq, Clone, Copy)]
647enum EntryType {
648    /// An indirect object that is at a specific offset in the original data.
649    Normal(usize),
650    /// An indirect object that is part of an object stream. First number indicates the object
651    /// number of the _object stream_ (the generation number is always 0), the second number indicates
652    /// the index in the object stream.
653    ObjStream(u32, u32),
654}
655
656type XrefMap = FxHashMap<ObjectIdentifier, EntryType>;
657
658/// Representation of a proper xref table.
659#[derive(Debug)]
660struct MapRepr {
661    xref_map: XrefMap,
662    repaired: bool,
663}
664
665#[derive(Debug, Copy, Clone)]
666pub(crate) struct TrailerData {
667    pub(crate) pages_ref: ObjectIdentifier,
668    pub(crate) root_ref: ObjectIdentifier,
669    pub(crate) version: Option<PdfVersion>,
670}
671
672impl TrailerData {
673    pub(crate) fn dummy() -> Self {
674        Self {
675            pages_ref: ObjectIdentifier::new(0, 0),
676            root_ref: ObjectIdentifier::new(0, 0),
677            version: None,
678        }
679    }
680}
681
682#[derive(Debug, Clone)]
683struct SomeRepr {
684    data: Arc<Data>,
685    map: Arc<RwLock<MapRepr>>,
686    metadata: Arc<Metadata>,
687    decryptor: Arc<Decryptor>,
688    has_ocgs: bool,
689    password: Vec<u8>,
690    trailer_data: TrailerData,
691}
692
693#[derive(Debug, Clone)]
694enum Inner {
695    /// A dummy xref table that doesn't have any entries.
696    Dummy,
697    /// A proper xref table.
698    Some(Arc<SomeRepr>),
699}
700
701#[derive(Debug)]
702struct XRefEntry {
703    offset: usize,
704    gen_number: i32,
705    used: bool,
706}
707
708impl XRefEntry {
709    pub(crate) fn read(data: &[u8]) -> Option<Self> {
710        #[inline(always)]
711        fn parse_u32(data: &[u8]) -> Option<u32> {
712            let mut accum = 0_u32;
713
714            for byte in data {
715                accum = accum.checked_mul(10)?;
716
717                match *byte {
718                    b'0'..=b'9' => accum = accum.checked_add((*byte - b'0') as u32)?,
719                    _ => return None,
720                }
721            }
722
723            Some(accum)
724        }
725
726        let offset = parse_u32(&data[0..10])? as usize;
727        let gen_number = i32::try_from(parse_u32(&data[11..16])?).ok()?;
728
729        let used = data[17] == b'n';
730
731        Some(Self {
732            offset,
733            gen_number,
734            used,
735        })
736    }
737}
738
739fn populate_xref_impl<'a>(data: &'a [u8], pos: usize, xref_map: &mut XrefMap) -> Option<&'a [u8]> {
740    let mut visited = BTreeSet::new();
741    populate_xref_impl_inner(data, pos, xref_map, &mut visited)
742}
743
744/// Maximum number of allowed xref `Prev` pointers before we abort.
745const MAX_XREF_CHAIN_DEPTH: usize = 256;
746
747fn populate_xref_impl_inner<'a>(
748    data: &'a [u8],
749    pos: usize,
750    xref_map: &mut XrefMap,
751    visited: &mut BTreeSet<usize>,
752) -> Option<&'a [u8]> {
753    if !visited.insert(pos) {
754        return None;
755    }
756
757    if visited.len() > MAX_XREF_CHAIN_DEPTH {
758        return None;
759    }
760
761    let mut reader = Reader::new(data);
762    reader.jump(pos);
763    // In case the position points to before the object number of a xref stream.
764    reader.skip_white_spaces_and_comments();
765
766    let mut r2 = reader.clone();
767    if reader
768        .clone()
769        .read_without_context::<ObjectIdentifier>()
770        .is_some()
771    {
772        populate_from_xref_stream(data, &mut r2, xref_map, visited)
773    } else {
774        populate_from_xref_table(data, &mut r2, xref_map, visited)
775    }
776}
777
778pub(super) struct SubsectionHeader {
779    pub(super) start: u32,
780    pub(super) num_entries: u32,
781}
782
783impl Readable<'_> for SubsectionHeader {
784    fn read(r: &mut Reader<'_>, _: &ReaderContext<'_>) -> Option<Self> {
785        r.skip_white_spaces();
786        let start = r.read_without_context::<u32>()?;
787        r.skip_white_spaces();
788        let num_entries = r.read_without_context::<u32>()?;
789        r.skip_white_spaces();
790
791        Some(Self { start, num_entries })
792    }
793}
794
795/// Populate the xref table, and return the trailer dict.
796fn populate_from_xref_table<'a>(
797    data: &'a [u8],
798    reader: &mut Reader<'a>,
799    insert_map: &mut XrefMap,
800    visited: &mut BTreeSet<usize>,
801) -> Option<&'a [u8]> {
802    let trailer = {
803        let mut reader = reader.clone();
804        read_xref_table_trailer(&mut reader, &ReaderContext::dummy())?
805    };
806
807    reader.skip_white_spaces();
808    reader.forward_tag(b"xref")?;
809    reader.skip_white_spaces();
810
811    let mut max_obj = 0;
812
813    if let Some(prev) = trailer.get::<i32>(PREV) {
814        // First insert the entries from any previous xref tables.
815        populate_xref_impl_inner(data, prev as usize, insert_map, visited)?;
816    }
817
818    // In hybrid files, entries in `XRefStm` should have higher priority, therefore we insert them
819    // after looking at `PREV`.
820    if let Some(xref_stm) = trailer.get::<i32>(XREF_STM) {
821        populate_xref_impl_inner(data, xref_stm as usize, insert_map, visited)?;
822    }
823
824    while let Some(header) = reader.read_without_context::<SubsectionHeader>() {
825        reader.skip_white_spaces();
826
827        let start = header.start;
828        let end = start.checked_add(header.num_entries)?;
829
830        for obj_number in start..end {
831            max_obj = max(max_obj, obj_number);
832            let bytes = reader.read_bytes(XREF_ENTRY_LEN)?;
833            let entry = XRefEntry::read(bytes)?;
834
835            // Specification says we should ignore any object number > SIZE, but probably
836            // not important?
837            if entry.used {
838                insert_map.insert(
839                    ObjectIdentifier::new(obj_number, entry.gen_number),
840                    EntryType::Normal(entry.offset),
841                );
842            }
843        }
844    }
845
846    Some(trailer.data())
847}
848
849fn populate_from_xref_stream<'a>(
850    data: &'a [u8],
851    reader: &mut Reader<'a>,
852    insert_map: &mut XrefMap,
853    visited: &mut BTreeSet<usize>,
854) -> Option<&'a [u8]> {
855    let stream = reader
856        .read_with_context::<IndirectObject<Stream<'_>>>(&ReaderContext::dummy())?
857        .get();
858
859    if let Some(prev) = stream.dict().get::<i32>(PREV) {
860        // First insert the entries from any previous xref tables.
861        let _ = populate_xref_impl_inner(data, prev as usize, insert_map, visited)?;
862    }
863
864    let size = stream.dict().get::<u32>(SIZE)?;
865
866    let [f1_len, f2_len, f3_len] = stream.dict().get::<[u8; 3]>(W)?;
867
868    if f2_len == 0 || f2_len > size_of::<u64>() as u8 {
869        return None;
870    }
871
872    let xref_data = stream.decoded_data().ok()?;
873    let mut xref_reader = Reader::new(xref_data.as_ref());
874
875    if let Some(arr) = stream.dict().get::<Array<'_>>(INDEX) {
876        let iter = arr.iter::<(u32, u32)>();
877
878        for (start, num_elements) in iter {
879            xref_stream_subsection(
880                &mut xref_reader,
881                start,
882                num_elements,
883                f1_len,
884                f2_len,
885                f3_len,
886                insert_map,
887            )?;
888        }
889    } else {
890        xref_stream_subsection(
891            &mut xref_reader,
892            0,
893            size,
894            f1_len,
895            f2_len,
896            f3_len,
897            insert_map,
898        )?;
899    }
900
901    Some(stream.dict().data())
902}
903
904fn xref_stream_num(data: &[u8]) -> Option<u32> {
905    Some(match data.len() {
906        0 => return None,
907        1 => u8::from_be(data[0]) as u32,
908        2 => u16::from_be_bytes(data[0..2].try_into().ok()?) as u32,
909        3 => u32::from_be_bytes([0, data[0], data[1], data[2]]),
910        4 => u32::from_be_bytes(data[0..4].try_into().ok()?),
911        8 => {
912            if let Ok(num) = u32::try_from(u64::from_be_bytes(data[0..8].try_into().ok()?)) {
913                return Some(num);
914            } else {
915                return None;
916            }
917        }
918        _n => {
919            return None;
920        }
921    })
922}
923
924fn xref_stream_subsection<'a>(
925    xref_reader: &mut Reader<'a>,
926    start: u32,
927    num_elements: u32,
928    f1_len: u8,
929    f2_len: u8,
930    f3_len: u8,
931    insert_map: &mut XrefMap,
932) -> Option<()> {
933    for i in 0..num_elements {
934        let f_type = if f1_len == 0 {
935            1
936        } else {
937            // We assume a length of 1.
938            xref_reader.read_bytes(1)?[0]
939        };
940
941        let obj_number = start.checked_add(i)?;
942
943        match f_type {
944            // We don't care about free objects.
945            0 => {
946                xref_reader.skip_bytes(f2_len as usize + f3_len as usize)?;
947            }
948            1 => {
949                let offset = if f2_len > 0 {
950                    let data = xref_reader.read_bytes(f2_len as usize)?;
951                    xref_stream_num(data)?
952                } else {
953                    0
954                };
955
956                let gen_number = if f3_len > 0 {
957                    let data = xref_reader.read_bytes(f3_len as usize)?;
958                    xref_stream_num(data)?
959                } else {
960                    0
961                };
962
963                insert_map.insert(
964                    ObjectIdentifier::new(obj_number, gen_number as i32),
965                    EntryType::Normal(offset as usize),
966                );
967            }
968            2 => {
969                let obj_stream_number = {
970                    let data = xref_reader.read_bytes(f2_len as usize)?;
971                    xref_stream_num(data)?
972                };
973                let gen_number = 0;
974                let index = if f3_len > 0 {
975                    let data = xref_reader.read_bytes(f3_len as usize)?;
976                    xref_stream_num(data)?
977                } else {
978                    0
979                };
980
981                insert_map.insert(
982                    ObjectIdentifier::new(obj_number, gen_number),
983                    EntryType::ObjStream(obj_stream_number, index),
984                );
985            }
986            _ => {
987                return None;
988            }
989        }
990    }
991
992    Some(())
993}
994
995fn read_xref_table_trailer<'a>(
996    reader: &mut Reader<'a>,
997    ctx: &ReaderContext<'a>,
998) -> Option<Dict<'a>> {
999    reader.skip_white_spaces();
1000    reader.forward_tag(b"xref")?;
1001    reader.skip_white_spaces();
1002
1003    while let Some(header) = reader.read_without_context::<SubsectionHeader>() {
1004        let len = XREF_ENTRY_LEN.checked_mul(header.num_entries as usize)?;
1005        reader.jump(reader.offset().checked_add(len)?);
1006    }
1007
1008    reader.skip_white_spaces();
1009    reader.forward_tag(b"trailer")?;
1010    reader.skip_white_spaces();
1011
1012    reader.read_with_context::<Dict<'_>>(ctx)
1013}
1014
1015fn get_decryptor(trailer_dict: &Dict<'_>, password: &[u8]) -> Result<Decryptor, XRefError> {
1016    if let Some(encryption_dict) = trailer_dict.get::<Dict<'_>>(ENCRYPT) {
1017        let id = if let Some(id) = trailer_dict
1018            .get::<Array<'_>>(ID)
1019            .and_then(|a| a.flex_iter().next::<object::String<'_>>())
1020        {
1021            id.to_vec()
1022        } else {
1023            // Assume an empty ID entry.
1024            vec![]
1025        };
1026
1027        get(&encryption_dict, &id, password).map_err(XRefError::Encryption)
1028    } else {
1029        Ok(Decryptor::None)
1030    }
1031}
1032
1033pub(crate) struct ObjectStream {
1034    data: Vec<u8>,
1035    offsets: Vec<(u32, Option<usize>)>,
1036}
1037
1038impl ObjectStream {
1039    pub(crate) fn new(stream: &Stream<'_>) -> Option<Self> {
1040        let data = stream.decoded_data().ok()?.into_owned();
1041        let offsets = Self::read_offsets(stream, &data).unwrap_or_default();
1042
1043        Some(Self { data, offsets })
1044    }
1045
1046    fn read_offsets(stream: &Stream<'_>, data: &[u8]) -> Option<Vec<(u32, Option<usize>)>> {
1047        let num_objects = stream.dict().get::<usize>(N)?;
1048        let first_offset = stream.dict().get::<usize>(FIRST)?;
1049
1050        let mut r = Reader::new(data);
1051
1052        let mut offsets = vec![];
1053        let mut last_offset = None;
1054
1055        for _ in 0..num_objects {
1056            r.skip_white_spaces_and_comments();
1057            // Skip object number
1058            let obj_num = r.read_without_context::<u32>()?;
1059            r.skip_white_spaces_and_comments();
1060            let relative_offset = r.read_without_context::<usize>()?;
1061            let offset = first_offset + relative_offset;
1062
1063            // Offsets must increase, otherwise any number of objects could share one member.
1064            // The start of the previous member excludes the white space before it, so that
1065            // offsets into that white space can't share it either.
1066            if offset >= data.len() || last_offset.is_some_and(|last| offset <= last) {
1067                offsets.push((obj_num, None));
1068            } else {
1069                let mut member = Reader::new(data);
1070                member.jump(offset);
1071                member.skip_white_spaces_and_comments();
1072                last_offset = Some(member.offset());
1073                offsets.push((obj_num, Some(member.offset())));
1074            }
1075        }
1076
1077        Some(offsets)
1078    }
1079
1080    fn obj_number(&self, index: u32) -> Option<u32> {
1081        Some(self.offsets.get(index as usize)?.0)
1082    }
1083
1084    fn get<'a, T>(&'a self, index: u32, ctx: &ReaderContext<'a>) -> Option<T>
1085    where
1086        T: ObjectLike<'a>,
1087    {
1088        let offset = self.offsets.get(index as usize)?.1?;
1089        let mut r = Reader::new(&self.data);
1090        r.jump(offset);
1091
1092        let mut ctx = ctx.clone();
1093        ctx.set_in_object_stream(true);
1094
1095        r.read_with_context::<T>(&ctx)
1096    }
1097}
1098
1099fn parse_metadata(info_dict: &Dict<'_>) -> Metadata {
1100    Metadata {
1101        creation_date: info_dict
1102            .get::<object::String<'_>>(CREATION_DATE)
1103            .and_then(|c| DateTime::from_bytes(&c)),
1104        modification_date: info_dict
1105            .get::<object::String<'_>>(MOD_DATE)
1106            .and_then(|c| DateTime::from_bytes(&c)),
1107        title: info_dict
1108            .get::<object::String<'_>>(TITLE)
1109            .map(|t| Box::from(t.as_bytes())),
1110        author: info_dict
1111            .get::<object::String<'_>>(AUTHOR)
1112            .map(|t| Box::from(t.as_bytes())),
1113        subject: info_dict
1114            .get::<object::String<'_>>(SUBJECT)
1115            .map(|t| Box::from(t.as_bytes())),
1116        keywords: info_dict
1117            .get::<object::String<'_>>(KEYWORDS)
1118            .map(|t| Box::from(t.as_bytes())),
1119        creator: info_dict
1120            .get::<object::String<'_>>(CREATOR)
1121            .map(|t| Box::from(t.as_bytes())),
1122        producer: info_dict
1123            .get::<object::String<'_>>(PRODUCER)
1124            .map(|t| Box::from(t.as_bytes())),
1125    }
1126}
1127
1128#[cfg(test)]
1129mod tests {
1130    use super::*;
1131
1132    #[test]
1133    fn circular_prev_chain() {
1134        let mut pdf = b"%PDF-1.0\n1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n".to_vec();
1135        let expected_xref_pos = pdf.len();
1136        pdf.extend_from_slice(
1137            format!(
1138                "xref\n\
1139                 0 1\n\
1140                 0000000000 65535 f \r\n\
1141                 trailer\n<< /Size 1 /Root 1 0 R /Prev {expected_xref_pos} >>\n\
1142                 startxref\n{expected_xref_pos}\n%%EOF"
1143            )
1144            .as_bytes(),
1145        );
1146
1147        let mut xref_map = FxHashMap::default();
1148        let xref_pos = find_last_xref_pos(pdf.as_ref()).unwrap();
1149        let _result = populate_xref_impl(pdf.as_ref(), xref_pos, &mut xref_map);
1150    }
1151
1152    #[test]
1153    fn find_last_xref_uses_last_startxref() {
1154        let pdf = b"%PDF-1.0\nstartxref\n5\n%%EOF\nstartxref\n42\n%%EOF";
1155        assert_eq!(find_last_xref_pos(pdf), Some(42));
1156    }
1157
1158    #[test]
1159    fn xref_table_trailer_rejects_overflowing_entry_skip() {
1160        let data = b"xref\n0 999999999999999999999\ntrailer\n<<>>";
1161        let mut reader = Reader::new(data);
1162        assert!(read_xref_table_trailer(&mut reader, &ReaderContext::dummy()).is_none());
1163    }
1164
1165    #[test]
1166    fn xref_stream_subsection_rejects_overflowing_object_number() {
1167        let data = [1, 0, 1, 0];
1168        let mut xref_map = FxHashMap::default();
1169        let result =
1170            xref_stream_subsection(&mut Reader::new(&data), u32::MAX, 2, 1, 1, 0, &mut xref_map);
1171        assert!(result.is_none());
1172        assert!(xref_map.contains_key(&ObjectIdentifier::new(u32::MAX, 0)));
1173    }
1174}