1use std::collections::{BTreeMap, HashMap, HashSet};
6use std::sync::{Arc, Mutex, OnceLock};
7
8use crate::crypt::{CryptMethod, SecurityHandler};
9use crate::diag::{Diagnostic, DiagnosticKind, Diagnostics};
10use crate::error::{Error, Result};
11use crate::filters::{self, Decoded};
12use crate::header::{Header, find_header};
13use crate::lexer::{Lexer, Token, is_regular, is_whitespace};
14use crate::limits::{LimitKind, Limits, Strictness};
15use crate::object::{Dict, ObjId, Object, Stream};
16use crate::parser::{ObjectEnd, Parser};
17use crate::source::Source;
18use crate::xref::{self, XrefEntry, XrefMap};
19
20#[derive(Debug, Clone, Default)]
22pub struct OpenOptions {
23 pub password: Vec<u8>,
26 pub strictness: Strictness,
28 pub limits: Limits,
30}
31
32pub type SharedSource = Arc<dyn Source + Send + Sync>;
34
35#[derive(Debug)]
37struct ObjStm {
38 data: Vec<u8>,
39 first: usize,
40 index: Vec<(u32, usize)>,
42}
43
44#[derive(Debug, Default)]
46struct Scan {
47 entries: XrefMap,
48 trailer: Option<Dict>,
49}
50
51const WINDOW: u64 = 4096;
53const SCAN_CHUNK: u64 = 1 << 20;
55const MAX_LOAD_DEPTH: u32 = 8;
57
58pub struct File {
61 source: SharedSource,
62 len: u64,
63 header: Option<Header>,
64 base: u64,
67 entries: XrefMap,
68 trailer: Dict,
69 sections: Vec<u64>,
71 scan: OnceLock<Scan>,
72 cache: Mutex<HashMap<ObjId, Arc<Object>>>,
73 objstms: Mutex<HashMap<u32, Arc<ObjStm>>>,
74 security: Option<SecurityHandler>,
75 encrypt_ref: Option<ObjId>,
76 limits: Limits,
77 strictness: Strictness,
78 diagnostics: Diagnostics,
79}
80
81impl std::fmt::Debug for File {
82 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
83 f.debug_struct("File")
84 .field("len", &self.len)
85 .field("header", &self.header)
86 .field("objects", &self.entries.len())
87 .field("encrypted", &self.security.is_some())
88 .finish_non_exhaustive()
89 }
90}
91
92impl File {
93 pub fn open(source: SharedSource, options: OpenOptions) -> Result<Self> {
95 let len = source.len();
96 let mut file = Self {
97 source,
98 len,
99 header: None,
100 base: 0,
101 entries: XrefMap::new(),
102 trailer: Dict::new(),
103 sections: Vec::new(),
104 scan: OnceLock::new(),
105 cache: Mutex::new(HashMap::new()),
106 objstms: Mutex::new(HashMap::new()),
107 security: None,
108 encrypt_ref: None,
109 limits: options.limits,
110 strictness: options.strictness,
111 diagnostics: Diagnostics::default(),
112 };
113
114 match find_header(&*file.source) {
115 Ok(h) => {
116 if h.has_leading_junk() {
117 file.repair(DiagnosticKind::JunkBeforeHeader, Some(h.offset))?;
118 }
119 file.base = h.offset;
120 file.header = Some(h);
121 }
122 Err(crate::header::HeaderError::Source(e)) => return Err(Error::Source(e)),
123 Err(_) => file.repair(DiagnosticKind::MissingHeader, Some(0))?,
124 }
125
126 match file.load_xref_chain() {
127 Ok((entries, trailer, sections)) => {
128 file.entries = entries;
129 file.trailer = trailer;
130 file.sections = sections;
131 }
132 Err(e @ (Error::Source(_) | Error::Limit(LimitKind::Budget))) => return Err(e),
133 Err(e) => {
134 if file.strictness == Strictness::Strict {
135 return Err(e);
136 }
137 file.adopt_scan()?;
138 }
139 }
140
141 file.init_security(&options.password)?;
144
145 if !file.root_is_usable() {
147 if file.strictness == Strictness::Strict {
148 return Err(Error::NoRoot);
149 }
150 file.adopt_scan()?;
151 if !file.root_is_usable() {
152 return Err(Error::NoRoot);
153 }
154 }
155 Ok(file)
156 }
157
158 fn adopt_scan(&mut self) -> Result<()> {
160 let scan = self.scan();
161 if scan.entries.is_empty() {
162 return Err(Error::NoXref);
163 }
164 let entries = scan.entries.clone();
165 let scanned_trailer = scan.trailer.clone();
166 self.diagnostics
167 .push(DiagnosticKind::XrefRebuilt, None, None);
168 for (num, entry) in entries {
171 self.entries.insert(num, entry);
172 }
173 if let Some(t) = scanned_trailer {
174 let mut merged = t;
176 for (k, v) in self.trailer.iter() {
177 if !merged.contains_key(k.as_bytes()) {
178 merged.insert(k.clone(), v.clone());
179 }
180 }
181 self.trailer = merged;
182 self.diagnostics
183 .push(DiagnosticKind::TrailerRecovered, None, None);
184 }
185 if let Ok(mut cache) = self.cache.lock() {
186 cache.clear();
187 }
188 Ok(())
189 }
190
191 fn root_is_usable(&self) -> bool {
192 match self.trailer.get(b"Root") {
193 Some(Object::Ref(id)) => self
194 .load(*id, 0)
195 .is_ok_and(|o| o.as_dict().is_some_and(|d| !d.is_empty())),
196 Some(Object::Dict(_)) => true,
197 _ => false,
198 }
199 }
200
201 fn init_security(&mut self, password: &[u8]) -> Result<()> {
202 let encrypt = match self.trailer.get(b"Encrypt") {
203 None => return Ok(()),
204 Some(Object::Ref(id)) => {
205 self.encrypt_ref = Some(*id);
206 let obj = self.load(*id, 0)?;
208 obj.as_dict().cloned().ok_or(Error::WrongType {
209 expected: "dictionary",
210 found: obj.type_name(),
211 })?
212 }
213 Some(Object::Dict(d)) => d.clone(),
214 Some(other) => {
215 return Err(Error::WrongType {
216 expected: "dictionary",
217 found: other.type_name(),
218 });
219 }
220 };
221 let id0 = self
222 .trailer
223 .get_array(b"ID")
224 .and_then(|a| a.first())
225 .and_then(Object::as_string)
226 .unwrap_or_default()
227 .to_vec();
228 let handler = SecurityHandler::new(&encrypt, &id0, password)?;
229 self.security = Some(handler);
230 if let Ok(mut cache) = self.cache.lock() {
232 cache.clear();
233 }
234 if let Ok(mut objstms) = self.objstms.lock() {
235 objstms.clear();
236 }
237 Ok(())
238 }
239
240 fn repair(&self, kind: DiagnosticKind, offset: Option<u64>) -> Result<()> {
242 if self.strictness == Strictness::Strict {
243 return Err(Error::syntax(
244 offset.unwrap_or(0),
245 "repair needed in strict mode",
246 ));
247 }
248 self.diagnostics.push(kind, offset, None);
249 Ok(())
250 }
251
252 pub fn header(&self) -> Option<Header> {
256 self.header
257 }
258
259 pub fn trailer(&self) -> &Dict {
261 &self.trailer
262 }
263
264 pub fn entries(&self) -> &XrefMap {
266 &self.entries
267 }
268
269 pub fn sections(&self) -> &[u64] {
272 &self.sections
273 }
274
275 pub fn startxref(&self) -> Result<Option<u64>> {
278 self.find_startxref()
279 }
280
281 pub fn newest_section_is_stream(&self) -> Result<Option<bool>> {
284 let Some(&at) = self.sections.first() else {
285 return Ok(None);
286 };
287 let head = self.read(at, 16)?;
288 let start = head.iter().position(|&b| !is_whitespace(b)).unwrap_or(0);
289 Ok(Some(
290 !head.get(start..).is_some_and(|h| h.starts_with(b"xref")),
291 ))
292 }
293
294 pub fn offset_shift(&self) -> Result<u64> {
299 let (Some(&at), Some(stated)) = (self.sections.first(), self.find_startxref()?) else {
300 return Ok(0);
301 };
302 Ok(if self.base > 0 && at == stated + self.base {
303 self.base
304 } else {
305 0
306 })
307 }
308
309 pub fn was_rebuilt(&self) -> bool {
312 self.diagnostics
313 .snapshot()
314 .iter()
315 .any(|d| d.kind == DiagnosticKind::XrefRebuilt)
316 }
317
318 pub fn read_all(&self) -> Result<Vec<u8>> {
320 let bytes = self.read(0, self.len)?;
321 if (bytes.len() as u64) < self.len {
322 return Err(Error::Source(crate::source::SourceError::new(
323 "source returned fewer bytes than its length",
324 )));
325 }
326 Ok(bytes)
327 }
328
329 pub fn security(&self) -> Option<&SecurityHandler> {
331 self.security.as_ref()
332 }
333
334 pub fn diagnostics(&self) -> Vec<Diagnostic> {
336 self.diagnostics.snapshot()
337 }
338
339 pub fn diagnostics_sink(&self) -> &Diagnostics {
341 &self.diagnostics
342 }
343
344 pub fn limits(&self) -> &Limits {
346 &self.limits
347 }
348
349 pub fn strictness(&self) -> Strictness {
351 self.strictness
352 }
353
354 pub fn len(&self) -> u64 {
356 self.len
357 }
358
359 pub fn is_empty(&self) -> bool {
361 self.len == 0
362 }
363
364 pub fn get(&self, id: ObjId) -> Result<Arc<Object>> {
369 if let Some(obj) = self.cache.lock().ok().and_then(|c| c.get(&id).cloned()) {
370 return Ok(obj);
371 }
372 self.limits.charge(1)?;
373 let obj = Arc::new(self.load_decrypted(id, 0)?);
374 if let Ok(mut cache) = self.cache.lock() {
375 cache.insert(id, Arc::clone(&obj));
376 }
377 Ok(obj)
378 }
379
380 pub fn resolve(&self, object: &Object) -> Object {
383 let mut current = object.clone();
384 for _ in 0..self.limits.max_depth {
385 match current {
386 Object::Ref(id) => match self.get(id) {
387 Ok(o) => current = (*o).clone(),
388 Err(_) => return Object::Null,
389 },
390 other => return other,
391 }
392 }
393 Object::Null
394 }
395
396 pub fn resolve_dict(&self, object: &Object) -> Option<Dict> {
398 match self.resolve(object) {
399 Object::Dict(d) => Some(d),
400 Object::Stream(s) => Some(s.dict),
401 _ => None,
402 }
403 }
404
405 pub fn decode_stream(&self, stream: &Stream) -> Result<Decoded> {
408 let mut dict = stream.dict.clone();
409 for key in [&b"Filter"[..], b"DecodeParms"] {
410 if let Some(v) = dict.get(key).cloned() {
411 let resolved = self.resolve_shallow(&v);
412 dict.insert(key, resolved);
413 }
414 }
415 filters::decode(
416 &stream.raw,
417 &filters::filter_chain(&dict),
418 &self.limits,
419 self.strictness,
420 Some(&self.diagnostics),
421 )
422 }
423
424 fn resolve_shallow(&self, object: &Object) -> Object {
426 match self.resolve(object) {
427 Object::Array(items) => Object::Array(items.iter().map(|i| self.resolve(i)).collect()),
428 other => other,
429 }
430 }
431
432 fn load_decrypted(&self, id: ObjId, depth: u32) -> Result<Object> {
433 let (mut obj, direct) = self.load_with_origin(id, depth)?;
434 if direct
436 && Some(id) != self.encrypt_ref
437 && let Some(sec) = &self.security
438 {
439 self.decrypt(sec, id, &mut obj);
440 }
441 Ok(obj)
442 }
443
444 fn decrypt(&self, sec: &SecurityHandler, id: ObjId, obj: &mut Object) {
445 sec.decrypt_strings(id, obj);
446 if let Object::Stream(s) = obj {
447 let method = self.stream_method(sec, &s.dict);
448 s.raw = sec.decrypt(id, method, &s.raw).into();
449 }
450 }
451
452 fn stream_method(&self, sec: &SecurityHandler, dict: &Dict) -> CryptMethod {
453 if dict.is_type(b"XRef") {
454 return CryptMethod::Identity;
455 }
456 if dict.is_type(b"Metadata") && !sec.encrypt_metadata() {
457 return CryptMethod::Identity;
458 }
459 let crypt = filters::filter_chain(dict)
462 .into_iter()
463 .find(|f| f.name.as_bytes() == b"Crypt");
464 if let Some(f) = crypt {
465 let name = f.params.as_ref().and_then(|p| p.get_name(b"Name"));
466 if name.is_none_or(|n| n == b"Identity") {
467 return CryptMethod::Identity;
468 }
469 }
470 if dict.is_type(b"EmbeddedFile") {
471 return sec.embedded_file_method();
472 }
473 sec.stream_method()
474 }
475
476 fn load(&self, id: ObjId, depth: u32) -> Result<Object> {
479 self.load_with_origin(id, depth).map(|(o, _)| o)
480 }
481
482 fn load_with_origin(&self, id: ObjId, depth: u32) -> Result<(Object, bool)> {
485 if depth > MAX_LOAD_DEPTH {
486 return Err(Error::Limit(LimitKind::Depth));
487 }
488 let entry = self.entries.get(&id.num).copied();
489 let result = match entry {
490 Some(XrefEntry::InUse { offset, generation }) => {
491 if generation != id.generation {
492 self.repair(DiagnosticKind::ObjectIdMismatch, Some(offset))?;
493 }
494 self.parse_at_xref_offset(id, offset, depth)
495 .map(|o| (o, true))
496 }
497 Some(XrefEntry::Compressed { stream, index }) => self
498 .load_compressed(id, stream, index, depth)
499 .map(|o| (o, false)),
500 Some(XrefEntry::Free) | None => Err(Error::MissingObject(id)),
501 };
502 match result {
503 Ok(found) => Ok(found),
504 Err(e @ (Error::Limit(_) | Error::Source(_))) => Err(e),
505 Err(e) if self.strictness == Strictness::Strict => Err(e),
506 Err(e) if entry == Some(XrefEntry::Free) => Err(e),
509 Err(e) => {
510 let scanned = self.scan().entries.get(&id.num).copied();
512 match scanned {
513 Some(XrefEntry::InUse { offset, .. }) if !matches!(entry, Some(XrefEntry::InUse { offset: o, .. }) if o == offset) =>
514 {
515 let obj = self.parse_at(offset, Some(id), depth)?;
516 if entry.is_some() {
517 self.diagnostics.push(
518 DiagnosticKind::BadXrefEntry,
519 Some(offset),
520 Some(id),
521 );
522 }
523 Ok((obj, true))
524 }
525 Some(XrefEntry::Compressed { stream, index })
526 if !matches!(entry, Some(XrefEntry::Compressed { .. })) =>
527 {
528 Ok((self.load_compressed(id, stream, index, depth)?, false))
529 }
530 _ => Err(e),
531 }
532 }
533 }
534 }
535
536 fn parse_at_xref_offset(&self, id: ObjId, offset: u64, depth: u32) -> Result<Object> {
537 match self.parse_at(offset, Some(id), depth) {
538 Err(e) if self.base > 0 => self
539 .parse_at(offset + self.base, Some(id), depth)
540 .map_err(|_| e),
541 other => other,
542 }
543 }
544
545 fn read(&self, offset: u64, len: u64) -> Result<Vec<u8>> {
547 let len = len.min(self.len.saturating_sub(offset));
548 let mut buf =
549 vec![0u8; usize::try_from(len).map_err(|_| Error::Limit(LimitKind::ObjectSize))?];
550 let mut filled = 0;
551 while filled < buf.len() {
552 let Some(rest) = buf.get_mut(filled..) else {
553 break;
554 };
555 let n = self.source.read_at(offset + filled as u64, rest)?;
556 if n == 0 {
557 break;
558 }
559 filled += n;
560 }
561 buf.truncate(filled);
562 Ok(buf)
563 }
564
565 fn with_window<T>(&self, offset: u64, mut parse: impl FnMut(&[u8]) -> Result<T>) -> Result<T> {
568 let mut size = WINDOW;
569 loop {
570 let buf = self.read(offset, size)?;
571 match parse(&buf) {
572 Err(Error::UnexpectedEof) if offset + size < self.len => {
573 if size >= self.limits.max_object_size {
574 return Err(Error::Limit(LimitKind::ObjectSize));
575 }
576 size = (size * 4).min(self.limits.max_object_size);
577 }
578 other => return other,
579 }
580 }
581 }
582
583 fn parse_at(&self, offset: u64, expected: Option<ObjId>, depth: u32) -> Result<Object> {
585 if offset >= self.len {
586 return Err(Error::syntax(offset, "object offset past end of file"));
587 }
588 let (id, object, end) = self.with_window(offset, |buf| {
589 Parser::new(buf, offset, &self.limits, self.strictness)
590 .with_diagnostics(&self.diagnostics)
591 .parse_indirect(expected)
592 })?;
593 let ObjectEnd::Stream { data_start } = end else {
594 return Ok(object);
595 };
596 let Object::Dict(dict) = object else {
597 return Err(Error::syntax(offset, "stream without dictionary"));
598 };
599 let data_offset = offset + data_start as u64;
600 let declared = match dict.get(b"Length") {
601 Some(Object::Int(n)) => u64::try_from(*n).ok(),
602 Some(Object::Ref(r)) if *r != id => self
603 .load_decrypted(*r, depth + 1)
604 .ok()
605 .and_then(|o| o.as_int())
606 .and_then(|n| u64::try_from(n).ok()),
607 _ => None,
608 };
609 let length = match declared {
610 Some(n) if self.endstream_at(data_offset.saturating_add(n))? => n,
611 _ => {
612 self.repair(DiagnosticKind::BadStreamLength, Some(data_offset))?;
613 self.find_stream_end(data_offset)?
614 }
615 };
616 let raw = self.read(data_offset, length)?;
617 Ok(Object::Stream(Stream {
618 dict,
619 raw: raw.into(),
620 }))
621 }
622
623 fn endstream_at(&self, pos: u64) -> Result<bool> {
625 if pos > self.len {
626 return Ok(false);
627 }
628 let buf = self.read(pos, 32)?;
629 let start = buf
630 .iter()
631 .position(|&b| !is_whitespace(b))
632 .unwrap_or(buf.len());
633 Ok(buf
634 .get(start..)
635 .is_some_and(|r| r.starts_with(b"endstream")))
636 }
637
638 fn find_stream_end(&self, start: u64) -> Result<u64> {
642 let found = self
643 .find_forward(start, b"endstream")?
644 .or(self.find_forward(start, b"endobj")?);
645 let Some(end) = found else {
646 return Ok(self.len.saturating_sub(start));
647 };
648 let tail = self.read(end.saturating_sub(2), 2.min(end))?;
649 let eol = match tail.as_slice() {
650 [b'\r', b'\n'] => 2,
651 [.., b'\n' | b'\r'] => 1,
652 _ => 0,
653 };
654 Ok(end.saturating_sub(eol).saturating_sub(start))
655 }
656
657 fn find_forward(&self, from: u64, needle: &[u8]) -> Result<Option<u64>> {
659 let overlap = needle.len() as u64;
660 let mut pos = from;
661 while pos < self.len {
662 let chunk = self.read(pos, SCAN_CHUNK)?;
663 if let Some(i) = chunk.windows(needle.len()).position(|w| w == needle) {
664 return Ok(Some(pos + i as u64));
665 }
666 if chunk.len() as u64 <= overlap {
667 break;
668 }
669 pos += chunk.len() as u64 - overlap;
670 }
671 Ok(None)
672 }
673
674 fn load_compressed(&self, id: ObjId, stream: u32, index: u32, depth: u32) -> Result<Object> {
675 if stream == id.num {
676 return Err(Error::syntax(0, "object stream contains itself"));
677 }
678 let objstm = self.objstm(stream, depth + 1)?;
679 let by_index = objstm
680 .index
681 .get(index as usize)
682 .filter(|(n, _)| *n == id.num);
683 let entry = match by_index {
685 Some(e) => Some(e),
686 None => objstm.index.iter().find(|(n, _)| *n == id.num),
687 };
688 let Some(&(_, offset)) = entry else {
689 return Err(Error::MissingObject(id));
690 };
691 let start = objstm.first.saturating_add(offset);
692 let buf = objstm.data.get(start..).unwrap_or_default();
693 Parser::new(buf, 0, &self.limits, self.strictness)
694 .with_diagnostics(&self.diagnostics)
695 .parse_object()
696 }
697
698 fn objstm(&self, num: u32, depth: u32) -> Result<Arc<ObjStm>> {
699 if let Some(s) = self.objstms.lock().ok().and_then(|m| m.get(&num).cloned()) {
700 return Ok(s);
701 }
702 let id = ObjId::new(num, 0);
703 let obj = self.load_decrypted(id, depth)?;
704 let Object::Stream(stream) = obj else {
705 return Err(Error::WrongType {
706 expected: "object stream",
707 found: obj.type_name(),
708 });
709 };
710 let decoded = self.decode_stream(&stream)?;
711 if !decoded.remaining.is_empty() {
712 return Err(Error::Filter("object stream uses an unsupported filter"));
713 }
714 let n = stream.dict.get_int(b"N").unwrap_or(0).max(0) as usize;
715 let first = stream.dict.get_int(b"First").unwrap_or(0).max(0) as usize;
716 let header = decoded
717 .data
718 .get(..first.min(decoded.data.len()))
719 .unwrap_or_default();
720 let mut lexer = Lexer::new(header);
721 let mut index = Vec::with_capacity(n.min(header.len() / 2 + 1));
722 for _ in 0..n {
723 match (lexer.next_token(), lexer.next_token()) {
724 (Ok(Token::Int(obj)), Ok(Token::Int(off))) => {
725 if let (Ok(obj), Ok(off)) = (u32::try_from(obj), usize::try_from(off)) {
726 index.push((obj, off));
727 }
728 }
729 _ => break,
730 }
731 }
732 let objstm = Arc::new(ObjStm {
733 data: decoded.data,
734 first,
735 index,
736 });
737 if let Ok(mut m) = self.objstms.lock() {
738 m.insert(num, Arc::clone(&objstm));
739 }
740 Ok(objstm)
741 }
742
743 fn find_startxref(&self) -> Result<Option<u64>> {
746 for tail in [1024u64, SCAN_CHUNK] {
747 let start = self.len.saturating_sub(tail);
748 let buf = self.read(start, tail)?;
749 let Some(i) = buf.windows(9).rposition(|w| w == b"startxref") else {
750 continue;
751 };
752 let mut lexer = Lexer::new(buf.get(i + 9..).unwrap_or_default());
753 if let Ok(Token::Int(n)) = lexer.next_token() {
754 return Ok(u64::try_from(n).ok());
755 }
756 return Ok(None);
757 }
758 Ok(None)
759 }
760
761 #[allow(clippy::type_complexity)]
762 fn load_xref_chain(&self) -> Result<(XrefMap, Dict, Vec<u64>)> {
763 let Some(start) = self.find_startxref()? else {
764 self.repair(DiagnosticKind::BadStartxref, None)?;
765 return Err(Error::NoXref);
766 };
767 let mut entries = XrefMap::new();
768 let mut trailer = Dict::new();
769 let mut sections = Vec::new();
770 let mut seen = HashSet::new();
771 let mut next = Some(start);
772 while let Some(offset) = next.take() {
773 if !seen.insert(offset) || sections.len() as u32 >= self.limits.max_xref_sections {
774 self.repair(DiagnosticKind::XrefLoop, Some(offset))?;
775 break;
776 }
777 let (section, section_trailer, at) = match self.load_section_near(offset) {
778 Ok(s) => s,
779 Err(e @ (Error::Source(_) | Error::Limit(LimitKind::Budget))) => return Err(e),
780 Err(e) if sections.is_empty() => {
781 if self.strictness == Strictness::Lenient {
782 self.diagnostics
783 .push(DiagnosticKind::BadStartxref, Some(offset), None);
784 }
785 return Err(e);
786 }
787 Err(_) => {
788 self.repair(DiagnosticKind::BadXrefSection, Some(offset))?;
791 break;
792 }
793 };
794 sections.push(at);
795 for (num, entry) in section {
796 entries.entry(num).or_insert(entry);
797 }
798 for (k, v) in section_trailer.iter() {
799 if !trailer.contains_key(k.as_bytes()) {
800 trailer.insert(k.clone(), v.clone());
801 }
802 }
803 next = section_trailer
804 .get_int(b"Prev")
805 .and_then(|p| u64::try_from(p).ok());
806 }
807 trailer.remove(b"Prev");
808 trailer.remove(b"XRefStm");
809 Ok((entries, trailer, sections))
810 }
811
812 fn load_section_near(&self, offset: u64) -> Result<(XrefMap, Dict, u64)> {
815 match self.load_section(offset) {
816 Err(e) if self.base > 0 => self
817 .load_section(offset + self.base)
818 .map(|(m, t)| (m, t, offset + self.base))
819 .map_err(|_| e),
820 other => other.map(|(m, t)| (m, t, offset)),
821 }
822 }
823
824 fn load_section(&self, offset: u64) -> Result<(XrefMap, Dict)> {
825 if offset >= self.len {
826 return Err(Error::syntax(offset, "xref offset past end of file"));
827 }
828 let head = self.read(offset, 16)?;
829 let skip = head.iter().position(|&b| !is_whitespace(b)).unwrap_or(0) as u64;
830 if head
831 .get(skip as usize..)
832 .is_some_and(|h| h.starts_with(b"xref"))
833 {
834 let at = offset + skip;
835 let (mut entries, trailer) = self.with_window(at, |buf| {
836 xref::parse_table(buf, at, &self.limits, self.strictness, &self.diagnostics)
837 })?;
838 if let Some(stm) = trailer
840 .get_int(b"XRefStm")
841 .and_then(|o| u64::try_from(o).ok())
842 {
843 match self.load_xref_stream(stm) {
844 Ok((stream_entries, _)) => {
845 for (num, entry) in stream_entries {
846 let replace = matches!(entries.get(&num), None | Some(XrefEntry::Free));
847 if replace {
848 entries.insert(num, entry);
849 }
850 }
851 }
852 Err(e @ Error::Source(_)) => return Err(e),
853 Err(_) => self.repair(DiagnosticKind::BadXrefSection, Some(stm))?,
854 }
855 }
856 return Ok((entries, trailer));
857 }
858 self.load_xref_stream(offset)
859 }
860
861 fn load_xref_stream(&self, offset: u64) -> Result<(XrefMap, Dict)> {
862 let obj = self.parse_at(offset, None, MAX_LOAD_DEPTH)?;
863 if !xref::is_xref_stream(&obj) {
864 return Err(Error::syntax(offset, "expected an xref stream"));
865 }
866 let Object::Stream(stream) = obj else {
867 return Err(Error::syntax(offset, "expected an xref stream"));
868 };
869 let decoded = filters::decode(
870 &stream.raw,
871 &filters::filter_chain(&stream.dict),
872 &self.limits,
873 self.strictness,
874 Some(&self.diagnostics),
875 )?;
876 let entries = xref::parse_stream(&stream.dict, &decoded.data, &self.limits)?;
877 let mut trailer = stream.dict;
878 for key in [
879 &b"Type"[..],
880 b"W",
881 b"Index",
882 b"Filter",
883 b"DecodeParms",
884 b"Length",
885 ] {
886 trailer.remove(key);
887 }
888 Ok((entries, trailer))
889 }
890
891 fn scan(&self) -> &Scan {
895 self.scan
896 .get_or_init(|| self.scan_file().unwrap_or_default())
897 }
898
899 fn scan_file(&self) -> Result<Scan> {
900 let mut found: BTreeMap<u32, (u64, u16)> = BTreeMap::new();
901 let mut trailers = Vec::new();
902 let mut candidates: Vec<(u64, Flags)> = Vec::new();
903 let overlap = 64u64;
904 let mut pos = 0u64;
905 while pos < self.len {
906 self.limits.charge(SCAN_CHUNK)?;
907 let chunk = self.read(pos, SCAN_CHUNK)?;
908 let last = pos + chunk.len() as u64 >= self.len;
909 let limit = if last {
912 chunk.len()
913 } else {
914 chunk.len().saturating_sub(overlap as usize)
915 };
916 for i in 0..limit {
917 let Some(window) = chunk.get(i..) else { break };
918 if window.starts_with(b"obj") && !is_end_keyword(&chunk, i) {
919 let after_ok = window.get(3).is_none_or(|&b| !is_regular(b));
920 if after_ok
921 && let Some((start, num, generation)) = object_header_before(&chunk, i)
922 {
923 let offset = pos + start as u64;
924 if num <= self.limits.max_objects {
925 found.insert(num, (offset, generation));
927 let body = chunk.get(i..(i + 600).min(chunk.len())).unwrap_or_default();
928 candidates.push((offset, Flags::of(body)));
929 }
930 }
931 } else if window.starts_with(b"trailer") {
932 trailers.push(pos + i as u64 + 7);
933 }
934 }
935 if last || chunk.len() as u64 <= overlap {
936 break;
937 }
938 pos += chunk.len() as u64 - overlap;
939 }
940
941 let mut scan = Scan::default();
942 for (&num, &(offset, generation)) in &found {
943 scan.entries
944 .insert(num, XrefEntry::InUse { offset, generation });
945 }
946
947 let mut trailer: Option<Dict> = None;
950 for &at in trailers.iter().rev() {
951 let parsed = self.with_window(at, |buf| {
952 Parser::new(buf, at, &self.limits, Strictness::Lenient).parse_dict()
953 });
954 if let Ok(d) = parsed {
955 match &mut trailer {
956 None => trailer = Some(d),
957 Some(t) => {
958 for (k, v) in d.iter() {
959 if !t.contains_key(k.as_bytes()) {
960 t.insert(k.clone(), v.clone());
961 }
962 }
963 }
964 }
965 }
966 }
967 let live: HashSet<u64> = found.values().map(|&(o, _)| o).collect();
969 for &(offset, flags) in candidates.iter().filter(|(o, _)| live.contains(o)) {
970 if flags.objstm
971 && let Ok(Object::Stream(s)) = self.parse_at(offset, None, MAX_LOAD_DEPTH)
972 && let Ok(d) = filters::decode(
973 &s.raw,
974 &filters::filter_chain(&s.dict),
975 &self.limits,
976 Strictness::Lenient,
977 None,
978 )
979 {
980 let n = s.dict.get_int(b"N").unwrap_or(0).max(0) as usize;
981 let first = s.dict.get_int(b"First").unwrap_or(0).max(0) as usize;
982 let mut lexer =
983 Lexer::new(d.data.get(..first.min(d.data.len())).unwrap_or_default());
984 let stream_num = found
985 .iter()
986 .find(|(_, (o, _))| *o == offset)
987 .map(|(&n, _)| n);
988 for index in 0..n {
989 let (Ok(Token::Int(obj)), Ok(Token::Int(_))) =
990 (lexer.next_token(), lexer.next_token())
991 else {
992 break;
993 };
994 if let (Ok(obj), Some(stream)) = (u32::try_from(obj), stream_num) {
995 scan.entries.entry(obj).or_insert(XrefEntry::Compressed {
996 stream,
997 index: index as u32,
998 });
999 }
1000 }
1001 }
1002 if flags.xref
1003 && trailer.as_ref().is_none_or(|t| !t.contains_key(b"Root"))
1004 && let Ok(Object::Stream(s)) = self.parse_at(offset, None, MAX_LOAD_DEPTH)
1005 && s.dict.contains_key(b"Root")
1006 {
1007 let mut d = s.dict;
1008 for key in [
1009 &b"Type"[..],
1010 b"W",
1011 b"Index",
1012 b"Filter",
1013 b"DecodeParms",
1014 b"Length",
1015 b"Prev",
1016 ] {
1017 d.remove(key);
1018 }
1019 trailer = Some(d);
1020 }
1021 }
1022 if trailer.as_ref().is_none_or(|t| !t.contains_key(b"Root")) {
1023 for &(offset, flags) in candidates.iter().rev().filter(|(o, _)| live.contains(o)) {
1025 if !flags.catalog {
1026 continue;
1027 }
1028 let Ok(obj) = self.with_window(offset, |buf| {
1029 Parser::new(buf, offset, &self.limits, Strictness::Lenient).parse_indirect(None)
1030 }) else {
1031 continue;
1032 };
1033 if obj.1.as_dict().is_some_and(|d| d.is_type(b"Catalog")) {
1034 let t = trailer.get_or_insert_with(Dict::new);
1035 t.insert("Root", obj.0);
1036 break;
1037 }
1038 }
1039 }
1040 scan.trailer = trailer;
1041 Ok(scan)
1042 }
1043}
1044
1045#[derive(Debug, Clone, Copy, Default)]
1047struct Flags {
1048 objstm: bool,
1049 xref: bool,
1050 catalog: bool,
1051}
1052
1053impl Flags {
1054 fn of(body: &[u8]) -> Self {
1055 let has = |n: &[u8]| body.windows(n.len()).any(|w| w == n);
1056 Self {
1057 objstm: has(b"/ObjStm"),
1058 xref: has(b"/XRef"),
1059 catalog: has(b"/Catalog"),
1060 }
1061 }
1062}
1063
1064fn is_end_keyword(buf: &[u8], i: usize) -> bool {
1066 i >= 3 && buf.get(i - 3..i) == Some(b"end")
1067}
1068
1069fn object_header_before(buf: &[u8], i: usize) -> Option<(usize, u32, u16)> {
1072 let mut j = i;
1073 while j > 0 && buf.get(j - 1).copied().is_some_and(is_whitespace) {
1074 j -= 1;
1075 }
1076 let gen_end = j;
1077 while j > 0 && buf.get(j - 1).is_some_and(u8::is_ascii_digit) {
1078 j -= 1;
1079 }
1080 if j == gen_end || gen_end - j > 5 {
1081 return None;
1082 }
1083 let gen_start = j;
1084 while j > 0 && buf.get(j - 1).copied().is_some_and(is_whitespace) {
1085 j -= 1;
1086 }
1087 if j == gen_start {
1088 return None;
1089 }
1090 let num_end = j;
1091 while j > 0 && buf.get(j - 1).is_some_and(u8::is_ascii_digit) {
1092 j -= 1;
1093 }
1094 if j == num_end || num_end - j > 10 {
1095 return None;
1096 }
1097 if j > 0 && buf.get(j - 1).copied().is_some_and(is_regular) {
1098 return None;
1099 }
1100 let num: u32 = std::str::from_utf8(buf.get(j..num_end)?)
1101 .ok()?
1102 .parse()
1103 .ok()?;
1104 let generation: u16 = std::str::from_utf8(buf.get(gen_start..gen_end)?)
1105 .ok()?
1106 .parse()
1107 .ok()?;
1108 Some((j, num, generation))
1109}
1110
1111#[cfg(test)]
1112mod tests {
1113 use super::*;
1114
1115 fn open(bytes: &[u8]) -> Result<File> {
1116 File::open(Arc::new(bytes.to_vec()), OpenOptions::default())
1117 }
1118
1119 fn open_strict(bytes: &[u8]) -> Result<File> {
1120 File::open(
1121 Arc::new(bytes.to_vec()),
1122 OpenOptions {
1123 strictness: Strictness::Strict,
1124 ..OpenOptions::default()
1125 },
1126 )
1127 }
1128
1129 fn build(objects: &[(u32, &str)], trailer_extra: &str) -> Vec<u8> {
1131 let mut out = b"%PDF-1.7\n".to_vec();
1132 let mut offsets = Vec::new();
1133 for (num, body) in objects {
1134 offsets.push((*num, out.len()));
1135 out.extend_from_slice(format!("{num} 0 obj\n{body}\nendobj\n").as_bytes());
1136 }
1137 let size = objects.iter().map(|(n, _)| n).max().copied().unwrap_or(0) + 1;
1138 let xref = out.len();
1139 out.extend_from_slice(format!("xref\n0 {size}\n0000000000 65535 f \n").as_bytes());
1140 for n in 1..size {
1141 match offsets.iter().find(|(m, _)| *m == n) {
1142 Some((_, o)) => out.extend_from_slice(format!("{o:010} 00000 n \n").as_bytes()),
1143 None => out.extend_from_slice(b"0000000000 65535 f \n"),
1144 }
1145 }
1146 out.extend_from_slice(
1147 format!(
1148 "trailer\n<< /Size {size} /Root 1 0 R {trailer_extra}>>\nstartxref\n{xref}\n%%EOF\n"
1149 )
1150 .as_bytes(),
1151 );
1152 out
1153 }
1154
1155 const CATALOG: (u32, &str) = (1, "<< /Type /Catalog /Pages 2 0 R >>");
1156 const PAGES: (u32, &str) = (2, "<< /Type /Pages /Kids [] /Count 0 >>");
1157
1158 #[test]
1159 fn opens_a_valid_file() {
1160 let f = open(&build(&[CATALOG, PAGES, (3, "(hello)")], "")).unwrap();
1161 assert_eq!(f.header().map(|h| h.version.minor), Some(7));
1162 assert!(f.diagnostics().is_empty(), "{:?}", f.diagnostics());
1163 assert_eq!(f.sections().len(), 1);
1164 let root = f.trailer().get_ref(b"Root").unwrap();
1165 assert!(f.get(root).unwrap().as_dict().unwrap().is_type(b"Catalog"));
1166 assert_eq!(
1167 f.get(ObjId::new(3, 0)).unwrap().as_string(),
1168 Some(&b"hello"[..])
1169 );
1170 assert_eq!(
1171 f.resolve(&Object::Ref(ObjId::new(3, 0))).as_string(),
1172 Some(&b"hello"[..])
1173 );
1174 assert_eq!(f.resolve(&Object::Ref(ObjId::new(99, 0))), Object::Null);
1175 assert!(matches!(
1176 f.get(ObjId::new(99, 0)),
1177 Err(Error::MissingObject(_))
1178 ));
1179 }
1180
1181 #[test]
1182 fn stream_with_indirect_length() {
1183 let f = open(&build(
1184 &[
1185 CATALOG,
1186 PAGES,
1187 (3, "<< /Length 4 0 R >>\nstream\nabcdef\nendstream"),
1188 (4, "6"),
1189 ],
1190 "",
1191 ))
1192 .unwrap();
1193 let s = f.get(ObjId::new(3, 0)).unwrap();
1194 assert_eq!(&*s.as_stream().unwrap().raw, b"abcdef");
1195 assert!(f.diagnostics().is_empty());
1196 }
1197
1198 #[test]
1199 fn wrong_stream_length_is_repaired() {
1200 let bytes = build(
1201 &[
1202 CATALOG,
1203 PAGES,
1204 (3, "<< /Length 2 >>\nstream\nabcdef\r\nendstream"),
1205 ],
1206 "",
1207 );
1208 let f = open(&bytes).unwrap();
1209 let s = f.get(ObjId::new(3, 0)).unwrap();
1210 assert_eq!(&*s.as_stream().unwrap().raw, b"abcdef");
1211 assert_eq!(f.diagnostics()[0].kind, DiagnosticKind::BadStreamLength);
1212 let strict = open_strict(&bytes).unwrap();
1213 assert!(strict.get(ObjId::new(3, 0)).is_err());
1214 }
1215
1216 #[test]
1217 fn rebuilds_a_missing_xref() {
1218 let mut bytes = build(&[CATALOG, PAGES], "");
1219 let cut = bytes.windows(4).position(|w| w == b"xref").unwrap();
1220 bytes.truncate(cut);
1221 bytes.extend_from_slice(b"trailer\n<< /Root 1 0 R >>\n%%EOF\n");
1222 let f = open(&bytes).unwrap();
1223 assert!(
1224 f.diagnostics()
1225 .iter()
1226 .any(|d| d.kind == DiagnosticKind::XrefRebuilt)
1227 );
1228 assert!(
1229 f.get(ObjId::new(2, 0))
1230 .unwrap()
1231 .as_dict()
1232 .unwrap()
1233 .is_type(b"Pages")
1234 );
1235 assert!(open_strict(&bytes).is_err());
1236 }
1237
1238 #[test]
1239 fn finds_a_catalog_without_any_trailer() {
1240 let bytes = b"%PDF-1.4\n1 0 obj << /Type /Catalog /Pages 2 0 R >> endobj\n2 0 obj << /Type /Pages /Kids [] /Count 0 >> endobj\n";
1241 let f = open(bytes).unwrap();
1242 assert_eq!(f.trailer().get_ref(b"Root"), Some(ObjId::new(1, 0)));
1243 }
1244
1245 #[test]
1246 fn nothing_to_find() {
1247 assert!(open(b"%PDF-1.4\nnothing here\n").is_err());
1248 assert!(open(b"").is_err());
1249 }
1250
1251 #[test]
1252 fn wrong_xref_offsets_fall_back_to_scan() {
1253 let bytes = build(&[CATALOG, PAGES, (3, "42")], "");
1254 let text = String::from_utf8(bytes).unwrap();
1255 let fixed: String = text
1257 .lines()
1258 .map(|l| {
1259 if l.len() == 19 && l.ends_with(" 00000 n ") {
1260 let n: u64 = l[..10].parse().unwrap();
1261 format!("{:010} 00000 n \n", n - 3)
1262 } else {
1263 format!("{l}\n")
1264 }
1265 })
1266 .collect();
1267 let f = open(fixed.as_bytes()).unwrap();
1268 assert_eq!(f.get(ObjId::new(3, 0)).unwrap().as_int(), Some(42));
1269 assert!(
1270 f.diagnostics()
1271 .iter()
1272 .any(|d| d.kind == DiagnosticKind::BadXrefEntry)
1273 );
1274 }
1275
1276 #[test]
1277 fn incremental_update_newest_wins() {
1278 let mut bytes = build(&[CATALOG, PAGES, (3, "(old)")], "");
1279 let first_xref = {
1280 let s = String::from_utf8_lossy(&bytes);
1281 let i = s.rfind("startxref\n").unwrap() + 10;
1282 s[i..].lines().next().unwrap().parse::<usize>().unwrap()
1283 };
1284 let obj_at = bytes.len();
1285 bytes.extend_from_slice(b"3 0 obj\n(new)\nendobj\n");
1286 let xref_at = bytes.len();
1287 bytes.extend_from_slice(
1288 format!("xref\n3 1\n{obj_at:010} 00000 n \ntrailer\n<< /Size 4 /Root 1 0 R /Prev {first_xref} >>\nstartxref\n{xref_at}\n%%EOF\n").as_bytes(),
1289 );
1290 let f = open(&bytes).unwrap();
1291 assert_eq!(f.sections().len(), 2);
1292 assert_eq!(
1293 f.get(ObjId::new(3, 0)).unwrap().as_string(),
1294 Some(&b"new"[..])
1295 );
1296 assert!(f.trailer().get(b"Prev").is_none());
1297 }
1298
1299 #[test]
1300 fn prev_loop_is_cut() {
1301 let bytes = build(&[CATALOG, PAGES], "/Prev 0 ");
1302 let xref = String::from_utf8_lossy(&bytes);
1303 let at = xref.rfind("startxref\n").unwrap() + 10;
1304 let offset: usize = xref[at..].lines().next().unwrap().parse().unwrap();
1305 let looped = String::from_utf8(bytes.clone())
1306 .unwrap()
1307 .replace("/Prev 0", &format!("/Prev {offset}"));
1308 let f = open(looped.as_bytes()).unwrap();
1309 assert!(
1310 f.diagnostics()
1311 .iter()
1312 .any(|d| d.kind == DiagnosticKind::XrefLoop)
1313 );
1314 }
1315
1316 #[test]
1317 fn revision_accessors() {
1318 let bytes = build(&[CATALOG, PAGES], "");
1319 let f = open(&bytes).unwrap();
1320 assert_eq!(f.newest_section_is_stream().unwrap(), Some(false));
1321 assert_eq!(f.startxref().unwrap(), Some(f.sections()[0]));
1322 assert!(!f.was_rebuilt());
1323 assert_eq!(f.read_all().unwrap(), bytes);
1324 let scanned =
1325 open(b"%PDF-1.4\n1 0 obj << /Type /Catalog /Pages 2 0 R >> endobj\n").unwrap();
1326 assert!(scanned.was_rebuilt());
1327 assert_eq!(scanned.newest_section_is_stream().unwrap(), None);
1328 }
1329
1330 #[test]
1331 fn object_header_scanner() {
1332 assert_eq!(object_header_before(b"\n12 0 obj", 6), Some((1, 12, 0)));
1333 assert_eq!(object_header_before(b"12 3obj", 4), Some((0, 12, 3)));
1334 assert_eq!(object_header_before(b"x12 0 obj", 6), None);
1335 assert_eq!(object_header_before(b"0 obj", 2), None);
1336 assert_eq!(object_header_before(b"1 999999 obj", 9), None);
1337 assert!(is_end_keyword(b"endobj", 3));
1338 }
1339}