1use std::cell::RefCell;
2use std::collections::BTreeMap;
3use std::path::Path;
4
5use crate::error::{PdfError, PdfResult};
6use crate::object::*;
7use crate::stream::{StreamDecoder, StreamFilter};
8
9#[derive(Debug, Clone, Copy, PartialEq, Default)]
10pub enum ParseMode {
11 Strict,
12 #[default]
13 Lenient,
14}
15
16#[derive(Debug, Clone, Copy)]
19pub struct ParserLimits {
20 pub max_objects: usize,
21 pub max_string_length: usize,
22 pub max_array_length: usize,
23 pub max_dict_entries: usize,
24 pub max_recursion_depth: u32,
25 pub max_stream_size: usize,
26 pub max_decoded_stream_size: usize,
27}
28
29impl Default for ParserLimits {
30 fn default() -> Self {
31 Self {
32 max_objects: 1_000_000,
33 max_string_length: 1_048_576,
34 max_array_length: 100_000,
35 max_dict_entries: 10_000,
36 max_recursion_depth: 64,
37 max_stream_size: 100 * 1024 * 1024,
38 max_decoded_stream_size: 100 * 1024 * 1024,
39 }
40 }
41}
42
43#[derive(Debug, Clone)]
44pub struct Document {
45 version: String,
46 objects: BTreeMap<ObjectId, PdfObject>,
51 catalog: Option<ObjectId>,
52 info: Option<ObjectId>,
53}
54
55impl Document {
56 pub fn new() -> Self {
57 Self {
58 version: "1.7".to_string(),
59 objects: BTreeMap::new(),
60 catalog: None,
61 info: None,
62 }
63 }
64
65 pub fn version(&self) -> &str {
66 &self.version
67 }
68
69 fn set_version(&mut self, version: String) {
70 self.version = version;
71 }
72
73 pub fn objects(&self) -> &BTreeMap<ObjectId, PdfObject> {
74 &self.objects
75 }
76
77 pub fn add_object(&mut self, id: ObjectId, obj: PdfObject) {
80 self.objects.entry(id).or_insert(obj);
81 }
82
83 pub fn get_object(&self, id: ObjectId) -> Option<&PdfObject> {
84 self.objects.get(&id)
85 }
86
87 pub fn get_object_mut(&mut self, id: ObjectId) -> Option<&mut PdfObject> {
88 self.objects.get_mut(&id)
89 }
90
91 pub fn set_catalog(&mut self, id: ObjectId) {
92 self.catalog = Some(id);
93 }
94
95 pub fn catalog(&self) -> Option<ObjectId> {
96 self.catalog
97 }
98
99 pub fn set_info(&mut self, id: ObjectId) {
100 self.info = Some(id);
101 }
102
103 pub fn info(&self) -> Option<ObjectId> {
104 self.info
105 }
106}
107
108impl Default for Document {
109 fn default() -> Self {
110 Self::new()
111 }
112}
113
114#[derive(Debug, Clone, Copy)]
115#[allow(dead_code)] enum XrefEntry {
117 Free { generation: u16 },
118 Used { offset: u64, generation: u16 },
119 Compressed { stream_number: u32, index: u32 },
120}
121
122type XrefSection = (BTreeMap<u32, (u16, XrefEntry)>, Option<PdfDictionary>);
123
124struct XrefInfo {
127 version: String,
128 xref: BTreeMap<u32, (u16, XrefEntry)>,
129 catalog: Option<ObjectId>,
130 info: Option<ObjectId>,
131}
132
133pub struct Parser {
134 mode: ParseMode,
135 limits: ParserLimits,
136}
137
138impl Parser {
139 pub fn new() -> Self {
140 Self {
141 mode: ParseMode::default(),
142 limits: ParserLimits::default(),
143 }
144 }
145
146 pub fn with_mode(mode: ParseMode) -> Self {
147 Self {
148 mode,
149 limits: ParserLimits::default(),
150 }
151 }
152
153 pub fn with_limits(limits: ParserLimits) -> Self {
154 Self {
155 mode: ParseMode::default(),
156 limits,
157 }
158 }
159
160 pub fn parse(&self, input: &[u8]) -> PdfResult<Document> {
161 let mut doc = Document::new();
162 let info = self.build_xref(input)?;
163 doc.set_version(info.version);
164 if let Some(id) = info.catalog {
165 doc.set_catalog(id);
166 }
167 if let Some(id) = info.info {
168 doc.set_info(id);
169 }
170
171 let mut object_streams: Vec<(u32, u32)> = Vec::new();
172 for (&number, &(generation, entry)) in &info.xref {
173 match entry {
174 XrefEntry::Free { .. } => {}
175 XrefEntry::Used { offset, .. } => {
176 let obj = self.parse_object_at(input, offset);
177 match obj {
178 Ok(obj) => doc.add_object(ObjectId::new(number, generation), obj),
179 Err(e) => {
180 if self.mode == ParseMode::Strict {
181 return Err(e);
182 }
183 }
184 }
185 }
186 XrefEntry::Compressed {
187 stream_number,
188 index,
189 } => {
190 object_streams.push((stream_number, index));
191 }
192 }
193 }
194
195 let mut resolved = BTreeMap::new();
197 for &(stream_number, index) in &object_streams {
198 let obj = doc.get_object(ObjectId::new(stream_number, 0));
199 if let Some(PdfObject::Stream(stream)) = obj {
200 let data = self.decode_stream(stream)?;
201 let n = stream.dictionary.get_integer("N").unwrap_or(0) as usize;
202 let first = stream.dictionary.get_integer("First").unwrap_or(0) as usize;
203 if let Some((obj_number, body)) =
204 parse_object_stream(&data, n, first, index, self.limits)
205 {
206 if let Some(header) = info.xref.get(&obj_number).copied() {
207 resolved.insert(obj_number, (header.0, body));
208 if resolved.len() > self.limits.max_objects {
209 return Err(PdfError::Parse {
210 offset: 0,
211 message: "compressed object count exceeds limit".to_string(),
212 });
213 }
214 }
215 }
216 }
217 }
218 for (number, (generation, body)) in resolved {
219 doc.add_object(ObjectId::new(number, generation), body);
220 }
221
222 Ok(doc)
223 }
224
225 fn build_xref(&self, input: &[u8]) -> PdfResult<XrefInfo> {
228 let mut version = String::new();
229 if let Some(eol) = input.iter().position(|&b| b == b'\n' || b == b'\r') {
230 if input.starts_with(b"%PDF-") {
231 version = String::from_utf8_lossy(&input[5..eol]).trim().to_string();
232 }
233 }
234
235 let kw_pos = find_startxref(input)?;
236 let mut lx = Lexer::new(input, kw_pos as usize, ParseMode::Lenient, self.limits);
237 lx.skip_ws();
238 lx.match_kw(b"startxref");
239 let mut offset = lx.parse_unsigned()?;
240 let mut xref: BTreeMap<u32, (u16, XrefEntry)> = BTreeMap::new();
241 let mut trailer: Option<PdfDictionary> = None;
242
243 loop {
244 let (section, dict) = self.parse_xref_section(input, offset)?;
245 for (number, entry) in section {
246 xref.entry(number).or_insert(entry);
247 }
248 if xref.len() > self.limits.max_objects {
249 return Err(PdfError::Parse {
250 offset,
251 message: format!(
252 "object count {} exceeds limit {}",
253 xref.len(),
254 self.limits.max_objects
255 ),
256 });
257 }
258 if dict.is_some() {
259 trailer = dict;
260 }
261 let prev = trailer
262 .as_ref()
263 .and_then(|d| d.get_integer("Prev"))
264 .filter(|p| *p > 0 && (*p as u64) < offset);
265 match prev {
266 Some(p) => offset = p as u64,
267 None => break,
268 }
269 }
270
271 let (catalog, info) = if let Some(t) = &trailer {
272 (
273 t.get("Root").and_then(|o| o.as_reference()),
274 t.get("Info").and_then(|o| o.as_reference()),
275 )
276 } else {
277 (None, None)
278 };
279 Ok(XrefInfo {
280 version,
281 xref,
282 catalog,
283 info,
284 })
285 }
286
287 pub fn parse_file(&self, path: &Path) -> PdfResult<Document> {
288 let data = std::fs::read(path)?;
289 self.parse(&data)
290 }
291
292 fn decode_stream(&self, stream: &PdfStream) -> PdfResult<Vec<u8>> {
293 let filter = stream.dictionary.get_name("Filter");
294 match filter {
295 Some(f) if f.as_str() == "FlateDecode" || f.as_str() == "Fl" => {
296 let decoded = StreamDecoder::new().decode_with_limit(
297 &stream.data,
298 StreamFilter::Flate,
299 self.limits.max_decoded_stream_size,
300 )?;
301 if decoded.len() > self.limits.max_decoded_stream_size {
302 return Err(PdfError::Parse {
303 offset: 0,
304 message: "decoded stream exceeds size limit".to_string(),
305 });
306 }
307 Ok(decoded)
308 }
309 Some(_) => Err(PdfError::NotImplemented(
310 "unsupported stream filter".to_string(),
311 )),
312 None => Ok(stream.data.clone()),
313 }
314 }
315
316 fn parse_xref_section(&self, input: &[u8], offset: u64) -> PdfResult<XrefSection> {
318 let mut lx = Lexer::new(input, offset as usize, self.mode, self.limits);
319 lx.skip_ws();
320 if lx.match_kw(b"xref") {
321 self.parse_classic_xref(&mut lx)
322 } else {
323 let obj = self.parse_object_at(input, offset)?;
324 match obj {
325 PdfObject::Stream(stream) => {
326 let dict = stream.dictionary.clone();
327 if dict.get_name("Type").map(|n| n.as_str()) != Some("XRef") {
328 return Err(PdfError::Parse {
329 offset,
330 message: "expected /Type /XRef in xref stream".to_string(),
331 });
332 }
333 let data = self.decode_stream(&stream)?;
334 let entries = parse_xref_stream_entries(&data, &dict, offset)?;
335 Ok((entries, Some(dict)))
336 }
337 _ => Err(PdfError::Parse {
338 offset,
339 message: "expected xref keyword or XRef stream".to_string(),
340 }),
341 }
342 }
343 }
344
345 fn parse_classic_xref(&self, lx: &mut Lexer<'_>) -> PdfResult<XrefSection> {
346 let mut entries = BTreeMap::new();
347 let max_entries = self.limits.max_objects;
348 loop {
349 lx.skip_ws();
350 let start = lx.parse_unsigned()?;
351 let count = lx.parse_unsigned()?;
352 for i in 0..count {
353 if entries.len() >= max_entries {
354 return Err(lx.err(format!(
355 "xref entry count {} exceeds limit {}",
356 entries.len(),
357 max_entries
358 )));
359 }
360 let n = (start + i) as u32;
361 lx.skip_ws();
362 let field1 = lx.parse_unsigned()?;
363 lx.skip_ws();
364 let field2 = lx.parse_unsigned()?;
365 lx.skip_ws();
366 match lx.bump() {
367 Some(b'n') => {
368 entries.insert(
369 n,
370 (
371 field2 as u16,
372 XrefEntry::Used {
373 offset: field1,
374 generation: field2 as u16,
375 },
376 ),
377 );
378 }
379 Some(b'f') => {
380 entries.insert(
381 n,
382 (
383 field2 as u16,
384 XrefEntry::Free {
385 generation: field2 as u16,
386 },
387 ),
388 );
389 }
390 _ => return Err(lx.err("expected `n` or `f` in xref entry")),
391 }
392 }
393 lx.skip_ws();
394 if lx.match_kw(b"trailer") {
395 break;
396 }
397 }
398 lx.skip_ws();
399 let dict = if lx.peek() == Some(b'<') {
400 let mut lx2 = Lexer::new(lx.data, lx.pos, self.mode, self.limits);
401 let d = lx2.parse_dict()?;
402 lx.pos = lx2.pos;
403 Some(d)
404 } else {
405 None
406 };
407 Ok((entries, dict))
408 }
409
410 fn parse_object_at(&self, input: &[u8], offset: u64) -> PdfResult<PdfObject> {
411 let mut lx = Lexer::new(input, offset as usize, self.mode, self.limits);
412 lx.skip_ws();
413 let _obj_number = lx.parse_unsigned()?;
414 lx.skip_ws();
415 let _generation = lx.parse_unsigned()?;
416 lx.skip_ws();
417 if !lx.match_kw(b"obj") {
418 return Err(lx.err("expected `obj` after object header"));
419 }
420 lx.skip_ws();
421 let obj = lx.parse_object()?;
422 lx.skip_ws();
423 if self.mode == ParseMode::Strict && !lx.match_kw(b"endobj") {
424 return Err(lx.err("expected `endobj`"));
425 }
426 Ok(obj)
427 }
428}
429
430pub struct StreamingParser {
436 parser: Parser,
437 file: std::fs::File,
438 file_len: u64,
439 xref: BTreeMap<u32, (u16, XrefEntry)>,
440 version: String,
441 catalog: Option<ObjectId>,
442 info: Option<ObjectId>,
443 cache: RefCell<BTreeMap<ObjectId, PdfObject>>,
444}
445
446impl StreamingParser {
447 pub fn open(path: impl AsRef<Path>) -> PdfResult<Self> {
449 Self::with_parser(Parser::new(), path)
450 }
451
452 pub fn with_parser(parser: Parser, path: impl AsRef<Path>) -> PdfResult<Self> {
455 let file = std::fs::File::open(path)?;
456 let file_len = file.metadata()?.len();
457 let info = Self::walk_xref(&parser, &file, file_len)?;
458 Ok(Self {
459 parser,
460 file,
461 file_len,
462 xref: info.xref,
463 version: info.version,
464 catalog: info.catalog,
465 info: info.info,
466 cache: RefCell::new(BTreeMap::new()),
467 })
468 }
469
470 pub fn version(&self) -> &str {
472 &self.version
473 }
474
475 pub fn catalog(&self) -> Option<ObjectId> {
476 self.catalog
477 }
478
479 pub fn info(&self) -> Option<ObjectId> {
480 self.info
481 }
482
483 pub fn object_count(&self) -> usize {
485 self.xref.len()
486 }
487
488 pub fn file_size(&self) -> u64 {
490 self.file_len
491 }
492
493 pub fn get_object(&self, id: ObjectId) -> PdfResult<Option<PdfObject>> {
496 if let Some(obj) = self.cache.borrow().get(&id) {
497 return Ok(Some(obj.clone()));
498 }
499 let Some(&(_, entry)) = self.xref.get(&id.number) else {
500 return Ok(None);
501 };
502 let obj = self.resolve_entry(id, entry, 0)?;
503 if let Some(obj) = &obj {
504 self.cache.borrow_mut().insert(id, obj.clone());
505 }
506 Ok(obj)
507 }
508
509 pub fn get_stream_data(&self, id: ObjectId) -> PdfResult<Option<Vec<u8>>> {
512 match self.get_object(id)? {
513 Some(PdfObject::Stream(stream)) => self.parser.decode_stream(&stream).map(Some),
514 Some(_) | None => Ok(None),
515 }
516 }
517
518 fn resolve_entry(
519 &self,
520 id: ObjectId,
521 entry: XrefEntry,
522 depth: u32,
523 ) -> PdfResult<Option<PdfObject>> {
524 if depth > 8 {
525 return Err(PdfError::Parse {
526 offset: id.number as u64,
527 message: "object stream nesting too deep".to_string(),
528 });
529 }
530 match entry {
531 XrefEntry::Free { .. } => Ok(None),
532 XrefEntry::Used { offset, .. } => self.parse_object_window(offset).map(Some),
533 XrefEntry::Compressed {
534 stream_number,
535 index,
536 } => {
537 let Some((_, stream_entry)) = self.xref.get(&stream_number) else {
538 return Err(PdfError::Parse {
539 offset: stream_number as u64,
540 message: "missing object stream".to_string(),
541 });
542 };
543 let stream_obj =
544 self.resolve_entry(ObjectId::new(stream_number, 0), *stream_entry, depth + 1)?;
545 let Some(PdfObject::Stream(stream)) = stream_obj else {
546 return Ok(None);
547 };
548 let data = self.parser.decode_stream(&stream)?;
549 let n = stream.dictionary.get_integer("N").unwrap_or(0) as usize;
550 let first = stream.dictionary.get_integer("First").unwrap_or(0) as usize;
551 let (_, body) = parse_object_stream(&data, n, first, index, self.parser.limits)
552 .ok_or_else(|| PdfError::Parse {
553 offset: index as u64,
554 message: "object not found in object stream".to_string(),
555 })?;
556 Ok(Some(body))
557 }
558 }
559 }
560
561 fn walk_xref(parser: &Parser, file: &std::fs::File, file_len: u64) -> PdfResult<XrefInfo> {
564 let mut version = String::new();
565 {
566 let head = read_at(file, file_len, 0, 1024)?;
567 if let Some(eol) = head.iter().position(|&b| b == b'\n' || b == b'\r') {
568 if head.starts_with(b"%PDF-") {
569 version = String::from_utf8_lossy(&head[5..eol]).trim().to_string();
570 }
571 }
572 }
573
574 let mut offset = find_startxref_windowed(file, file_len)?;
575 let mut xref: BTreeMap<u32, (u16, XrefEntry)> = BTreeMap::new();
576 let mut trailer: Option<PdfDictionary> = None;
577 loop {
578 let (section, dict) = parse_growing(file, file_len, offset, |buf| {
579 let mut lx = Lexer::new(buf, 0, parser.mode, parser.limits);
580 lx.skip_ws();
581 if lx.match_kw(b"xref") {
582 return parser.parse_classic_xref(&mut lx);
583 }
584 let obj = parser.parse_object_at(buf, 0)?;
585 match obj {
586 PdfObject::Stream(stream) => {
587 let dict = stream.dictionary.clone();
588 if dict.get_name("Type").map(|n| n.as_str()) != Some("XRef") {
589 return Err(PdfError::Parse {
590 offset: 0,
591 message: "expected /Type /XRef in xref stream".to_string(),
592 });
593 }
594 let data = parser.decode_stream(&stream)?;
595 let entries = parse_xref_stream_entries(&data, &dict, 0)?;
596 Ok((entries, Some(dict)))
597 }
598 _ => Err(PdfError::Parse {
599 offset: 0,
600 message: "expected xref keyword or XRef stream".to_string(),
601 }),
602 }
603 })?;
604 for (number, entry) in section {
605 xref.entry(number).or_insert(entry);
606 }
607 if xref.len() > parser.limits.max_objects {
608 return Err(PdfError::Parse {
609 offset,
610 message: format!(
611 "object count {} exceeds limit {}",
612 xref.len(),
613 parser.limits.max_objects
614 ),
615 });
616 }
617 if dict.is_some() {
618 trailer = dict;
619 }
620 let prev = trailer
621 .as_ref()
622 .and_then(|d| d.get_integer("Prev"))
623 .filter(|p| *p > 0 && (*p as u64) < offset);
624 match prev {
625 Some(p) => offset = p as u64,
626 None => break,
627 }
628 }
629
630 let (catalog, info) = if let Some(t) = &trailer {
631 (
632 t.get("Root").and_then(|o| o.as_reference()),
633 t.get("Info").and_then(|o| o.as_reference()),
634 )
635 } else {
636 (None, None)
637 };
638 Ok(XrefInfo {
639 version,
640 xref,
641 catalog,
642 info,
643 })
644 }
645
646 fn parse_object_window(&self, offset: u64) -> PdfResult<PdfObject> {
649 parse_growing(&self.file, self.file_len, offset, |buf| {
650 self.parser.parse_object_at(buf, 0)
651 })
652 }
653}
654
655fn parse_growing<R>(
659 file: &std::fs::File,
660 file_len: u64,
661 offset: u64,
662 f: impl Fn(&[u8]) -> PdfResult<R>,
663) -> PdfResult<R> {
664 let mut len = 64 * 1024u64;
665 loop {
666 let buf = read_at(file, file_len, offset, len)?;
667 match f(&buf) {
668 Ok(r) => return Ok(r),
669 Err(e) if (buf.len() as u64) < file_len.saturating_sub(offset) => {
670 if len >= file_len {
671 return Err(e);
672 }
673 len = len.saturating_mul(4);
674 }
675 Err(e) => return Err(e),
676 }
677 }
678}
679
680fn read_at(mut file: &std::fs::File, file_len: u64, offset: u64, len: u64) -> PdfResult<Vec<u8>> {
682 use std::io::{Read, Seek, SeekFrom};
683 let end = offset.saturating_add(len).min(file_len);
684 if end <= offset {
685 return Ok(Vec::new());
686 }
687 let mut buf = vec![0u8; (end - offset) as usize];
688 file.seek(SeekFrom::Start(offset))?;
689 file.read_exact(&mut buf)?;
690 Ok(buf)
691}
692
693fn find_startxref_windowed(file: &std::fs::File, file_len: u64) -> PdfResult<u64> {
698 let mut chunk = 64 * 1024u64;
699 loop {
700 let start = file_len.saturating_sub(chunk);
701 let buf = read_at(file, file_len, start, chunk)?;
702 if let Ok(i) = find_startxref(&buf) {
703 let abs = start + i;
704 let tail = read_at(file, file_len, abs, 16)?;
705 let mut lx = Lexer::new(&tail, 0, ParseMode::Lenient, ParserLimits::default());
706 lx.skip_ws();
707 lx.match_kw(b"startxref");
708 if let Ok(n) = lx.parse_unsigned() {
709 return Ok(n);
710 }
711 }
712 if start == 0 {
713 return Err(PdfError::Parse {
714 offset: 0,
715 message: "no startxref keyword found".to_string(),
716 });
717 }
718 chunk = chunk.saturating_mul(4);
719 }
720}
721
722impl Default for Parser {
723 fn default() -> Self {
724 Self::new()
725 }
726}
727
728fn parse_object_stream(
730 data: &[u8],
731 n: usize,
732 first: usize,
733 index: u32,
734 limits: ParserLimits,
735) -> Option<(u32, PdfObject)> {
736 let mut header_lexer = Lexer::new(data, 0, ParseMode::Lenient, limits);
737 let mut pairs = Vec::with_capacity(n.min(limits.max_objects));
738 for _ in 0..n.min(limits.max_objects) {
739 let num = header_lexer.parse_unsigned().ok()?;
740 let off = header_lexer.parse_unsigned().ok()?;
741 pairs.push((num as u32, off as usize));
742 }
743 let (num, off) = *pairs.get(index as usize)?;
744 let mut body = Lexer::new(data, first + off, ParseMode::Lenient, limits);
745 let obj = body.parse_object().ok()?;
746 Some((num, obj))
747}
748
749fn parse_xref_stream_entries(
751 data: &[u8],
752 dict: &PdfDictionary,
753 offset: u64,
754) -> PdfResult<BTreeMap<u32, (u16, XrefEntry)>> {
755 let w = dict.get_array("W").ok_or_else(|| PdfError::Parse {
756 offset,
757 message: "xref stream missing /W".to_string(),
758 })?;
759 let mut widths = Vec::new();
760 for item in w.0.iter() {
761 let i = item.as_integer().ok_or_else(|| PdfError::Parse {
762 offset,
763 message: "invalid /W entry".to_string(),
764 })?;
765 widths.push(i as usize);
766 }
767 let first = match dict.get_array("Index") {
768 Some(arr) => {
769 let mut out = Vec::new();
770 let mut it = arr.0.iter();
771 while let Some(f) = it.next() {
772 let count = it.next().ok_or_else(|| PdfError::Parse {
773 offset,
774 message: "invalid /Index".to_string(),
775 })?;
776 out.push((
777 f.as_integer().unwrap_or(0) as u32,
778 count.as_integer().unwrap_or(0) as u32,
779 ));
780 }
781 out
782 }
783 None => vec![(0, dict.get_integer("Size").unwrap_or(0) as u32)],
784 };
785
786 let record = widths[0] + widths[1] + widths[2];
787 if record == 0 {
788 return Ok(BTreeMap::new());
789 }
790 let mut entries = BTreeMap::new();
791 let mut pos = 0usize;
792 let field = |offset: usize, width: usize| -> u64 {
793 if width == 0 || offset + width > data.len() {
794 return 0;
795 }
796 let mut v: u64 = 0;
797 for &b in &data[offset..offset + width] {
798 v = (v << 8) | b as u64;
799 }
800 v
801 };
802 for (first, count) in first {
803 for i in 0..count {
804 if pos + record > data.len() {
805 break;
806 }
807 let typ = field(pos, widths[0]);
808 let f2 = field(pos + widths[0], widths[1]);
809 let f3 = field(pos + widths[0] + widths[1], widths[2]);
810 pos += record;
811 let number = first + i;
812 match typ {
813 0 => {
814 entries.insert(
815 number,
816 (
817 f3 as u16,
818 XrefEntry::Free {
819 generation: f3 as u16,
820 },
821 ),
822 );
823 }
824 1 => {
825 entries.insert(
826 number,
827 (
828 f3 as u16,
829 XrefEntry::Used {
830 offset: f2,
831 generation: f3 as u16,
832 },
833 ),
834 );
835 }
836 2 => {
837 entries.insert(
838 number,
839 (
840 0,
841 XrefEntry::Compressed {
842 stream_number: f2 as u32,
843 index: f3 as u32,
844 },
845 ),
846 );
847 }
848 _ => {}
849 }
850 }
851 }
852 Ok(entries)
853}
854
855fn find_startxref(input: &[u8]) -> PdfResult<u64> {
856 let needle = b"startxref";
857 let mut search = input.len();
858 loop {
859 let end = input[..search].len();
860 let idx = input[..end]
861 .windows(needle.len())
862 .rposition(|w| w == needle);
863 let i = match idx {
864 Some(i) => i,
865 None => {
866 return Err(PdfError::Parse {
867 offset: 0,
868 message: "no startxref keyword found".to_string(),
869 });
870 }
871 };
872 let prev_ok = i == 0 || matches!(input[i - 1], b' ' | b'\t' | b'\r' | b'\n' | b'\x0c' | 0);
873 let next = input.get(i + needle.len()).copied();
874 let next_ok = next
875 .map(|b| matches!(b, b' ' | b'\t' | b'\r' | b'\n' | b'\x0c' | 0))
876 .unwrap_or(true);
877 if prev_ok && next_ok {
878 return Ok(i as u64);
879 }
880 search = i;
881 if search == 0 {
882 break;
883 }
884 }
885 Err(PdfError::Parse {
886 offset: 0,
887 message: "no startxref keyword found".to_string(),
888 })
889}
890
891struct Lexer<'a> {
892 data: &'a [u8],
893 pos: usize,
894 strict: bool,
895 max_depth: u32,
896 depth: u32,
897 limits: ParserLimits,
898}
899
900impl<'a> Lexer<'a> {
901 fn new(data: &'a [u8], pos: usize, mode: ParseMode, limits: ParserLimits) -> Self {
902 Self {
903 data,
904 pos,
905 strict: mode == ParseMode::Strict,
906 max_depth: limits.max_recursion_depth,
907 depth: 0,
908 limits,
909 }
910 }
911
912 fn is_ws(b: u8) -> bool {
913 matches!(b, b' ' | b'\t' | b'\r' | b'\n' | 0x0c | 0)
914 }
915
916 fn is_delim(b: u8) -> bool {
917 matches!(
918 b,
919 b'(' | b')' | b'<' | b'>' | b'[' | b']' | b'{' | b'}' | b'/' | b'%'
920 )
921 }
922
923 fn peek(&self) -> Option<u8> {
924 self.data.get(self.pos).copied()
925 }
926
927 fn bump(&mut self) -> Option<u8> {
928 let b = self.peek();
929 if b.is_some() {
930 self.pos += 1;
931 }
932 b
933 }
934
935 fn err(&self, message: impl Into<String>) -> PdfError {
936 PdfError::Parse {
937 offset: self.pos as u64,
938 message: message.into(),
939 }
940 }
941
942 fn skip_ws(&mut self) {
943 loop {
944 while self
945 .data
946 .get(self.pos)
947 .copied()
948 .map(Self::is_ws)
949 .unwrap_or(false)
950 {
951 self.pos += 1;
952 }
953 if self.data.get(self.pos) == Some(&b'%') {
954 while self.pos < self.data.len() && self.data[self.pos] != b'\n' {
955 self.pos += 1;
956 }
957 } else {
958 break;
959 }
960 }
961 }
962
963 fn match_kw(&mut self, kw: &[u8]) -> bool {
964 if self
967 .data
968 .get(self.pos..)
969 .is_some_and(|rest| rest.starts_with(kw))
970 {
971 let after = self.data.get(self.pos + kw.len()).copied().unwrap_or(b' ');
972 if Self::is_ws(after) || Self::is_delim(after) {
973 self.pos += kw.len();
974 return true;
975 }
976 }
977 false
978 }
979
980 fn parse_unsigned(&mut self) -> PdfResult<u64> {
981 self.skip_ws();
982 let start = self.pos;
983 while self
984 .data
985 .get(self.pos)
986 .map(|b| b.is_ascii_digit())
987 .unwrap_or(false)
988 {
989 self.pos += 1;
990 }
991 if start == self.pos {
992 return Err(self.err("expected number"));
993 }
994 let mut value: u64 = 0;
995 for &b in &self.data[start..self.pos] {
996 value = match value
997 .checked_mul(10)
998 .and_then(|v| v.checked_add(u64::from(b - b'0')))
999 {
1000 Some(v) => v,
1001 None => return Err(self.err("number out of range")),
1002 };
1003 }
1004 Ok(value)
1005 }
1006
1007 fn parse_number_object(&mut self) -> PdfResult<PdfObject> {
1008 self.skip_ws();
1009 let start = self.pos;
1010 while self
1011 .data
1012 .get(self.pos)
1013 .is_some_and(|b| b.is_ascii_digit() || matches!(b, b'+' | b'-' | b'.' | b'e' | b'E'))
1014 {
1015 self.pos += 1;
1016 }
1017 if start == self.pos {
1018 return Err(self.err("expected number"));
1019 }
1020 let tok = &self.data[start..self.pos];
1021 if !tok.contains(&b'.') && !tok.contains(&b'e') && !tok.contains(&b'E') {
1022 let (negative, digits) = match tok.first() {
1025 Some(b'-') => (true, &tok[1..]),
1026 Some(b'+') => (false, &tok[1..]),
1027 _ => (false, tok),
1028 };
1029 if !digits.is_empty() && digits.iter().all(u8::is_ascii_digit) {
1033 let mut acc: i64 = 0;
1036 let mut overflow = false;
1037 for &b in digits {
1038 match acc
1039 .checked_mul(10)
1040 .and_then(|v| v.checked_sub(i64::from(b - b'0')))
1041 {
1042 Some(v) => acc = v,
1043 None => {
1044 overflow = true;
1045 break;
1046 }
1047 }
1048 }
1049 if !overflow {
1050 let value = if negative {
1051 Some(acc)
1052 } else {
1053 acc.checked_neg()
1054 };
1055 if let Some(value) = value {
1056 return Ok(PdfObject::Integer(value));
1057 }
1058 }
1059 }
1063 }
1064 let s = std::str::from_utf8(tok).map_err(|_| self.err("invalid number bytes"))?;
1065 let f: f64 = s
1066 .parse()
1067 .map_err(|_| self.err(format!("invalid number `{s}`")))?;
1068 Ok(PdfObject::Real(f))
1069 }
1070
1071 fn parse_name(&mut self) -> PdfResult<PdfName> {
1072 if self.peek() != Some(b'/') {
1073 return Err(self.err("expected name"));
1074 }
1075 self.bump();
1076 let mut bytes = Vec::with_capacity(8);
1077 loop {
1078 match self.peek() {
1079 None => break,
1080 Some(b) if Self::is_ws(b) || Self::is_delim(b) => break,
1081 Some(b'#') => {
1082 self.bump();
1083 let hex = [
1084 self.bump().ok_or_else(|| self.err("truncated #-escape"))?,
1085 self.bump().ok_or_else(|| self.err("truncated #-escape"))?,
1086 ];
1087 let pair = std::str::from_utf8(&hex).map_err(|_| self.err("bad #-escape"))?;
1088 let v = u8::from_str_radix(pair, 16).map_err(|_| self.err("bad #-escape"))?;
1089 bytes.push(v);
1090 }
1091 Some(b) => {
1092 self.bump();
1093 bytes.push(b);
1094 }
1095 }
1096 }
1097 let name = match String::from_utf8(bytes) {
1100 Ok(s) => s,
1101 Err(e) => String::from_utf8_lossy(e.as_bytes()).into_owned(),
1102 };
1103 Ok(PdfName::new(&name))
1104 }
1105
1106 fn enter_nested(&mut self) -> PdfResult<()> {
1107 self.depth += 1;
1108 if self.depth > self.max_depth {
1109 return Err(self.err("recursion limit exceeded"));
1110 }
1111 Ok(())
1112 }
1113
1114 fn exit_nested(&mut self) {
1115 self.depth = self.depth.saturating_sub(1);
1116 }
1117
1118 fn parse_literal_string(&mut self) -> PdfResult<PdfString> {
1119 self.bump(); let mut out = Vec::new();
1121 let mut depth = 1u32;
1122 loop {
1123 if out.len() >= self.limits.max_string_length {
1124 return Err(self.err("string length exceeds limit"));
1125 }
1126 match self.bump() {
1127 None => return Err(self.err("unterminated string")),
1128 Some(b'(') => {
1129 depth += 1;
1130 out.push(b'(');
1131 }
1132 Some(b')') => {
1133 depth -= 1;
1134 if depth == 0 {
1135 break;
1136 }
1137 out.push(b')');
1138 }
1139 Some(b'\\') => match self.bump() {
1140 None => return Err(self.err("truncated escape")),
1141 Some(b'n') => out.push(b'\n'),
1142 Some(b'r') => out.push(b'\r'),
1143 Some(b't') => out.push(b'\t'),
1144 Some(b'b') => out.push(8),
1145 Some(b'f') => out.push(12),
1146 Some(b'(') => out.push(b'('),
1147 Some(b')') => out.push(b')'),
1148 Some(b'\\') => out.push(b'\\'),
1149 Some(d @ b'0'..=b'7') => {
1150 let mut v = d - b'0';
1151 for _ in 0..2 {
1152 match self.peek() {
1153 Some(e @ b'0'..=b'7') => {
1154 v = v * 8 + (e - b'0');
1155 self.bump();
1156 }
1157 _ => break,
1158 }
1159 }
1160 out.push(v);
1161 }
1162 Some(b'\r') => {
1163 if self.peek() == Some(b'\n') {
1164 self.bump();
1165 }
1166 }
1167 Some(b'\n') => {}
1168 Some(other) => out.push(other),
1169 },
1170 Some(b'\r') => {
1171 if self.peek() == Some(b'\n') {
1172 self.bump();
1173 }
1174 out.push(b'\n');
1175 }
1176 Some(b) => out.push(b),
1177 }
1178 }
1179 Ok(PdfString(out))
1180 }
1181
1182 fn parse_hex_string(&mut self) -> PdfResult<PdfString> {
1183 self.bump(); let mut out = Vec::new();
1185 let mut hi: Option<u8> = None;
1186 loop {
1187 if out.len() >= self.limits.max_string_length {
1188 return Err(self.err("string length exceeds limit"));
1189 }
1190 match self.bump() {
1191 None => return Err(self.err("unterminated hex string")),
1192 Some(b'>') => {
1193 if let Some(h) = hi {
1194 out.push(h << 4);
1195 }
1196 break;
1197 }
1198 Some(b) if b.is_ascii_whitespace() => {}
1199 Some(b) => {
1200 let v = match b {
1201 b'0'..=b'9' => b - b'0',
1202 b'a'..=b'f' => b - b'a' + 10,
1203 b'A'..=b'F' => b - b'A' + 10,
1204 _ => return Err(self.err("invalid hex digit")),
1205 };
1206 match hi {
1207 None => hi = Some(v),
1208 Some(h) => {
1209 out.push((h << 4) | v);
1210 hi = None;
1211 }
1212 }
1213 }
1214 }
1215 }
1216 Ok(PdfString(out))
1217 }
1218
1219 fn parse_array(&mut self) -> PdfResult<PdfArray> {
1220 self.bump(); self.enter_nested()?;
1222 let mut arr = PdfArray::new();
1223 loop {
1224 self.skip_ws();
1225 match self.peek() {
1226 None => return Err(self.err("unterminated array")),
1227 Some(b']') => {
1228 self.bump();
1229 self.exit_nested();
1230 break;
1231 }
1232 _ => {
1233 if arr.len() >= self.limits.max_array_length {
1234 return Err(self.err("array length exceeds limit"));
1235 }
1236 arr.push(self.parse_object()?);
1237 }
1238 }
1239 }
1240 Ok(arr)
1241 }
1242
1243 fn parse_dict(&mut self) -> PdfResult<PdfDictionary> {
1244 self.bump();
1245 self.bump(); self.enter_nested()?;
1247 let mut dict = PdfDictionary::new();
1248 loop {
1249 self.skip_ws();
1250 match self.peek() {
1251 None => return Err(self.err("unterminated dictionary")),
1252 Some(b'>') => {
1253 if self.data.get(self.pos + 1) == Some(&b'>') {
1254 self.bump();
1255 self.bump();
1256 self.exit_nested();
1257 break;
1258 }
1259 return Err(self.err("single `>` inside dictionary"));
1260 }
1261 Some(b'/') => {
1262 if dict.len() >= self.limits.max_dict_entries {
1263 return Err(self.err("dictionary entry count exceeds limit"));
1264 }
1265 let key = self.parse_name()?;
1266 self.skip_ws();
1267 let val = self.parse_object()?;
1268 dict.insert(key.as_str(), val);
1269 }
1270 _ => return Err(self.err("expected /Name in dictionary")),
1271 }
1272 }
1273 Ok(dict)
1274 }
1275
1276 fn parse_object(&mut self) -> PdfResult<PdfObject> {
1277 self.skip_ws();
1278 match self.peek() {
1279 None => Err(self.err("expected object")),
1280 Some(b'[') => Ok(PdfObject::Array(self.parse_array()?)),
1281 Some(b'<') => {
1282 if self.data.get(self.pos + 1) == Some(&b'<') {
1283 let dict = self.parse_dict()?;
1284 self.skip_ws();
1285 if self.match_kw(b"stream") {
1286 if self.peek() == Some(b'\r') {
1287 self.bump();
1288 }
1289 if self.peek() == Some(b'\n') {
1290 self.bump();
1291 }
1292 let len = dict
1293 .get_integer("Length")
1294 .filter(|l| *l >= 0)
1295 .ok_or_else(|| self.err("stream missing valid /Length"))?
1296 as usize;
1297 if len > self.limits.max_stream_size {
1298 return Err(self.err("stream length exceeds limit"));
1299 }
1300 if self.pos + len > self.data.len() {
1301 return Err(self.err("stream /Length exceeds file"));
1302 }
1303 let data = self.data[self.pos..self.pos + len].to_vec();
1304 self.pos += len;
1305 self.skip_ws();
1306 if self.strict && !self.match_kw(b"endstream") {
1307 return Err(self.err("expected `endstream`"));
1308 }
1309 Ok(PdfObject::Stream(PdfStream::with_dict(dict, data)))
1310 } else {
1311 Ok(PdfObject::Dictionary(dict))
1312 }
1313 } else {
1314 Ok(PdfObject::String(self.parse_hex_string()?))
1315 }
1316 }
1317 Some(b'(') => Ok(PdfObject::String(self.parse_literal_string()?)),
1318 Some(b'/') => Ok(PdfObject::Name(self.parse_name()?)),
1319 Some(b'+') | Some(b'-') | Some(b'.') | Some(b'0'..=b'9') => {
1320 let first = self.parse_number_object()?;
1321 self.skip_ws();
1322 let next_is_number = matches!(
1323 self.peek(),
1324 Some(b'+') | Some(b'-') | Some(b'.') | Some(b'0'..=b'9')
1325 );
1326 if next_is_number {
1327 let before_second = self.pos;
1328 let second = self.parse_number_object()?;
1329 self.skip_ws();
1330 if self.peek() == Some(b'R') {
1331 self.bump();
1332 let num = first
1333 .as_integer()
1334 .ok_or_else(|| self.err("non-integer object number in reference"))?
1335 as u32;
1336 let gen = second
1337 .as_integer()
1338 .ok_or_else(|| self.err("non-integer generation in reference"))?
1339 as u16;
1340 return Ok(PdfObject::Reference(ObjectId::new(num, gen)));
1341 }
1342 self.pos = before_second;
1344 }
1345 Ok(first)
1346 }
1347 Some(b'T' | b't' | b'F' | b'f' | b'N' | b'n') => {
1348 for kw in [b"true".as_slice(), b"True".as_slice(), b"TRUE".as_slice()] {
1349 if self.match_kw(kw) {
1350 return Ok(PdfObject::Boolean(true));
1351 }
1352 }
1353 for kw in [
1354 b"false".as_slice(),
1355 b"False".as_slice(),
1356 b"FALSE".as_slice(),
1357 ] {
1358 if self.match_kw(kw) {
1359 return Ok(PdfObject::Boolean(false));
1360 }
1361 }
1362 for kw in [b"null".as_slice(), b"Null".as_slice(), b"NULL".as_slice()] {
1363 if self.match_kw(kw) {
1364 return Ok(PdfObject::Null);
1365 }
1366 }
1367 Err(self.err("unknown keyword"))
1368 }
1369 Some(_) => Err(self.err("unexpected token")),
1370 }
1371 }
1372}
1373
1374#[cfg(test)]
1375mod tests {
1376 use super::*;
1377 use crate::serializer::Serializer;
1378
1379 #[test]
1380 fn roundtrip_via_serializer() {
1381 let mut doc = Document::new();
1382 doc.set_catalog(ObjectId::new(1, 0));
1383 doc.set_info(ObjectId::new(4, 0));
1384 let mut catalog = PdfDictionary::new();
1385 catalog.insert("Type", PdfObject::Name(PdfName::new("Catalog")));
1386 catalog.insert("Pages", PdfObject::Reference(ObjectId::new(2, 0)));
1387 doc.add_object(ObjectId::new(1, 0), PdfObject::Dictionary(catalog));
1388 let mut pages = PdfDictionary::new();
1389 pages.insert("Type", PdfObject::Name(PdfName::new("Pages")));
1390 pages.insert("Count", PdfObject::Integer(1));
1391 let mut kids = PdfArray::new();
1392 kids.push(PdfObject::Reference(ObjectId::new(3, 0)));
1393 pages.insert("Kids", PdfObject::Array(kids));
1394 doc.add_object(ObjectId::new(2, 0), PdfObject::Dictionary(pages));
1395 let mut page = PdfDictionary::new();
1396 page.insert("Type", PdfObject::Name(PdfName::new("Page")));
1397 let mut media = PdfArray::new();
1398 media.push(PdfObject::Integer(0));
1399 media.push(PdfObject::Integer(0));
1400 media.push(PdfObject::Integer(612));
1401 media.push(PdfObject::Integer(792));
1402 page.insert("MediaBox", PdfObject::Array(media));
1403 doc.add_object(ObjectId::new(3, 0), PdfObject::Dictionary(page));
1404 let mut info = PdfDictionary::new();
1405 info.insert("Title", PdfObject::String(PdfString::from_literal("t")));
1406 doc.add_object(ObjectId::new(4, 0), PdfObject::Dictionary(info));
1407
1408 let mut buf = std::io::Cursor::new(Vec::new());
1409 Serializer::new().serialize(&doc, &mut buf).unwrap();
1410 let parsed = Parser::new().parse(buf.get_ref()).unwrap();
1411
1412 assert_eq!(parsed.catalog(), Some(ObjectId::new(1, 0)));
1413 assert_eq!(parsed.info(), Some(ObjectId::new(4, 0)));
1414 assert_eq!(parsed.objects().len(), 4);
1415 let media_data = parsed
1416 .get_object(ObjectId::new(3, 0))
1417 .unwrap()
1418 .as_dict()
1419 .unwrap()
1420 .get_array("MediaBox")
1421 .unwrap()
1422 .clone();
1423 assert_eq!(
1424 media_data.0,
1425 vec![
1426 PdfObject::Integer(0),
1427 PdfObject::Integer(0),
1428 PdfObject::Integer(612),
1429 PdfObject::Integer(792),
1430 ]
1431 );
1432 }
1433
1434 #[test]
1435 fn string_escaping_survives_serialize_parse_roundtrip() {
1436 let cases: &[&[u8]] = &[
1437 b"plain",
1438 b"with (parens) and \\ backslash",
1439 b"line1\nline2\r\nline3\rline4",
1440 "café ☕ 漢字 𝄞".as_bytes(),
1441 &[0x01, 0x07, 0x0b, 0x1b, 0x7f], b"tab\there\t",
1443 b"",
1444 ];
1445 for &input in cases {
1446 let mut doc = Document::new();
1447 doc.set_catalog(ObjectId::new(1, 0));
1448 let mut dict = PdfDictionary::new();
1449 dict.insert("S", PdfObject::String(PdfString::from_bytes(input)));
1450 dict.insert("N", PdfObject::Real(1.5));
1451 doc.add_object(ObjectId::new(1, 0), PdfObject::Dictionary(dict));
1452
1453 let mut buf = std::io::Cursor::new(Vec::new());
1454 Serializer::new().serialize(&doc, &mut buf).unwrap();
1455 let parsed = Parser::new().parse(buf.get_ref()).unwrap();
1456 let round = parsed
1457 .get_object(ObjectId::new(1, 0))
1458 .unwrap()
1459 .as_dict()
1460 .unwrap()
1461 .get_string_bytes("S")
1462 .unwrap();
1463 assert_eq!(round, input, "roundtrip failed for {input:?}");
1464 }
1465 }
1466
1467 #[test]
1468 fn text_operator_escaping_roundtrips_tj_string() {
1469 let tricky = vec![
1471 b"(a)".to_vec(),
1472 b"line1\nline2".to_vec(),
1473 "café ☕".as_bytes().to_vec(),
1474 b"\\back\\slash".to_vec(),
1475 ];
1476 for text in tricky {
1477 let mut doc = Document::new();
1478 doc.set_catalog(ObjectId::new(1, 0));
1479 let mut dict = PdfDictionary::new();
1480 dict.insert("Length", PdfObject::Integer(text.len() as i64));
1481 let stream = PdfStream::with_dict(dict, text.clone());
1482 doc.add_object(ObjectId::new(1, 0), PdfObject::Stream(stream));
1483
1484 let mut buf = std::io::Cursor::new(Vec::new());
1485 Serializer::new().serialize(&doc, &mut buf).unwrap();
1486 let parsed = Parser::new().parse(buf.get_ref()).unwrap();
1487 let round = parsed
1488 .get_object(ObjectId::new(1, 0))
1489 .unwrap()
1490 .as_stream()
1491 .unwrap()
1492 .data
1493 .clone();
1494 assert_eq!(round, text, "stream roundtrip failed for {text:?}");
1495 }
1496 }
1497
1498 #[test]
1499 fn parses_xref_stream_entries() {
1500 let mut dict = PdfDictionary::new();
1501 dict.insert("Type", PdfObject::Name(PdfName::new("XRef")));
1502 dict.insert("Size", PdfObject::Integer(4));
1503 let mut w = PdfArray::new();
1504 w.push(PdfObject::Integer(1));
1505 w.push(PdfObject::Integer(4));
1506 w.push(PdfObject::Integer(2));
1507 dict.insert("W", PdfObject::Array(w));
1508
1509 let mut data = Vec::new();
1510 data.extend_from_slice(&[0, 0, 0, 0, 0, 0xff, 0xff]);
1511 data.extend_from_slice(&[1, 0, 0, 0, 15, 0, 0]);
1512 data.extend_from_slice(&[2, 0, 0, 0, 10, 0, 3]);
1513 data.extend_from_slice(&[1, 0, 0, 3, 232, 0, 0]);
1514
1515 let entries = parse_xref_stream_entries(&data, &dict, 0).unwrap();
1516 assert_eq!(entries.len(), 4);
1517 assert!(matches!(
1518 entries[&0].1,
1519 XrefEntry::Free { generation: 65535 }
1520 ));
1521 assert!(matches!(
1522 entries[&1].1,
1523 XrefEntry::Used {
1524 offset: 15,
1525 generation: 0
1526 }
1527 ));
1528 assert!(matches!(
1529 entries[&2].1,
1530 XrefEntry::Compressed {
1531 stream_number: 10,
1532 index: 3
1533 }
1534 ));
1535 assert!(matches!(
1536 entries[&3].1,
1537 XrefEntry::Used {
1538 offset: 1000,
1539 generation: 0
1540 }
1541 ));
1542 }
1543
1544 #[test]
1545 fn decodes_flate_streams() {
1546 let plain = b"BT /F1 12 Tf 10 20 Td (hi) Tj ET";
1547 let mut dict = PdfDictionary::new();
1548 dict.insert("Filter", PdfObject::Name(PdfName::new("FlateDecode")));
1549 let encoded = crate::stream::StreamEncoder::new()
1550 .encode(plain, crate::stream::StreamFilter::Flate)
1551 .unwrap();
1552 let stream = PdfStream::with_dict(dict, encoded);
1553 let decoded = Parser::new().decode_stream(&stream).unwrap();
1554 assert_eq!(decoded, plain);
1555 }
1556
1557 #[test]
1558 fn parses_object_stream_objects() {
1559 let mut data = Vec::new();
1560 data.extend_from_slice(b"7 0 8 6");
1561 while data.len() < 12 {
1562 data.push(b' ');
1563 }
1564 data.extend_from_slice(b"42 ");
1565 while data.len() < 18 {
1566 data.push(b' ');
1567 }
1568 data.extend_from_slice(b"<< /A (x) >>");
1569
1570 let (num, obj) = parse_object_stream(&data, 2, 12, 0, ParserLimits::default()).unwrap();
1571 assert_eq!(num, 7);
1572 assert_eq!(obj, PdfObject::Integer(42));
1573
1574 let (num, obj) = parse_object_stream(&data, 2, 12, 1, ParserLimits::default()).unwrap();
1575 assert_eq!(num, 8);
1576 assert_eq!(
1577 obj.as_dict().unwrap().get("A"),
1578 Some(&PdfObject::String(PdfString::from_literal("x")))
1579 );
1580 assert!(parse_object_stream(&data, 2, 12, 9, ParserLimits::default()).is_none());
1581 }
1582
1583 #[test]
1584 fn parses_strings_names_and_numbers() {
1585 let mut lx = Lexer::new(
1586 b"(a\\(b\\)c) /A#20B 42 -1.5e2 true null <48656c6c6f>",
1587 0,
1588 ParseMode::Strict,
1589 ParserLimits::default(),
1590 );
1591 assert_eq!(
1592 lx.parse_object().unwrap(),
1593 PdfObject::String(PdfString::from_bytes(&b"a(b)c"[..]))
1594 );
1595 lx.skip_ws();
1596 assert_eq!(
1597 lx.parse_object().unwrap(),
1598 PdfObject::Name(PdfName::new("A B"))
1599 );
1600 lx.skip_ws();
1601 assert_eq!(lx.parse_object().unwrap(), PdfObject::Integer(42));
1602 lx.skip_ws();
1603 match lx.parse_object().unwrap() {
1604 PdfObject::Real(f) => assert!((f - -150.0).abs() < 0.001),
1605 _ => panic!("expected real"),
1606 }
1607 lx.skip_ws();
1608 assert_eq!(lx.parse_object().unwrap(), PdfObject::Boolean(true));
1609 lx.skip_ws();
1610 assert_eq!(lx.parse_object().unwrap(), PdfObject::Null);
1611 lx.skip_ws();
1612 assert_eq!(
1613 lx.parse_object().unwrap(),
1614 PdfObject::String(PdfString::from_bytes(&b"Hello"[..]))
1615 );
1616 }
1617
1618 fn tight_limits() -> ParserLimits {
1619 ParserLimits {
1620 max_objects: 4,
1621 max_string_length: 8,
1622 max_array_length: 4,
1623 max_dict_entries: 4,
1624 max_recursion_depth: 3,
1625 max_stream_size: 16,
1626 max_decoded_stream_size: 16,
1627 }
1628 }
1629
1630 #[test]
1631 fn limits_reject_overlong_string() {
1632 let mut lx = Lexer::new(b"(0123456789)", 0, ParseMode::Strict, tight_limits());
1633 assert!(lx.parse_object().is_err());
1634 }
1635
1636 #[test]
1637 fn limits_reject_deep_nesting() {
1638 let deep = b"[[[[[[[[[0]]]]]]]]]";
1639 let mut lx = Lexer::new(deep, 0, ParseMode::Strict, tight_limits());
1640 assert!(lx.parse_object().is_err());
1641 }
1642
1643 #[test]
1644 fn limits_reject_huge_array() {
1645 let mut lx = Lexer::new(b"[1 2 3 4 5]", 0, ParseMode::Strict, tight_limits());
1646 assert!(lx.parse_object().is_err());
1647 }
1648
1649 #[test]
1650 fn limits_reject_huge_stream_length() {
1651 let input = b"<< /Length 1000 >>\nstream\n0123456789\nendstream";
1653 let mut lx = Lexer::new(input, 0, ParseMode::Strict, tight_limits());
1654 assert!(lx.parse_object().is_err());
1655 }
1656
1657 #[test]
1658 fn limits_reject_too_many_objects() {
1659 let mut doc = Document::new();
1661 for i in 1..=6u32 {
1662 doc.add_object(ObjectId::new(i, 0), PdfObject::Integer(i as i64));
1663 }
1664 let mut buf = std::io::Cursor::new(Vec::new());
1665 Serializer::new().serialize(&doc, &mut buf).unwrap();
1666 let limits = ParserLimits {
1667 max_objects: 4,
1668 ..tight_limits()
1669 };
1670 assert!(Parser::with_limits(limits).parse(buf.get_ref()).is_err());
1671 }
1672
1673 #[test]
1674 fn limits_reject_too_many_xref_entries() {
1675 let mut pdf = Vec::new();
1678 pdf.extend_from_slice(b"%PDF-1.7\n");
1679 pdf.extend_from_slice(b"1 0 obj\n<<>>\nendobj\n");
1680 let xref_at = pdf.len() as u64;
1681 pdf.extend_from_slice(b"xref\n0 6\n");
1682 pdf.extend_from_slice(b"0000000000 65535 f \n");
1683 for i in 1..=5u64 {
1684 pdf.extend_from_slice(format!("{i:010} 00000 n \n").as_bytes());
1685 }
1686 pdf.extend_from_slice(b"trailer\n<< /Size 6 /Root 1 0 R >>\nstartxref\n");
1687 pdf.extend_from_slice(format!("{xref_at}\n").as_bytes());
1688 pdf.extend_from_slice(b"%%EOF");
1689
1690 let limits = ParserLimits {
1691 max_objects: 4,
1692 ..tight_limits()
1693 };
1694 let err = Parser::with_limits(limits).parse(&pdf).unwrap_err();
1695 assert!(
1696 err.to_string().contains("xref entry count"),
1697 "unexpected error: {err}"
1698 );
1699 }
1700
1701 struct XorShift(u64);
1703
1704 impl XorShift {
1705 fn next(&mut self) -> u64 {
1706 let mut x = self.0;
1707 x ^= x << 13;
1708 x ^= x >> 7;
1709 x ^= x << 17;
1710 self.0 = x;
1711 x
1712 }
1713 }
1714
1715 #[test]
1716 fn random_bytes_never_panic() {
1717 let mut rng = XorShift(0x9E37_79B9_7F4A_7C15);
1721 for _ in 0..5000 {
1722 let len = (rng.next() % 512) as usize;
1723 let bytes: Vec<u8> = (0..len).map(|_| (rng.next() & 0xff) as u8).collect();
1724 let _ = Parser::new().parse(&bytes);
1725 }
1726 }
1727
1728 #[test]
1729 fn byte_flipped_pdf_never_panics() {
1730 let mut doc = Document::new();
1733 doc.set_catalog(ObjectId::new(1, 0));
1734 for i in 1..=4u32 {
1735 let mut dict = PdfDictionary::new();
1736 dict.insert("Type", PdfObject::Name(PdfName::new(&format!("T{i}"))));
1737 dict.insert("N", PdfObject::Integer(i as i64));
1738 doc.add_object(ObjectId::new(i, 0), PdfObject::Dictionary(dict));
1739 }
1740 let mut buf = std::io::Cursor::new(Vec::new());
1741 Serializer::new().serialize(&doc, &mut buf).unwrap();
1742 let original = buf.into_inner();
1743 let mut rng = XorShift(0xD1B5_4A32_D192_ED03);
1744 for _ in 0..1000 {
1745 let mut mutated = original.clone();
1746 let flips = 1 + (rng.next() % 4) as usize;
1747 for _ in 0..flips {
1748 let idx = (rng.next() as usize) % mutated.len().max(1);
1749 mutated[idx] ^= 1 << (rng.next() % 8);
1750 }
1751 let _ = Parser::new().parse(&mutated);
1752 }
1753 }
1754
1755 #[test]
1756 fn malformed_inputs_error_without_panicking() {
1757 let cases: &[&[u8]] = &[
1758 b"",
1759 b"%PDF-1.7",
1760 b"garbage",
1761 b"%PDF-1.7\n1 0 obj<<>>endobj\nxref\n0 1\ntrailer\nstartxref\n0\n%%EOF",
1762 b"%PDF-1.7\n1 0 obj\n(abc",
1763 b"%PDF-1.7\n1 0 obj\n<< /Length -5 >>\nstream\nx",
1764 &b"%PDF-1.7\n".repeat(2),
1765 &[0xff, 0xfe, 0x00, 0x00, 0x41, 0x42],
1766 ];
1767 for input in cases {
1768 let _ = Parser::new().parse(input);
1770 }
1771 }
1772
1773 #[test]
1774 fn stream_roundtrip_with_small_and_large_data() {
1775 for size in [0, 1, 15, 16, 17, 1000] {
1776 let payload: Vec<u8> = (0..size).map(|i| (i % 251) as u8).collect();
1777 let mut dict = PdfDictionary::new();
1778 dict.insert("Filter", PdfObject::Name(PdfName::new("FlateDecode")));
1779 let encoded = crate::stream::StreamEncoder::new()
1780 .encode(&payload, crate::stream::StreamFilter::Flate)
1781 .unwrap();
1782 let stream = PdfStream::with_dict(dict, encoded);
1783 let decoded = Parser::new().decode_stream(&stream).unwrap();
1784 assert_eq!(decoded, payload, "size {size} mismatch");
1785 }
1786 }
1787
1788 #[test]
1789 fn decoded_stream_size_is_bounded() {
1790 let payload = vec![0u8; 1_000_000];
1793 let mut dict = PdfDictionary::new();
1794 dict.insert("Filter", PdfObject::Name(PdfName::new("FlateDecode")));
1795 let encoded = crate::stream::StreamEncoder::new()
1796 .encode(&payload, crate::stream::StreamFilter::Flate)
1797 .unwrap();
1798 assert!(encoded.len() < 2000, "test premise: bomb must compress");
1799 let stream = PdfStream::with_dict(dict, encoded);
1800 let limits = ParserLimits {
1801 max_decoded_stream_size: 64 * 1024,
1802 ..ParserLimits::default()
1803 };
1804 if let Ok(d) = Parser::with_limits(limits).decode_stream(&stream) {
1805 assert!(d.len() <= 64 * 1024 + 1);
1806 }
1807 }
1808
1809 #[test]
1810 fn streaming_matches_full_parse_on_file() {
1811 let mut doc = Document::new();
1812 doc.set_version("1.7".to_string());
1813 let mut catalog = PdfDictionary::new();
1814 catalog.insert("Type", PdfObject::Name(PdfName::new("Catalog")));
1815 doc.add_object(ObjectId::new(1, 0), PdfObject::Dictionary(catalog));
1816 let mut info = PdfDictionary::new();
1817 info.insert("Title", PdfObject::String(PdfString::from_literal("t")));
1818 doc.add_object(ObjectId::new(2, 0), PdfObject::Dictionary(info));
1819 for i in 0..30 {
1820 doc.add_object(
1821 ObjectId::new(3 + i, 0),
1822 PdfObject::Array(PdfArray(
1823 (0..i).map(|j| PdfObject::Integer(j as i64)).collect(),
1824 )),
1825 );
1826 }
1827 let payload: Vec<u8> = (0..70000).map(|i| (i % 251) as u8).collect();
1828 let mut dict = PdfDictionary::new();
1829 dict.insert("Filter", PdfObject::Name(PdfName::new("FlateDecode")));
1830 let encoded = crate::stream::StreamEncoder::new()
1831 .encode(&payload, crate::stream::StreamFilter::Flate)
1832 .unwrap();
1833 doc.add_object(
1834 ObjectId::new(40, 0),
1835 PdfObject::Stream(PdfStream::with_dict(dict, encoded)),
1836 );
1837
1838 let mut buf = std::io::Cursor::new(Vec::new());
1839 Serializer::new().serialize(&doc, &mut buf).unwrap();
1840 let path = std::env::temp_dir().join(format!("pf_stream_{}.pdf", std::process::id()));
1841 std::fs::write(&path, buf.get_ref()).unwrap();
1842
1843 let parsed = Parser::new().parse(buf.get_ref()).unwrap();
1844 let streaming = StreamingParser::open(&path).unwrap();
1845 assert_eq!(streaming.version(), parsed.version());
1846 assert_eq!(streaming.catalog(), parsed.catalog());
1847 assert_eq!(streaming.info(), parsed.info());
1848 assert!(streaming.object_count() >= parsed.objects().len());
1849 assert!(streaming.file_size() > 0);
1850
1851 for (id, obj) in parsed.objects() {
1852 assert_eq!(streaming.get_object(*id).unwrap().as_ref(), Some(obj));
1853 }
1854 assert!(streaming
1855 .get_object(ObjectId::new(999, 0))
1856 .unwrap()
1857 .is_none());
1858
1859 let data = streaming
1860 .get_stream_data(ObjectId::new(40, 0))
1861 .unwrap()
1862 .unwrap();
1863 assert_eq!(data, payload);
1864 let _ = std::fs::remove_file(&path);
1865 }
1866
1867 #[test]
1868 fn streaming_resolves_compressed_objects() {
1869 let mut pdf = Vec::new();
1872 pdf.extend_from_slice(b"%PDF-1.7\n");
1873 let p1 = pdf.len() as u32;
1874 pdf.extend_from_slice(b"1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n");
1875 let p2 = pdf.len() as u32;
1876 pdf.extend_from_slice(b"2 0 obj\n<< /Type /Pages /Kids [] /Count 0 >>\nendobj\n");
1877 let p3 = pdf.len() as u32;
1878 pdf.extend_from_slice(
1879 b"3 0 obj\n<< /Type /ObjStm /N 2 /First 9 /Length 42 >>\nstream\n\
18817 0 8 17\n<< /A (seven) >> << /B (eight) >>\nendstream\nendobj\n",
1882 );
1883
1884 let mut xref_data = Vec::new();
1885 xref_data.extend_from_slice(&[0, 0, 0, 0, 0, 0xff, 0xff]);
1886 for pos in [p1, p2, p3] {
1887 let b = pos.to_be_bytes();
1888 xref_data.extend_from_slice(&[1, b[0], b[1], b[2], b[3], 0, 0]);
1889 }
1890 xref_data.extend_from_slice(&[2, 0, 0, 0, 3, 0, 0]);
1891 xref_data.extend_from_slice(&[2, 0, 0, 0, 3, 0, 1]);
1892
1893 let p10 = pdf.len() as u32;
1894 let b = p10.to_be_bytes();
1895 xref_data.extend_from_slice(&[1, b[0], b[1], b[2], b[3], 0, 0]);
1896 let xref_obj = format!(
1897 "10 0 obj\n<< /Type /XRef /Size 11 /Root 1 0 R /W [1 4 2] /Index [0 4 7 2 10 1] /Length {} >>\nstream\n",
1898 xref_data.len()
1899 );
1900 pdf.extend_from_slice(xref_obj.as_bytes());
1901 pdf.extend_from_slice(&xref_data);
1902 pdf.extend_from_slice(b"\nendstream\nendobj\n");
1903 pdf.extend_from_slice(format!("startxref\n{p10}\n%%EOF\n").as_bytes());
1904
1905 let path = std::env::temp_dir().join(format!("pf_objstm_{}.pdf", std::process::id()));
1906 std::fs::write(&path, &pdf).unwrap();
1907
1908 let streaming = StreamingParser::open(&path).unwrap();
1909 assert_eq!(streaming.catalog(), Some(ObjectId::new(1, 0)));
1910 assert_eq!(streaming.object_count(), 7);
1911 let seven = streaming.get_object(ObjectId::new(7, 0)).unwrap().unwrap();
1912 assert_eq!(
1913 seven.as_dict().unwrap().get("A"),
1914 Some(&PdfObject::String(PdfString::from_literal("seven")))
1915 );
1916 let eight = streaming.get_object(ObjectId::new(8, 0)).unwrap().unwrap();
1917 assert_eq!(
1918 eight.as_dict().unwrap().get("B"),
1919 Some(&PdfObject::String(PdfString::from_literal("eight")))
1920 );
1921 let _ = std::fs::remove_file(&path);
1922 }
1923}