1use std::collections::BTreeMap;
2use std::path::Path;
3
4use crate::error::{PdfError, PdfResult};
5use crate::object::*;
6use crate::stream::{StreamDecoder, StreamFilter};
7
8#[derive(Debug, Clone, Copy, PartialEq, Default)]
9pub enum ParseMode {
10 Strict,
11 #[default]
12 Lenient,
13}
14
15#[derive(Debug, Clone, Copy)]
18pub struct ParserLimits {
19 pub max_objects: usize,
20 pub max_string_length: usize,
21 pub max_array_length: usize,
22 pub max_dict_entries: usize,
23 pub max_recursion_depth: u32,
24 pub max_stream_size: usize,
25 pub max_decoded_stream_size: usize,
26}
27
28impl Default for ParserLimits {
29 fn default() -> Self {
30 Self {
31 max_objects: 1_000_000,
32 max_string_length: 1_048_576,
33 max_array_length: 100_000,
34 max_dict_entries: 10_000,
35 max_recursion_depth: 64,
36 max_stream_size: 100 * 1024 * 1024,
37 max_decoded_stream_size: 100 * 1024 * 1024,
38 }
39 }
40}
41
42#[derive(Debug, Clone)]
43pub struct Document {
44 version: String,
45 objects: BTreeMap<ObjectId, PdfObject>,
50 catalog: Option<ObjectId>,
51 info: Option<ObjectId>,
52}
53
54impl Document {
55 pub fn new() -> Self {
56 Self {
57 version: "1.7".to_string(),
58 objects: BTreeMap::new(),
59 catalog: None,
60 info: None,
61 }
62 }
63
64 pub fn version(&self) -> &str {
65 &self.version
66 }
67
68 fn set_version(&mut self, version: String) {
69 self.version = version;
70 }
71
72 pub fn objects(&self) -> &BTreeMap<ObjectId, PdfObject> {
73 &self.objects
74 }
75
76 pub fn add_object(&mut self, id: ObjectId, obj: PdfObject) {
79 self.objects.entry(id).or_insert(obj);
80 }
81
82 pub fn get_object(&self, id: ObjectId) -> Option<&PdfObject> {
83 self.objects.get(&id)
84 }
85
86 pub fn get_object_mut(&mut self, id: ObjectId) -> Option<&mut PdfObject> {
87 self.objects.get_mut(&id)
88 }
89
90 pub fn set_catalog(&mut self, id: ObjectId) {
91 self.catalog = Some(id);
92 }
93
94 pub fn catalog(&self) -> Option<ObjectId> {
95 self.catalog
96 }
97
98 pub fn set_info(&mut self, id: ObjectId) {
99 self.info = Some(id);
100 }
101
102 pub fn info(&self) -> Option<ObjectId> {
103 self.info
104 }
105}
106
107impl Default for Document {
108 fn default() -> Self {
109 Self::new()
110 }
111}
112
113#[derive(Debug, Clone, Copy)]
114#[allow(dead_code)] enum XrefEntry {
116 Free { generation: u16 },
117 Used { offset: u64, generation: u16 },
118 Compressed { stream_number: u32, index: u32 },
119}
120
121type XrefSection = (BTreeMap<u32, (u16, XrefEntry)>, Option<PdfDictionary>);
122
123pub struct Parser {
124 mode: ParseMode,
125 limits: ParserLimits,
126}
127
128impl Parser {
129 pub fn new() -> Self {
130 Self {
131 mode: ParseMode::default(),
132 limits: ParserLimits::default(),
133 }
134 }
135
136 pub fn with_mode(mode: ParseMode) -> Self {
137 Self {
138 mode,
139 limits: ParserLimits::default(),
140 }
141 }
142
143 pub fn with_limits(limits: ParserLimits) -> Self {
144 Self {
145 mode: ParseMode::default(),
146 limits,
147 }
148 }
149
150 pub fn parse(&self, input: &[u8]) -> PdfResult<Document> {
151 let mut doc = Document::new();
152 if let Some(eol) = input.iter().position(|&b| b == b'\n' || b == b'\r') {
153 if input.starts_with(b"%PDF-") {
154 doc.set_version(String::from_utf8_lossy(&input[5..eol]).trim().to_string());
155 }
156 }
157
158 let kw_pos = find_startxref(input)?;
159 let mut lx = Lexer::new(input, kw_pos as usize, ParseMode::Lenient, self.limits);
160 lx.skip_ws();
161 lx.match_kw(b"startxref");
162 let mut offset = lx.parse_unsigned()?;
163 let mut xref: BTreeMap<u32, (u16, XrefEntry)> = BTreeMap::new();
164 let mut trailer: Option<PdfDictionary> = None;
165
166 loop {
167 let (section, dict) = self.parse_xref_section(input, offset)?;
168 for (number, entry) in section {
169 xref.entry(number).or_insert(entry);
170 }
171 if xref.len() > self.limits.max_objects {
172 return Err(PdfError::Parse {
173 offset,
174 message: format!(
175 "object count {} exceeds limit {}",
176 xref.len(),
177 self.limits.max_objects
178 ),
179 });
180 }
181 if dict.is_some() {
182 trailer = dict;
183 }
184 let prev = trailer
185 .as_ref()
186 .and_then(|d| d.get_integer("Prev"))
187 .filter(|p| *p > 0 && (*p as u64) < offset);
188 match prev {
189 Some(p) => offset = p as u64,
190 None => break,
191 }
192 }
193
194 if let Some(t) = &trailer {
195 if let Some(id) = t.get("Root").and_then(|o| o.as_reference()) {
196 doc.set_catalog(id);
197 }
198 if let Some(id) = t.get("Info").and_then(|o| o.as_reference()) {
199 doc.set_info(id);
200 }
201 }
202
203 let mut object_streams: Vec<(u32, u32)> = Vec::new();
204 for (&number, &(generation, entry)) in &xref {
205 match entry {
206 XrefEntry::Free { .. } => {}
207 XrefEntry::Used { offset, .. } => {
208 let obj = self.parse_object_at(input, offset);
209 match obj {
210 Ok(obj) => doc.add_object(ObjectId::new(number, generation), obj),
211 Err(e) => {
212 if self.mode == ParseMode::Strict {
213 return Err(e);
214 }
215 }
216 }
217 }
218 XrefEntry::Compressed {
219 stream_number,
220 index,
221 } => {
222 object_streams.push((stream_number, index));
223 }
224 }
225 }
226
227 let mut resolved = BTreeMap::new();
229 for &(stream_number, index) in &object_streams {
230 let obj = doc.get_object(ObjectId::new(stream_number, 0));
231 if let Some(PdfObject::Stream(stream)) = obj {
232 let data = self.decode_stream(stream)?;
233 let n = stream.dictionary.get_integer("N").unwrap_or(0) as usize;
234 let first = stream.dictionary.get_integer("First").unwrap_or(0) as usize;
235 if let Some((obj_number, body)) =
236 parse_object_stream(&data, n, first, index, self.limits)
237 {
238 if let Some(header) = xref.get(&obj_number).copied() {
239 resolved.insert(obj_number, (header.0, body));
240 if resolved.len() > self.limits.max_objects {
241 return Err(PdfError::Parse {
242 offset: 0,
243 message: "compressed object count exceeds limit".to_string(),
244 });
245 }
246 }
247 }
248 }
249 }
250 for (number, (generation, body)) in resolved {
251 doc.add_object(ObjectId::new(number, generation), body);
252 }
253
254 Ok(doc)
255 }
256
257 pub fn parse_file(&self, path: &Path) -> PdfResult<Document> {
258 let data = std::fs::read(path)?;
259 self.parse(&data)
260 }
261
262 fn decode_stream(&self, stream: &PdfStream) -> PdfResult<Vec<u8>> {
263 let filter = stream.dictionary.get_name("Filter");
264 match filter {
265 Some(f) if f.as_str() == "FlateDecode" || f.as_str() == "Fl" => {
266 let decoded = StreamDecoder::new().decode_with_limit(
267 &stream.data,
268 StreamFilter::Flate,
269 self.limits.max_decoded_stream_size,
270 )?;
271 if decoded.len() > self.limits.max_decoded_stream_size {
272 return Err(PdfError::Parse {
273 offset: 0,
274 message: "decoded stream exceeds size limit".to_string(),
275 });
276 }
277 Ok(decoded)
278 }
279 Some(_) => Err(PdfError::NotImplemented(
280 "unsupported stream filter".to_string(),
281 )),
282 None => Ok(stream.data.clone()),
283 }
284 }
285
286 fn parse_xref_section(&self, input: &[u8], offset: u64) -> PdfResult<XrefSection> {
288 let mut lx = Lexer::new(input, offset as usize, self.mode, self.limits);
289 lx.skip_ws();
290 if lx.match_kw(b"xref") {
291 self.parse_classic_xref(&mut lx)
292 } else {
293 let obj = self.parse_object_at(input, offset)?;
294 match obj {
295 PdfObject::Stream(stream) => {
296 let dict = stream.dictionary.clone();
297 if dict.get_name("Type").map(|n| n.as_str()) != Some("XRef") {
298 return Err(PdfError::Parse {
299 offset,
300 message: "expected /Type /XRef in xref stream".to_string(),
301 });
302 }
303 let data = self.decode_stream(&stream)?;
304 let entries = parse_xref_stream_entries(&data, &dict, offset)?;
305 Ok((entries, Some(dict)))
306 }
307 _ => Err(PdfError::Parse {
308 offset,
309 message: "expected xref keyword or XRef stream".to_string(),
310 }),
311 }
312 }
313 }
314
315 fn parse_classic_xref(&self, lx: &mut Lexer<'_>) -> PdfResult<XrefSection> {
316 let mut entries = BTreeMap::new();
317 let max_entries = self.limits.max_objects;
318 loop {
319 lx.skip_ws();
320 let start = lx.parse_unsigned()?;
321 let count = lx.parse_unsigned()?;
322 for i in 0..count {
323 if entries.len() >= max_entries {
324 return Err(lx.err(format!(
325 "xref entry count {} exceeds limit {}",
326 entries.len(),
327 max_entries
328 )));
329 }
330 let n = (start + i) as u32;
331 lx.skip_ws();
332 let field1 = lx.parse_unsigned()?;
333 lx.skip_ws();
334 let field2 = lx.parse_unsigned()?;
335 lx.skip_ws();
336 match lx.bump() {
337 Some(b'n') => {
338 entries.insert(
339 n,
340 (
341 field2 as u16,
342 XrefEntry::Used {
343 offset: field1,
344 generation: field2 as u16,
345 },
346 ),
347 );
348 }
349 Some(b'f') => {
350 entries.insert(
351 n,
352 (
353 field2 as u16,
354 XrefEntry::Free {
355 generation: field2 as u16,
356 },
357 ),
358 );
359 }
360 _ => return Err(lx.err("expected `n` or `f` in xref entry")),
361 }
362 }
363 lx.skip_ws();
364 if lx.match_kw(b"trailer") {
365 break;
366 }
367 }
368 lx.skip_ws();
369 let dict = if lx.peek() == Some(b'<') {
370 let mut lx2 = Lexer::new(lx.data, lx.pos, self.mode, self.limits);
371 let d = lx2.parse_dict()?;
372 lx.pos = lx2.pos;
373 Some(d)
374 } else {
375 None
376 };
377 Ok((entries, dict))
378 }
379
380 fn parse_object_at(&self, input: &[u8], offset: u64) -> PdfResult<PdfObject> {
381 let mut lx = Lexer::new(input, offset as usize, self.mode, self.limits);
382 lx.skip_ws();
383 let _obj_number = lx.parse_unsigned()?;
384 lx.skip_ws();
385 let _generation = lx.parse_unsigned()?;
386 lx.skip_ws();
387 if !lx.match_kw(b"obj") {
388 return Err(lx.err("expected `obj` after object header"));
389 }
390 lx.skip_ws();
391 let obj = lx.parse_object()?;
392 lx.skip_ws();
393 if self.mode == ParseMode::Strict && !lx.match_kw(b"endobj") {
394 return Err(lx.err("expected `endobj`"));
395 }
396 Ok(obj)
397 }
398}
399
400impl Default for Parser {
401 fn default() -> Self {
402 Self::new()
403 }
404}
405
406fn parse_object_stream(
408 data: &[u8],
409 n: usize,
410 first: usize,
411 index: u32,
412 limits: ParserLimits,
413) -> Option<(u32, PdfObject)> {
414 let mut header_lexer = Lexer::new(data, 0, ParseMode::Lenient, limits);
415 let mut pairs = Vec::with_capacity(n.min(limits.max_objects));
416 for _ in 0..n.min(limits.max_objects) {
417 let num = header_lexer.parse_unsigned().ok()?;
418 let off = header_lexer.parse_unsigned().ok()?;
419 pairs.push((num as u32, off as usize));
420 }
421 let (num, off) = *pairs.get(index as usize)?;
422 let mut body = Lexer::new(data, first + off, ParseMode::Lenient, limits);
423 let obj = body.parse_object().ok()?;
424 Some((num, obj))
425}
426
427fn parse_xref_stream_entries(
429 data: &[u8],
430 dict: &PdfDictionary,
431 offset: u64,
432) -> PdfResult<BTreeMap<u32, (u16, XrefEntry)>> {
433 let w = dict.get_array("W").ok_or_else(|| PdfError::Parse {
434 offset,
435 message: "xref stream missing /W".to_string(),
436 })?;
437 let mut widths = Vec::new();
438 for item in w.0.iter() {
439 let i = item.as_integer().ok_or_else(|| PdfError::Parse {
440 offset,
441 message: "invalid /W entry".to_string(),
442 })?;
443 widths.push(i as usize);
444 }
445 let first = match dict.get_array("Index") {
446 Some(arr) => {
447 let mut out = Vec::new();
448 let mut it = arr.0.iter();
449 while let Some(f) = it.next() {
450 let count = it.next().ok_or_else(|| PdfError::Parse {
451 offset,
452 message: "invalid /Index".to_string(),
453 })?;
454 out.push((
455 f.as_integer().unwrap_or(0) as u32,
456 count.as_integer().unwrap_or(0) as u32,
457 ));
458 }
459 out
460 }
461 None => vec![(0, dict.get_integer("Size").unwrap_or(0) as u32)],
462 };
463
464 let record = widths[0] + widths[1] + widths[2];
465 if record == 0 {
466 return Ok(BTreeMap::new());
467 }
468 let mut entries = BTreeMap::new();
469 let mut pos = 0usize;
470 let field = |offset: usize, width: usize| -> u64 {
471 if width == 0 || offset + width > data.len() {
472 return 0;
473 }
474 let mut v: u64 = 0;
475 for &b in &data[offset..offset + width] {
476 v = (v << 8) | b as u64;
477 }
478 v
479 };
480 for (first, count) in first {
481 for i in 0..count {
482 if pos + record > data.len() {
483 break;
484 }
485 let typ = field(pos, widths[0]);
486 let f2 = field(pos + widths[0], widths[1]);
487 let f3 = field(pos + widths[0] + widths[1], widths[2]);
488 pos += record;
489 let number = first + i;
490 match typ {
491 0 => {
492 entries.insert(
493 number,
494 (
495 f3 as u16,
496 XrefEntry::Free {
497 generation: f3 as u16,
498 },
499 ),
500 );
501 }
502 1 => {
503 entries.insert(
504 number,
505 (
506 f3 as u16,
507 XrefEntry::Used {
508 offset: f2,
509 generation: f3 as u16,
510 },
511 ),
512 );
513 }
514 2 => {
515 entries.insert(
516 number,
517 (
518 0,
519 XrefEntry::Compressed {
520 stream_number: f2 as u32,
521 index: f3 as u32,
522 },
523 ),
524 );
525 }
526 _ => {}
527 }
528 }
529 }
530 Ok(entries)
531}
532
533fn find_startxref(input: &[u8]) -> PdfResult<u64> {
534 let needle = b"startxref";
535 let mut search = input.len();
536 loop {
537 let end = input[..search].len();
538 let idx = input[..end]
539 .windows(needle.len())
540 .rposition(|w| w == needle);
541 let i = match idx {
542 Some(i) => i,
543 None => {
544 return Err(PdfError::Parse {
545 offset: 0,
546 message: "no startxref keyword found".to_string(),
547 });
548 }
549 };
550 let prev_ok = i == 0 || matches!(input[i - 1], b' ' | b'\t' | b'\r' | b'\n' | b'\x0c' | 0);
551 let next = input.get(i + needle.len()).copied();
552 let next_ok = next
553 .map(|b| matches!(b, b' ' | b'\t' | b'\r' | b'\n' | b'\x0c' | 0))
554 .unwrap_or(true);
555 if prev_ok && next_ok {
556 return Ok(i as u64);
557 }
558 search = i;
559 if search == 0 {
560 break;
561 }
562 }
563 Err(PdfError::Parse {
564 offset: 0,
565 message: "no startxref keyword found".to_string(),
566 })
567}
568
569struct Lexer<'a> {
570 data: &'a [u8],
571 pos: usize,
572 strict: bool,
573 max_depth: u32,
574 depth: u32,
575 limits: ParserLimits,
576}
577
578impl<'a> Lexer<'a> {
579 fn new(data: &'a [u8], pos: usize, mode: ParseMode, limits: ParserLimits) -> Self {
580 Self {
581 data,
582 pos,
583 strict: mode == ParseMode::Strict,
584 max_depth: limits.max_recursion_depth,
585 depth: 0,
586 limits,
587 }
588 }
589
590 fn is_ws(b: u8) -> bool {
591 matches!(b, b' ' | b'\t' | b'\r' | b'\n' | 0x0c | 0)
592 }
593
594 fn is_delim(b: u8) -> bool {
595 matches!(
596 b,
597 b'(' | b')' | b'<' | b'>' | b'[' | b']' | b'{' | b'}' | b'/' | b'%'
598 )
599 }
600
601 fn peek(&self) -> Option<u8> {
602 self.data.get(self.pos).copied()
603 }
604
605 fn bump(&mut self) -> Option<u8> {
606 let b = self.peek();
607 if b.is_some() {
608 self.pos += 1;
609 }
610 b
611 }
612
613 fn err(&self, message: impl Into<String>) -> PdfError {
614 PdfError::Parse {
615 offset: self.pos as u64,
616 message: message.into(),
617 }
618 }
619
620 fn skip_ws(&mut self) {
621 loop {
622 while self
623 .data
624 .get(self.pos)
625 .copied()
626 .map(Self::is_ws)
627 .unwrap_or(false)
628 {
629 self.pos += 1;
630 }
631 if self.data.get(self.pos) == Some(&b'%') {
632 while self.pos < self.data.len() && self.data[self.pos] != b'\n' {
633 self.pos += 1;
634 }
635 } else {
636 break;
637 }
638 }
639 }
640
641 fn match_kw(&mut self, kw: &[u8]) -> bool {
642 if self
645 .data
646 .get(self.pos..)
647 .is_some_and(|rest| rest.starts_with(kw))
648 {
649 let after = self.data.get(self.pos + kw.len()).copied().unwrap_or(b' ');
650 if Self::is_ws(after) || Self::is_delim(after) {
651 self.pos += kw.len();
652 return true;
653 }
654 }
655 false
656 }
657
658 fn parse_unsigned(&mut self) -> PdfResult<u64> {
659 self.skip_ws();
660 let start = self.pos;
661 while self
662 .data
663 .get(self.pos)
664 .map(|b| b.is_ascii_digit())
665 .unwrap_or(false)
666 {
667 self.pos += 1;
668 }
669 if start == self.pos {
670 return Err(self.err("expected number"));
671 }
672 let mut value: u64 = 0;
673 for &b in &self.data[start..self.pos] {
674 value = match value
675 .checked_mul(10)
676 .and_then(|v| v.checked_add(u64::from(b - b'0')))
677 {
678 Some(v) => v,
679 None => return Err(self.err("number out of range")),
680 };
681 }
682 Ok(value)
683 }
684
685 fn parse_number_object(&mut self) -> PdfResult<PdfObject> {
686 self.skip_ws();
687 let start = self.pos;
688 while self
689 .data
690 .get(self.pos)
691 .is_some_and(|b| b.is_ascii_digit() || matches!(b, b'+' | b'-' | b'.' | b'e' | b'E'))
692 {
693 self.pos += 1;
694 }
695 if start == self.pos {
696 return Err(self.err("expected number"));
697 }
698 let tok = &self.data[start..self.pos];
699 if !tok.contains(&b'.') && !tok.contains(&b'e') && !tok.contains(&b'E') {
700 let (negative, digits) = match tok.first() {
703 Some(b'-') => (true, &tok[1..]),
704 Some(b'+') => (false, &tok[1..]),
705 _ => (false, tok),
706 };
707 if !digits.is_empty() && digits.iter().all(u8::is_ascii_digit) {
711 let mut acc: i64 = 0;
714 let mut overflow = false;
715 for &b in digits {
716 match acc
717 .checked_mul(10)
718 .and_then(|v| v.checked_sub(i64::from(b - b'0')))
719 {
720 Some(v) => acc = v,
721 None => {
722 overflow = true;
723 break;
724 }
725 }
726 }
727 if !overflow {
728 let value = if negative {
729 Some(acc)
730 } else {
731 acc.checked_neg()
732 };
733 if let Some(value) = value {
734 return Ok(PdfObject::Integer(value));
735 }
736 }
737 }
741 }
742 let s = std::str::from_utf8(tok).map_err(|_| self.err("invalid number bytes"))?;
743 let f: f64 = s
744 .parse()
745 .map_err(|_| self.err(format!("invalid number `{s}`")))?;
746 Ok(PdfObject::Real(f))
747 }
748
749 fn parse_name(&mut self) -> PdfResult<PdfName> {
750 if self.peek() != Some(b'/') {
751 return Err(self.err("expected name"));
752 }
753 self.bump();
754 let mut bytes = Vec::with_capacity(8);
755 loop {
756 match self.peek() {
757 None => break,
758 Some(b) if Self::is_ws(b) || Self::is_delim(b) => break,
759 Some(b'#') => {
760 self.bump();
761 let hex = [
762 self.bump().ok_or_else(|| self.err("truncated #-escape"))?,
763 self.bump().ok_or_else(|| self.err("truncated #-escape"))?,
764 ];
765 let pair = std::str::from_utf8(&hex).map_err(|_| self.err("bad #-escape"))?;
766 let v = u8::from_str_radix(pair, 16).map_err(|_| self.err("bad #-escape"))?;
767 bytes.push(v);
768 }
769 Some(b) => {
770 self.bump();
771 bytes.push(b);
772 }
773 }
774 }
775 let name = match String::from_utf8(bytes) {
778 Ok(s) => s,
779 Err(e) => String::from_utf8_lossy(e.as_bytes()).into_owned(),
780 };
781 Ok(PdfName(name))
782 }
783
784 fn enter_nested(&mut self) -> PdfResult<()> {
785 self.depth += 1;
786 if self.depth > self.max_depth {
787 return Err(self.err("recursion limit exceeded"));
788 }
789 Ok(())
790 }
791
792 fn exit_nested(&mut self) {
793 self.depth = self.depth.saturating_sub(1);
794 }
795
796 fn parse_literal_string(&mut self) -> PdfResult<PdfString> {
797 self.bump(); let mut out = Vec::new();
799 let mut depth = 1u32;
800 loop {
801 if out.len() >= self.limits.max_string_length {
802 return Err(self.err("string length exceeds limit"));
803 }
804 match self.bump() {
805 None => return Err(self.err("unterminated string")),
806 Some(b'(') => {
807 depth += 1;
808 out.push(b'(');
809 }
810 Some(b')') => {
811 depth -= 1;
812 if depth == 0 {
813 break;
814 }
815 out.push(b')');
816 }
817 Some(b'\\') => match self.bump() {
818 None => return Err(self.err("truncated escape")),
819 Some(b'n') => out.push(b'\n'),
820 Some(b'r') => out.push(b'\r'),
821 Some(b't') => out.push(b'\t'),
822 Some(b'b') => out.push(8),
823 Some(b'f') => out.push(12),
824 Some(b'(') => out.push(b'('),
825 Some(b')') => out.push(b')'),
826 Some(b'\\') => out.push(b'\\'),
827 Some(d @ b'0'..=b'7') => {
828 let mut v = d - b'0';
829 for _ in 0..2 {
830 match self.peek() {
831 Some(e @ b'0'..=b'7') => {
832 v = v * 8 + (e - b'0');
833 self.bump();
834 }
835 _ => break,
836 }
837 }
838 out.push(v);
839 }
840 Some(b'\r') => {
841 if self.peek() == Some(b'\n') {
842 self.bump();
843 }
844 }
845 Some(b'\n') => {}
846 Some(other) => out.push(other),
847 },
848 Some(b'\r') => {
849 if self.peek() == Some(b'\n') {
850 self.bump();
851 }
852 out.push(b'\n');
853 }
854 Some(b) => out.push(b),
855 }
856 }
857 Ok(PdfString(out))
858 }
859
860 fn parse_hex_string(&mut self) -> PdfResult<PdfString> {
861 self.bump(); let mut out = Vec::new();
863 let mut hi: Option<u8> = None;
864 loop {
865 if out.len() >= self.limits.max_string_length {
866 return Err(self.err("string length exceeds limit"));
867 }
868 match self.bump() {
869 None => return Err(self.err("unterminated hex string")),
870 Some(b'>') => {
871 if let Some(h) = hi {
872 out.push(h << 4);
873 }
874 break;
875 }
876 Some(b) if b.is_ascii_whitespace() => {}
877 Some(b) => {
878 let v = match b {
879 b'0'..=b'9' => b - b'0',
880 b'a'..=b'f' => b - b'a' + 10,
881 b'A'..=b'F' => b - b'A' + 10,
882 _ => return Err(self.err("invalid hex digit")),
883 };
884 match hi {
885 None => hi = Some(v),
886 Some(h) => {
887 out.push((h << 4) | v);
888 hi = None;
889 }
890 }
891 }
892 }
893 }
894 Ok(PdfString(out))
895 }
896
897 fn parse_array(&mut self) -> PdfResult<PdfArray> {
898 self.bump(); self.enter_nested()?;
900 let mut arr = PdfArray::new();
901 loop {
902 self.skip_ws();
903 match self.peek() {
904 None => return Err(self.err("unterminated array")),
905 Some(b']') => {
906 self.bump();
907 self.exit_nested();
908 break;
909 }
910 _ => {
911 if arr.len() >= self.limits.max_array_length {
912 return Err(self.err("array length exceeds limit"));
913 }
914 arr.push(self.parse_object()?);
915 }
916 }
917 }
918 Ok(arr)
919 }
920
921 fn parse_dict(&mut self) -> PdfResult<PdfDictionary> {
922 self.bump();
923 self.bump(); self.enter_nested()?;
925 let mut dict = PdfDictionary::new();
926 loop {
927 self.skip_ws();
928 match self.peek() {
929 None => return Err(self.err("unterminated dictionary")),
930 Some(b'>') => {
931 if self.data.get(self.pos + 1) == Some(&b'>') {
932 self.bump();
933 self.bump();
934 self.exit_nested();
935 break;
936 }
937 return Err(self.err("single `>` inside dictionary"));
938 }
939 Some(b'/') => {
940 if dict.len() >= self.limits.max_dict_entries {
941 return Err(self.err("dictionary entry count exceeds limit"));
942 }
943 let key = self.parse_name()?;
944 self.skip_ws();
945 let val = self.parse_object()?;
946 dict.insert(&key.0, val);
947 }
948 _ => return Err(self.err("expected /Name in dictionary")),
949 }
950 }
951 Ok(dict)
952 }
953
954 fn parse_object(&mut self) -> PdfResult<PdfObject> {
955 self.skip_ws();
956 match self.peek() {
957 None => Err(self.err("expected object")),
958 Some(b'[') => Ok(PdfObject::Array(self.parse_array()?)),
959 Some(b'<') => {
960 if self.data.get(self.pos + 1) == Some(&b'<') {
961 let dict = self.parse_dict()?;
962 self.skip_ws();
963 if self.match_kw(b"stream") {
964 if self.peek() == Some(b'\r') {
965 self.bump();
966 }
967 if self.peek() == Some(b'\n') {
968 self.bump();
969 }
970 let len = dict
971 .get_integer("Length")
972 .filter(|l| *l >= 0)
973 .ok_or_else(|| self.err("stream missing valid /Length"))?
974 as usize;
975 if len > self.limits.max_stream_size {
976 return Err(self.err("stream length exceeds limit"));
977 }
978 if self.pos + len > self.data.len() {
979 return Err(self.err("stream /Length exceeds file"));
980 }
981 let data = self.data[self.pos..self.pos + len].to_vec();
982 self.pos += len;
983 self.skip_ws();
984 if self.strict && !self.match_kw(b"endstream") {
985 return Err(self.err("expected `endstream`"));
986 }
987 Ok(PdfObject::Stream(PdfStream::with_dict(dict, data)))
988 } else {
989 Ok(PdfObject::Dictionary(dict))
990 }
991 } else {
992 Ok(PdfObject::String(self.parse_hex_string()?))
993 }
994 }
995 Some(b'(') => Ok(PdfObject::String(self.parse_literal_string()?)),
996 Some(b'/') => Ok(PdfObject::Name(self.parse_name()?)),
997 Some(b'+') | Some(b'-') | Some(b'.') | Some(b'0'..=b'9') => {
998 let first = self.parse_number_object()?;
999 self.skip_ws();
1000 let next_is_number = matches!(
1001 self.peek(),
1002 Some(b'+') | Some(b'-') | Some(b'.') | Some(b'0'..=b'9')
1003 );
1004 if next_is_number {
1005 let before_second = self.pos;
1006 let second = self.parse_number_object()?;
1007 self.skip_ws();
1008 if self.peek() == Some(b'R') {
1009 self.bump();
1010 let num = first
1011 .as_integer()
1012 .ok_or_else(|| self.err("non-integer object number in reference"))?
1013 as u32;
1014 let gen = second
1015 .as_integer()
1016 .ok_or_else(|| self.err("non-integer generation in reference"))?
1017 as u16;
1018 return Ok(PdfObject::Reference(ObjectId::new(num, gen)));
1019 }
1020 self.pos = before_second;
1022 }
1023 Ok(first)
1024 }
1025 Some(b'T' | b't' | b'F' | b'f' | b'N' | b'n') => {
1026 for kw in [b"true".as_slice(), b"True".as_slice(), b"TRUE".as_slice()] {
1027 if self.match_kw(kw) {
1028 return Ok(PdfObject::Boolean(true));
1029 }
1030 }
1031 for kw in [
1032 b"false".as_slice(),
1033 b"False".as_slice(),
1034 b"FALSE".as_slice(),
1035 ] {
1036 if self.match_kw(kw) {
1037 return Ok(PdfObject::Boolean(false));
1038 }
1039 }
1040 for kw in [b"null".as_slice(), b"Null".as_slice(), b"NULL".as_slice()] {
1041 if self.match_kw(kw) {
1042 return Ok(PdfObject::Null);
1043 }
1044 }
1045 Err(self.err("unknown keyword"))
1046 }
1047 Some(_) => Err(self.err("unexpected token")),
1048 }
1049 }
1050}
1051
1052#[cfg(test)]
1053mod tests {
1054 use super::*;
1055 use crate::serializer::Serializer;
1056
1057 #[test]
1058 fn roundtrip_via_serializer() {
1059 let mut doc = Document::new();
1060 doc.set_catalog(ObjectId::new(1, 0));
1061 doc.set_info(ObjectId::new(4, 0));
1062 let mut catalog = PdfDictionary::new();
1063 catalog.insert("Type", PdfObject::Name(PdfName::new("Catalog")));
1064 catalog.insert("Pages", PdfObject::Reference(ObjectId::new(2, 0)));
1065 doc.add_object(ObjectId::new(1, 0), PdfObject::Dictionary(catalog));
1066 let mut pages = PdfDictionary::new();
1067 pages.insert("Type", PdfObject::Name(PdfName::new("Pages")));
1068 pages.insert("Count", PdfObject::Integer(1));
1069 let mut kids = PdfArray::new();
1070 kids.push(PdfObject::Reference(ObjectId::new(3, 0)));
1071 pages.insert("Kids", PdfObject::Array(kids));
1072 doc.add_object(ObjectId::new(2, 0), PdfObject::Dictionary(pages));
1073 let mut page = PdfDictionary::new();
1074 page.insert("Type", PdfObject::Name(PdfName::new("Page")));
1075 let mut media = PdfArray::new();
1076 media.push(PdfObject::Integer(0));
1077 media.push(PdfObject::Integer(0));
1078 media.push(PdfObject::Integer(612));
1079 media.push(PdfObject::Integer(792));
1080 page.insert("MediaBox", PdfObject::Array(media));
1081 doc.add_object(ObjectId::new(3, 0), PdfObject::Dictionary(page));
1082 let mut info = PdfDictionary::new();
1083 info.insert("Title", PdfObject::String(PdfString::from_literal("t")));
1084 doc.add_object(ObjectId::new(4, 0), PdfObject::Dictionary(info));
1085
1086 let mut buf = std::io::Cursor::new(Vec::new());
1087 Serializer::new().serialize(&doc, &mut buf).unwrap();
1088 let parsed = Parser::new().parse(buf.get_ref()).unwrap();
1089
1090 assert_eq!(parsed.catalog(), Some(ObjectId::new(1, 0)));
1091 assert_eq!(parsed.info(), Some(ObjectId::new(4, 0)));
1092 assert_eq!(parsed.objects().len(), 4);
1093 let media_data = parsed
1094 .get_object(ObjectId::new(3, 0))
1095 .unwrap()
1096 .as_dict()
1097 .unwrap()
1098 .get_array("MediaBox")
1099 .unwrap()
1100 .clone();
1101 assert_eq!(
1102 media_data.0,
1103 vec![
1104 PdfObject::Integer(0),
1105 PdfObject::Integer(0),
1106 PdfObject::Integer(612),
1107 PdfObject::Integer(792),
1108 ]
1109 );
1110 }
1111
1112 #[test]
1113 fn string_escaping_survives_serialize_parse_roundtrip() {
1114 let cases: &[&[u8]] = &[
1115 b"plain",
1116 b"with (parens) and \\ backslash",
1117 b"line1\nline2\r\nline3\rline4",
1118 "café ☕ 漢字 𝄞".as_bytes(),
1119 &[0x01, 0x07, 0x0b, 0x1b, 0x7f], b"tab\there\t",
1121 b"",
1122 ];
1123 for &input in cases {
1124 let mut doc = Document::new();
1125 doc.set_catalog(ObjectId::new(1, 0));
1126 let mut dict = PdfDictionary::new();
1127 dict.insert("S", PdfObject::String(PdfString::from_bytes(input)));
1128 dict.insert("N", PdfObject::Real(1.5));
1129 doc.add_object(ObjectId::new(1, 0), PdfObject::Dictionary(dict));
1130
1131 let mut buf = std::io::Cursor::new(Vec::new());
1132 Serializer::new().serialize(&doc, &mut buf).unwrap();
1133 let parsed = Parser::new().parse(buf.get_ref()).unwrap();
1134 let round = parsed
1135 .get_object(ObjectId::new(1, 0))
1136 .unwrap()
1137 .as_dict()
1138 .unwrap()
1139 .get_string_bytes("S")
1140 .unwrap();
1141 assert_eq!(round, input, "roundtrip failed for {input:?}");
1142 }
1143 }
1144
1145 #[test]
1146 fn text_operator_escaping_roundtrips_tj_string() {
1147 let tricky = vec![
1149 b"(a)".to_vec(),
1150 b"line1\nline2".to_vec(),
1151 "café ☕".as_bytes().to_vec(),
1152 b"\\back\\slash".to_vec(),
1153 ];
1154 for text in tricky {
1155 let mut doc = Document::new();
1156 doc.set_catalog(ObjectId::new(1, 0));
1157 let mut dict = PdfDictionary::new();
1158 dict.insert("Length", PdfObject::Integer(text.len() as i64));
1159 let stream = PdfStream::with_dict(dict, text.clone());
1160 doc.add_object(ObjectId::new(1, 0), PdfObject::Stream(stream));
1161
1162 let mut buf = std::io::Cursor::new(Vec::new());
1163 Serializer::new().serialize(&doc, &mut buf).unwrap();
1164 let parsed = Parser::new().parse(buf.get_ref()).unwrap();
1165 let round = parsed
1166 .get_object(ObjectId::new(1, 0))
1167 .unwrap()
1168 .as_stream()
1169 .unwrap()
1170 .data
1171 .clone();
1172 assert_eq!(round, text, "stream roundtrip failed for {text:?}");
1173 }
1174 }
1175
1176 #[test]
1177 fn parses_xref_stream_entries() {
1178 let mut dict = PdfDictionary::new();
1179 dict.insert("Type", PdfObject::Name(PdfName::new("XRef")));
1180 dict.insert("Size", PdfObject::Integer(4));
1181 let mut w = PdfArray::new();
1182 w.push(PdfObject::Integer(1));
1183 w.push(PdfObject::Integer(4));
1184 w.push(PdfObject::Integer(2));
1185 dict.insert("W", PdfObject::Array(w));
1186
1187 let mut data = Vec::new();
1188 data.extend_from_slice(&[0, 0, 0, 0, 0, 0xff, 0xff]);
1189 data.extend_from_slice(&[1, 0, 0, 0, 15, 0, 0]);
1190 data.extend_from_slice(&[2, 0, 0, 0, 10, 0, 3]);
1191 data.extend_from_slice(&[1, 0, 0, 3, 232, 0, 0]);
1192
1193 let entries = parse_xref_stream_entries(&data, &dict, 0).unwrap();
1194 assert_eq!(entries.len(), 4);
1195 assert!(matches!(
1196 entries[&0].1,
1197 XrefEntry::Free { generation: 65535 }
1198 ));
1199 assert!(matches!(
1200 entries[&1].1,
1201 XrefEntry::Used {
1202 offset: 15,
1203 generation: 0
1204 }
1205 ));
1206 assert!(matches!(
1207 entries[&2].1,
1208 XrefEntry::Compressed {
1209 stream_number: 10,
1210 index: 3
1211 }
1212 ));
1213 assert!(matches!(
1214 entries[&3].1,
1215 XrefEntry::Used {
1216 offset: 1000,
1217 generation: 0
1218 }
1219 ));
1220 }
1221
1222 #[test]
1223 fn decodes_flate_streams() {
1224 let plain = b"BT /F1 12 Tf 10 20 Td (hi) Tj ET";
1225 let mut dict = PdfDictionary::new();
1226 dict.insert("Filter", PdfObject::Name(PdfName::new("FlateDecode")));
1227 let encoded = crate::stream::StreamEncoder::new()
1228 .encode(plain, crate::stream::StreamFilter::Flate)
1229 .unwrap();
1230 let stream = PdfStream::with_dict(dict, encoded);
1231 let decoded = Parser::new().decode_stream(&stream).unwrap();
1232 assert_eq!(decoded, plain);
1233 }
1234
1235 #[test]
1236 fn parses_object_stream_objects() {
1237 let mut data = Vec::new();
1238 data.extend_from_slice(b"7 0 8 6");
1239 while data.len() < 12 {
1240 data.push(b' ');
1241 }
1242 data.extend_from_slice(b"42 ");
1243 while data.len() < 18 {
1244 data.push(b' ');
1245 }
1246 data.extend_from_slice(b"<< /A (x) >>");
1247
1248 let (num, obj) = parse_object_stream(&data, 2, 12, 0, ParserLimits::default()).unwrap();
1249 assert_eq!(num, 7);
1250 assert_eq!(obj, PdfObject::Integer(42));
1251
1252 let (num, obj) = parse_object_stream(&data, 2, 12, 1, ParserLimits::default()).unwrap();
1253 assert_eq!(num, 8);
1254 assert_eq!(
1255 obj.as_dict().unwrap().get("A"),
1256 Some(&PdfObject::String(PdfString::from_literal("x")))
1257 );
1258 assert!(parse_object_stream(&data, 2, 12, 9, ParserLimits::default()).is_none());
1259 }
1260
1261 #[test]
1262 fn parses_strings_names_and_numbers() {
1263 let mut lx = Lexer::new(
1264 b"(a\\(b\\)c) /A#20B 42 -1.5e2 true null <48656c6c6f>",
1265 0,
1266 ParseMode::Strict,
1267 ParserLimits::default(),
1268 );
1269 assert_eq!(
1270 lx.parse_object().unwrap(),
1271 PdfObject::String(PdfString::from_bytes(&b"a(b)c"[..]))
1272 );
1273 lx.skip_ws();
1274 assert_eq!(
1275 lx.parse_object().unwrap(),
1276 PdfObject::Name(PdfName::new("A B"))
1277 );
1278 lx.skip_ws();
1279 assert_eq!(lx.parse_object().unwrap(), PdfObject::Integer(42));
1280 lx.skip_ws();
1281 match lx.parse_object().unwrap() {
1282 PdfObject::Real(f) => assert!((f - -150.0).abs() < 0.001),
1283 _ => panic!("expected real"),
1284 }
1285 lx.skip_ws();
1286 assert_eq!(lx.parse_object().unwrap(), PdfObject::Boolean(true));
1287 lx.skip_ws();
1288 assert_eq!(lx.parse_object().unwrap(), PdfObject::Null);
1289 lx.skip_ws();
1290 assert_eq!(
1291 lx.parse_object().unwrap(),
1292 PdfObject::String(PdfString::from_bytes(&b"Hello"[..]))
1293 );
1294 }
1295
1296 fn tight_limits() -> ParserLimits {
1297 ParserLimits {
1298 max_objects: 4,
1299 max_string_length: 8,
1300 max_array_length: 4,
1301 max_dict_entries: 4,
1302 max_recursion_depth: 3,
1303 max_stream_size: 16,
1304 max_decoded_stream_size: 16,
1305 }
1306 }
1307
1308 #[test]
1309 fn limits_reject_overlong_string() {
1310 let mut lx = Lexer::new(b"(0123456789)", 0, ParseMode::Strict, tight_limits());
1311 assert!(lx.parse_object().is_err());
1312 }
1313
1314 #[test]
1315 fn limits_reject_deep_nesting() {
1316 let deep = b"[[[[[[[[[0]]]]]]]]]";
1317 let mut lx = Lexer::new(deep, 0, ParseMode::Strict, tight_limits());
1318 assert!(lx.parse_object().is_err());
1319 }
1320
1321 #[test]
1322 fn limits_reject_huge_array() {
1323 let mut lx = Lexer::new(b"[1 2 3 4 5]", 0, ParseMode::Strict, tight_limits());
1324 assert!(lx.parse_object().is_err());
1325 }
1326
1327 #[test]
1328 fn limits_reject_huge_stream_length() {
1329 let input = b"<< /Length 1000 >>\nstream\n0123456789\nendstream";
1331 let mut lx = Lexer::new(input, 0, ParseMode::Strict, tight_limits());
1332 assert!(lx.parse_object().is_err());
1333 }
1334
1335 #[test]
1336 fn limits_reject_too_many_objects() {
1337 let mut doc = Document::new();
1339 for i in 1..=6u32 {
1340 doc.add_object(ObjectId::new(i, 0), PdfObject::Integer(i as i64));
1341 }
1342 let mut buf = std::io::Cursor::new(Vec::new());
1343 Serializer::new().serialize(&doc, &mut buf).unwrap();
1344 let limits = ParserLimits {
1345 max_objects: 4,
1346 ..tight_limits()
1347 };
1348 assert!(Parser::with_limits(limits).parse(buf.get_ref()).is_err());
1349 }
1350
1351 #[test]
1352 fn limits_reject_too_many_xref_entries() {
1353 let mut pdf = Vec::new();
1356 pdf.extend_from_slice(b"%PDF-1.7\n");
1357 pdf.extend_from_slice(b"1 0 obj\n<<>>\nendobj\n");
1358 let xref_at = pdf.len() as u64;
1359 pdf.extend_from_slice(b"xref\n0 6\n");
1360 pdf.extend_from_slice(b"0000000000 65535 f \n");
1361 for i in 1..=5u64 {
1362 pdf.extend_from_slice(format!("{i:010} 00000 n \n").as_bytes());
1363 }
1364 pdf.extend_from_slice(b"trailer\n<< /Size 6 /Root 1 0 R >>\nstartxref\n");
1365 pdf.extend_from_slice(format!("{xref_at}\n").as_bytes());
1366 pdf.extend_from_slice(b"%%EOF");
1367
1368 let limits = ParserLimits {
1369 max_objects: 4,
1370 ..tight_limits()
1371 };
1372 let err = Parser::with_limits(limits).parse(&pdf).unwrap_err();
1373 assert!(
1374 err.to_string().contains("xref entry count"),
1375 "unexpected error: {err}"
1376 );
1377 }
1378
1379 struct XorShift(u64);
1381
1382 impl XorShift {
1383 fn next(&mut self) -> u64 {
1384 let mut x = self.0;
1385 x ^= x << 13;
1386 x ^= x >> 7;
1387 x ^= x << 17;
1388 self.0 = x;
1389 x
1390 }
1391 }
1392
1393 #[test]
1394 fn random_bytes_never_panic() {
1395 let mut rng = XorShift(0x9E37_79B9_7F4A_7C15);
1399 for _ in 0..5000 {
1400 let len = (rng.next() % 512) as usize;
1401 let bytes: Vec<u8> = (0..len).map(|_| (rng.next() & 0xff) as u8).collect();
1402 let _ = Parser::new().parse(&bytes);
1403 }
1404 }
1405
1406 #[test]
1407 fn byte_flipped_pdf_never_panics() {
1408 let mut doc = Document::new();
1411 doc.set_catalog(ObjectId::new(1, 0));
1412 for i in 1..=4u32 {
1413 let mut dict = PdfDictionary::new();
1414 dict.insert("Type", PdfObject::Name(PdfName::new(&format!("T{i}"))));
1415 dict.insert("N", PdfObject::Integer(i as i64));
1416 doc.add_object(ObjectId::new(i, 0), PdfObject::Dictionary(dict));
1417 }
1418 let mut buf = std::io::Cursor::new(Vec::new());
1419 Serializer::new().serialize(&doc, &mut buf).unwrap();
1420 let original = buf.into_inner();
1421 let mut rng = XorShift(0xD1B5_4A32_D192_ED03);
1422 for _ in 0..1000 {
1423 let mut mutated = original.clone();
1424 let flips = 1 + (rng.next() % 4) as usize;
1425 for _ in 0..flips {
1426 let idx = (rng.next() as usize) % mutated.len().max(1);
1427 mutated[idx] ^= 1 << (rng.next() % 8);
1428 }
1429 let _ = Parser::new().parse(&mutated);
1430 }
1431 }
1432
1433 #[test]
1434 fn malformed_inputs_error_without_panicking() {
1435 let cases: &[&[u8]] = &[
1436 b"",
1437 b"%PDF-1.7",
1438 b"garbage",
1439 b"%PDF-1.7\n1 0 obj<<>>endobj\nxref\n0 1\ntrailer\nstartxref\n0\n%%EOF",
1440 b"%PDF-1.7\n1 0 obj\n(abc",
1441 b"%PDF-1.7\n1 0 obj\n<< /Length -5 >>\nstream\nx",
1442 &b"%PDF-1.7\n".repeat(2),
1443 &[0xff, 0xfe, 0x00, 0x00, 0x41, 0x42],
1444 ];
1445 for input in cases {
1446 let _ = Parser::new().parse(input);
1448 }
1449 }
1450
1451 #[test]
1452 fn stream_roundtrip_with_small_and_large_data() {
1453 for size in [0, 1, 15, 16, 17, 1000] {
1454 let payload: Vec<u8> = (0..size).map(|i| (i % 251) as u8).collect();
1455 let mut dict = PdfDictionary::new();
1456 dict.insert("Filter", PdfObject::Name(PdfName::new("FlateDecode")));
1457 let encoded = crate::stream::StreamEncoder::new()
1458 .encode(&payload, crate::stream::StreamFilter::Flate)
1459 .unwrap();
1460 let stream = PdfStream::with_dict(dict, encoded);
1461 let decoded = Parser::new().decode_stream(&stream).unwrap();
1462 assert_eq!(decoded, payload, "size {size} mismatch");
1463 }
1464 }
1465
1466 #[test]
1467 fn decoded_stream_size_is_bounded() {
1468 let payload = vec![0u8; 1_000_000];
1471 let mut dict = PdfDictionary::new();
1472 dict.insert("Filter", PdfObject::Name(PdfName::new("FlateDecode")));
1473 let encoded = crate::stream::StreamEncoder::new()
1474 .encode(&payload, crate::stream::StreamFilter::Flate)
1475 .unwrap();
1476 assert!(encoded.len() < 2000, "test premise: bomb must compress");
1477 let stream = PdfStream::with_dict(dict, encoded);
1478 let limits = ParserLimits {
1479 max_decoded_stream_size: 64 * 1024,
1480 ..ParserLimits::default()
1481 };
1482 if let Ok(d) = Parser::with_limits(limits).decode_stream(&stream) {
1483 assert!(d.len() <= 64 * 1024 + 1);
1484 }
1485 }
1486}