Skip to main content

otf_pdf_syntax/
xref.rs

1//! Cross-reference sections: classic tables and xref streams (§7.5.4,
2//! §7.5.8).
3
4use std::collections::BTreeMap;
5
6use crate::diag::{DiagnosticKind, Diagnostics};
7use crate::error::{Error, Result};
8use crate::lexer::{Lexer, Token};
9use crate::limits::{Limits, Strictness};
10use crate::object::{Dict, Object};
11use crate::parser::Parser;
12
13/// Where an object lives.
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum XrefEntry {
16    /// A free (deleted or never used) object number.
17    Free,
18    /// An object written directly in the file.
19    InUse {
20        /// Byte offset of `num gen obj`.
21        offset: u64,
22        /// Generation number.
23        generation: u16,
24    },
25    /// An object inside an object stream.
26    Compressed {
27        /// Object number of the object stream.
28        stream: u32,
29        /// Index of the object within the stream.
30        index: u32,
31    },
32}
33
34/// The entries of one xref section, by object number.
35pub type XrefMap = BTreeMap<u32, XrefEntry>;
36
37/// Parses a classic `xref` table and the trailer after it. `buf` starts at
38/// the `xref` keyword, at file offset `base`.
39pub fn parse_table(
40    buf: &[u8],
41    base: u64,
42    limits: &Limits,
43    strictness: Strictness,
44    diagnostics: &Diagnostics,
45) -> Result<(XrefMap, Dict)> {
46    let mut lexer = Lexer::new(buf);
47    if lexer.next_token()? != Token::Keyword(b"xref") {
48        return Err(Error::syntax(base, "expected `xref`"));
49    }
50    let mut entries = XrefMap::new();
51    let mut first_subsection = true;
52    loop {
53        let at = base + lexer.pos() as u64;
54        match lexer.next_token()? {
55            Token::Keyword(b"trailer") => break,
56            Token::Int(first) => {
57                let Token::Int(count) = lexer.next_token()? else {
58                    return Err(Error::syntax(at, "expected xref subsection count"));
59                };
60                let (Ok(first), Ok(count)) = (u32::try_from(first), u32::try_from(count)) else {
61                    return Err(Error::syntax(at, "invalid xref subsection"));
62                };
63                if u64::from(first) + u64::from(count) > u64::from(limits.max_objects) + 1 {
64                    return Err(Error::syntax(at, "xref subsection too large"));
65                }
66                let mut parsed = Vec::new();
67                for _ in 0..count {
68                    let save = lexer.pos();
69                    match (
70                        lexer.next_token()?,
71                        lexer.next_token()?,
72                        lexer.next_token()?,
73                    ) {
74                        (Token::Int(offset), Token::Int(generation), Token::Keyword(kind))
75                            if kind == b"n" || kind == b"f" =>
76                        {
77                            parsed.push((offset, generation, kind == b"n"));
78                        }
79                        _ => {
80                            // Fewer entries than claimed.
81                            lexer.set_pos(save);
82                            if strictness == Strictness::Strict {
83                                return Err(Error::syntax(at, "short xref subsection"));
84                            }
85                            diagnostics.push(DiagnosticKind::BadXrefSection, Some(at), None);
86                            break;
87                        }
88                    }
89                }
90                // A common writer bug: the first subsection starts at 1 but
91                // its first entry is object 0's free-list head.
92                let mut first = first;
93                if first_subsection
94                    && first == 1
95                    && parsed
96                        .first()
97                        .is_some_and(|&(o, g, used)| o == 0 && g == 65535 && !used)
98                {
99                    first = 0;
100                }
101                first_subsection = false;
102                for (i, (offset, generation, used)) in parsed.into_iter().enumerate() {
103                    let Some(num) = first.checked_add(i as u32) else {
104                        break;
105                    };
106                    let entry = if used && offset > 0 {
107                        XrefEntry::InUse {
108                            offset: offset as u64,
109                            generation: generation.clamp(0, 65535) as u16,
110                        }
111                    } else {
112                        XrefEntry::Free
113                    };
114                    entries.insert(num, entry);
115                }
116            }
117            Token::Eof => return Err(Error::UnexpectedEof),
118            _ => return Err(Error::syntax(at, "expected xref subsection or `trailer`")),
119        }
120    }
121    let pos = lexer.pos();
122    let rest = buf.get(pos..).unwrap_or_default();
123    let trailer = Parser::new(rest, base + pos as u64, limits, strictness)
124        .with_diagnostics(diagnostics)
125        .parse_dict()?;
126    Ok((entries, trailer))
127}
128
129/// Parses the decoded data of an xref stream with dictionary `dict`.
130pub fn parse_stream(dict: &Dict, data: &[u8], limits: &Limits) -> Result<XrefMap> {
131    let widths: Vec<usize> = dict
132        .get_array(b"W")
133        .ok_or(Error::syntax(0, "xref stream without /W"))?
134        .iter()
135        .map(|w| {
136            w.as_int()
137                .and_then(|w| usize::try_from(w).ok())
138                .unwrap_or(usize::MAX)
139        })
140        .collect();
141    let [w0, w1, w2] = widths[..] else {
142        return Err(Error::syntax(0, "xref stream /W must have three entries"));
143    };
144    if w0 > 8 || w1 > 8 || w2 > 8 {
145        return Err(Error::syntax(0, "xref stream field wider than 8 bytes"));
146    }
147    let width = w0 + w1 + w2;
148    if width == 0 {
149        return Err(Error::syntax(0, "xref stream entries have zero width"));
150    }
151    let size = dict.get_int(b"Size").unwrap_or(0);
152    let index: Vec<(i64, i64)> = match dict.get_array(b"Index") {
153        Some(a) => a
154            .chunks(2)
155            .filter_map(|p| match p {
156                [a, b] => Some((a.as_int()?, b.as_int()?)),
157                _ => None,
158            })
159            .collect(),
160        None => vec![(0, size)],
161    };
162
163    let mut entries = XrefMap::new();
164    let mut rows = data.chunks_exact(width);
165    for (first, count) in index {
166        let (Ok(first), Ok(count)) = (u32::try_from(first), u32::try_from(count)) else {
167            return Err(Error::syntax(0, "invalid xref stream /Index"));
168        };
169        for i in 0..count {
170            let Some(num) = first.checked_add(i) else {
171                break;
172            };
173            if num > limits.max_objects {
174                return Err(Error::syntax(0, "xref stream object number too large"));
175            }
176            // Fewer rows than /Index claims: stop at the data's end.
177            let Some(row) = rows.next() else {
178                return Ok(entries);
179            };
180            let field = |from: usize, len: usize| -> u64 {
181                row.get(from..from + len)
182                    .unwrap_or_default()
183                    .iter()
184                    .fold(0u64, |acc, &b| acc << 8 | u64::from(b))
185            };
186            let kind = if w0 == 0 { 1 } else { field(0, w0) };
187            let a = field(w0, w1);
188            let b = field(w0 + w1, w2);
189            let entry = match kind {
190                0 => XrefEntry::Free,
191                1 if a > 0 => XrefEntry::InUse {
192                    offset: a,
193                    generation: b.min(65535) as u16,
194                },
195                1 => XrefEntry::Free,
196                2 => XrefEntry::Compressed {
197                    stream: u32::try_from(a).unwrap_or(u32::MAX),
198                    index: u32::try_from(b).unwrap_or(u32::MAX),
199                },
200                // Unknown types are references to the null object (§7.5.8.3).
201                _ => XrefEntry::Free,
202            };
203            entries.insert(num, entry);
204        }
205    }
206    Ok(entries)
207}
208
209/// Whether a stream dictionary is an xref stream.
210pub fn is_xref_stream(object: &Object) -> bool {
211    object.as_stream().is_some_and(|s| {
212        s.dict.is_type(b"XRef") || (s.dict.contains_key(b"W") && s.dict.contains_key(b"Size"))
213    })
214}
215
216#[cfg(test)]
217mod tests {
218    use super::*;
219    use crate::object::Name;
220
221    fn table(input: &[u8]) -> Result<(XrefMap, Dict)> {
222        parse_table(
223            input,
224            0,
225            &Limits::default(),
226            Strictness::Lenient,
227            &Diagnostics::default(),
228        )
229    }
230
231    #[test]
232    fn classic_table() {
233        let (entries, trailer) = table(
234            b"xref\n0 3\n0000000000 65535 f \n0000000015 00000 n \n0000000079 00001 n \n\
235              5 1\n0000000200 00000 n\r\ntrailer\n<< /Size 6 /Root 1 0 R >>",
236        )
237        .unwrap();
238        assert_eq!(entries.get(&0), Some(&XrefEntry::Free));
239        assert_eq!(
240            entries.get(&1),
241            Some(&XrefEntry::InUse {
242                offset: 15,
243                generation: 0
244            })
245        );
246        assert_eq!(
247            entries.get(&2),
248            Some(&XrefEntry::InUse {
249                offset: 79,
250                generation: 1
251            })
252        );
253        assert!(matches!(
254            entries.get(&5),
255            Some(XrefEntry::InUse { offset: 200, .. })
256        ));
257        assert_eq!(trailer.get_int(b"Size"), Some(6));
258    }
259
260    #[test]
261    fn off_by_one_first_subsection() {
262        let (entries, _) =
263            table(b"xref\n1 2\n0000000000 65535 f \n0000000015 00000 n \ntrailer\n<<>>").unwrap();
264        assert!(matches!(
265            entries.get(&1),
266            Some(XrefEntry::InUse { offset: 15, .. })
267        ));
268    }
269
270    #[test]
271    fn short_subsection() {
272        let diags = Diagnostics::default();
273        let (entries, _) = parse_table(
274            b"xref\n0 5\n0000000000 65535 f \n0000000015 00000 n \ntrailer\n<<>>",
275            0,
276            &Limits::default(),
277            Strictness::Lenient,
278            &diags,
279        )
280        .unwrap();
281        assert_eq!(entries.len(), 2);
282        assert_eq!(diags.len(), 1);
283        assert!(
284            parse_table(
285                b"xref\n0 5\n0000000000 65535 f \ntrailer\n<<>>",
286                0,
287                &Limits::default(),
288                Strictness::Strict,
289                &diags,
290            )
291            .is_err()
292        );
293    }
294
295    #[test]
296    fn huge_claimed_count_is_rejected() {
297        assert!(table(b"xref\n0 2000000000\n0000000000 65535 f \ntrailer\n<<>>").is_err());
298    }
299
300    #[test]
301    fn zero_offset_in_use_is_free() {
302        let (entries, _) =
303            table(b"xref\n0 2\n0000000000 65535 f \n0000000000 00000 n \ntrailer\n<<>>").unwrap();
304        assert_eq!(entries.get(&1), Some(&XrefEntry::Free));
305    }
306
307    #[test]
308    fn malformed_tables() {
309        assert!(table(b"xref\nfoo").is_err());
310        assert!(table(b"xreff").is_err());
311        assert_eq!(table(b"xref\n0 1\n"), Err(Error::UnexpectedEof));
312        assert!(table(b"xref\n0 x").is_err());
313        assert!(table(b"xref\n-1 1\n").is_err());
314    }
315
316    fn stream_dict(w: [i64; 3], index: Option<Vec<i64>>, size: i64) -> Dict {
317        let mut d = Dict::new();
318        d.insert("Type", Name::from("XRef"));
319        d.insert("W", w.iter().map(|&x| Object::Int(x)).collect::<Vec<_>>());
320        d.insert("Size", size);
321        if let Some(index) = index {
322            d.insert(
323                "Index",
324                index.into_iter().map(Object::Int).collect::<Vec<_>>(),
325            );
326        }
327        d
328    }
329
330    #[test]
331    fn xref_stream_entries() {
332        let dict = stream_dict([1, 2, 1], None, 3);
333        let data = [0, 0, 0, 255, 1, 0, 15, 0, 2, 0, 7, 3];
334        let entries = parse_stream(&dict, &data, &Limits::default()).unwrap();
335        assert_eq!(entries.get(&0), Some(&XrefEntry::Free));
336        assert_eq!(
337            entries.get(&1),
338            Some(&XrefEntry::InUse {
339                offset: 15,
340                generation: 0
341            })
342        );
343        assert_eq!(
344            entries.get(&2),
345            Some(&XrefEntry::Compressed {
346                stream: 7,
347                index: 3
348            })
349        );
350    }
351
352    #[test]
353    fn xref_stream_index_and_defaults() {
354        // /W [0 2 0]: type defaults to 1, generation to 0.
355        let dict = stream_dict([0, 2, 0], Some(vec![10, 2]), 12);
356        let entries = parse_stream(&dict, &[0, 5, 0, 9], &Limits::default()).unwrap();
357        assert!(matches!(
358            entries.get(&10),
359            Some(XrefEntry::InUse { offset: 5, .. })
360        ));
361        assert!(matches!(
362            entries.get(&11),
363            Some(XrefEntry::InUse { offset: 9, .. })
364        ));
365        // Short data: stops early.
366        let entries = parse_stream(&dict, &[0, 5], &Limits::default()).unwrap();
367        assert_eq!(entries.len(), 1);
368    }
369
370    #[test]
371    fn xref_stream_errors() {
372        let limits = Limits::default();
373        assert!(parse_stream(&Dict::new(), &[], &limits).is_err());
374        assert!(parse_stream(&stream_dict([9, 1, 1], None, 1), &[], &limits).is_err());
375        assert!(parse_stream(&stream_dict([0, 0, 0], None, 1), &[], &limits).is_err());
376        let mut d = stream_dict([1, 1, 1], None, 1);
377        d.insert("W", vec![Object::Int(1)]);
378        assert!(parse_stream(&d, &[], &limits).is_err());
379        let huge = stream_dict([1, 1, 1], Some(vec![u32::MAX as i64, 1]), 1);
380        assert!(parse_stream(&huge, &[1, 1, 1], &limits).is_err());
381    }
382}