1use zpdf_core::{Error, ObjectId, ParseLimits, PdfName, PdfObject, PdfString, Result};
2
3pub struct Lexer<'a> {
4 data: &'a [u8],
5 pos: usize,
6 limits: &'a ParseLimits,
7 depth: u32,
8}
9
10impl<'a> Lexer<'a> {
11 pub fn new(data: &'a [u8], pos: usize, limits: &'a ParseLimits) -> Self {
12 Self {
13 data,
14 pos,
15 limits,
16 depth: 0,
17 }
18 }
19
20 fn enter_container(&mut self) -> Result<()> {
23 self.depth += 1;
24 if self.depth > self.limits.max_object_depth {
25 return Err(Error::RecursionLimit(self.limits.max_object_depth));
26 }
27 Ok(())
28 }
29
30 fn leave_container(&mut self) {
31 self.depth = self.depth.saturating_sub(1);
32 }
33
34 pub fn pos(&self) -> usize {
35 self.pos
36 }
37
38 pub fn set_pos(&mut self, pos: usize) {
39 self.pos = pos;
40 }
41
42 pub fn is_eof(&self) -> bool {
43 self.pos >= self.data.len()
44 }
45
46 fn peek(&self) -> Option<u8> {
47 self.data.get(self.pos).copied()
48 }
49
50 fn advance(&mut self) -> Option<u8> {
51 let b = self.data.get(self.pos).copied()?;
52 self.pos += 1;
53 Some(b)
54 }
55
56 pub fn skip_whitespace_and_comments(&mut self) {
57 loop {
58 match self.peek() {
59 Some(b' ' | b'\t' | b'\r' | b'\n' | b'\x00' | b'\x0c') => {
60 self.pos += 1;
61 }
62 Some(b'%') => {
63 self.pos += 1;
64 while let Some(b) = self.peek() {
65 self.pos += 1;
66 if b == b'\r' || b == b'\n' {
67 break;
68 }
69 }
70 }
71 _ => break,
72 }
73 }
74 }
75
76 pub fn next_token(&mut self) -> Result<PdfObject> {
77 self.skip_whitespace_and_comments();
78
79 if self.is_eof() {
80 return Err(Error::UnexpectedEof(self.pos as u64));
81 }
82
83 match self.peek().unwrap() {
84 b'/' => self.read_name(),
85 b'(' => self.read_literal_string(),
86 b'<' => {
87 if self.data.get(self.pos + 1) == Some(&b'<') {
88 self.read_dict()
89 } else {
90 self.read_hex_string()
91 }
92 }
93 b'[' => self.read_array(),
94 b'+' | b'-' | b'.' | b'0'..=b'9' => self.read_number(),
95 b't' | b'f' => self.read_bool_or_keyword(),
96 b'n' => self.read_null_or_keyword(),
97 _ => Err(Error::InvalidObject(
98 self.pos as u64,
99 format!("unexpected byte: 0x{:02x}", self.peek().unwrap()),
100 )),
101 }
102 }
103
104 fn read_name(&mut self) -> Result<PdfObject> {
105 self.advance(); let start = self.pos;
107 while let Some(b) = self.peek() {
108 if is_delimiter(b) || is_whitespace(b) {
109 break;
110 }
111 self.pos += 1;
112 }
113 let raw = &self.data[start..self.pos];
114 if raw.len() > self.limits.max_string_length as usize {
115 return Err(Error::StringLengthLimit(self.limits.max_string_length));
116 }
117 let name = decode_name(raw);
118 Ok(PdfObject::Name(PdfName::new(name)))
119 }
120
121 fn read_literal_string(&mut self) -> Result<PdfObject> {
122 self.advance(); let mut buf = Vec::new();
124 let mut depth = 1u32;
125 let max = self.limits.max_string_length as usize;
126
127 while let Some(b) = self.advance() {
128 match b {
129 b'(' => {
130 depth += 1;
131 buf.push(b'(');
132 }
133 b')' => {
134 depth -= 1;
135 if depth == 0 {
136 break;
137 }
138 buf.push(b')');
139 }
140 b'\\' => {
141 if let Some(esc) = self.advance() {
142 match esc {
143 b'n' => buf.push(b'\n'),
144 b'r' => buf.push(b'\r'),
145 b't' => buf.push(b'\t'),
146 b'b' => buf.push(0x08),
147 b'f' => buf.push(0x0c),
148 b'(' => buf.push(b'('),
149 b')' => buf.push(b')'),
150 b'\\' => buf.push(b'\\'),
151 b'0'..=b'7' => {
152 let mut octal = (esc - b'0') as u16;
153 for _ in 0..2 {
154 match self.peek() {
155 Some(c @ b'0'..=b'7') => {
156 octal = octal * 8 + (c - b'0') as u16;
157 self.pos += 1;
158 }
159 _ => break,
160 }
161 }
162 buf.push(octal as u8);
163 }
164 b'\r' => {
165 if self.peek() == Some(b'\n') {
166 self.pos += 1;
167 }
168 }
169 b'\n' => {}
170 _ => buf.push(esc),
171 }
172 }
173 }
174 _ => buf.push(b),
175 }
176 if buf.len() > max {
179 return Err(Error::StringLengthLimit(self.limits.max_string_length));
180 }
181 }
182
183 Ok(PdfObject::String(PdfString::new(buf)))
184 }
185
186 fn read_hex_string(&mut self) -> Result<PdfObject> {
187 self.advance(); let mut buf = Vec::new();
189 let mut high: Option<u8> = None;
190 let max = self.limits.max_string_length as usize;
191
192 loop {
193 match self.advance() {
194 Some(b'>') => break,
195 Some(b) if is_whitespace(b) => continue,
196 Some(b) => {
197 let nibble = hex_digit(b).ok_or_else(|| {
198 Error::InvalidObject(self.pos as u64 - 1, "invalid hex digit".into())
199 })?;
200 match high {
201 None => high = Some(nibble),
202 Some(h) => {
203 buf.push((h << 4) | nibble);
204 high = None;
205 if buf.len() > max {
206 return Err(Error::StringLengthLimit(
207 self.limits.max_string_length,
208 ));
209 }
210 }
211 }
212 }
213 None => return Err(Error::UnexpectedEof(self.pos as u64)),
214 }
215 }
216
217 if let Some(h) = high {
218 buf.push(h << 4);
219 }
220
221 Ok(PdfObject::String(PdfString::new(buf)))
222 }
223
224 fn read_number(&mut self) -> Result<PdfObject> {
225 let start = self.pos;
226 let mut has_dot = false;
227
228 if matches!(self.peek(), Some(b'+' | b'-')) {
229 self.pos += 1;
230 }
231
232 while let Some(b) = self.peek() {
233 match b {
234 b'0'..=b'9' => self.pos += 1,
235 b'.' if !has_dot => {
236 has_dot = true;
237 self.pos += 1;
238 }
239 _ => break,
240 }
241 }
242
243 let s = std::str::from_utf8(&self.data[start..self.pos])
244 .map_err(|_| Error::InvalidObject(start as u64, "invalid number".into()))?;
245
246 if has_dot {
247 let n: f64 = s
248 .parse()
249 .map_err(|_| Error::InvalidObject(start as u64, format!("bad real: {s}")))?;
250 if !n.is_finite() {
251 return Err(Error::InvalidObject(
252 start as u64,
253 "non-finite real number".into(),
254 ));
255 }
256 Ok(PdfObject::Real(n))
257 } else {
258 let n: i64 = s
259 .parse()
260 .map_err(|_| Error::InvalidObject(start as u64, format!("bad integer: {s}")))?;
261 Ok(PdfObject::Integer(n))
262 }
263 }
264
265 fn read_bool_or_keyword(&mut self) -> Result<PdfObject> {
266 let start = self.pos;
267 while let Some(b) = self.peek() {
268 if is_delimiter(b) || is_whitespace(b) {
269 break;
270 }
271 self.pos += 1;
272 }
273 let word = &self.data[start..self.pos];
274 match word {
275 b"true" => Ok(PdfObject::Bool(true)),
276 b"false" => Ok(PdfObject::Bool(false)),
277 _ => Err(Error::InvalidObject(
278 start as u64,
279 format!("unexpected keyword: {}", String::from_utf8_lossy(word)),
280 )),
281 }
282 }
283
284 fn read_null_or_keyword(&mut self) -> Result<PdfObject> {
285 let start = self.pos;
286 while let Some(b) = self.peek() {
287 if is_delimiter(b) || is_whitespace(b) {
288 break;
289 }
290 self.pos += 1;
291 }
292 let word = &self.data[start..self.pos];
293 match word {
294 b"null" => Ok(PdfObject::Null),
295 _ => Err(Error::InvalidObject(
296 start as u64,
297 format!("unexpected keyword: {}", String::from_utf8_lossy(word)),
298 )),
299 }
300 }
301
302 fn read_array(&mut self) -> Result<PdfObject> {
303 self.enter_container()?;
304 self.advance(); let mut items = Vec::new();
306 loop {
307 self.skip_whitespace_and_comments();
308 if self.peek() == Some(b']') {
309 self.pos += 1;
310 break;
311 }
312 if self.is_eof() {
313 return Err(Error::UnexpectedEof(self.pos as u64));
314 }
315 let obj = self.next_token()?;
316 items.push(self.maybe_resolve_ref(obj)?);
317 }
318 self.leave_container();
319 Ok(PdfObject::Array(items))
320 }
321
322 fn read_dict(&mut self) -> Result<PdfObject> {
323 self.enter_container()?;
324 self.pos += 2; let mut dict = zpdf_core::PdfDict::new();
326 let mut bad = 0u32;
329 const MAX_BAD_TOKENS: u32 = 64;
330 loop {
331 self.skip_whitespace_and_comments();
332 if self.data.get(self.pos..self.pos + 2) == Some(b">>") {
333 self.pos += 2;
334 break;
335 }
336 if self.is_eof() {
337 break;
341 }
342 let key = match self.next_token() {
347 Ok(PdfObject::Name(n)) => n,
348 Err(e @ Error::RecursionLimit(_)) => return Err(e),
349 Ok(_non_name) => {
350 bad += 1;
352 if bad > MAX_BAD_TOKENS {
353 break;
354 }
355 continue;
356 }
357 Err(_) => {
358 bad += 1;
359 if bad > MAX_BAD_TOKENS {
360 break;
361 }
362 self.pos += 1; continue;
364 }
365 };
366 let value = match self.next_token() {
369 Ok(v) => v,
370 Err(e @ Error::RecursionLimit(_)) => return Err(e),
371 Err(_) => break,
372 };
373 let value = match self.maybe_resolve_ref(value) {
374 Ok(v) => v,
375 Err(e @ Error::RecursionLimit(_)) => return Err(e),
376 Err(_) => break,
377 };
378 dict.insert(key, value);
379 }
380 self.leave_container();
381 Ok(PdfObject::Dict(dict))
382 }
383
384 pub(crate) fn maybe_resolve_ref(&mut self, obj: PdfObject) -> Result<PdfObject> {
385 if let PdfObject::Integer(num) = obj {
386 let saved = self.pos;
387 self.skip_whitespace_and_comments();
388 if let Ok(PdfObject::Integer(gen)) = self.read_number_if_available() {
389 self.skip_whitespace_and_comments();
390 if self.peek() == Some(b'R') {
391 self.pos += 1;
392 if let (Ok(num), Ok(gen)) = (u32::try_from(num), u16::try_from(gen)) {
393 return Ok(PdfObject::Ref(ObjectId(num, gen)));
394 }
395 return Ok(PdfObject::Null);
396 }
397 }
398 self.pos = saved;
399 Ok(PdfObject::Integer(num))
400 } else {
401 Ok(obj)
402 }
403 }
404
405 fn read_number_if_available(&mut self) -> Result<PdfObject> {
406 if matches!(self.peek(), Some(b'0'..=b'9' | b'+' | b'-' | b'.')) {
407 self.read_number()
408 } else {
409 Err(Error::InvalidObject(self.pos as u64, "not a number".into()))
410 }
411 }
412}
413
414fn is_whitespace(b: u8) -> bool {
415 matches!(b, b' ' | b'\t' | b'\r' | b'\n' | b'\x00' | b'\x0c')
416}
417
418fn is_delimiter(b: u8) -> bool {
419 matches!(
420 b,
421 b'(' | b')' | b'<' | b'>' | b'[' | b']' | b'{' | b'}' | b'/' | b'%'
422 )
423}
424
425fn hex_digit(b: u8) -> Option<u8> {
426 match b {
427 b'0'..=b'9' => Some(b - b'0'),
428 b'a'..=b'f' => Some(b - b'a' + 10),
429 b'A'..=b'F' => Some(b - b'A' + 10),
430 _ => None,
431 }
432}
433
434fn decode_name(raw: &[u8]) -> String {
435 let mut result = Vec::with_capacity(raw.len());
436 let mut i = 0;
437 while i < raw.len() {
438 if raw[i] == b'#' && i + 2 < raw.len() {
439 if let (Some(h), Some(l)) = (hex_digit(raw[i + 1]), hex_digit(raw[i + 2])) {
440 result.push((h << 4) | l);
441 i += 3;
442 continue;
443 }
444 }
445 result.push(raw[i]);
446 i += 1;
447 }
448 String::from_utf8_lossy(&result).into_owned()
449}
450
451#[cfg(test)]
452mod tests {
453 use super::*;
454
455 fn lim() -> ParseLimits {
456 ParseLimits::default()
457 }
458
459 #[test]
460 fn lex_name() {
461 let l = lim();
462 let mut lex = Lexer::new(b"/Type", 0, &l);
463 let obj = lex.next_token().unwrap();
464 assert_eq!(obj, PdfObject::Name(PdfName::new("Type")));
465 }
466
467 #[test]
468 fn lex_name_with_hex_escape() {
469 let l = lim();
470 let mut lex = Lexer::new(b"/A#20B", 0, &l);
471 let obj = lex.next_token().unwrap();
472 assert_eq!(obj, PdfObject::Name(PdfName::new("A B")));
473 }
474
475 #[test]
476 fn lex_integer() {
477 let l = lim();
478 let mut lex = Lexer::new(b"42 ", 0, &l);
479 assert_eq!(lex.next_token().unwrap(), PdfObject::Integer(42));
480 }
481
482 #[test]
483 fn lex_negative_real() {
484 let l = lim();
485 let mut lex = Lexer::new(b"-3.5 ", 0, &l);
486 match lex.next_token().unwrap() {
487 PdfObject::Real(n) => assert!((n - (-3.5)).abs() < 1e-10),
488 other => panic!("expected Real, got {other:?}"),
489 }
490 }
491
492 #[test]
493 fn reject_real_that_overflows_to_infinity() {
494 let l = lim();
495 let data = format!("{}.0", "9".repeat(400));
496 let mut lex = Lexer::new(data.as_bytes(), 0, &l);
497 assert!(matches!(lex.next_token(), Err(Error::InvalidObject(_, _))));
498 }
499
500 #[test]
501 fn lex_literal_string() {
502 let l = lim();
503 let mut lex = Lexer::new(b"(hello world)", 0, &l);
504 let obj = lex.next_token().unwrap();
505 assert_eq!(
506 obj,
507 PdfObject::String(PdfString::new(b"hello world".to_vec()))
508 );
509 }
510
511 #[test]
512 fn lex_literal_string_nested_parens() {
513 let l = lim();
514 let mut lex = Lexer::new(b"(a (b) c)", 0, &l);
515 let obj = lex.next_token().unwrap();
516 assert_eq!(obj, PdfObject::String(PdfString::new(b"a (b) c".to_vec())));
517 }
518
519 #[test]
520 fn lex_hex_string() {
521 let l = lim();
522 let mut lex = Lexer::new(b"<48656C6C6F>", 0, &l);
523 let obj = lex.next_token().unwrap();
524 assert_eq!(obj, PdfObject::String(PdfString::new(b"Hello".to_vec())));
525 }
526
527 #[test]
528 fn lex_array() {
529 let l = lim();
530 let mut lex = Lexer::new(b"[1 2 3]", 0, &l);
531 let obj = lex.next_token().unwrap();
532 assert_eq!(
533 obj,
534 PdfObject::Array(vec![
535 PdfObject::Integer(1),
536 PdfObject::Integer(2),
537 PdfObject::Integer(3),
538 ])
539 );
540 }
541
542 #[test]
543 fn lex_dict() {
544 let l = lim();
545 let mut lex = Lexer::new(b"<< /Type /Page /Count 5 >>", 0, &l);
546 let obj = lex.next_token().unwrap();
547 match obj {
548 PdfObject::Dict(d) => {
549 assert_eq!(d.get_name("Type").unwrap(), "Page");
550 assert_eq!(d.get_i64("Count").unwrap(), 5);
551 }
552 other => panic!("expected Dict, got {other:?}"),
553 }
554 }
555
556 #[test]
557 fn lex_bool_and_null() {
558 let l = lim();
559 let mut lex = Lexer::new(b"true", 0, &l);
560 assert_eq!(lex.next_token().unwrap(), PdfObject::Bool(true));
561
562 let mut lex = Lexer::new(b"false", 0, &l);
563 assert_eq!(lex.next_token().unwrap(), PdfObject::Bool(false));
564
565 let mut lex = Lexer::new(b"null", 0, &l);
566 assert_eq!(lex.next_token().unwrap(), PdfObject::Null);
567 }
568
569 #[test]
570 fn lex_indirect_ref_in_array() {
571 let l = lim();
572 let mut lex = Lexer::new(b"[12 0 R]", 0, &l);
573 let obj = lex.next_token().unwrap();
574 assert_eq!(obj, PdfObject::Array(vec![PdfObject::Ref(ObjectId(12, 0))]));
575 }
576
577 #[test]
578 fn skip_comments() {
579 let l = lim();
580 let mut lex = Lexer::new(b"% comment\n42 ", 0, &l);
581 assert_eq!(lex.next_token().unwrap(), PdfObject::Integer(42));
582 }
583
584 #[test]
585 fn reject_deeply_nested_array() {
586 let mut l = lim();
587 l.max_object_depth = 10;
588 let depth = 50usize;
589 let mut data = vec![b'['; depth];
590 data.extend(std::iter::repeat_n(b']', depth));
591 let mut lex = Lexer::new(&data, 0, &l);
592 let err = lex.next_token().unwrap_err();
593 assert!(matches!(err, Error::RecursionLimit(10)), "got {err:?}");
594 }
595
596 #[test]
597 fn reject_deeply_nested_dict() {
598 let mut l = lim();
599 l.max_object_depth = 5;
600 let n = 20usize;
601 let mut s = String::new();
602 for _ in 0..n {
603 s.push_str("<< /a ");
604 }
605 s.push('1');
606 for _ in 0..n {
607 s.push_str(" >>");
608 }
609 let data = s.into_bytes();
610 let mut lex = Lexer::new(&data, 0, &l);
611 let err = lex.next_token().unwrap_err();
612 assert!(matches!(err, Error::RecursionLimit(5)), "got {err:?}");
613 }
614
615 #[test]
616 fn nested_within_limit_ok() {
617 let l = lim(); let data = b"[[[[[1]]]]]"; let mut lex = Lexer::new(data, 0, &l);
620 assert!(lex.next_token().is_ok());
621 }
622
623 #[test]
624 fn reject_oversized_literal_string() {
625 let mut l = lim();
626 l.max_string_length = 8;
627 let mut data = vec![b'('];
628 data.extend(std::iter::repeat_n(b'a', 100));
629 data.push(b')');
630 let mut lex = Lexer::new(&data, 0, &l);
631 let err = lex.next_token().unwrap_err();
632 assert!(matches!(err, Error::StringLengthLimit(8)), "got {err:?}");
633 }
634
635 #[test]
636 fn reject_oversized_hex_string() {
637 let mut l = lim();
638 l.max_string_length = 4;
639 let mut data = vec![b'<'];
641 data.extend(std::iter::repeat_n(b'4', 20));
642 data.push(b'>');
643 let mut lex = Lexer::new(&data, 0, &l);
644 let err = lex.next_token().unwrap_err();
645 assert!(matches!(err, Error::StringLengthLimit(4)), "got {err:?}");
646 }
647
648 #[test]
649 fn reject_oversized_name() {
650 let mut l = lim();
651 l.max_string_length = 4;
652 let mut lex = Lexer::new(b"/abcde", 0, &l);
653 assert!(matches!(lex.next_token(), Err(Error::StringLengthLimit(4))));
654 }
655
656 #[test]
657 fn out_of_range_reference_does_not_wrap() {
658 let l = lim();
659 let mut lex = Lexer::new(b"[4294967296 0 R -1 0 R 1 65536 R]", 0, &l);
660 let obj = lex.next_token().unwrap();
661 let arr = obj.as_array().unwrap();
662 assert!(arr.iter().all(|o| !matches!(o, PdfObject::Ref(_))));
663 }
664
665 #[test]
666 fn small_string_within_limit_ok() {
667 let l = lim(); let mut lex = Lexer::new(b"(hello)", 0, &l);
669 assert_eq!(
670 lex.next_token().unwrap(),
671 PdfObject::String(PdfString::new(b"hello".to_vec()))
672 );
673 }
674}