1use super::encryption_handler::EncryptionHandler;
6use super::header::PdfHeader;
7use super::object_stream::ObjectStream;
8use super::objects::{PdfArray, PdfDictionary, PdfObject, PdfString};
9use super::stack_safe::StackSafeContext;
10use super::trailer::PdfTrailer;
11use super::xref::{
12 find_byte_pattern, read_object_window, read_window_at, scan_page_object_refs, XRefTable,
13};
14use super::{ParseError, ParseResult};
15use crate::objects::ObjectId;
16use std::collections::HashMap;
17use std::fs::File;
18use std::io::{BufReader, Read, Seek, SeekFrom};
19use std::path::Path;
20
21pub(crate) fn resolve_to_array<R: Read + Seek>(
31 reader: &mut PdfReader<R>,
32 value: Option<&PdfObject>,
33) -> Option<PdfArray> {
34 match value {
35 Some(PdfObject::Reference(num, gen)) => {
36 reader.get_object(*num, *gen).ok()?.as_array().cloned()
37 }
38 other => other.and_then(|o| o.as_array()).cloned(),
39 }
40}
41
42const MANUAL_DICT_WINDOW: usize = 256 * 1024;
47
48fn is_immediate_stream_start(data: &[u8]) -> bool {
50 let mut i = 0;
51
52 while i < data.len() && matches!(data[i], b' ' | b'\t' | b'\n' | b'\r') {
54 i += 1;
55 }
56
57 data[i..].starts_with(b"stream")
59}
60
61fn slice_between(haystack: &str, open: u8, close: u8) -> Option<&str> {
71 debug_assert!(open.is_ascii() && close.is_ascii());
72 let open_idx = haystack.as_bytes().iter().position(|&b| b == open)?;
73 let rest = &haystack[open_idx + 1..];
74 let close_idx = rest.as_bytes().iter().position(|&b| b == close)?;
75 Some(&rest[..close_idx])
76}
77
78fn find_dict_end(after: &str) -> Option<usize> {
87 let bytes = after.as_bytes();
88 let mut depth = 1usize;
89 let mut i = 0usize;
90
91 while i + 1 < bytes.len() {
92 if bytes[i] == b'<' && bytes[i + 1] == b'<' {
93 depth += 1;
94 i += 2;
95 } else if bytes[i] == b'>' && bytes[i + 1] == b'>' {
96 depth -= 1;
97 if depth == 0 {
98 return Some(i);
99 }
100 i += 2;
101 } else {
102 i += 1;
103 }
104 }
105 None
106}
107
108fn truncate_on_char_boundary(s: &str, max_bytes: usize) -> &str {
110 if s.len() <= max_bytes {
111 return s;
112 }
113 let mut end = max_bytes;
114 while end > 0 && !s.is_char_boundary(end) {
115 end -= 1;
116 }
117 &s[..end]
118}
119
120pub struct PdfReader<R: Read + Seek> {
122 reader: BufReader<R>,
123 header: PdfHeader,
124 xref: XRefTable,
125 trailer: PdfTrailer,
126 object_cache: HashMap<(u32, u16), PdfObject>,
128 object_stream_cache: HashMap<u32, ObjectStream>,
130 page_tree: Option<super::page_tree::PageTree>,
132 parse_context: StackSafeContext,
134 options: super::ParseOptions,
136 encryption_handler: Option<EncryptionHandler>,
138 objects_being_reconstructed: std::sync::Mutex<std::collections::HashSet<u32>>,
140 max_reconstruction_depth: u32,
142}
143
144impl<R: Read + Seek> PdfReader<R> {
145 pub fn options(&self) -> &super::ParseOptions {
147 &self.options
148 }
149
150 pub fn is_encrypted(&self) -> bool {
152 self.encryption_handler.is_some()
153 }
154
155 pub fn trailer(&self) -> &PdfTrailer {
163 &self.trailer
164 }
165
166 pub fn is_unlocked(&self) -> bool {
168 match &self.encryption_handler {
169 Some(handler) => handler.is_unlocked(),
170 None => true, }
172 }
173
174 pub fn encryption_handler_mut(&mut self) -> Option<&mut EncryptionHandler> {
176 self.encryption_handler.as_mut()
177 }
178
179 pub fn encryption_handler(&self) -> Option<&EncryptionHandler> {
181 self.encryption_handler.as_ref()
182 }
183
184 pub fn unlock_with_password(&mut self, password: &str) -> ParseResult<bool> {
186 match &mut self.encryption_handler {
187 Some(handler) => {
188 let user = handler.unlock_with_user_password(password);
198 if matches!(user, Ok(true)) {
199 return Ok(true);
200 }
201 let owner = handler.unlock_with_owner_password(password);
202 if matches!(owner, Ok(true)) {
203 return Ok(true);
204 }
205 match (user, owner) {
206 (Err(e), _) | (Ok(_), Err(e)) => Err(e),
207 (Ok(_), Ok(_)) => Ok(false),
208 }
209 }
210 None => Ok(true), }
212 }
213
214 pub fn try_empty_password(&mut self) -> ParseResult<bool> {
216 match &mut self.encryption_handler {
217 Some(handler) => Ok(handler.try_empty_password().unwrap_or(false)),
218 None => Ok(true), }
220 }
221
222 pub fn unlock(&mut self, password: &str) -> ParseResult<()> {
253 if !self.is_encrypted() {
255 return Ok(());
256 }
257
258 if self.is_unlocked() {
260 return Ok(());
261 }
262
263 let success = self.unlock_with_password(password)?;
265
266 if success {
267 Ok(())
268 } else {
269 Err(ParseError::WrongPassword)
270 }
271 }
272
273 fn ensure_unlocked(&self) -> ParseResult<()> {
275 if self.is_encrypted() && !self.is_unlocked() {
276 return Err(ParseError::PdfLocked);
277 }
278 Ok(())
279 }
280
281 fn decrypt_object_if_needed(
287 &self,
288 obj: PdfObject,
289 obj_num: u32,
290 gen_num: u16,
291 ) -> ParseResult<PdfObject> {
292 let handler = match &self.encryption_handler {
294 Some(h) if h.is_unlocked() => h,
295 _ => return Ok(obj), };
297
298 let obj_id = ObjectId::new(obj_num, gen_num);
299
300 match obj {
301 PdfObject::String(ref s) => {
302 let decrypted_bytes = handler.decrypt_string(s.as_bytes(), &obj_id)?;
304 Ok(PdfObject::String(PdfString::new(decrypted_bytes)))
305 }
306 PdfObject::Stream(ref stream) => {
307 let should_decrypt = stream
309 .dict
310 .get("StmF")
311 .and_then(|o| o.as_name())
312 .map(|n| n.0.as_str() != "Identity")
313 .unwrap_or(true); if should_decrypt {
316 let decrypted_data = handler.decrypt_stream(&stream.data, &obj_id)?;
317
318 let mut new_stream = stream.clone();
320 new_stream.data = decrypted_data;
321 Ok(PdfObject::Stream(new_stream))
322 } else {
323 Ok(obj) }
325 }
326 PdfObject::Dictionary(ref dict) => {
327 let mut new_dict = PdfDictionary::new();
329 for (key, value) in dict.0.iter() {
330 let decrypted_value =
331 self.decrypt_object_if_needed(value.clone(), obj_num, gen_num)?;
332 new_dict.insert(key.0.clone(), decrypted_value);
333 }
334 Ok(PdfObject::Dictionary(new_dict))
335 }
336 PdfObject::Array(ref arr) => {
337 let mut new_arr = Vec::new();
339 for elem in arr.0.iter() {
340 let decrypted_elem =
341 self.decrypt_object_if_needed(elem.clone(), obj_num, gen_num)?;
342 new_arr.push(decrypted_elem);
343 }
344 Ok(PdfObject::Array(PdfArray(new_arr)))
345 }
346 _ => Ok(obj),
348 }
349 }
350}
351
352impl PdfReader<File> {
353 pub fn open<P: AsRef<Path>>(path: P) -> ParseResult<Self> {
355 #[cfg(feature = "verbose-debug")]
356 {
357 use std::io::Write;
358 if let Ok(mut f) = std::fs::File::create("/tmp/pdf_open_debug.log") {
359 writeln!(f, "Opening file: {:?}", path.as_ref()).ok();
360 }
361 }
362 let file = File::open(path)?;
363 let options = super::ParseOptions::lenient();
365 Self::new_with_options(file, options)
366 }
367
368 pub fn open_strict<P: AsRef<Path>>(path: P) -> ParseResult<Self> {
370 let file = File::open(path)?;
371 let options = super::ParseOptions::strict();
372 Self::new_with_options(file, options)
373 }
374
375 pub fn open_with_options<P: AsRef<Path>>(
377 path: P,
378 options: super::ParseOptions,
379 ) -> ParseResult<Self> {
380 let file = File::open(path)?;
381 Self::new_with_options(file, options)
382 }
383
384 pub fn open_document<P: AsRef<Path>>(
386 path: P,
387 ) -> ParseResult<super::document::PdfDocument<File>> {
388 let reader = Self::open(path)?;
389 Ok(reader.into_document())
390 }
391}
392
393impl<R: Read + Seek> PdfReader<R> {
394 pub fn new(reader: R) -> ParseResult<Self> {
401 let mut options = super::ParseOptions::default();
404 options.lenient_streams = true;
405 Self::new_with_options(reader, options)
406 }
407
408 pub fn new_with_options(reader: R, options: super::ParseOptions) -> ParseResult<Self> {
410 let mut buf_reader = BufReader::new(reader);
411
412 let start_pos = buf_reader.stream_position()?;
414 buf_reader.seek(SeekFrom::End(0))?;
415 let file_size = buf_reader.stream_position()?;
416 buf_reader.seek(SeekFrom::Start(start_pos))?;
417
418 if file_size == 0 {
419 return Err(ParseError::EmptyFile);
420 }
421
422 let header = PdfHeader::parse(&mut buf_reader)?;
424 #[cfg(feature = "verbose-debug")]
425 tracing::debug!("Header parsed: version {}", header.version);
426
427 let xref = XRefTable::parse_with_options(&mut buf_reader, &options)?;
429 #[cfg(feature = "verbose-debug")]
430 tracing::debug!("XRef table parsed with {} entries", xref.len());
431
432 let trailer_dict = xref.trailer().ok_or(ParseError::InvalidTrailer)?.clone();
434
435 let xref_offset = xref.xref_offset();
436 let trailer = PdfTrailer::from_dict(trailer_dict, xref_offset)?;
437
438 trailer.validate()?;
440
441 let encryption_handler = if EncryptionHandler::detect_encryption(trailer.dict()) {
443 if let Ok(Some((encrypt_obj_num, encrypt_gen_num))) = trailer.encrypt() {
444 let mut temp_reader = Self {
446 reader: buf_reader,
447 header: header.clone(),
448 xref: xref.clone(),
449 trailer: trailer.clone(),
450 object_cache: HashMap::new(),
451 object_stream_cache: HashMap::new(),
452 page_tree: None,
453 parse_context: StackSafeContext::new(),
454 options: options.clone(),
455 encryption_handler: None,
456 objects_being_reconstructed: std::sync::Mutex::new(
457 std::collections::HashSet::new(),
458 ),
459 max_reconstruction_depth: 100,
460 };
461
462 let encrypt_obj = temp_reader.get_object(encrypt_obj_num, encrypt_gen_num)?;
464 if let Some(encrypt_dict) = encrypt_obj.as_dict() {
465 let file_id = trailer.id().and_then(|id_obj| {
467 if let PdfObject::Array(ref id_array) = id_obj {
468 if let Some(PdfObject::String(ref id_bytes)) = id_array.get(0) {
469 Some(id_bytes.as_bytes().to_vec())
470 } else {
471 None
472 }
473 } else {
474 None
475 }
476 });
477
478 match EncryptionHandler::new(encrypt_dict, file_id) {
479 Ok(mut handler) => {
480 let _ = handler.try_empty_password();
482 buf_reader = temp_reader.reader;
484 Some(handler)
485 }
486 Err(_) => {
487 let _ = temp_reader.reader;
489 return Err(ParseError::EncryptionNotSupported);
490 }
491 }
492 } else {
493 let _ = temp_reader.reader;
494 return Err(ParseError::EncryptionNotSupported);
495 }
496 } else {
497 return Err(ParseError::EncryptionNotSupported);
498 }
499 } else {
500 None
501 };
502
503 Ok(Self {
504 reader: buf_reader,
505 header,
506 xref,
507 trailer,
508 object_cache: HashMap::new(),
509 object_stream_cache: HashMap::new(),
510 page_tree: None,
511 parse_context: StackSafeContext::new(),
512 options,
513 encryption_handler,
514 objects_being_reconstructed: std::sync::Mutex::new(std::collections::HashSet::new()),
515 max_reconstruction_depth: 100,
516 })
517 }
518
519 pub fn version(&self) -> &super::header::PdfVersion {
521 &self.header.version
522 }
523
524 pub fn catalog(&mut self) -> ParseResult<&PdfDictionary> {
526 let (obj_num, gen_num) = match self.trailer.root() {
528 Ok(root) => {
529 if let Ok(obj) = self.get_object(root.0, root.1) {
532 if let Some(dict) = obj.as_dict() {
533 if let Some(type_obj) = dict.get("Type") {
535 if let Some(type_name) = type_obj.as_name() {
536 if type_name.0 != "Catalog" {
537 tracing::warn!("Trailer /Root points to /Type/{} (not Catalog), scanning for real catalog", type_name.0);
538 if let Ok(catalog_ref) = self.find_catalog_object() {
540 catalog_ref
541 } else {
542 root }
544 } else {
545 root }
547 } else {
548 root }
550 } else {
551 root }
553 } else {
554 root }
556 } else {
557 root }
559 }
560 Err(_) => {
561 #[cfg(debug_assertions)]
563 tracing::warn!("Trailer missing Root entry, attempting recovery");
564
565 if let Some(root) = self.trailer.find_root_fallback() {
567 root
568 } else {
569 if let Ok(catalog_ref) = self.find_catalog_object() {
571 catalog_ref
572 } else {
573 return Err(ParseError::MissingKey("Root".to_string()));
574 }
575 }
576 }
577 };
578
579 let key = (obj_num, gen_num);
581 let needs_reconstruction = {
582 match self.get_object(obj_num, gen_num) {
583 Ok(catalog) => {
584 if catalog.as_dict().is_some() {
586 false
588 } else {
589 true
591 }
592 }
593 Err(_) => {
594 true
596 }
597 }
598 };
599
600 if !needs_reconstruction {
601 let catalog = self.get_object(obj_num, gen_num)?;
603 return catalog.as_dict().ok_or_else(|| ParseError::SyntaxError {
604 position: 0,
605 message: format!("Catalog object {} {} is not a dictionary", obj_num, gen_num),
606 });
607 }
608
609 match self.extract_object_manually(obj_num) {
612 Ok(dict) => {
613 let obj = PdfObject::Dictionary(dict);
615 self.object_cache.insert(key, obj);
616
617 use crate::parser::xref::XRefEntry;
619 let xref_entry = XRefEntry {
620 offset: 0, generation: gen_num,
622 in_use: true,
623 };
624 self.xref.add_entry(obj_num, xref_entry);
625
626 if let Some(PdfObject::Dictionary(ref dict)) = self.object_cache.get(&key) {
628 return Ok(dict);
629 }
630 }
631 Err(_e) => {}
632 }
633
634 Err(ParseError::SyntaxError {
636 position: 0,
637 message: format!(
638 "Catalog object {} could not be parsed or reconstructed as a dictionary",
639 obj_num
640 ),
641 })
642 }
643
644 pub fn info(&mut self) -> ParseResult<Option<&PdfDictionary>> {
646 match self.trailer.info() {
647 Some((obj_num, gen_num)) => {
648 let info = self.get_object(obj_num, gen_num)?;
649 Ok(info.as_dict())
650 }
651 None => Ok(None),
652 }
653 }
654
655 pub fn get_object(&mut self, obj_num: u32, gen_num: u16) -> ParseResult<&PdfObject> {
657 self.ensure_unlocked()?;
659
660 let key = (obj_num, gen_num);
661
662 if self.object_cache.contains_key(&key) {
664 return Ok(&self.object_cache[&key]);
665 }
666
667 {
669 let being_loaded =
670 self.objects_being_reconstructed
671 .lock()
672 .map_err(|_| ParseError::SyntaxError {
673 position: 0,
674 message: "Mutex poisoned during circular reference check".to_string(),
675 })?;
676 if being_loaded.contains(&obj_num) {
677 drop(being_loaded);
678 if self.options.collect_warnings {}
679 self.object_cache.insert(key, PdfObject::Null);
680 return Ok(&self.object_cache[&key]);
681 }
682 }
683
684 {
686 let being_loaded =
687 self.objects_being_reconstructed
688 .lock()
689 .map_err(|_| ParseError::SyntaxError {
690 position: 0,
691 message: "Mutex poisoned during depth limit check".to_string(),
692 })?;
693 let depth = being_loaded.len() as u32;
694 if depth >= self.max_reconstruction_depth {
695 drop(being_loaded);
696 if self.options.collect_warnings {}
697 return Err(ParseError::SyntaxError {
698 position: 0,
699 message: format!(
700 "Maximum object loading depth ({}) exceeded",
701 self.max_reconstruction_depth
702 ),
703 });
704 }
705 }
706
707 self.objects_being_reconstructed
709 .lock()
710 .map_err(|_| ParseError::SyntaxError {
711 position: 0,
712 message: "Mutex poisoned while marking object as being loaded".to_string(),
713 })?
714 .insert(obj_num);
715
716 match self.load_object_from_disk(obj_num, gen_num) {
718 Ok(_) => {
719 self.objects_being_reconstructed
721 .lock()
722 .map_err(|_| ParseError::SyntaxError {
723 position: 0,
724 message: "Mutex poisoned while unmarking object after successful load"
725 .to_string(),
726 })?
727 .remove(&obj_num);
728 Ok(&self.object_cache[&key])
730 }
731 Err(e) => {
732 if let Ok(mut guard) = self.objects_being_reconstructed.lock() {
735 guard.remove(&obj_num);
736 }
737 Err(e)
738 }
739 }
740 }
741
742 fn load_object_from_disk(&mut self, obj_num: u32, gen_num: u16) -> ParseResult<&PdfObject> {
744 let key = (obj_num, gen_num);
745
746 if self.object_cache.contains_key(&key) {
748 return Ok(&self.object_cache[&key]);
749 }
750
751 if let Some(ext_entry) = self.xref.get_extended_entry(obj_num) {
753 if let Some((stream_obj_num, index_in_stream)) = ext_entry.compressed_info {
754 return self.get_compressed_object(
756 obj_num,
757 gen_num,
758 stream_obj_num,
759 index_in_stream,
760 );
761 }
762 } else {
763 }
764
765 let (current_offset, _generation) = {
767 let entry = self.xref.get_entry(obj_num);
768
769 match entry {
770 Some(entry) => {
771 if !entry.in_use {
772 self.object_cache.insert(key, PdfObject::Null);
774 return Ok(&self.object_cache[&key]);
775 }
776
777 if entry.generation != gen_num {
778 if self.options.lenient_syntax {
779 if self.options.collect_warnings {
781 tracing::warn!("Object {} generation mismatch - expected {}, found {}, using available",
782 obj_num, gen_num, entry.generation);
783 }
784 } else {
785 return Err(ParseError::InvalidReference(obj_num, gen_num));
786 }
787 }
788
789 (entry.offset, entry.generation)
790 }
791 None => {
792 if self.is_reconstructible_object(obj_num) {
794 return self.attempt_manual_object_reconstruction(obj_num, gen_num, 0);
795 } else {
796 if self.options.lenient_syntax {
797 if self.options.collect_warnings {
799 tracing::warn!(
800 "Object {} {} R not found in XRef, returning null object",
801 obj_num,
802 gen_num
803 );
804 }
805 self.object_cache.insert(key, PdfObject::Null);
806 return Ok(&self.object_cache[&key]);
807 } else {
808 return Err(ParseError::InvalidReference(obj_num, gen_num));
809 }
810 }
811 }
812 }
813 };
814
815 self.reader.seek(std::io::SeekFrom::Start(current_offset))?;
819
820 let mut lexer =
822 super::lexer::Lexer::new_with_options(&mut self.reader, self.options.clone());
823
824 {
826 let token = lexer.next_token()?;
828 let read_obj_num = match token {
829 super::lexer::Token::Integer(n) => n as u32,
830 _ => {
831 if self.options.lenient_syntax {
833 if self.options.collect_warnings {
835 tracing::debug!(
836 "Warning: Using expected object number {obj_num} instead of parsed token: {:?}",
837 token
838 );
839 }
840 obj_num
841 } else {
842 return Err(ParseError::SyntaxError {
843 position: current_offset as usize,
844 message: "Expected object number".to_string(),
845 });
846 }
847 }
848 };
849
850 if read_obj_num != obj_num && !self.options.lenient_syntax {
851 return Err(ParseError::SyntaxError {
852 position: current_offset as usize,
853 message: format!(
854 "Object number mismatch: expected {obj_num}, found {read_obj_num}"
855 ),
856 });
857 }
858
859 let token = lexer.next_token()?;
861 let _read_gen_num = match token {
862 super::lexer::Token::Integer(n) => n as u16,
863 _ => {
864 if self.options.lenient_syntax {
866 if self.options.collect_warnings {
867 tracing::warn!(
868 "Using generation 0 instead of parsed token for object {obj_num}"
869 );
870 }
871 0
872 } else {
873 return Err(ParseError::SyntaxError {
874 position: current_offset as usize,
875 message: "Expected generation number".to_string(),
876 });
877 }
878 }
879 };
880
881 let token = lexer.next_token()?;
883 match token {
884 super::lexer::Token::Obj => {}
885 _ => {
886 if self.options.lenient_syntax {
887 if self.options.collect_warnings {
889 tracing::warn!("Expected 'obj' keyword for object {obj_num} {gen_num}, continuing anyway");
890 }
891 } else {
892 return Err(ParseError::SyntaxError {
893 position: current_offset as usize,
894 message: "Expected 'obj' keyword".to_string(),
895 });
896 }
897 }
898 }
899 }
900
901 self.parse_context.enter()?;
903
904 let obj = match PdfObject::parse_with_options(&mut lexer, &self.options) {
905 Ok(obj) => {
906 self.parse_context.exit();
907 if obj_num == 102 && self.options.collect_warnings {}
909 obj
910 }
911 Err(e) => {
912 self.parse_context.exit();
913
914 if self.is_reconstructible_object(obj_num)
916 && self.can_attempt_manual_reconstruction(&e)
917 {
918 match self.attempt_manual_object_reconstruction(
919 obj_num,
920 gen_num,
921 current_offset,
922 ) {
923 Ok(reconstructed_obj) => {
924 return Ok(reconstructed_obj);
925 }
926 Err(_reconstruction_error) => {}
927 }
928 }
929
930 return Err(e);
931 }
932 };
933
934 let token = lexer.next_token()?;
936 match token {
937 super::lexer::Token::EndObj => {}
938 _ => {
939 if self.options.lenient_syntax {
940 if self.options.collect_warnings {
942 tracing::warn!("Expected 'endobj' keyword after object {obj_num} {gen_num}, continuing anyway");
943 }
944 } else {
945 return Err(ParseError::SyntaxError {
946 position: current_offset as usize,
947 message: "Expected 'endobj' keyword".to_string(),
948 });
949 }
950 }
951 };
952
953 let decrypted_obj = self.decrypt_object_if_needed(obj, obj_num, gen_num)?;
955
956 self.object_cache.insert(key, decrypted_obj);
958
959 Ok(&self.object_cache[&key])
960 }
961
962 pub fn resolve<'a>(&'a mut self, obj: &'a PdfObject) -> ParseResult<&'a PdfObject> {
964 match obj {
965 PdfObject::Reference(obj_num, gen_num) => self.get_object(*obj_num, *gen_num),
966 _ => Ok(obj),
967 }
968 }
969
970 pub fn resolve_stream_length(&mut self, obj: &PdfObject) -> ParseResult<Option<usize>> {
973 match obj {
974 PdfObject::Integer(len) => {
975 if *len >= 0 {
976 Ok(Some(*len as usize))
977 } else {
978 Ok(None)
980 }
981 }
982 PdfObject::Reference(obj_num, gen_num) => {
983 let resolved = self.get_object(*obj_num, *gen_num)?;
984 match resolved {
985 PdfObject::Integer(len) => {
986 if *len >= 0 {
987 Ok(Some(*len as usize))
988 } else {
989 Ok(None)
990 }
991 }
992 _ => {
993 Ok(None)
995 }
996 }
997 }
998 _ => {
999 Ok(None)
1001 }
1002 }
1003 }
1004
1005 fn get_compressed_object(
1007 &mut self,
1008 obj_num: u32,
1009 gen_num: u16,
1010 stream_obj_num: u32,
1011 _index_in_stream: u32,
1012 ) -> ParseResult<&PdfObject> {
1013 let key = (obj_num, gen_num);
1014
1015 if !self.object_stream_cache.contains_key(&stream_obj_num) {
1017 let stream_obj = self.get_object(stream_obj_num, 0)?;
1019
1020 if let Some(stream) = stream_obj.as_stream() {
1021 let obj_stream = ObjectStream::parse(stream.clone(), &self.options)?;
1023 self.object_stream_cache.insert(stream_obj_num, obj_stream);
1024 } else {
1025 return Err(ParseError::SyntaxError {
1026 position: 0,
1027 message: format!("Object {stream_obj_num} is not a stream"),
1028 });
1029 }
1030 }
1031
1032 let obj_stream = &self.object_stream_cache[&stream_obj_num];
1034 let obj = obj_stream
1035 .get_object(obj_num)
1036 .ok_or_else(|| ParseError::SyntaxError {
1037 position: 0,
1038 message: format!("Object {obj_num} not found in object stream {stream_obj_num}"),
1039 })?;
1040
1041 let decrypted_obj = self.decrypt_object_if_needed(obj.clone(), obj_num, gen_num)?;
1043
1044 self.object_cache.insert(key, decrypted_obj);
1046 Ok(&self.object_cache[&key])
1047 }
1048
1049 pub fn pages(&mut self) -> ParseResult<&PdfDictionary> {
1051 let (pages_obj_num, pages_gen_num) = {
1053 let catalog = self.catalog()?;
1054
1055 if let Some(pages_ref) = catalog.get("Pages") {
1057 match pages_ref {
1058 PdfObject::Reference(obj_num, gen_num) => (*obj_num, *gen_num),
1059 _ => {
1060 return Err(ParseError::SyntaxError {
1061 position: 0,
1062 message: "Pages must be a reference".to_string(),
1063 })
1064 }
1065 }
1066 } else {
1067 #[cfg(debug_assertions)]
1069 tracing::warn!("Catalog missing Pages entry, attempting recovery");
1070
1071 if let Ok(page_refs) = self.find_page_objects() {
1073 if !page_refs.is_empty() {
1074 return self.create_synthetic_pages_dict(&page_refs);
1076 }
1077 }
1078
1079 if self.options.lenient_syntax {
1081 if self.options.collect_warnings {
1082 tracing::warn!("Missing Pages in catalog, searching for page tree");
1083 }
1084 let mut found_pages = None;
1086 for i in 1..self.xref.len() as u32 {
1087 if let Ok(obj) = self.get_object(i, 0) {
1088 if let Some(dict) = obj.as_dict() {
1089 if let Some(obj_type) = dict.get("Type").and_then(|t| t.as_name()) {
1090 if obj_type.0 == "Pages" {
1091 found_pages = Some((i, 0));
1092 break;
1093 }
1094 }
1095 }
1096 }
1097 }
1098 if let Some((obj_num, gen_num)) = found_pages {
1099 (obj_num, gen_num)
1100 } else {
1101 return Err(ParseError::MissingKey("Pages".to_string()));
1102 }
1103 } else {
1104 return Err(ParseError::MissingKey("Pages".to_string()));
1105 }
1106 }
1107 };
1108
1109 let needs_double_resolve = {
1112 let pages_obj = self.get_object(pages_obj_num, pages_gen_num)?;
1113 pages_obj.as_reference()
1114 };
1115
1116 let (final_obj_num, final_gen_num) =
1118 if let Some((ref_obj_num, ref_gen_num)) = needs_double_resolve {
1119 (ref_obj_num, ref_gen_num)
1120 } else {
1121 (pages_obj_num, pages_gen_num)
1122 };
1123
1124 let actual_pages_num = {
1126 let is_valid_dict = {
1128 let pages_obj = self.get_object(final_obj_num, final_gen_num)?;
1129 pages_obj.as_dict().is_some()
1130 };
1131
1132 if is_valid_dict {
1133 final_obj_num
1135 } else {
1136 #[cfg(debug_assertions)]
1138 tracing::warn!("Pages reference invalid, searching for valid Pages object");
1139
1140 if self.options.lenient_syntax {
1141 let xref_len = self.xref.len() as u32;
1143 let mut found_pages_num = None;
1144
1145 for i in 1..xref_len {
1146 let is_pages = {
1148 if let Ok(obj) = self.get_object(i, 0) {
1149 if let Some(dict) = obj.as_dict() {
1150 if let Some(obj_type) =
1151 dict.get("Type").and_then(|t| t.as_name())
1152 {
1153 obj_type.0 == "Pages"
1154 } else {
1155 false
1156 }
1157 } else {
1158 false
1159 }
1160 } else {
1161 false
1162 }
1163 };
1164
1165 if is_pages {
1166 found_pages_num = Some(i);
1167 break;
1168 }
1169 }
1170
1171 if let Some(obj_num) = found_pages_num {
1172 #[cfg(debug_assertions)]
1173 tracing::debug!("Found valid Pages object at {} 0 R", obj_num);
1174 obj_num
1175 } else {
1176 return Err(ParseError::SyntaxError {
1178 position: 0,
1179 message: "Pages is not a dictionary and no valid Pages object found"
1180 .to_string(),
1181 });
1182 }
1183 } else {
1184 return Err(ParseError::SyntaxError {
1186 position: 0,
1187 message: "Pages is not a dictionary".to_string(),
1188 });
1189 }
1190 }
1191 };
1192
1193 let pages_obj = self.get_object(actual_pages_num, 0)?;
1195 pages_obj.as_dict().ok_or_else(|| ParseError::SyntaxError {
1196 position: 0,
1197 message: "Pages object is not a dictionary".to_string(),
1198 })
1199 }
1200
1201 pub fn page_count(&mut self) -> ParseResult<u32> {
1203 const MAX_PAGE_COUNT: u32 = 100_000;
1206
1207 match self.pages() {
1209 Ok(pages) => {
1210 let count_inline = pages.get("Count").and_then(|o| o.as_integer());
1214 let count_ref = pages.get("Count").and_then(|o| o.as_reference());
1215 let kids_len_inline = pages
1216 .get("Kids")
1217 .and_then(|o| o.as_array())
1218 .map(|a| a.0.len());
1219 let kids_ref = pages.get("Kids").and_then(|o| o.as_reference());
1220
1221 let count = count_inline.or_else(|| {
1223 count_ref
1224 .and_then(|(n, g)| self.get_object(n, g).ok().and_then(|o| o.as_integer()))
1225 });
1226 if let Some(count) = count {
1227 let count = count as u32;
1228 if count <= MAX_PAGE_COUNT {
1229 return Ok(count);
1230 }
1231 tracing::warn!(
1232 "PDF /Count {} exceeds limit {}, falling back to Kids array length",
1233 count,
1234 MAX_PAGE_COUNT
1235 );
1236 }
1238
1239 if let Some(len) = kids_len_inline {
1242 return Ok(len as u32);
1243 }
1244 if let Some((n, g)) = kids_ref {
1245 if let Some(len) = self
1246 .get_object(n, g)
1247 .ok()
1248 .and_then(|o| o.as_array())
1249 .map(|a| a.0.len())
1250 {
1251 return Ok(len as u32);
1252 }
1253 }
1254
1255 Ok(0)
1256 }
1257 Err(_) => {
1258 tracing::debug!("Standard page extraction failed, trying direct extraction");
1260 self.page_count_fallback()
1261 }
1262 }
1263 }
1264
1265 fn page_count_fallback(&mut self) -> ParseResult<u32> {
1267 if let Some(count) = self.extract_page_count_from_linearization() {
1269 tracing::debug!("Found page count {} from linearization", count);
1270 return Ok(count);
1271 }
1272
1273 if let Some(count) = self.count_page_objects_directly() {
1275 tracing::debug!("Found {} pages by counting page objects", count);
1276 return Ok(count);
1277 }
1278
1279 Ok(0)
1280 }
1281
1282 fn extract_page_count_from_linearization(&mut self) -> Option<u32> {
1284 match self.get_object(100, 0) {
1286 Ok(obj) => {
1287 tracing::debug!("Found object 100: {:?}", obj);
1288 if let Some(dict) = obj.as_dict() {
1289 tracing::debug!("Object 100 is a dictionary with {} keys", dict.0.len());
1290 if let Some(n_obj) = dict.get("N") {
1292 tracing::debug!("Found /N field: {:?}", n_obj);
1293 if let Some(count) = n_obj.as_integer() {
1294 tracing::debug!("Extracted page count from linearization: {}", count);
1295 return Some(count as u32);
1296 }
1297 } else {
1298 tracing::debug!("No /N field found in object 100");
1299 for (key, value) in &dict.0 {
1300 tracing::debug!(" {:?}: {:?}", key, value);
1301 }
1302 }
1303 } else {
1304 tracing::debug!("Object 100 is not a dictionary: {:?}", obj);
1305 }
1306 }
1307 Err(e) => {
1308 tracing::debug!("Failed to get object 100: {:?}", e);
1309 tracing::debug!("Attempting direct content extraction...");
1310 return self.extract_n_value_from_raw_object_100();
1312 }
1313 }
1314
1315 None
1316 }
1317
1318 fn extract_n_value_from_raw_object_100(&mut self) -> Option<u32> {
1319 if let Some(entry) = self.xref.get_entry(100) {
1321 if self.reader.seek(SeekFrom::Start(entry.offset)).is_err() {
1323 return None;
1324 }
1325
1326 let mut buffer = vec![0u8; 1024];
1328 if let Ok(bytes_read) = self.reader.read(&mut buffer) {
1329 if bytes_read == 0 {
1330 return None;
1331 }
1332
1333 let content = String::from_utf8_lossy(&buffer[..bytes_read]);
1335 tracing::debug!("Raw content around object 100:\n{}", content);
1336
1337 if let Some(n_pos) = content.find("/N ") {
1339 let after_n = &content[n_pos + 3..];
1340 tracing::debug!(
1341 "Content after /N: {}",
1342 truncate_on_char_boundary(after_n, 50)
1343 );
1344
1345 let mut num_str = String::new();
1347 for ch in after_n.chars() {
1348 if ch.is_ascii_digit() {
1349 num_str.push(ch);
1350 } else if !num_str.is_empty() {
1351 break;
1353 }
1354 }
1356
1357 if !num_str.is_empty() {
1358 if let Ok(page_count) = num_str.parse::<u32>() {
1359 tracing::debug!(
1360 "Extracted page count from raw content: {}",
1361 page_count
1362 );
1363 return Some(page_count);
1364 }
1365 }
1366 }
1367 }
1368 }
1369 None
1370 }
1371
1372 #[allow(dead_code)]
1373 fn find_object_pattern(&mut self, obj_num: u32, gen_num: u16) -> Option<u64> {
1374 let pattern = format!("{} {} obj", obj_num, gen_num);
1375
1376 let original_pos = self.reader.stream_position().unwrap_or(0);
1378
1379 if self.reader.seek(SeekFrom::Start(0)).is_err() {
1381 return None;
1382 }
1383
1384 let mut buffer = vec![0u8; 8192];
1386 let mut file_content = Vec::new();
1387
1388 loop {
1389 match self.reader.read(&mut buffer) {
1390 Ok(0) => break, Ok(bytes_read) => {
1392 file_content.extend_from_slice(&buffer[..bytes_read]);
1393 }
1394 Err(_) => return None,
1395 }
1396 }
1397
1398 let content = String::from_utf8_lossy(&file_content);
1400 if let Some(pattern_pos) = content.find(&pattern) {
1401 let after_pattern = pattern_pos + pattern.len();
1403 let search_area = &content[after_pattern..];
1404
1405 if let Some(dict_start_offset) = search_area.find("<<") {
1406 let dict_start_pos = after_pattern + dict_start_offset;
1407
1408 self.reader.seek(SeekFrom::Start(original_pos)).ok();
1410 return Some(dict_start_pos as u64);
1411 } else {
1412 }
1413 }
1414
1415 self.reader.seek(SeekFrom::Start(original_pos)).ok();
1417 None
1418 }
1419
1420 fn can_attempt_manual_reconstruction(&self, error: &ParseError) -> bool {
1422 match error {
1423 ParseError::SyntaxError { .. } => true,
1425 ParseError::UnexpectedToken { .. } => true,
1426 _ => false,
1428 }
1429 }
1430
1431 fn is_reconstructible_object(&self, obj_num: u32) -> bool {
1433 if obj_num == 102 || obj_num == 113 || obj_num == 114 {
1435 return true;
1436 }
1437
1438 let page_objects = [
1441 1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 30, 34, 37, 39, 42, 44, 46, 49, 52,
1442 54, 56, 58, 60, 62, 64, 67, 69, 71, 73, 75, 77, 79, 81, 83, 85, 87, 89, 91, 93, 104,
1443 ];
1444
1445 let content_objects = [
1448 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 29, 31, 32, 33, 35, 36, 38, 40, 41,
1449 43, 45, 47, 48, 50, 51, 53, 55, 57, 59, 61, 63, 65, 66, 68, 70, 72, 74, 76, 78, 80, 82,
1450 84, 86, 88, 90, 92, 94, 95, 96, 97, 98, 99, 100, 101, 105, 106, 107, 108, 109, 110,
1451 111,
1452 ];
1453
1454 page_objects.contains(&obj_num) || content_objects.contains(&obj_num)
1455 }
1456
1457 fn is_page_object(&self, obj_num: u32) -> bool {
1459 let page_objects = [
1460 1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 30, 34, 37, 39, 42, 44, 46, 49, 52,
1461 54, 56, 58, 60, 62, 64, 67, 69, 71, 73, 75, 77, 79, 81, 83, 85, 87, 89, 91, 93, 104,
1462 ];
1463 page_objects.contains(&obj_num)
1464 }
1465
1466 fn parse_page_dictionary_content(
1468 &self,
1469 dict_content: &str,
1470 result_dict: &mut std::collections::HashMap<
1471 crate::parser::objects::PdfName,
1472 crate::parser::objects::PdfObject,
1473 >,
1474 _obj_num: u32,
1475 ) -> ParseResult<()> {
1476 use crate::parser::objects::{PdfArray, PdfName, PdfObject};
1477 use std::collections::HashMap;
1478
1479 if let Some(mediabox_start) = dict_content.find("/MediaBox") {
1481 let mediabox_area = &dict_content[mediabox_start..];
1482 if let Some(mediabox_content) = slice_between(mediabox_area, b'[', b']') {
1483 let values: Vec<f32> = mediabox_content
1484 .split_whitespace()
1485 .filter_map(|s| s.parse().ok())
1486 .collect();
1487
1488 if values.len() == 4 {
1489 let mediabox = PdfArray(vec![
1490 PdfObject::Integer(values[0] as i64),
1491 PdfObject::Integer(values[1] as i64),
1492 PdfObject::Integer(values[2] as i64),
1493 PdfObject::Integer(values[3] as i64),
1494 ]);
1495 result_dict.insert(PdfName("MediaBox".to_string()), PdfObject::Array(mediabox));
1496 }
1497 }
1498 }
1499
1500 if let Some(contents_match) = dict_content.find("/Contents") {
1502 let contents_area = &dict_content[contents_match..];
1503 let parts: Vec<&str> = contents_area.split_whitespace().collect();
1505 if parts.len() >= 3 {
1506 if let (Ok(obj_ref), Ok(gen_ref)) =
1507 (parts[1].parse::<u32>(), parts[2].parse::<u16>())
1508 {
1509 if parts.len() > 3 && parts[3] == "R" {
1510 result_dict.insert(
1511 PdfName("Contents".to_string()),
1512 PdfObject::Reference(obj_ref, gen_ref),
1513 );
1514 }
1515 }
1516 }
1517 }
1518
1519 if dict_content.contains("/Parent") {
1521 result_dict.insert(
1522 PdfName("Parent".to_string()),
1523 PdfObject::Reference(113, 0), );
1525 }
1526
1527 if dict_content.contains("/Resources") {
1529 if let Ok(parsed_resources) = self.parse_resources_from_content(&dict_content) {
1530 result_dict.insert(PdfName("Resources".to_string()), parsed_resources);
1531 } else {
1532 let resources = HashMap::new();
1534 result_dict.insert(
1535 PdfName("Resources".to_string()),
1536 PdfObject::Dictionary(crate::parser::objects::PdfDictionary(resources)),
1537 );
1538 }
1539 }
1540
1541 Ok(())
1542 }
1543
1544 fn attempt_manual_object_reconstruction(
1546 &mut self,
1547 obj_num: u32,
1548 gen_num: u16,
1549 _current_offset: u64,
1550 ) -> ParseResult<&PdfObject> {
1551 let is_circular = self
1553 .objects_being_reconstructed
1554 .lock()
1555 .map_err(|_| ParseError::SyntaxError {
1556 position: 0,
1557 message: "Mutex poisoned during circular reference check".to_string(),
1558 })?
1559 .contains(&obj_num);
1560
1561 if is_circular {
1562 tracing::debug!(
1563 "Warning: Circular reconstruction detected for object {} {} - attempting manual extraction",
1564 obj_num, gen_num
1565 );
1566
1567 match self.extract_object_or_stream_manually(obj_num) {
1571 Ok(obj) => {
1572 tracing::debug!(
1573 " Successfully extracted object {} {} manually despite circular reference",
1574 obj_num, gen_num
1575 );
1576 self.object_cache.insert((obj_num, gen_num), obj);
1577 return Ok(&self.object_cache[&(obj_num, gen_num)]);
1578 }
1579 Err(e) => {
1580 tracing::debug!(
1581 " Manual extraction failed: {} - breaking cycle with null object",
1582 e
1583 );
1584 self.object_cache
1586 .insert((obj_num, gen_num), PdfObject::Null);
1587 return Ok(&self.object_cache[&(obj_num, gen_num)]);
1588 }
1589 }
1590 }
1591
1592 let current_depth = self
1594 .objects_being_reconstructed
1595 .lock()
1596 .map_err(|_| ParseError::SyntaxError {
1597 position: 0,
1598 message: "Mutex poisoned during depth check".to_string(),
1599 })?
1600 .len() as u32;
1601 if current_depth >= self.max_reconstruction_depth {
1602 return Err(ParseError::SyntaxError {
1603 position: 0,
1604 message: format!(
1605 "Maximum reconstruction depth ({}) exceeded for object {} {}",
1606 self.max_reconstruction_depth, obj_num, gen_num
1607 ),
1608 });
1609 }
1610
1611 self.objects_being_reconstructed
1613 .lock()
1614 .map_err(|_| ParseError::SyntaxError {
1615 position: 0,
1616 message: "Mutex poisoned while marking object as being reconstructed".to_string(),
1617 })?
1618 .insert(obj_num);
1619
1620 let reconstructed_obj = match self.smart_object_reconstruction(obj_num, gen_num) {
1622 Ok(obj) => obj,
1623 Err(_) => {
1624 match self.extract_object_or_stream_manually(obj_num) {
1626 Ok(obj) => obj,
1627 Err(e) => {
1628 if self.options.lenient_syntax {
1630 PdfObject::Null
1631 } else {
1632 if let Ok(mut guard) = self.objects_being_reconstructed.lock() {
1634 guard.remove(&obj_num);
1635 }
1636 return Err(e);
1637 }
1638 }
1639 }
1640 }
1641 };
1642
1643 self.objects_being_reconstructed
1645 .lock()
1646 .map_err(|_| ParseError::SyntaxError {
1647 position: 0,
1648 message: "Mutex poisoned while unmarking reconstructed object".to_string(),
1649 })?
1650 .remove(&obj_num);
1651
1652 self.object_cache
1653 .insert((obj_num, gen_num), reconstructed_obj);
1654
1655 use crate::parser::xref::XRefEntry;
1657 let xref_entry = XRefEntry {
1658 offset: 0, generation: gen_num,
1660 in_use: true,
1661 };
1662 self.xref.add_entry(obj_num, xref_entry);
1663
1664 self.object_cache
1665 .get(&(obj_num, gen_num))
1666 .ok_or_else(|| ParseError::SyntaxError {
1667 position: 0,
1668 message: format!(
1669 "Object {} {} not in cache after reconstruction",
1670 obj_num, gen_num
1671 ),
1672 })
1673 }
1674
1675 fn smart_object_reconstruction(
1677 &mut self,
1678 obj_num: u32,
1679 gen_num: u16,
1680 ) -> ParseResult<PdfObject> {
1681 if let Ok(inferred_obj) = self.infer_object_from_context(obj_num) {
1685 return Ok(inferred_obj);
1686 }
1687
1688 if let Ok(scanned_obj) = self.scan_for_object_patterns(obj_num) {
1690 return Ok(scanned_obj);
1691 }
1692
1693 if let Ok(synthetic_obj) = self.create_synthetic_object(obj_num) {
1695 return Ok(synthetic_obj);
1696 }
1697
1698 Err(ParseError::SyntaxError {
1699 position: 0,
1700 message: format!("Could not reconstruct object {} {}", obj_num, gen_num),
1701 })
1702 }
1703
1704 fn infer_object_from_context(&mut self, obj_num: u32) -> ParseResult<PdfObject> {
1706 for (_key, obj) in self.object_cache.iter() {
1710 if let PdfObject::Dictionary(dict) = obj {
1711 for (key, value) in dict.0.iter() {
1712 if let PdfObject::Reference(ref_num, _) = value {
1713 if *ref_num == obj_num {
1714 match key.as_str() {
1716 "Font" | "F1" | "F2" | "F3" => {
1717 return Ok(self.create_font_object(obj_num));
1718 }
1719 "XObject" | "Image" | "Im1" => {
1720 return Ok(self.create_xobject(obj_num));
1721 }
1722 "Contents" => {
1723 return Ok(self.create_content_stream(obj_num));
1724 }
1725 "Resources" => {
1726 return Ok(self.create_resources_dict(obj_num));
1727 }
1728 _ => continue,
1729 }
1730 }
1731 }
1732 }
1733 }
1734 }
1735
1736 Err(ParseError::SyntaxError {
1737 position: 0,
1738 message: "Cannot infer object type from context".to_string(),
1739 })
1740 }
1741
1742 fn scan_for_object_patterns(&mut self, obj_num: u32) -> ParseResult<PdfObject> {
1744 self.extract_object_or_stream_manually(obj_num)
1747 }
1748
1749 fn create_synthetic_object(&mut self, obj_num: u32) -> ParseResult<PdfObject> {
1751 use super::objects::{PdfDictionary, PdfName, PdfObject};
1752
1753 match obj_num {
1755 1..=10 => {
1756 let mut dict = PdfDictionary::new();
1758 dict.insert(
1759 "Type".to_string(),
1760 PdfObject::Name(PdfName("Null".to_string())),
1761 );
1762 Ok(PdfObject::Dictionary(dict))
1763 }
1764 _ => {
1765 Ok(PdfObject::Null)
1767 }
1768 }
1769 }
1770
1771 fn create_font_object(&self, _obj_num: u32) -> PdfObject {
1772 use super::objects::{PdfDictionary, PdfName, PdfObject};
1773 let mut font_dict = PdfDictionary::new();
1774 font_dict.insert(
1775 "Type".to_string(),
1776 PdfObject::Name(PdfName("Font".to_string())),
1777 );
1778 font_dict.insert(
1779 "Subtype".to_string(),
1780 PdfObject::Name(PdfName("Type1".to_string())),
1781 );
1782 font_dict.insert(
1783 "BaseFont".to_string(),
1784 PdfObject::Name(PdfName("Helvetica".to_string())),
1785 );
1786 PdfObject::Dictionary(font_dict)
1787 }
1788
1789 fn create_xobject(&self, _obj_num: u32) -> PdfObject {
1790 use super::objects::{PdfDictionary, PdfName, PdfObject};
1791 let mut xobj_dict = PdfDictionary::new();
1792 xobj_dict.insert(
1793 "Type".to_string(),
1794 PdfObject::Name(PdfName("XObject".to_string())),
1795 );
1796 xobj_dict.insert(
1797 "Subtype".to_string(),
1798 PdfObject::Name(PdfName("Form".to_string())),
1799 );
1800 PdfObject::Dictionary(xobj_dict)
1801 }
1802
1803 fn create_content_stream(&self, _obj_num: u32) -> PdfObject {
1804 use super::objects::{PdfDictionary, PdfObject, PdfStream};
1805 let mut stream_dict = PdfDictionary::new();
1806 stream_dict.insert("Length".to_string(), PdfObject::Integer(0));
1807
1808 let stream = PdfStream {
1809 dict: stream_dict,
1810 data: Vec::new(),
1811 };
1812 PdfObject::Stream(stream)
1813 }
1814
1815 fn create_resources_dict(&self, _obj_num: u32) -> PdfObject {
1816 use super::objects::{PdfArray, PdfDictionary, PdfObject};
1817 let mut res_dict = PdfDictionary::new();
1818 res_dict.insert("ProcSet".to_string(), PdfObject::Array(PdfArray::new()));
1819 PdfObject::Dictionary(res_dict)
1820 }
1821
1822 fn extract_object_manually(
1823 &mut self,
1824 obj_num: u32,
1825 ) -> ParseResult<crate::parser::objects::PdfDictionary> {
1826 use crate::parser::objects::{PdfArray, PdfDictionary, PdfName, PdfObject};
1827 use std::collections::HashMap;
1828
1829 let original_pos = self.reader.stream_position().unwrap_or(0);
1831
1832 let window = match read_object_window(&mut self.reader, obj_num, MANUAL_DICT_WINDOW) {
1836 Ok(Some((_, w))) => w,
1837 Ok(None) => {
1838 self.reader.seek(SeekFrom::Start(original_pos)).ok();
1839 return Err(ParseError::SyntaxError {
1840 position: 0,
1841 message: format!("Object {obj_num} not found in manual extraction"),
1842 });
1843 }
1844 Err(_) => {
1845 self.reader.seek(SeekFrom::Start(original_pos)).ok();
1846 return Err(ParseError::SyntaxError {
1847 position: 0,
1848 message: "Failed to read file for manual extraction".to_string(),
1849 });
1850 }
1851 };
1852
1853 let content = String::from_utf8_lossy(&window);
1854
1855 let pattern = format!("{} 0 obj", obj_num);
1857 if let Some(start) = content.find(&pattern) {
1858 let search_area = &content[start..];
1859 if let Some(dict_start) = search_area.find("<<") {
1860 let after_bracket = &search_area[dict_start + 2..];
1861
1862 if let Some(dict_end) = find_dict_end(after_bracket) {
1863 let dict_content = &after_bracket[..dict_end];
1864
1865 let mut result_dict = HashMap::new();
1867
1868 if dict_content.contains("/Type/Catalog")
1871 || dict_content.contains("/Type /Catalog")
1872 {
1873 result_dict.insert(
1874 PdfName("Type".to_string()),
1875 PdfObject::Name(PdfName("Catalog".to_string())),
1876 );
1877
1878 if let Some(pages_start) = dict_content.find("/Pages") {
1882 let after_pages = &dict_content[pages_start + 6..]; let trimmed = after_pages.trim_start();
1885 let parts: Vec<&str> = trimmed.split_whitespace().collect();
1887 if parts.len() >= 3 {
1888 if let (Ok(obj), Ok(gen)) =
1892 (parts[0].parse::<u32>(), parts[1].parse::<u16>())
1893 {
1894 if parts[2] == "R" || parts[2].starts_with('R') {
1895 result_dict.insert(
1896 PdfName("Pages".to_string()),
1897 PdfObject::Reference(obj, gen),
1898 );
1899 }
1900 }
1901 }
1902 }
1903
1904 if let Some(ver_start) = dict_content.find("/Version") {
1907 let after_ver = &dict_content[ver_start + 8..];
1908 if let Some(ver_end) = after_ver.find(|c: char| c == '/' || c == '>') {
1909 let version_str = after_ver[..ver_end].trim();
1910 result_dict.insert(
1911 PdfName("Version".to_string()),
1912 PdfObject::Name(PdfName(
1913 version_str.trim_start_matches('/').to_string(),
1914 )),
1915 );
1916 }
1917 }
1918
1919 if let Some(meta_start) = dict_content.find("/Metadata") {
1921 let after_meta = &dict_content[meta_start + 9..];
1922 let parts: Vec<&str> = after_meta.split_whitespace().collect();
1923 if parts.len() >= 3 {
1924 if let (Ok(obj), Ok(gen)) =
1925 (parts[0].parse::<u32>(), parts[1].parse::<u16>())
1926 {
1927 if parts[2] == "R" {
1928 result_dict.insert(
1929 PdfName("Metadata".to_string()),
1930 PdfObject::Reference(obj, gen),
1931 );
1932 }
1933 }
1934 }
1935 }
1936
1937 if let Some(acro_start) = dict_content.find("/AcroForm") {
1939 let after_acro = &dict_content[acro_start + 9..];
1940 if after_acro.trim_start().starts_with("<<") {
1942 } else {
1944 let parts: Vec<&str> = after_acro.split_whitespace().collect();
1945 if parts.len() >= 3 {
1946 if let (Ok(obj), Ok(gen)) =
1947 (parts[0].parse::<u32>(), parts[1].parse::<u16>())
1948 {
1949 if parts[2] == "R" {
1950 result_dict.insert(
1951 PdfName("AcroForm".to_string()),
1952 PdfObject::Reference(obj, gen),
1953 );
1954 }
1955 }
1956 }
1957 }
1958 }
1959 } else if obj_num == 102 {
1960 if dict_content.contains("/Type /Catalog") {
1962 result_dict.insert(
1964 PdfName("Type".to_string()),
1965 PdfObject::Name(PdfName("Catalog".to_string())),
1966 );
1967
1968 if dict_content.contains("/Dests 139 0 R") {
1970 result_dict.insert(
1971 PdfName("Dests".to_string()),
1972 PdfObject::Reference(139, 0),
1973 );
1974 }
1975
1976 if dict_content.contains("/Pages 113 0 R") {
1978 result_dict.insert(
1979 PdfName("Pages".to_string()),
1980 PdfObject::Reference(113, 0),
1981 );
1982 }
1983 } else {
1984 self.reader.seek(SeekFrom::Start(original_pos)).ok();
1987 return Err(ParseError::SyntaxError {
1988 position: 0,
1989 message:
1990 "Object 102 is not a corrupted catalog, cannot reconstruct"
1991 .to_string(),
1992 });
1993 }
1994 } else if obj_num == 113 {
1995 result_dict.insert(
1998 PdfName("Type".to_string()),
1999 PdfObject::Name(PdfName("Pages".to_string())),
2000 );
2001
2002 let page_refs = match self.find_page_objects() {
2004 Ok(refs) => refs,
2005 Err(_e) => {
2006 vec![]
2007 }
2008 };
2009
2010 let page_count = if page_refs.is_empty() {
2012 44
2013 } else {
2014 page_refs.len() as i64
2015 };
2016 result_dict
2017 .insert(PdfName("Count".to_string()), PdfObject::Integer(page_count));
2018
2019 let kids_array: Vec<PdfObject> = page_refs
2021 .into_iter()
2022 .map(|(obj_num, gen_num)| PdfObject::Reference(obj_num, gen_num))
2023 .collect();
2024
2025 result_dict.insert(
2026 PdfName("Kids".to_string()),
2027 PdfObject::Array(PdfArray(kids_array)),
2028 );
2029 } else if obj_num == 114 {
2030 result_dict.insert(
2033 PdfName("Type".to_string()),
2034 PdfObject::Name(PdfName("Pages".to_string())),
2035 );
2036
2037 let page_refs = match self.find_page_objects() {
2039 Ok(refs) => refs,
2040 Err(_e) => {
2041 vec![]
2042 }
2043 };
2044
2045 let page_count = if page_refs.is_empty() {
2047 44
2048 } else {
2049 page_refs.len() as i64
2050 };
2051 result_dict
2052 .insert(PdfName("Count".to_string()), PdfObject::Integer(page_count));
2053
2054 let kids_array: Vec<PdfObject> = page_refs
2056 .into_iter()
2057 .map(|(obj_num, gen_num)| PdfObject::Reference(obj_num, gen_num))
2058 .collect();
2059
2060 result_dict.insert(
2061 PdfName("Kids".to_string()),
2062 PdfObject::Array(PdfArray(kids_array)),
2063 );
2064 } else if self.is_page_object(obj_num) {
2065 result_dict.insert(
2068 PdfName("Type".to_string()),
2069 PdfObject::Name(PdfName("Page".to_string())),
2070 );
2071
2072 self.parse_page_dictionary_content(
2074 &dict_content,
2075 &mut result_dict,
2076 obj_num,
2077 )?;
2078 }
2079
2080 self.reader.seek(SeekFrom::Start(original_pos)).ok();
2082
2083 return Ok(PdfDictionary(result_dict));
2084 }
2085 }
2086 }
2087
2088 self.reader.seek(SeekFrom::Start(original_pos)).ok();
2090
2091 if obj_num == 113 {
2093 let mut result_dict = HashMap::new();
2094 result_dict.insert(
2095 PdfName("Type".to_string()),
2096 PdfObject::Name(PdfName("Pages".to_string())),
2097 );
2098
2099 let page_refs = match self.find_page_objects() {
2101 Ok(refs) => refs,
2102 Err(_e) => {
2103 vec![]
2104 }
2105 };
2106
2107 let page_count = if page_refs.is_empty() {
2109 44
2110 } else {
2111 page_refs.len() as i64
2112 };
2113 result_dict.insert(PdfName("Count".to_string()), PdfObject::Integer(page_count));
2114
2115 let kids_array: Vec<PdfObject> = page_refs
2117 .into_iter()
2118 .map(|(obj_num, gen_num)| PdfObject::Reference(obj_num, gen_num))
2119 .collect();
2120
2121 result_dict.insert(
2122 PdfName("Kids".to_string()),
2123 PdfObject::Array(PdfArray(kids_array)),
2124 );
2125
2126 return Ok(PdfDictionary(result_dict));
2127 } else if obj_num == 114 {
2128 let mut result_dict = HashMap::new();
2129 result_dict.insert(
2130 PdfName("Type".to_string()),
2131 PdfObject::Name(PdfName("Pages".to_string())),
2132 );
2133
2134 let page_refs = match self.find_page_objects() {
2136 Ok(refs) => refs,
2137 Err(_e) => {
2138 vec![]
2139 }
2140 };
2141
2142 let page_count = if page_refs.is_empty() {
2144 44
2145 } else {
2146 page_refs.len() as i64
2147 };
2148 result_dict.insert(PdfName("Count".to_string()), PdfObject::Integer(page_count));
2149
2150 let kids_array: Vec<PdfObject> = page_refs
2152 .into_iter()
2153 .map(|(obj_num, gen_num)| PdfObject::Reference(obj_num, gen_num))
2154 .collect();
2155
2156 result_dict.insert(
2157 PdfName("Kids".to_string()),
2158 PdfObject::Array(PdfArray(kids_array)),
2159 );
2160
2161 return Ok(PdfDictionary(result_dict));
2162 }
2163
2164 Err(ParseError::SyntaxError {
2165 position: 0,
2166 message: "Could not find catalog dictionary in manual extraction".to_string(),
2167 })
2168 }
2169
2170 fn extract_object_or_stream_manually(&mut self, obj_num: u32) -> ParseResult<PdfObject> {
2172 use crate::parser::objects::PdfObject;
2173
2174 let original_pos = self.reader.stream_position().unwrap_or(0);
2176
2177 let (obj_offset, window) =
2182 match read_object_window(&mut self.reader, obj_num, MANUAL_DICT_WINDOW) {
2183 Ok(Some(v)) => v,
2184 Ok(None) => {
2185 self.reader.seek(SeekFrom::Start(original_pos)).ok();
2186 return Err(ParseError::SyntaxError {
2187 position: 0,
2188 message: format!("Could not manually extract object {obj_num}"),
2189 });
2190 }
2191 Err(_) => {
2192 self.reader.seek(SeekFrom::Start(original_pos)).ok();
2193 return Err(ParseError::SyntaxError {
2194 position: 0,
2195 message: "Failed to read file for manual extraction".to_string(),
2196 });
2197 }
2198 };
2199
2200 if let Some(dict_start) = find_byte_pattern(&window, b"<<") {
2203 let mut bracket_count = 1;
2205 let mut pos = dict_start + 2;
2206 let mut dict_end = None;
2207
2208 while pos < window.len().saturating_sub(1) && bracket_count > 0 {
2209 if window[pos] == b'<' && window[pos + 1] == b'<' {
2210 bracket_count += 1;
2211 pos += 2;
2212 } else if window[pos] == b'>' && window[pos + 1] == b'>' {
2213 bracket_count -= 1;
2214 if bracket_count == 0 {
2215 dict_end = Some(pos);
2216 break;
2217 }
2218 pos += 2;
2219 } else {
2220 pos += 1;
2221 }
2222 }
2223
2224 if let Some(dict_end_pos) = dict_end {
2225 let dict_content = String::from_utf8_lossy(&window[dict_start + 2..dict_end_pos]);
2226 let dict_bytes = &window[dict_start..dict_end_pos + 2];
2228
2229 let after_dict = &window[dict_end_pos + 2..];
2231 if is_immediate_stream_start(after_dict) {
2232 let after_dict_abs = obj_offset + (dict_end_pos + 2) as u64;
2234 return self.reconstruct_stream_object_bounded(
2235 obj_num,
2236 dict_bytes,
2237 &dict_content,
2238 after_dict_abs,
2239 after_dict,
2240 );
2241 } else {
2242 self.reader.seek(SeekFrom::Start(original_pos)).ok();
2244 return self
2245 .extract_object_manually(obj_num)
2246 .map(PdfObject::Dictionary);
2247 }
2248 }
2249 }
2250
2251 self.reader.seek(SeekFrom::Start(original_pos)).ok();
2253
2254 Err(ParseError::SyntaxError {
2255 position: 0,
2256 message: format!("Could not manually extract object {obj_num}"),
2257 })
2258 }
2259
2260 fn reconstruct_stream_object_bounded(
2268 &mut self,
2269 obj_num: u32,
2270 dict_bytes: &[u8],
2271 dict_content: &str,
2272 after_dict_abs: u64,
2273 after_dict: &[u8],
2274 ) -> ParseResult<PdfObject> {
2275 use crate::parser::objects::{PdfDictionary, PdfName, PdfObject, PdfStream};
2276 use std::collections::HashMap;
2277
2278 let opts = self.options.clone();
2287 let mut dict: HashMap<PdfName, PdfObject> = {
2288 let mut lexer = super::lexer::Lexer::new_with_options(
2289 std::io::Cursor::new(dict_bytes),
2290 opts.clone(),
2291 );
2292 match PdfObject::parse_with_options(&mut lexer, &opts) {
2293 Ok(PdfObject::Dictionary(d)) => d.0,
2294 Ok(PdfObject::Stream(s)) => s.dict.0,
2297 _ => {
2300 let mut d = HashMap::new();
2301 if dict_content.contains("/Filter /FlateDecode") {
2302 d.insert(
2303 PdfName("Filter".to_string()),
2304 PdfObject::Name(PdfName("FlateDecode".to_string())),
2305 );
2306 }
2307 d
2308 }
2309 }
2310 };
2311
2312 let length = self.parse_stream_length(dict_content)?;
2314
2315 let Some(stream_kw) = find_byte_pattern(after_dict, b"stream") else {
2317 return Err(ParseError::SyntaxError {
2318 position: 0,
2319 message: format!("Could not reconstruct stream for object {obj_num}"),
2320 });
2321 };
2322 let after_kw = stream_kw + 6; let data_rel = match after_dict.get(after_kw) {
2324 Some(b'\r') if after_dict.get(after_kw + 1) == Some(&b'\n') => after_kw + 2,
2325 Some(b'\r') | Some(b'\n') => after_kw + 1,
2326 _ => after_kw,
2327 };
2328 let data_abs = after_dict_abs + data_rel as u64;
2329
2330 let data = match length {
2332 Some(len) => {
2333 let body = read_window_at(&mut self.reader, data_abs, len)?;
2340 dict.insert(
2341 PdfName("Length".to_string()),
2342 PdfObject::Integer(len as i64),
2343 );
2344 body
2345 }
2346 None => {
2347 let body = self.read_stream_until_endstream(data_abs)?;
2350 dict.insert(
2355 PdfName("Length".to_string()),
2356 PdfObject::Integer(body.len() as i64),
2357 );
2358 body
2359 }
2360 };
2361
2362 Ok(PdfObject::Stream(PdfStream {
2363 dict: PdfDictionary(dict),
2364 data,
2365 }))
2366 }
2367
2368 fn parse_stream_length(&mut self, dict_content: &str) -> ParseResult<Option<usize>> {
2372 let Some(idx) = dict_content.find("/Length") else {
2373 return Ok(None);
2374 };
2375 let rest = dict_content[idx + "/Length".len()..].trim_start();
2376 let tokens: Vec<&str> = rest.split_whitespace().collect();
2377
2378 if tokens.len() >= 3 && tokens[2] == "R" {
2380 if let Ok(len_obj) = tokens[0].parse::<u32>() {
2381 return self.resolve_length_object(len_obj);
2382 }
2383 }
2384
2385 if let Some(tok) = tokens.first() {
2387 if let Ok(n) = tok.parse::<i64>() {
2388 if n >= 0 {
2389 return Ok(Some(n as usize));
2390 }
2391 }
2392 }
2393 Ok(None)
2394 }
2395
2396 fn resolve_length_object(&mut self, len_obj: u32) -> ParseResult<Option<usize>> {
2401 let Some((_, window)) = read_object_window(&mut self.reader, len_obj, 4096)? else {
2402 return Ok(None);
2403 };
2404 let text = String::from_utf8_lossy(&window);
2405 if let Some(obj_pos) = text.find("obj") {
2407 let after = text[obj_pos + 3..].trim_start();
2408 let digits: String = after.chars().take_while(|c| c.is_ascii_digit()).collect();
2409 if let Ok(n) = digits.parse::<usize>() {
2410 return Ok(Some(n));
2411 }
2412 }
2413 Ok(None)
2414 }
2415
2416 fn read_stream_until_endstream(&mut self, data_abs: u64) -> ParseResult<Vec<u8>> {
2420 const WIN: usize = 64 * 1024;
2421 const MAX_STREAM: usize = 64 * 1024 * 1024; let mut acc: Vec<u8> = Vec::new();
2424 let mut offset = data_abs;
2425 let mut searched = 0usize;
2426 loop {
2427 let chunk = read_window_at(&mut self.reader, offset, WIN)?;
2428 if chunk.is_empty() {
2429 break; }
2431 acc.extend_from_slice(&chunk);
2432
2433 let from = searched.saturating_sub(b"endstream".len() - 1);
2436 if let Some(rel) = find_byte_pattern(&acc[from..], b"endstream") {
2437 acc.truncate(from + rel);
2438 break;
2439 }
2440 searched = acc.len();
2441 offset += chunk.len() as u64;
2442
2443 if chunk.len() < WIN {
2444 break; }
2446 if acc.len() > MAX_STREAM {
2447 tracing::warn!(
2451 "stream body exceeds {} MiB runaway guard without endstream; truncating",
2452 MAX_STREAM / (1024 * 1024)
2453 );
2454 break;
2455 }
2456 }
2457
2458 if acc.last() == Some(&b'\n') {
2460 acc.pop();
2461 if acc.last() == Some(&b'\r') {
2462 acc.pop();
2463 }
2464 } else if acc.last() == Some(&b'\r') {
2465 acc.pop();
2466 }
2467 Ok(acc)
2468 }
2469
2470 fn parse_resources_from_content(&self, dict_content: &str) -> ParseResult<PdfObject> {
2472 use crate::parser::objects::{PdfDictionary, PdfName, PdfObject};
2473 use std::collections::HashMap;
2474
2475 if let Some(resources_start) = dict_content.find("/Resources") {
2477 if let Some(bracket_start) = dict_content[resources_start..].find("<<") {
2479 let abs_bracket_start = resources_start + bracket_start + 2;
2480
2481 let after_bracket = &dict_content[abs_bracket_start..];
2484 if let Some(end_rel) = find_dict_end(after_bracket) {
2485 let resources_content = &after_bracket[..end_rel];
2486
2487 let mut resources_dict = HashMap::new();
2489
2490 if let Some(font_start) = resources_content.find("/Font") {
2492 if let Some(font_bracket) = resources_content[font_start..].find("<<") {
2493 let abs_font_start = font_start + font_bracket + 2;
2494
2495 let mut font_dict = HashMap::new();
2497
2498 let font_section = &resources_content[abs_font_start..];
2500 let mut pos = 0;
2501 while let Some(f_pos) = font_section[pos..].find("/F") {
2502 let abs_f_pos = pos + f_pos;
2503 if let Some(space_pos) = font_section[abs_f_pos..].find(" ") {
2504 let font_name = &font_section[abs_f_pos..abs_f_pos + space_pos];
2505
2506 let after_name = &font_section[abs_f_pos + space_pos..];
2508 if let Some(r_pos) = after_name.find(" R") {
2509 let ref_part = after_name[..r_pos].trim();
2510 if let Some(parts) = ref_part
2511 .split_whitespace()
2512 .collect::<Vec<&str>>()
2513 .get(0..2)
2514 {
2515 if let (Ok(obj_num), Ok(gen_num)) =
2516 (parts[0].parse::<u32>(), parts[1].parse::<u16>())
2517 {
2518 font_dict.insert(
2519 PdfName(font_name[1..].to_string()), PdfObject::Reference(obj_num, gen_num),
2521 );
2522 }
2523 }
2524 }
2525 }
2526 pos = abs_f_pos + 1;
2527 }
2528
2529 if !font_dict.is_empty() {
2530 resources_dict.insert(
2531 PdfName("Font".to_string()),
2532 PdfObject::Dictionary(PdfDictionary(font_dict)),
2533 );
2534 }
2535 }
2536 }
2537
2538 return Ok(PdfObject::Dictionary(PdfDictionary(resources_dict)));
2539 }
2540 }
2541 }
2542
2543 Err(ParseError::SyntaxError {
2544 position: 0,
2545 message: "Could not parse Resources".to_string(),
2546 })
2547 }
2548
2549 #[allow(dead_code)]
2550 fn extract_catalog_directly(
2551 &mut self,
2552 obj_num: u32,
2553 gen_num: u16,
2554 ) -> ParseResult<&PdfDictionary> {
2555 if let Some(entry) = self.xref.get_entry(obj_num) {
2557 if self.reader.seek(SeekFrom::Start(entry.offset)).is_err() {
2559 return Err(ParseError::SyntaxError {
2560 position: 0,
2561 message: "Failed to seek to catalog object".to_string(),
2562 });
2563 }
2564
2565 let mut buffer = vec![0u8; 2048];
2567 if let Ok(bytes_read) = self.reader.read(&mut buffer) {
2568 let content = String::from_utf8_lossy(&buffer[..bytes_read]);
2569 tracing::debug!("Raw catalog content:\n{}", content);
2570
2571 if let Some(dict_start) = content.find("<<") {
2573 if let Some(dict_end) = content[dict_start..].find(">>") {
2574 let dict_content = &content[dict_start..dict_start + dict_end + 2];
2575 tracing::debug!("Found dictionary content: {}", dict_content);
2576
2577 if let Ok(dict) = self.parse_dictionary_from_string(dict_content) {
2579 let key = (obj_num, gen_num);
2581 self.object_cache.insert(key, PdfObject::Dictionary(dict));
2582
2583 if let Some(PdfObject::Dictionary(ref dict)) =
2585 self.object_cache.get(&key)
2586 {
2587 return Ok(dict);
2588 }
2589 }
2590 }
2591 }
2592 }
2593 }
2594
2595 Err(ParseError::SyntaxError {
2596 position: 0,
2597 message: "Failed to extract catalog directly".to_string(),
2598 })
2599 }
2600
2601 #[allow(dead_code)]
2602 fn parse_dictionary_from_string(&self, dict_str: &str) -> ParseResult<PdfDictionary> {
2603 use crate::parser::lexer::{Lexer, Token};
2604
2605 let mut cursor = std::io::Cursor::new(dict_str.as_bytes());
2607 let mut lexer = Lexer::new_with_options(&mut cursor, self.options.clone());
2608
2609 match lexer.next_token()? {
2611 Token::DictStart => {
2612 let mut dict = std::collections::HashMap::new();
2613
2614 loop {
2615 let token = lexer.next_token()?;
2616 match token {
2617 Token::DictEnd => break,
2618 Token::Name(key) => {
2619 let value = PdfObject::parse_with_options(&mut lexer, &self.options)?;
2621 dict.insert(crate::parser::objects::PdfName(key), value);
2622 }
2623 _ => {
2624 return Err(ParseError::SyntaxError {
2625 position: 0,
2626 message: "Invalid dictionary format".to_string(),
2627 });
2628 }
2629 }
2630 }
2631
2632 Ok(PdfDictionary(dict))
2633 }
2634 _ => Err(ParseError::SyntaxError {
2635 position: 0,
2636 message: "Expected dictionary start".to_string(),
2637 }),
2638 }
2639 }
2640
2641 fn count_page_objects_directly(&mut self) -> Option<u32> {
2643 let mut page_count = 0;
2644
2645 for obj_num in 1..self.xref.len() as u32 {
2647 if let Ok(obj) = self.get_object(obj_num, 0) {
2648 if let Some(dict) = obj.as_dict() {
2649 if let Some(obj_type) = dict.get("Type").and_then(|t| t.as_name()) {
2650 if obj_type.0 == "Page" {
2651 page_count += 1;
2652 }
2653 }
2654 }
2655 }
2656 }
2657
2658 if page_count > 0 {
2659 Some(page_count)
2660 } else {
2661 None
2662 }
2663 }
2664
2665 pub fn metadata(&mut self) -> ParseResult<DocumentMetadata> {
2667 let mut metadata = DocumentMetadata::default();
2668
2669 if let Some(info_dict) = self.info()? {
2670 if let Some(title) = info_dict.get("Title").and_then(|o| o.as_string()) {
2671 metadata.title = Some(title.to_text());
2672 }
2673 if let Some(author) = info_dict.get("Author").and_then(|o| o.as_string()) {
2674 metadata.author = Some(author.to_text());
2675 }
2676 if let Some(subject) = info_dict.get("Subject").and_then(|o| o.as_string()) {
2677 metadata.subject = Some(subject.to_text());
2678 }
2679 if let Some(keywords) = info_dict.get("Keywords").and_then(|o| o.as_string()) {
2680 metadata.keywords = Some(keywords.to_text());
2681 }
2682 if let Some(creator) = info_dict.get("Creator").and_then(|o| o.as_string()) {
2683 metadata.creator = Some(creator.to_text());
2684 }
2685 if let Some(producer) = info_dict.get("Producer").and_then(|o| o.as_string()) {
2686 metadata.producer = Some(producer.to_text());
2687 }
2688 }
2689
2690 metadata.version = self.version().to_string();
2691 metadata.page_count = self.page_count().ok();
2692
2693 Ok(metadata)
2694 }
2695
2696 fn ensure_page_tree(&mut self) -> ParseResult<()> {
2698 if self.page_tree.is_none() {
2699 let page_count = self.page_count()?;
2700 self.page_tree = Some(super::page_tree::PageTree::new(page_count));
2701 }
2702 Ok(())
2703 }
2704
2705 pub fn get_page(&mut self, _index: u32) -> ParseResult<&super::page_tree::ParsedPage> {
2711 self.ensure_page_tree()?;
2712
2713 Err(ParseError::SyntaxError {
2717 position: 0,
2718 message: "get_page not implemented due to borrow checker constraints. Use PdfDocument instead.".to_string(),
2719 })
2720 }
2721
2722 pub fn get_all_pages(&mut self) -> ParseResult<Vec<super::page_tree::ParsedPage>> {
2724 let page_count = self.page_count()?;
2725 let mut pages = Vec::with_capacity(page_count as usize);
2726
2727 for i in 0..page_count {
2728 let page = self.get_page(i)?.clone();
2729 pages.push(page);
2730 }
2731
2732 Ok(pages)
2733 }
2734
2735 pub fn into_document(self) -> super::document::PdfDocument<R> {
2737 super::document::PdfDocument::new(self)
2738 }
2739
2740 pub fn clear_parse_context(&mut self) {
2742 self.parse_context = StackSafeContext::new();
2743 }
2744
2745 pub fn parse_context_mut(&mut self) -> &mut StackSafeContext {
2747 &mut self.parse_context
2748 }
2749
2750 fn find_page_objects(&mut self) -> ParseResult<Vec<(u32, u16)>> {
2757 let original_pos = self.reader.stream_position().unwrap_or(0);
2758 let result = scan_page_object_refs(&mut self.reader);
2759 self.reader.seek(SeekFrom::Start(original_pos)).ok();
2760 Ok(result.unwrap_or_default())
2761 }
2762
2763 fn find_catalog_object(&mut self) -> ParseResult<(u32, u16)> {
2765 let obj_numbers: Vec<u32> = self.xref.entries().keys().copied().collect();
2770
2771 for obj_num in obj_numbers {
2773 if let Ok(obj) = self.get_object(obj_num, 0) {
2775 if let Some(dict) = obj.as_dict() {
2776 if let Some(type_obj) = dict.get("Type") {
2778 if let Some(type_name) = type_obj.as_name() {
2779 if type_name.0 == "Catalog" {
2780 return Ok((obj_num, 0));
2781 }
2782 if type_name.0 == "Sig"
2784 || type_name.0 == "Pages"
2785 || type_name.0 == "Page"
2786 {
2787 continue;
2788 }
2789 }
2790 }
2791 }
2792 }
2793 }
2794
2795 for obj_num in [1, 2, 3, 4, 5] {
2797 if let Ok(obj) = self.get_object(obj_num, 0) {
2798 if let Some(dict) = obj.as_dict() {
2799 if dict.contains_key("Pages") {
2801 return Ok((obj_num, 0));
2802 }
2803 }
2804 }
2805 }
2806
2807 Err(ParseError::MissingKey(
2808 "Could not find Catalog object".to_string(),
2809 ))
2810 }
2811
2812 fn create_synthetic_pages_dict(
2814 &mut self,
2815 page_refs: &[(u32, u16)],
2816 ) -> ParseResult<&PdfDictionary> {
2817 use super::objects::{PdfArray, PdfName};
2818
2819 let mut valid_page_refs = Vec::new();
2821 for (obj_num, gen_num) in page_refs {
2822 if let Ok(page_obj) = self.get_object(*obj_num, *gen_num) {
2823 if let Some(page_dict) = page_obj.as_dict() {
2824 if let Some(obj_type) = page_dict.get("Type").and_then(|t| t.as_name()) {
2826 if obj_type.0 == "Page" {
2827 valid_page_refs.push((*obj_num, *gen_num));
2828 continue;
2829 }
2830 }
2831
2832 if page_dict.contains_key("MediaBox") || page_dict.contains_key("Contents") {
2834 valid_page_refs.push((*obj_num, *gen_num));
2835 }
2836 }
2837 }
2838 }
2839
2840 if valid_page_refs.is_empty() {
2841 return Err(ParseError::SyntaxError {
2842 position: 0,
2843 message: "No valid page objects found for synthetic Pages tree".to_string(),
2844 });
2845 }
2846
2847 if valid_page_refs.len() > 10 {
2849 return self.create_hierarchical_pages_tree(&valid_page_refs);
2850 }
2851
2852 let mut kids = PdfArray::new();
2854 for (obj_num, gen_num) in &valid_page_refs {
2855 kids.push(PdfObject::Reference(*obj_num, *gen_num));
2856 }
2857
2858 let mut pages_dict = PdfDictionary::new();
2860 pages_dict.insert(
2861 "Type".to_string(),
2862 PdfObject::Name(PdfName("Pages".to_string())),
2863 );
2864 pages_dict.insert("Kids".to_string(), PdfObject::Array(kids));
2865 pages_dict.insert(
2866 "Count".to_string(),
2867 PdfObject::Integer(valid_page_refs.len() as i64),
2868 );
2869
2870 let mut media_box = None;
2872 for (obj_num, gen_num) in valid_page_refs.iter().take(3) {
2873 if let Ok(page_obj) = self.get_object(*obj_num, *gen_num) {
2874 if let Some(page_dict) = page_obj.as_dict() {
2875 if let Some(mb) = page_dict.get("MediaBox") {
2876 media_box = Some(mb.clone());
2877 }
2878 }
2879 }
2880 }
2881
2882 if let Some(mb) = media_box {
2884 pages_dict.insert("MediaBox".to_string(), mb);
2885 } else {
2886 let mut mb_array = PdfArray::new();
2887 mb_array.push(PdfObject::Integer(0));
2888 mb_array.push(PdfObject::Integer(0));
2889 mb_array.push(PdfObject::Integer(612));
2890 mb_array.push(PdfObject::Integer(792));
2891 pages_dict.insert("MediaBox".to_string(), PdfObject::Array(mb_array));
2892 }
2893
2894 let synthetic_key = (u32::MAX - 1, 0);
2896 self.object_cache
2897 .insert(synthetic_key, PdfObject::Dictionary(pages_dict));
2898
2899 if let PdfObject::Dictionary(dict) = &self.object_cache[&synthetic_key] {
2901 Ok(dict)
2902 } else {
2903 unreachable!("Just inserted dictionary")
2904 }
2905 }
2906
2907 fn create_hierarchical_pages_tree(
2909 &mut self,
2910 page_refs: &[(u32, u16)],
2911 ) -> ParseResult<&PdfDictionary> {
2912 use super::objects::{PdfArray, PdfName};
2913
2914 const PAGES_PER_NODE: usize = 10; let chunks: Vec<&[(u32, u16)]> = page_refs.chunks(PAGES_PER_NODE).collect();
2918 let mut intermediate_nodes = Vec::new();
2919
2920 for (chunk_idx, chunk) in chunks.iter().enumerate() {
2922 let mut kids = PdfArray::new();
2923 for (obj_num, gen_num) in chunk.iter() {
2924 kids.push(PdfObject::Reference(*obj_num, *gen_num));
2925 }
2926
2927 let mut intermediate_dict = PdfDictionary::new();
2928 intermediate_dict.insert(
2929 "Type".to_string(),
2930 PdfObject::Name(PdfName("Pages".to_string())),
2931 );
2932 intermediate_dict.insert("Kids".to_string(), PdfObject::Array(kids));
2933 intermediate_dict.insert("Count".to_string(), PdfObject::Integer(chunk.len() as i64));
2934
2935 let intermediate_key = (u32::MAX - 2 - chunk_idx as u32, 0);
2937 self.object_cache
2938 .insert(intermediate_key, PdfObject::Dictionary(intermediate_dict));
2939
2940 intermediate_nodes.push(intermediate_key);
2941 }
2942
2943 let mut root_kids = PdfArray::new();
2945 for (obj_num, gen_num) in &intermediate_nodes {
2946 root_kids.push(PdfObject::Reference(*obj_num, *gen_num));
2947 }
2948
2949 let mut root_pages_dict = PdfDictionary::new();
2950 root_pages_dict.insert(
2951 "Type".to_string(),
2952 PdfObject::Name(PdfName("Pages".to_string())),
2953 );
2954 root_pages_dict.insert("Kids".to_string(), PdfObject::Array(root_kids));
2955 root_pages_dict.insert(
2956 "Count".to_string(),
2957 PdfObject::Integer(page_refs.len() as i64),
2958 );
2959
2960 if let Some((obj_num, gen_num)) = page_refs.first() {
2962 if let Ok(page_obj) = self.get_object(*obj_num, *gen_num) {
2963 if let Some(page_dict) = page_obj.as_dict() {
2964 if let Some(mb) = page_dict.get("MediaBox") {
2965 root_pages_dict.insert("MediaBox".to_string(), mb.clone());
2966 }
2967 }
2968 }
2969 }
2970
2971 let root_key = (u32::MAX - 1, 0);
2973 self.object_cache
2974 .insert(root_key, PdfObject::Dictionary(root_pages_dict));
2975
2976 if let PdfObject::Dictionary(dict) = &self.object_cache[&root_key] {
2978 Ok(dict)
2979 } else {
2980 unreachable!("Just inserted dictionary")
2981 }
2982 }
2983
2984 pub fn signatures(&mut self) -> ParseResult<Vec<crate::signatures::SignatureField>> {
3014 crate::signatures::detect_signature_fields(self).map_err(|e| ParseError::SyntaxError {
3015 position: 0,
3016 message: format!("Failed to detect signatures: {}", e),
3017 })
3018 }
3019
3020 pub fn verify_signatures(
3055 &mut self,
3056 ) -> ParseResult<Vec<crate::signatures::FullSignatureValidationResult>> {
3057 self.verify_signatures_with_trust_store(crate::signatures::TrustStore::default())
3058 }
3059
3060 pub fn verify_signatures_with_trust_store(
3092 &mut self,
3093 trust_store: crate::signatures::TrustStore,
3094 ) -> ParseResult<Vec<crate::signatures::FullSignatureValidationResult>> {
3095 use crate::signatures::{
3096 has_incremental_update, parse_pkcs7_signature, validate_certificate, verify_signature,
3097 FullSignatureValidationResult,
3098 };
3099
3100 let original_pos = self.reader.stream_position().unwrap_or(0);
3102 self.reader.seek(SeekFrom::Start(0))?;
3103
3104 let mut pdf_bytes = Vec::new();
3105 self.reader.read_to_end(&mut pdf_bytes)?;
3106
3107 self.reader.seek(SeekFrom::Start(original_pos)).ok();
3109
3110 let signature_fields = self.signatures()?;
3112
3113 let mut results = Vec::new();
3114
3115 for field in signature_fields {
3116 let mut result = FullSignatureValidationResult {
3117 field: field.clone(),
3118 signer_name: None,
3119 signing_time: None,
3120 hash_valid: false,
3121 signature_valid: false,
3122 certificate_result: None,
3123 has_modifications_after_signing: false,
3124 errors: Vec::new(),
3125 warnings: Vec::new(),
3126 };
3127
3128 result.has_modifications_after_signing =
3130 has_incremental_update(&pdf_bytes, &field.byte_range);
3131
3132 let parsed_sig = match parse_pkcs7_signature(&field.contents) {
3134 Ok(sig) => sig,
3135 Err(e) => {
3136 result
3137 .errors
3138 .push(format!("Failed to parse signature: {}", e));
3139 results.push(result);
3140 continue;
3141 }
3142 };
3143
3144 result.signing_time = parsed_sig.signing_time.clone();
3146 result.signer_name = parsed_sig.signer_common_name().ok();
3147
3148 match verify_signature(&pdf_bytes, &parsed_sig, &field.byte_range) {
3150 Ok(verification) => {
3151 result.hash_valid = verification.hash_valid;
3152 result.signature_valid = verification.signature_valid;
3153 if let Some(details) = verification.details {
3154 result.warnings.push(details);
3155 }
3156 }
3157 Err(e) => {
3158 result
3159 .errors
3160 .push(format!("Signature verification failed: {}", e));
3161 }
3162 }
3163
3164 match validate_certificate(&parsed_sig.signer_certificate_der, &trust_store) {
3166 Ok(cert_result) => {
3167 result.certificate_result = Some(cert_result);
3168 }
3169 Err(e) => {
3170 result
3171 .warnings
3172 .push(format!("Certificate validation failed: {}", e));
3173 }
3174 }
3175
3176 results.push(result);
3177 }
3178
3179 Ok(results)
3180 }
3181}
3182
3183#[derive(Debug, Default, Clone)]
3185pub struct DocumentMetadata {
3186 pub title: Option<String>,
3187 pub author: Option<String>,
3188 pub subject: Option<String>,
3189 pub keywords: Option<String>,
3190 pub creator: Option<String>,
3191 pub producer: Option<String>,
3192 pub creation_date: Option<String>,
3193 pub modification_date: Option<String>,
3194 pub version: String,
3195 pub page_count: Option<u32>,
3196}
3197
3198pub struct EOLIter<'s> {
3199 remainder: &'s str,
3200}
3201impl<'s> Iterator for EOLIter<'s> {
3202 type Item = &'s str;
3203
3204 fn next(&mut self) -> Option<Self::Item> {
3205 if self.remainder.is_empty() {
3206 return None;
3207 }
3208
3209 if let Some((i, sep)) = ["\r\n", "\n", "\r"]
3210 .iter()
3211 .filter_map(|&sep| self.remainder.find(sep).map(|i| (i, sep)))
3212 .min_by_key(|(i, _)| *i)
3213 {
3214 let (line, rest) = self.remainder.split_at(i);
3215 self.remainder = &rest[sep.len()..];
3216 Some(line)
3217 } else {
3218 let line = self.remainder;
3219 self.remainder = "";
3220 Some(line)
3221 }
3222 }
3223}
3224pub trait PDFLines: AsRef<str> {
3225 fn pdf_lines(&self) -> EOLIter<'_> {
3226 EOLIter {
3227 remainder: self.as_ref(),
3228 }
3229 }
3230}
3231impl PDFLines for &str {}
3232impl<'a> PDFLines for std::borrow::Cow<'a, str> {}
3233impl PDFLines for String {}
3234
3235#[cfg(test)]
3236mod tests {
3237
3238 use super::*;
3239 use crate::parser::objects::{PdfName, PdfString};
3240 use crate::parser::test_helpers::*;
3241 use crate::parser::ParseOptions;
3242 use std::io::Cursor;
3243
3244 #[test]
3245 fn test_reader_construction() {
3246 let pdf_data = create_minimal_pdf();
3247 let cursor = Cursor::new(pdf_data);
3248 let result = PdfReader::new(cursor);
3249 assert!(result.is_ok());
3250 }
3251
3252 #[test]
3260 fn slice_between_extracts_delimited_content() {
3261 assert_eq!(
3262 slice_between("/MediaBox [0 0 612 792] /X", b'[', b']'),
3263 Some("0 0 612 792")
3264 );
3265 assert_eq!(slice_between("[]", b'[', b']'), Some(""));
3266 }
3267
3268 #[test]
3269 fn slice_between_searches_the_closer_after_the_opener() {
3270 assert_eq!(slice_between("/MediaBox ][", b'[', b']'), None);
3275 assert_eq!(slice_between("] a [ b ] c", b'[', b']'), Some(" b "));
3276 }
3277
3278 #[test]
3279 fn slice_between_returns_none_when_a_delimiter_is_missing() {
3280 assert_eq!(slice_between("/MediaBox 0 0 612 792", b'[', b']'), None);
3281 assert_eq!(slice_between("/MediaBox [0 0 612 792", b'[', b']'), None);
3282 assert_eq!(slice_between("", b'[', b']'), None);
3283 }
3284
3285 #[test]
3286 fn slice_between_never_splits_a_multibyte_char() {
3287 assert_eq!(
3290 slice_between("[\u{FFFD}\u{20AC}]", b'[', b']'),
3291 Some("\u{FFFD}\u{20AC}")
3292 );
3293 assert_eq!(slice_between("\u{20AC}[x]\u{20AC}", b'[', b']'), Some("x"));
3296 }
3297
3298 #[test]
3299 fn find_dict_end_locates_the_closing_marker() {
3300 assert_eq!(find_dict_end(" /A 1 >>"), Some(6));
3303 assert_eq!(find_dict_end(">>"), Some(0));
3304 }
3305
3306 #[test]
3307 fn find_dict_end_matches_nesting() {
3308 assert_eq!(find_dict_end(" /A << /B 1 >> >> tail"), Some(15));
3309 }
3310
3311 #[test]
3312 fn find_dict_end_returns_none_when_unbalanced() {
3313 assert_eq!(find_dict_end(" /A 1"), None);
3314 assert_eq!(find_dict_end(" /A << /B 1 >>"), None);
3315 assert_eq!(find_dict_end(""), None);
3316 assert_eq!(find_dict_end(">"), None);
3317 }
3318
3319 #[test]
3320 fn find_dict_end_offset_is_a_char_boundary_with_multibyte_content() {
3321 let after = " /Font \u{FFFD} >> tail";
3324 let end = find_dict_end(after).expect("closes");
3325 assert_eq!(&after[..end], " /Font \u{FFFD} ");
3326 }
3327
3328 #[test]
3329 fn truncate_on_char_boundary_cuts_without_splitting() {
3330 assert_eq!(truncate_on_char_boundary("abc", 10), "abc");
3331 assert_eq!(truncate_on_char_boundary("abcdef", 3), "abc");
3332 assert_eq!(truncate_on_char_boundary("\u{FFFD}x", 1), "");
3334 assert_eq!(truncate_on_char_boundary("\u{FFFD}x", 3), "\u{FFFD}");
3335 assert_eq!(truncate_on_char_boundary("", 5), "");
3336 }
3337
3338 struct CountingReader<R> {
3345 inner: R,
3346 total_read: std::sync::Arc<std::sync::atomic::AtomicUsize>,
3347 max_read: std::sync::Arc<std::sync::atomic::AtomicUsize>,
3348 }
3349 impl<R: Read> Read for CountingReader<R> {
3350 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
3351 self.max_read
3352 .fetch_max(buf.len(), std::sync::atomic::Ordering::SeqCst);
3353 let n = self.inner.read(buf)?;
3354 self.total_read
3355 .fetch_add(n, std::sync::atomic::Ordering::SeqCst);
3356 Ok(n)
3357 }
3358 }
3359 impl<R: Seek> Seek for CountingReader<R> {
3360 fn seek(&mut self, p: SeekFrom) -> std::io::Result<u64> {
3361 self.inner.seek(p)
3362 }
3363 }
3364
3365 #[test]
3366 fn test_extract_stream_manually_bounded_honors_length() {
3367 use crate::parser::objects::PdfObject;
3368 use std::sync::atomic::{AtomicUsize, Ordering};
3369 use std::sync::Arc;
3370
3371 const L: usize = 300 * 1024; const FILLER: usize = 4 * 1024 * 1024; let payload: Vec<u8> = (0..L).map(|i| (i % 251) as u8).collect();
3383 let filler: Vec<u8> = (0..FILLER).map(|i| ((i + 7) % 251) as u8).collect();
3384
3385 let header = b"%PDF-1.4\n";
3386 let obj1 = b"1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n";
3387 let obj2 = b"2 0 obj\n<< /Type /Pages /Kids [] /Count 0 >>\nendobj\n";
3388
3389 let mut data = Vec::new();
3390 data.extend_from_slice(header);
3391 let obj1_start = data.len();
3392 data.extend_from_slice(obj1);
3393 let obj2_start = data.len();
3394 data.extend_from_slice(obj2);
3395
3396 data.extend_from_slice(
3399 b"4 0 obj\n<< /Type /Metadata /Subtype /XML /Length 307200 >>\nstream\n",
3400 );
3401 data.extend_from_slice(&payload);
3402 data.extend_from_slice(b"\nendstream\nendobj\n");
3403
3404 data.extend_from_slice(b"5 0 obj\n<< /Length 4194304 >>\nstream\n");
3406 data.extend_from_slice(&filler);
3407 data.extend_from_slice(b"\nendstream\nendobj\n");
3408
3409 let xref_start = data.len();
3411 let xref = format!(
3412 "xref\n0 3\n0000000000 65535 f \n{obj1_start:010} 00000 n \n{obj2_start:010} 00000 n \ntrailer\n<< /Size 3 /Root 1 0 R >>\nstartxref\n{xref_start}\n%%EOF"
3413 );
3414 data.extend_from_slice(xref.as_bytes());
3415 let file_len = data.len();
3416
3417 let counter = Arc::new(AtomicUsize::new(0));
3418 let reader = CountingReader {
3419 inner: Cursor::new(data),
3420 total_read: counter.clone(),
3421 max_read: Arc::new(AtomicUsize::new(0)),
3422 };
3423 let mut pdf = PdfReader::new_with_options(reader, ParseOptions::tolerant())
3424 .expect("minimal PDF must parse");
3425
3426 counter.store(0, Ordering::SeqCst);
3428 let obj = pdf
3429 .extract_object_or_stream_manually(4)
3430 .expect("stream object 4 must be extracted");
3431
3432 let stream = match &obj {
3434 PdfObject::Stream(s) => s,
3435 other => panic!("expected a stream, got {other:?}"),
3436 };
3437 assert_eq!(
3438 stream.data.len(),
3439 L,
3440 "stream body must equal /Length (no truncation)"
3441 );
3442 assert_eq!(stream.data, payload, "stream body content must be intact");
3443
3444 let total_read = counter.load(Ordering::SeqCst);
3448 assert!(
3449 total_read <= L + 2 * MANUAL_DICT_WINDOW,
3450 "manual stream extraction read {total_read} bytes total (file={file_len}); not bounded by object size"
3451 );
3452 }
3453
3454 #[cfg(test)]
3458 fn build_manual_stream_pdf(obj4_dict_body: &str, payload: &[u8]) -> Vec<u8> {
3459 let header = b"%PDF-1.4\n";
3460 let obj1 = b"1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n";
3461 let obj2 = b"2 0 obj\n<< /Type /Pages /Kids [] /Count 0 >>\nendobj\n";
3462
3463 let mut data = Vec::new();
3464 data.extend_from_slice(header);
3465 let obj1_start = data.len();
3466 data.extend_from_slice(obj1);
3467 let obj2_start = data.len();
3468 data.extend_from_slice(obj2);
3469
3470 data.extend_from_slice(format!("4 0 obj\n<<{obj4_dict_body}>>\nstream\n").as_bytes());
3472 data.extend_from_slice(payload);
3473 data.extend_from_slice(b"\nendstream\nendobj\n");
3474
3475 let xref_start = data.len();
3477 let xref = format!(
3478 "xref\n0 3\n0000000000 65535 f \n{obj1_start:010} 00000 n \n{obj2_start:010} 00000 n \ntrailer\n<< /Size 3 /Root 1 0 R >>\nstartxref\n{xref_start}\n%%EOF"
3479 );
3480 data.extend_from_slice(xref.as_bytes());
3481 data
3482 }
3483
3484 #[test]
3485 fn test_reconstruct_preserves_non_flate_filter() {
3486 use crate::parser::objects::PdfObject;
3487
3488 let payload = b"\xff\xd8\xff\xe0not-a-real-jpeg\xff\xd9";
3494 let len = payload.len();
3495 let dict_body =
3496 format!(" /Type /XObject /Subtype /Image /Filter /DCTDecode /Length {len} ");
3497 let data = build_manual_stream_pdf(&dict_body, payload);
3498
3499 let mut pdf = PdfReader::new_with_options(Cursor::new(data), ParseOptions::tolerant())
3500 .expect("minimal PDF must parse");
3501 let obj = pdf
3502 .extract_object_or_stream_manually(4)
3503 .expect("stream object 4 must be extracted");
3504 let stream = match &obj {
3505 PdfObject::Stream(s) => s,
3506 other => panic!("expected a stream, got {other:?}"),
3507 };
3508
3509 assert_eq!(
3510 stream
3511 .dict
3512 .get("Filter")
3513 .and_then(|o| o.as_name())
3514 .map(|n| n.0.as_str()),
3515 Some("DCTDecode"),
3516 "non-Flate /Filter must be preserved in reconstructed stream dict"
3517 );
3518 assert_eq!(
3519 stream
3520 .dict
3521 .get("Subtype")
3522 .and_then(|o| o.as_name())
3523 .map(|n| n.0.as_str()),
3524 Some("Image"),
3525 "/Subtype must be preserved, not dropped"
3526 );
3527 assert_eq!(stream.data, payload, "stream body must be intact");
3528 }
3529
3530 #[test]
3531 fn test_reconstruct_preserves_filter_array_and_decodeparms() {
3532 use crate::parser::objects::PdfObject;
3533
3534 let payload = b"compressed-bytes-placeholder";
3539 let len = payload.len();
3540 let dict_body = format!(
3541 " /Filter[/LZWDecode /FlateDecode] /DecodeParms[null<< /Predictor 12 /Columns 4 >>] /Length {len} "
3542 );
3543 let data = build_manual_stream_pdf(&dict_body, payload);
3544
3545 let mut pdf = PdfReader::new_with_options(Cursor::new(data), ParseOptions::tolerant())
3546 .expect("minimal PDF must parse");
3547 let obj = pdf
3548 .extract_object_or_stream_manually(4)
3549 .expect("stream object 4 must be extracted");
3550 let stream = match &obj {
3551 PdfObject::Stream(s) => s,
3552 other => panic!("expected a stream, got {other:?}"),
3553 };
3554
3555 let filter = stream
3556 .dict
3557 .get("Filter")
3558 .and_then(|o| o.as_array())
3559 .expect("/Filter array must be preserved");
3560 let names: Vec<&str> = filter
3561 .0
3562 .iter()
3563 .filter_map(|o| o.as_name())
3564 .map(|n| n.0.as_str())
3565 .collect();
3566 assert_eq!(
3567 names,
3568 vec!["LZWDecode", "FlateDecode"],
3569 "filter array entries must be preserved in order"
3570 );
3571 assert!(
3572 stream.dict.get("DecodeParms").is_some(),
3573 "/DecodeParms must be preserved alongside the filter chain"
3574 );
3575 assert_eq!(stream.data, payload, "stream body must be intact");
3576 }
3577
3578 #[test]
3579 fn test_reconstruct_malformed_dict_falls_back_without_panic() {
3580 use crate::parser::objects::PdfObject;
3581
3582 let payload = b"0123456789";
3588 let len = payload.len();
3589 let dict_body = format!(" /Length {len} /BadKey ");
3591 let data = build_manual_stream_pdf(&dict_body, payload);
3592
3593 let mut pdf = PdfReader::new_with_options(Cursor::new(data), ParseOptions::tolerant())
3594 .expect("minimal PDF must parse");
3595 let obj = pdf
3596 .extract_object_or_stream_manually(4)
3597 .expect("malformed-dict stream must still be reconstructed via fallback");
3598 let stream = match &obj {
3599 PdfObject::Stream(s) => s,
3600 other => panic!("expected a stream, got {other:?}"),
3601 };
3602
3603 assert_eq!(
3604 stream.data, payload,
3605 "fallback path must still read the bounded body intact"
3606 );
3607 assert!(
3609 stream.dict.get("Filter").is_none(),
3610 "fallback dict must not invent a /Filter"
3611 );
3612 }
3613
3614 #[test]
3615 fn test_find_page_objects_bounded() {
3616 use std::sync::atomic::{AtomicUsize, Ordering};
3617 use std::sync::Arc;
3618
3619 const FILLER: usize = 2 * 1024 * 1024;
3625 let filler: Vec<u8> = (0..FILLER).map(|i| (i % 251) as u8).collect();
3626
3627 let header = b"%PDF-1.4\n";
3628 let obj1 = b"1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n";
3629 let obj2 = b"2 0 obj\n<< /Type /Pages /Kids [4 0 R 5 0 R 6 0 R] /Count 3 >>\nendobj\n";
3630
3631 let mut data = Vec::new();
3632 data.extend_from_slice(header);
3633 let obj1_start = data.len();
3634 data.extend_from_slice(obj1);
3635 let obj2_start = data.len();
3636 data.extend_from_slice(obj2);
3637
3638 for n in [4u32, 5, 6] {
3640 data.extend_from_slice(
3641 format!(
3642 "{n} 0 obj\n<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] >>\nendobj\n"
3643 )
3644 .as_bytes(),
3645 );
3646 }
3647
3648 data.extend_from_slice(b"7 0 obj\n<< /Length 2097152 >>\nstream\n");
3650 data.extend_from_slice(&filler);
3651 data.extend_from_slice(b"\nendstream\nendobj\n");
3652
3653 let xref_start = data.len();
3654 let xref = format!(
3655 "xref\n0 3\n0000000000 65535 f \n{obj1_start:010} 00000 n \n{obj2_start:010} 00000 n \ntrailer\n<< /Size 3 /Root 1 0 R >>\nstartxref\n{xref_start}\n%%EOF"
3656 );
3657 data.extend_from_slice(xref.as_bytes());
3658 let file_len = data.len();
3659
3660 let max_read = Arc::new(AtomicUsize::new(0));
3661 let reader = CountingReader {
3662 inner: Cursor::new(data),
3663 total_read: Arc::new(AtomicUsize::new(0)),
3664 max_read: max_read.clone(),
3665 };
3666 let mut pdf = PdfReader::new_with_options(reader, ParseOptions::tolerant())
3667 .expect("minimal PDF must parse");
3668
3669 max_read.store(0, Ordering::SeqCst);
3671 let pages = pdf.find_page_objects().expect("page scan must succeed");
3672
3673 assert_eq!(
3675 pages,
3676 vec![(4, 0), (5, 0), (6, 0)],
3677 "must find exactly the three /Type /Page objects"
3678 );
3679
3680 let peak = max_read.load(Ordering::SeqCst);
3682 assert!(
3683 peak <= 128 * 1024,
3684 "page scan requested {peak} bytes in one read (file={file_len}); not bounded"
3685 );
3686 }
3687
3688 #[test]
3689 fn test_signature_verification_reads_full_file_intentionally() {
3690 use std::sync::atomic::{AtomicUsize, Ordering};
3691 use std::sync::Arc;
3692
3693 let data = create_minimal_pdf();
3698 let file_len = data.len();
3699
3700 let total = Arc::new(AtomicUsize::new(0));
3701 let reader = CountingReader {
3702 inner: Cursor::new(data),
3703 total_read: total.clone(),
3704 max_read: Arc::new(AtomicUsize::new(0)),
3705 };
3706 let mut pdf = PdfReader::new_with_options(reader, ParseOptions::tolerant())
3707 .expect("minimal PDF must parse");
3708
3709 total.store(0, Ordering::SeqCst);
3710 let _ = pdf.verify_signatures();
3712 let read = total.load(Ordering::SeqCst);
3713 assert!(
3714 read >= file_len,
3715 "signature verification must read the whole file (read {read}, file {file_len})"
3716 );
3717 }
3718
3719 #[test]
3720 fn test_reader_version() {
3721 let pdf_data = create_minimal_pdf();
3722 let cursor = Cursor::new(pdf_data);
3723 let reader = PdfReader::new(cursor).unwrap();
3724 assert_eq!(reader.version().major, 1);
3725 assert_eq!(reader.version().minor, 4);
3726 }
3727
3728 #[test]
3729 fn test_reader_different_versions() {
3730 let versions = vec![
3731 "1.0", "1.1", "1.2", "1.3", "1.4", "1.5", "1.6", "1.7", "2.0",
3732 ];
3733
3734 for version in versions {
3735 let pdf_data = create_pdf_with_version(version);
3736 let cursor = Cursor::new(pdf_data);
3737 let reader = PdfReader::new(cursor).unwrap();
3738
3739 let parts: Vec<&str> = version.split('.').collect();
3740 assert_eq!(reader.version().major, parts[0].parse::<u8>().unwrap());
3741 assert_eq!(reader.version().minor, parts[1].parse::<u8>().unwrap());
3742 }
3743 }
3744
3745 #[test]
3746 fn test_reader_catalog() {
3747 let pdf_data = create_minimal_pdf();
3748 let cursor = Cursor::new(pdf_data);
3749 let mut reader = PdfReader::new(cursor).unwrap();
3750
3751 let catalog = reader.catalog();
3752 assert!(catalog.is_ok());
3753
3754 let catalog_dict = catalog.unwrap();
3755 assert_eq!(
3756 catalog_dict.get("Type"),
3757 Some(&PdfObject::Name(PdfName("Catalog".to_string())))
3758 );
3759 }
3760
3761 #[test]
3762 fn test_reader_info_none() {
3763 let pdf_data = create_minimal_pdf();
3764 let cursor = Cursor::new(pdf_data);
3765 let mut reader = PdfReader::new(cursor).unwrap();
3766
3767 let info = reader.info().unwrap();
3768 assert!(info.is_none());
3769 }
3770
3771 #[test]
3772 fn test_reader_info_present() {
3773 let pdf_data = create_pdf_with_info();
3774 let cursor = Cursor::new(pdf_data);
3775 let mut reader = PdfReader::new(cursor).unwrap();
3776
3777 let info = reader.info().unwrap();
3778 assert!(info.is_some());
3779
3780 let info_dict = info.unwrap();
3781 assert_eq!(
3782 info_dict.get("Title"),
3783 Some(&PdfObject::String(PdfString(
3784 "Test PDF".to_string().into_bytes()
3785 )))
3786 );
3787 assert_eq!(
3788 info_dict.get("Author"),
3789 Some(&PdfObject::String(PdfString(
3790 "Test Author".to_string().into_bytes()
3791 )))
3792 );
3793 }
3794
3795 #[test]
3796 fn test_reader_get_object() {
3797 let pdf_data = create_minimal_pdf();
3798 let cursor = Cursor::new(pdf_data);
3799 let mut reader = PdfReader::new(cursor).unwrap();
3800
3801 let obj = reader.get_object(1, 0);
3803 assert!(obj.is_ok());
3804
3805 let catalog = obj.unwrap();
3806 assert!(catalog.as_dict().is_some());
3807 }
3808
3809 #[test]
3810 fn test_reader_get_invalid_object() {
3811 let pdf_data = create_minimal_pdf();
3812 let cursor = Cursor::new(pdf_data);
3813 let mut reader = PdfReader::new(cursor).unwrap();
3814
3815 let obj = reader.get_object(999, 0);
3817 assert!(obj.is_err());
3818 }
3819
3820 #[test]
3821 fn test_reader_get_free_object() {
3822 let pdf_data = create_minimal_pdf();
3823 let cursor = Cursor::new(pdf_data);
3824 let mut reader = PdfReader::new(cursor).unwrap();
3825
3826 let obj = reader.get_object(0, 65535);
3828 assert!(obj.is_ok());
3829 assert_eq!(obj.unwrap(), &PdfObject::Null);
3830 }
3831
3832 #[test]
3833 fn test_reader_resolve_reference() {
3834 let pdf_data = create_minimal_pdf();
3835 let cursor = Cursor::new(pdf_data);
3836 let mut reader = PdfReader::new(cursor).unwrap();
3837
3838 let ref_obj = PdfObject::Reference(1, 0);
3840 let resolved = reader.resolve(&ref_obj);
3841
3842 assert!(resolved.is_ok());
3843 assert!(resolved.unwrap().as_dict().is_some());
3844 }
3845
3846 #[test]
3847 fn test_reader_resolve_non_reference() {
3848 let pdf_data = create_minimal_pdf();
3849 let cursor = Cursor::new(pdf_data);
3850 let mut reader = PdfReader::new(cursor).unwrap();
3851
3852 let int_obj = PdfObject::Integer(42);
3854 let resolved = reader.resolve(&int_obj).unwrap();
3855
3856 assert_eq!(resolved, &PdfObject::Integer(42));
3857 }
3858
3859 #[test]
3860 fn test_reader_cache_behavior() {
3861 let pdf_data = create_minimal_pdf();
3862 let cursor = Cursor::new(pdf_data);
3863 let mut reader = PdfReader::new(cursor).unwrap();
3864
3865 let obj1 = reader.get_object(1, 0).unwrap();
3867 assert!(obj1.as_dict().is_some());
3868
3869 let obj2 = reader.get_object(1, 0).unwrap();
3871 assert!(obj2.as_dict().is_some());
3872 }
3873
3874 #[test]
3875 fn test_reader_wrong_generation() {
3876 let pdf_data = create_minimal_pdf();
3877 let cursor = Cursor::new(pdf_data);
3878 let mut reader = PdfReader::new(cursor).unwrap();
3879
3880 let obj = reader.get_object(1, 99);
3882 assert!(obj.is_err());
3883 }
3884
3885 #[test]
3886 fn test_reader_invalid_pdf() {
3887 let invalid_data = b"This is not a PDF file";
3888 let cursor = Cursor::new(invalid_data.to_vec());
3889 let result = PdfReader::new(cursor);
3890
3891 assert!(result.is_err());
3892 }
3893
3894 #[test]
3895 fn test_reader_corrupt_xref() {
3896 let corrupt_pdf = b"%PDF-1.4
38971 0 obj
3898<< /Type /Catalog >>
3899endobj
3900xref
3901corrupted xref table
3902trailer
3903<< /Size 2 /Root 1 0 R >>
3904startxref
390524
3906%%EOF"
3907 .to_vec();
3908
3909 let cursor = Cursor::new(corrupt_pdf.clone());
3913 let mut reader = PdfReader::new(cursor).expect("corrupt xref is reconstructed by scan");
3914 let catalog = reader
3915 .catalog()
3916 .expect("catalog resolves after reconstruction");
3917 assert_eq!(
3918 catalog.get("Type"),
3919 Some(&PdfObject::Name(PdfName("Catalog".to_string()))),
3920 );
3921
3922 let cursor = Cursor::new(corrupt_pdf);
3924 assert!(PdfReader::new_with_options(cursor, ParseOptions::strict()).is_err());
3925 }
3926
3927 #[test]
3928 fn test_reader_missing_trailer() {
3929 let pdf_no_trailer = b"%PDF-1.4
39301 0 obj
3931<< /Type /Catalog >>
3932endobj
3933xref
39340 2
39350000000000 65535 f
39360000000009 00000 n
3937startxref
393824
3939%%EOF"
3940 .to_vec();
3941
3942 let cursor = Cursor::new(pdf_no_trailer.clone());
3946 let mut reader = PdfReader::new(cursor).expect("missing trailer recovered by object scan");
3947 let catalog = reader
3948 .catalog()
3949 .expect("catalog resolved from scanned /Root");
3950 assert_eq!(
3951 catalog.get("Type"),
3952 Some(&PdfObject::Name(PdfName("Catalog".to_string()))),
3953 );
3954
3955 let cursor = Cursor::new(pdf_no_trailer);
3957 assert!(PdfReader::new_with_options(cursor, ParseOptions::strict()).is_err());
3958 }
3959
3960 #[test]
3961 fn test_reader_empty_pdf() {
3962 let cursor = Cursor::new(Vec::new());
3963 let result = PdfReader::new(cursor);
3964 assert!(result.is_err());
3965 }
3966
3967 #[test]
3968 fn test_reader_page_count() {
3969 let pdf_data = create_minimal_pdf();
3970 let cursor = Cursor::new(pdf_data);
3971 let mut reader = PdfReader::new(cursor).unwrap();
3972
3973 let count = reader.page_count();
3974 assert!(count.is_ok());
3975 assert_eq!(count.unwrap(), 0); }
3977
3978 #[test]
3979 fn test_reader_into_document() {
3980 let pdf_data = create_minimal_pdf();
3981 let cursor = Cursor::new(pdf_data);
3982 let reader = PdfReader::new(cursor).unwrap();
3983
3984 let document = reader.into_document();
3985 let page_count = document.page_count();
3987 assert!(page_count.is_ok());
3988 }
3989
3990 #[test]
3991 fn test_reader_pages_dict() {
3992 let pdf_data = create_minimal_pdf();
3993 let cursor = Cursor::new(pdf_data);
3994 let mut reader = PdfReader::new(cursor).unwrap();
3995
3996 let pages = reader.pages();
3997 assert!(pages.is_ok());
3998 let pages_dict = pages.unwrap();
3999 assert_eq!(
4000 pages_dict.get("Type"),
4001 Some(&PdfObject::Name(PdfName("Pages".to_string())))
4002 );
4003 }
4004
4005 #[test]
4006 fn test_reader_pdf_with_binary_data() {
4007 let pdf_data = create_pdf_with_binary_marker();
4008
4009 let cursor = Cursor::new(pdf_data);
4010 let result = PdfReader::new(cursor);
4011 assert!(result.is_ok());
4012 }
4013
4014 #[test]
4015 fn test_reader_metadata() {
4016 let pdf_data = create_pdf_with_info();
4017 let cursor = Cursor::new(pdf_data);
4018 let mut reader = PdfReader::new(cursor).unwrap();
4019
4020 let metadata = reader.metadata().unwrap();
4021 assert_eq!(metadata.title, Some("Test PDF".to_string()));
4022 assert_eq!(metadata.author, Some("Test Author".to_string()));
4023 assert_eq!(metadata.subject, Some("Testing".to_string()));
4024 assert_eq!(metadata.version, "1.4".to_string());
4025 }
4026
4027 #[test]
4028 fn test_reader_metadata_empty() {
4029 let pdf_data = create_minimal_pdf();
4030 let cursor = Cursor::new(pdf_data);
4031 let mut reader = PdfReader::new(cursor).unwrap();
4032
4033 let metadata = reader.metadata().unwrap();
4034 assert!(metadata.title.is_none());
4035 assert!(metadata.author.is_none());
4036 assert_eq!(metadata.version, "1.4".to_string());
4037 assert_eq!(metadata.page_count, Some(0));
4038 }
4039
4040 #[test]
4041 fn test_reader_object_number_mismatch() {
4042 let pdf_data = create_minimal_pdf();
4046 let cursor = Cursor::new(pdf_data);
4047 let mut reader = PdfReader::new(cursor).unwrap();
4048
4049 let result = reader.get_object(1, 99);
4052 assert!(result.is_err());
4053
4054 let result2 = reader.get_object(999, 0);
4056 assert!(result2.is_err());
4057 }
4058
4059 #[test]
4060 fn test_document_metadata_struct() {
4061 let metadata = DocumentMetadata {
4062 title: Some("Title".to_string()),
4063 author: Some("Author".to_string()),
4064 subject: Some("Subject".to_string()),
4065 keywords: Some("Keywords".to_string()),
4066 creator: Some("Creator".to_string()),
4067 producer: Some("Producer".to_string()),
4068 creation_date: Some("D:20240101".to_string()),
4069 modification_date: Some("D:20240102".to_string()),
4070 version: "1.5".to_string(),
4071 page_count: Some(10),
4072 };
4073
4074 assert_eq!(metadata.title, Some("Title".to_string()));
4075 assert_eq!(metadata.page_count, Some(10));
4076 }
4077
4078 #[test]
4079 fn test_document_metadata_default() {
4080 let metadata = DocumentMetadata::default();
4081 assert!(metadata.title.is_none());
4082 assert!(metadata.author.is_none());
4083 assert!(metadata.subject.is_none());
4084 assert!(metadata.keywords.is_none());
4085 assert!(metadata.creator.is_none());
4086 assert!(metadata.producer.is_none());
4087 assert!(metadata.creation_date.is_none());
4088 assert!(metadata.modification_date.is_none());
4089 assert_eq!(metadata.version, "".to_string());
4090 assert!(metadata.page_count.is_none());
4091 }
4092
4093 #[test]
4094 fn test_document_metadata_clone() {
4095 let metadata = DocumentMetadata {
4096 title: Some("Test".to_string()),
4097 version: "1.4".to_string(),
4098 ..Default::default()
4099 };
4100
4101 let cloned = metadata;
4102 assert_eq!(cloned.title, Some("Test".to_string()));
4103 assert_eq!(cloned.version, "1.4".to_string());
4104 }
4105
4106 #[test]
4107 fn test_reader_trailer_validation_error() {
4108 let bad_pdf = b"%PDF-1.4
41101 0 obj
4111<< /Type /Catalog >>
4112endobj
4113xref
41140 2
41150000000000 65535 f
41160000000009 00000 n
4117trailer
4118<< /Size 2 >>
4119startxref
412046
4121%%EOF"
4122 .to_vec();
4123
4124 let cursor = Cursor::new(bad_pdf.clone());
4128 let mut reader = PdfReader::new(cursor).expect("missing /Root recovered by object scan");
4129 let catalog = reader
4130 .catalog()
4131 .expect("catalog resolved from scanned /Root");
4132 assert_eq!(
4133 catalog.get("Type"),
4134 Some(&PdfObject::Name(PdfName("Catalog".to_string()))),
4135 );
4136
4137 let cursor = Cursor::new(bad_pdf);
4139 assert!(PdfReader::new_with_options(cursor, ParseOptions::strict()).is_err());
4140 }
4141
4142 #[test]
4143 fn test_reader_with_options() {
4144 let pdf_data = create_minimal_pdf();
4145 let cursor = Cursor::new(pdf_data);
4146 let mut options = ParseOptions::default();
4147 options.lenient_streams = true;
4148 options.max_recovery_bytes = 2000;
4149 options.collect_warnings = true;
4150
4151 let reader = PdfReader::new_with_options(cursor, options);
4152 assert!(reader.is_ok());
4153 }
4154
4155 #[test]
4156 fn test_lenient_stream_parsing() {
4157 let pdf_data = b"%PDF-1.4
41591 0 obj
4160<< /Type /Catalog /Pages 2 0 R >>
4161endobj
41622 0 obj
4163<< /Type /Pages /Kids [3 0 R] /Count 1 >>
4164endobj
41653 0 obj
4166<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] /Contents 4 0 R >>
4167endobj
41684 0 obj
4169<< /Length 10 >>
4170stream
4171This is a longer stream than 10 bytes
4172endstream
4173endobj
4174xref
41750 5
41760000000000 65535 f
41770000000009 00000 n
41780000000058 00000 n
41790000000116 00000 n
41800000000219 00000 n
4181trailer
4182<< /Size 5 /Root 1 0 R >>
4183startxref
4184299
4185%%EOF"
4186 .to_vec();
4187
4188 let cursor = Cursor::new(pdf_data.clone());
4190 let strict_options = ParseOptions::strict();
4191 let strict_reader = PdfReader::new_with_options(cursor, strict_options);
4192 assert!(strict_reader.is_err());
4194
4195 let cursor = Cursor::new(pdf_data);
4199 let mut options = ParseOptions::default();
4200 options.lenient_streams = true;
4201 options.max_recovery_bytes = 1000;
4202 options.collect_warnings = false;
4203 let mut reader =
4204 PdfReader::new_with_options(cursor, options).expect("mismatched xref reconstructed");
4205 let catalog = reader
4206 .catalog()
4207 .expect("catalog resolves after reconstruction");
4208 assert_eq!(
4209 catalog.get("Type"),
4210 Some(&PdfObject::Name(PdfName("Catalog".to_string()))),
4211 );
4212 }
4213
4214 #[test]
4215 fn test_parse_options_default() {
4216 let options = ParseOptions::default();
4217 assert!(!options.lenient_streams);
4218 assert_eq!(options.max_recovery_bytes, 1000);
4219 assert!(!options.collect_warnings);
4220 }
4221
4222 #[test]
4223 fn test_parse_options_clone() {
4224 let mut options = ParseOptions::default();
4225 options.lenient_streams = true;
4226 options.max_recovery_bytes = 2000;
4227 options.collect_warnings = true;
4228 let cloned = options;
4229 assert!(cloned.lenient_streams);
4230 assert_eq!(cloned.max_recovery_bytes, 2000);
4231 assert!(cloned.collect_warnings);
4232 }
4233
4234 #[allow(dead_code)]
4237 fn create_encrypted_pdf_dict() -> PdfDictionary {
4238 let mut dict = PdfDictionary::new();
4239 dict.insert(
4240 "Filter".to_string(),
4241 PdfObject::Name(PdfName("Standard".to_string())),
4242 );
4243 dict.insert("V".to_string(), PdfObject::Integer(1));
4244 dict.insert("R".to_string(), PdfObject::Integer(2));
4245 dict.insert("O".to_string(), PdfObject::String(PdfString(vec![0u8; 32])));
4246 dict.insert("U".to_string(), PdfObject::String(PdfString(vec![0u8; 32])));
4247 dict.insert("P".to_string(), PdfObject::Integer(-4));
4248 dict
4249 }
4250
4251 fn create_pdf_with_encryption() -> Vec<u8> {
4252 b"%PDF-1.4
42541 0 obj
4255<< /Type /Catalog /Pages 2 0 R >>
4256endobj
42572 0 obj
4258<< /Type /Pages /Kids [3 0 R] /Count 1 >>
4259endobj
42603 0 obj
4261<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] >>
4262endobj
42634 0 obj
4264<< /Filter /Standard /V 1 /R 2 /O (32 bytes of owner password hash data) /U (32 bytes of user password hash data) /P -4 >>
4265endobj
4266xref
42670 5
42680000000000 65535 f
42690000000009 00000 n
42700000000058 00000 n
42710000000116 00000 n
42720000000201 00000 n
4273trailer
4274<< /Size 5 /Root 1 0 R /Encrypt 4 0 R /ID [(file id)] >>
4275startxref
4276295
4277%%EOF"
4278 .to_vec()
4279 }
4280
4281 #[test]
4282 fn test_reader_encryption_detection() {
4283 let unencrypted_pdf = create_minimal_pdf();
4285 let cursor = Cursor::new(unencrypted_pdf);
4286 let reader = PdfReader::new(cursor).unwrap();
4287 assert!(!reader.is_encrypted());
4288 assert!(reader.is_unlocked()); let encrypted_pdf = create_pdf_with_encryption();
4296 let cursor = Cursor::new(encrypted_pdf);
4297 let reader = PdfReader::new(cursor).expect("encrypted PDF opens as locked, not rejected");
4298 assert!(
4299 reader.is_encrypted(),
4300 "must report encrypted, not plaintext"
4301 );
4302 assert!(
4303 !reader.is_unlocked(),
4304 "must stay locked without a valid password"
4305 );
4306 }
4307
4308 #[test]
4309 fn test_reader_encryption_methods_unencrypted() {
4310 let pdf_data = create_minimal_pdf();
4311 let cursor = Cursor::new(pdf_data);
4312 let mut reader = PdfReader::new(cursor).unwrap();
4313
4314 assert!(!reader.is_encrypted());
4316 assert!(reader.is_unlocked());
4317 assert!(reader.encryption_handler().is_none());
4318 assert!(reader.encryption_handler_mut().is_none());
4319
4320 assert!(reader.unlock_with_password("any_password").unwrap());
4322 assert!(reader.try_empty_password().unwrap());
4323 }
4324
4325 #[test]
4326 fn test_reader_encryption_handler_access() {
4327 let pdf_data = create_minimal_pdf();
4328 let cursor = Cursor::new(pdf_data);
4329 let mut reader = PdfReader::new(cursor).unwrap();
4330
4331 assert!(reader.encryption_handler().is_none());
4333 assert!(reader.encryption_handler_mut().is_none());
4334
4335 assert!(!reader.is_encrypted());
4337 assert!(reader.is_unlocked());
4338 }
4339
4340 #[test]
4341 fn test_reader_multiple_password_attempts() {
4342 let pdf_data = create_minimal_pdf();
4343 let cursor = Cursor::new(pdf_data);
4344 let mut reader = PdfReader::new(cursor).unwrap();
4345
4346 let passwords = vec!["test1", "test2", "admin", "", "password"];
4348 for password in passwords {
4349 assert!(reader.unlock_with_password(password).unwrap());
4350 }
4351
4352 for _ in 0..5 {
4354 assert!(reader.try_empty_password().unwrap());
4355 }
4356 }
4357
4358 #[test]
4359 fn test_reader_encryption_state_consistency() {
4360 let pdf_data = create_minimal_pdf();
4361 let cursor = Cursor::new(pdf_data);
4362 let mut reader = PdfReader::new(cursor).unwrap();
4363
4364 assert!(!reader.is_encrypted());
4366 assert!(reader.is_unlocked());
4367 assert!(reader.encryption_handler().is_none());
4368
4369 let _ = reader.unlock_with_password("test");
4371 assert!(!reader.is_encrypted());
4372 assert!(reader.is_unlocked());
4373 assert!(reader.encryption_handler().is_none());
4374
4375 let _ = reader.try_empty_password();
4376 assert!(!reader.is_encrypted());
4377 assert!(reader.is_unlocked());
4378 assert!(reader.encryption_handler().is_none());
4379 }
4380
4381 #[test]
4382 fn test_reader_encryption_error_handling() {
4383 let encrypted_pdf = create_pdf_with_encryption();
4388 let cursor = Cursor::new(encrypted_pdf);
4389
4390 match PdfReader::new(cursor) {
4391 Err(_) => {}
4393 Ok(reader) => {
4396 assert!(
4397 reader.is_encrypted() && !reader.is_unlocked(),
4398 "recovered encrypted PDF must stay encrypted+locked, not open as plaintext"
4399 );
4400 }
4401 }
4402 }
4403
4404 #[test]
4405 fn test_reader_encryption_with_options() {
4406 let pdf_data = create_minimal_pdf();
4407 let cursor = Cursor::new(pdf_data);
4408
4409 let strict_options = ParseOptions::strict();
4411 let strict_reader = PdfReader::new_with_options(cursor, strict_options).unwrap();
4412 assert!(!strict_reader.is_encrypted());
4413 assert!(strict_reader.is_unlocked());
4414
4415 let pdf_data = create_minimal_pdf();
4416 let cursor = Cursor::new(pdf_data);
4417 let lenient_options = ParseOptions::lenient();
4418 let lenient_reader = PdfReader::new_with_options(cursor, lenient_options).unwrap();
4419 assert!(!lenient_reader.is_encrypted());
4420 assert!(lenient_reader.is_unlocked());
4421 }
4422
4423 #[test]
4424 fn test_reader_encryption_integration_edge_cases() {
4425 let pdf_data = create_minimal_pdf();
4426 let cursor = Cursor::new(pdf_data);
4427 let mut reader = PdfReader::new(cursor).unwrap();
4428
4429 assert!(reader.unlock_with_password("").unwrap());
4431 assert!(reader.unlock_with_password(" ").unwrap()); assert!(reader
4433 .unlock_with_password("very_long_password_that_exceeds_normal_length")
4434 .unwrap());
4435 assert!(reader.unlock_with_password("unicode_test_ñáéíóú").unwrap());
4436
4437 assert!(reader.unlock_with_password("pass@#$%^&*()").unwrap());
4439 assert!(reader.unlock_with_password("pass\nwith\nnewlines").unwrap());
4440 assert!(reader.unlock_with_password("pass\twith\ttabs").unwrap());
4441 }
4442
4443 mod rigorous {
4444 use super::*;
4445
4446 #[test]
4451 fn test_reader_invalid_pdf_header() {
4452 let invalid_data = b"This is not a PDF file";
4454 let cursor = Cursor::new(invalid_data.to_vec());
4455 let result = PdfReader::new(cursor);
4456
4457 assert!(result.is_err(), "Should fail on invalid PDF header");
4458 }
4459
4460 #[test]
4461 fn test_reader_truncated_header() {
4462 let truncated = b"%PDF";
4464 let cursor = Cursor::new(truncated.to_vec());
4465 let result = PdfReader::new(cursor);
4466
4467 assert!(result.is_err(), "Should fail on truncated header");
4468 }
4469
4470 #[test]
4471 fn test_reader_empty_file() {
4472 let empty = Vec::new();
4473 let cursor = Cursor::new(empty);
4474 let result = PdfReader::new(cursor);
4475
4476 assert!(result.is_err(), "Should fail on empty file");
4477 }
4478
4479 #[test]
4480 fn test_reader_malformed_version() {
4481 let malformed = b"%PDF-X.Y\n%%\xE2\xE3\xCF\xD3\n";
4483 let cursor = Cursor::new(malformed.to_vec());
4484 let result = PdfReader::new(cursor);
4485
4486 if let Ok(reader) = result {
4488 let _version = reader.version();
4490 }
4491 }
4492
4493 #[test]
4494 fn test_reader_get_nonexistent_object() {
4495 let pdf_data = create_minimal_pdf();
4496 let cursor = Cursor::new(pdf_data);
4497 let mut reader = PdfReader::new(cursor).unwrap();
4498
4499 let result = reader.get_object(999, 0);
4501
4502 assert!(result.is_err(), "Should fail when object doesn't exist");
4503 }
4504
4505 #[test]
4506 fn test_reader_get_object_wrong_generation() {
4507 let pdf_data = create_minimal_pdf();
4508 let cursor = Cursor::new(pdf_data);
4509 let mut reader = PdfReader::new(cursor).unwrap();
4510
4511 let result = reader.get_object(1, 99);
4513
4514 if let Err(e) = result {
4516 let _ = e;
4518 }
4519 }
4520
4521 #[test]
4526 fn test_resolve_direct_object() {
4527 let pdf_data = create_minimal_pdf();
4528 let cursor = Cursor::new(pdf_data);
4529 let mut reader = PdfReader::new(cursor).unwrap();
4530
4531 let direct_obj = PdfObject::Integer(42);
4533
4534 let resolved = reader.resolve(&direct_obj).unwrap();
4535
4536 assert_eq!(resolved, &PdfObject::Integer(42));
4538 }
4539
4540 #[test]
4541 fn test_resolve_reference() {
4542 let pdf_data = create_minimal_pdf();
4543 let cursor = Cursor::new(pdf_data);
4544 let mut reader = PdfReader::new(cursor).unwrap();
4545
4546 let pages_ref = {
4548 let catalog = reader.catalog().unwrap();
4549 if let Some(PdfObject::Reference(obj_num, gen_num)) = catalog.get("Pages") {
4550 PdfObject::Reference(*obj_num, *gen_num)
4551 } else {
4552 panic!("Catalog /Pages must be a Reference");
4553 }
4554 };
4555
4556 let resolved = reader.resolve(&pages_ref).unwrap();
4558
4559 if let PdfObject::Dictionary(dict) = resolved {
4561 assert_eq!(
4562 dict.get("Type"),
4563 Some(&PdfObject::Name(PdfName("Pages".to_string())))
4564 );
4565 } else {
4566 panic!("Expected dictionary, got: {:?}", resolved);
4567 }
4568 }
4569
4570 #[test]
4575 fn test_is_encrypted_on_unencrypted() {
4576 let pdf_data = create_minimal_pdf();
4577 let cursor = Cursor::new(pdf_data);
4578 let reader = PdfReader::new(cursor).unwrap();
4579
4580 assert!(
4581 !reader.is_encrypted(),
4582 "Minimal PDF should not be encrypted"
4583 );
4584 }
4585
4586 #[test]
4587 fn test_is_unlocked_on_unencrypted() {
4588 let pdf_data = create_minimal_pdf();
4589 let cursor = Cursor::new(pdf_data);
4590 let reader = PdfReader::new(cursor).unwrap();
4591
4592 assert!(reader.is_unlocked(), "Unencrypted PDF should be unlocked");
4594 }
4595
4596 #[test]
4597 fn test_try_empty_password_on_unencrypted() {
4598 let pdf_data = create_minimal_pdf();
4599 let cursor = Cursor::new(pdf_data);
4600 let mut reader = PdfReader::new(cursor).unwrap();
4601
4602 let result = reader.try_empty_password();
4604 assert!(result.is_ok());
4605 }
4606
4607 #[test]
4612 fn test_reader_with_strict_options() {
4613 let pdf_data = create_minimal_pdf();
4614 let cursor = Cursor::new(pdf_data);
4615
4616 let options = ParseOptions::strict();
4617 let result = PdfReader::new_with_options(cursor, options);
4618
4619 assert!(result.is_ok(), "Minimal PDF should parse in strict mode");
4620 }
4621
4622 #[test]
4623 fn test_reader_with_lenient_options() {
4624 let pdf_data = create_minimal_pdf();
4625 let cursor = Cursor::new(pdf_data);
4626
4627 let options = ParseOptions::lenient();
4628 let result = PdfReader::new_with_options(cursor, options);
4629
4630 assert!(result.is_ok(), "Minimal PDF should parse in lenient mode");
4631 }
4632
4633 #[test]
4634 fn test_reader_options_accessible() {
4635 let pdf_data = create_minimal_pdf();
4636 let cursor = Cursor::new(pdf_data);
4637
4638 let options = ParseOptions::lenient();
4639 let reader = PdfReader::new_with_options(cursor, options.clone()).unwrap();
4640
4641 let reader_options = reader.options();
4643 assert_eq!(reader_options.strict_mode, options.strict_mode);
4644 }
4645
4646 #[test]
4651 fn test_catalog_has_required_fields() {
4652 let pdf_data = create_minimal_pdf();
4653 let cursor = Cursor::new(pdf_data);
4654 let mut reader = PdfReader::new(cursor).unwrap();
4655
4656 let catalog = reader.catalog().unwrap();
4657
4658 assert_eq!(
4660 catalog.get("Type"),
4661 Some(&PdfObject::Name(PdfName("Catalog".to_string()))),
4662 "Catalog must have /Type /Catalog"
4663 );
4664
4665 assert!(
4667 catalog.contains_key("Pages"),
4668 "Catalog must have /Pages entry"
4669 );
4670 }
4671
4672 #[test]
4673 fn test_info_fields_when_present() {
4674 let pdf_data = create_pdf_with_info();
4675 let cursor = Cursor::new(pdf_data);
4676 let mut reader = PdfReader::new(cursor).unwrap();
4677
4678 let info = reader.info().unwrap();
4679 assert!(info.is_some(), "PDF should have Info dictionary");
4680
4681 let info_dict = info.unwrap();
4682
4683 assert!(info_dict.contains_key("Title"), "Info should have Title");
4685 assert!(info_dict.contains_key("Author"), "Info should have Author");
4686 }
4687
4688 #[test]
4689 fn test_info_none_when_absent() {
4690 let pdf_data = create_minimal_pdf();
4691 let cursor = Cursor::new(pdf_data);
4692 let mut reader = PdfReader::new(cursor).unwrap();
4693
4694 let info = reader.info().unwrap();
4695 assert!(info.is_none(), "Minimal PDF should not have Info");
4696 }
4697
4698 #[test]
4703 fn test_version_exact_values() {
4704 let pdf_data = create_pdf_with_version("1.7");
4705 let cursor = Cursor::new(pdf_data);
4706 let reader = PdfReader::new(cursor).unwrap();
4707
4708 let version = reader.version();
4709 assert_eq!(version.major, 1, "Major version must be exact");
4710 assert_eq!(version.minor, 7, "Minor version must be exact");
4711 }
4712
4713 #[test]
4714 fn test_version_pdf_20() {
4715 let pdf_data = create_pdf_with_version("2.0");
4716 let cursor = Cursor::new(pdf_data);
4717 let reader = PdfReader::new(cursor).unwrap();
4718
4719 let version = reader.version();
4720 assert_eq!(version.major, 2, "PDF 2.0 major version");
4721 assert_eq!(version.minor, 0, "PDF 2.0 minor version");
4722 }
4723
4724 #[test]
4729 fn test_pages_returns_pages_dict() {
4730 let pdf_data = create_minimal_pdf();
4731 let cursor = Cursor::new(pdf_data);
4732 let mut reader = PdfReader::new(cursor).unwrap();
4733
4734 let pages_dict = reader
4735 .pages()
4736 .expect("pages() must return Pages dictionary");
4737
4738 assert_eq!(
4739 pages_dict.get("Type"),
4740 Some(&PdfObject::Name(PdfName("Pages".to_string()))),
4741 "Pages dict must have /Type /Pages"
4742 );
4743 }
4744
4745 #[test]
4746 fn test_page_count_minimal_pdf() {
4747 let pdf_data = create_minimal_pdf();
4748 let cursor = Cursor::new(pdf_data);
4749 let mut reader = PdfReader::new(cursor).unwrap();
4750
4751 let count = reader.page_count().expect("page_count() must succeed");
4752 assert_eq!(count, 0, "Minimal PDF has 0 pages");
4753 }
4754
4755 #[test]
4756 fn test_page_count_with_info_pdf() {
4757 let pdf_data = create_pdf_with_info();
4758 let cursor = Cursor::new(pdf_data);
4759 let mut reader = PdfReader::new(cursor).unwrap();
4760
4761 let count = reader.page_count().expect("page_count() must succeed");
4762 assert_eq!(count, 0, "create_pdf_with_info() has Count 0 in Pages dict");
4763 }
4764
4765 #[test]
4770 fn test_metadata_minimal_pdf() {
4771 let pdf_data = create_minimal_pdf();
4772 let cursor = Cursor::new(pdf_data);
4773 let mut reader = PdfReader::new(cursor).unwrap();
4774
4775 let meta = reader.metadata().expect("metadata() must succeed");
4776
4777 assert!(meta.title.is_none(), "Minimal PDF has no title");
4779 assert!(meta.author.is_none(), "Minimal PDF has no author");
4780 }
4781
4782 #[test]
4783 fn test_metadata_with_info() {
4784 let pdf_data = create_pdf_with_info();
4785 let cursor = Cursor::new(pdf_data);
4786 let mut reader = PdfReader::new(cursor).unwrap();
4787
4788 let meta = reader.metadata().expect("metadata() must succeed");
4789
4790 assert!(meta.title.is_some(), "PDF with Info has title");
4791 assert_eq!(meta.title.unwrap(), "Test PDF", "Title must match");
4792 assert!(meta.author.is_some(), "PDF with Info has author");
4793 assert_eq!(meta.author.unwrap(), "Test Author", "Author must match");
4794 }
4795
4796 #[test]
4801 fn test_resolve_stream_length_direct_integer() {
4802 let pdf_data = create_minimal_pdf();
4803 let cursor = Cursor::new(pdf_data);
4804 let mut reader = PdfReader::new(cursor).unwrap();
4805
4806 let length_obj = PdfObject::Integer(100);
4808
4809 let length = reader
4810 .resolve_stream_length(&length_obj)
4811 .expect("resolve_stream_length must succeed");
4812 assert_eq!(length, Some(100), "Direct integer must be resolved");
4813 }
4814
4815 #[test]
4816 fn test_resolve_stream_length_negative_integer() {
4817 let pdf_data = create_minimal_pdf();
4818 let cursor = Cursor::new(pdf_data);
4819 let mut reader = PdfReader::new(cursor).unwrap();
4820
4821 let length_obj = PdfObject::Integer(-10);
4823
4824 let length = reader
4825 .resolve_stream_length(&length_obj)
4826 .expect("resolve_stream_length must succeed");
4827 assert_eq!(length, None, "Negative integer returns None");
4828 }
4829
4830 #[test]
4831 fn test_resolve_stream_length_non_integer() {
4832 let pdf_data = create_minimal_pdf();
4833 let cursor = Cursor::new(pdf_data);
4834 let mut reader = PdfReader::new(cursor).unwrap();
4835
4836 let name_obj = PdfObject::Name(PdfName("Test".to_string()));
4838
4839 let length = reader
4840 .resolve_stream_length(&name_obj)
4841 .expect("resolve_stream_length must succeed");
4842 assert_eq!(length, None, "Non-integer object returns None");
4843 }
4844
4845 #[test]
4850 fn test_get_all_pages_empty_pdf() {
4851 let pdf_data = create_minimal_pdf();
4852 let cursor = Cursor::new(pdf_data);
4853 let mut reader = PdfReader::new(cursor).unwrap();
4854
4855 let pages = reader
4856 .get_all_pages()
4857 .expect("get_all_pages() must succeed");
4858 assert_eq!(pages.len(), 0, "Minimal PDF has 0 pages");
4859 }
4860
4861 #[test]
4862 fn test_get_all_pages_with_info() {
4863 let pdf_data = create_pdf_with_info();
4864 let cursor = Cursor::new(pdf_data);
4865 let mut reader = PdfReader::new(cursor).unwrap();
4866
4867 let pages = reader
4868 .get_all_pages()
4869 .expect("get_all_pages() must succeed");
4870 assert_eq!(
4871 pages.len(),
4872 0,
4873 "create_pdf_with_info() has 0 pages (Count 0)"
4874 );
4875 }
4876
4877 #[test]
4882 fn test_into_document_consumes_reader() {
4883 let pdf_data = create_minimal_pdf();
4884 let cursor = Cursor::new(pdf_data);
4885 let reader = PdfReader::new(cursor).unwrap();
4886
4887 let document = reader.into_document();
4888
4889 let version = document.version().expect("Document must have version");
4891 assert!(
4892 version.starts_with("1."),
4893 "Document must have PDF 1.x version, got: {}",
4894 version
4895 );
4896
4897 let page_count = document
4899 .page_count()
4900 .expect("Document must allow page_count()");
4901 assert_eq!(
4902 page_count, 0,
4903 "Minimal PDF has 0 pages (Count 0 in test helper)"
4904 );
4905 }
4906
4907 #[test]
4912 fn test_clear_parse_context() {
4913 let pdf_data = create_minimal_pdf();
4914 let cursor = Cursor::new(pdf_data);
4915 let mut reader = PdfReader::new(cursor).unwrap();
4916
4917 reader.clear_parse_context();
4919
4920 let version = reader.version();
4922 assert_eq!(version.major, 1, "Reader must still work after clear");
4923 }
4924
4925 #[test]
4926 fn test_parse_context_mut_accessible() {
4927 let pdf_data = create_minimal_pdf();
4928 let cursor = Cursor::new(pdf_data);
4929 let mut reader = PdfReader::new(cursor).unwrap();
4930
4931 let context = reader.parse_context_mut();
4932
4933 let initial_depth = context.depth;
4935 assert_eq!(initial_depth, 0, "Parse context must start with depth 0");
4936
4937 assert!(
4939 context.max_depth > 0,
4940 "Parse context must have positive max_depth"
4941 );
4942 }
4943
4944 #[test]
4949 fn test_find_byte_pattern_basic() {
4950 let haystack = b"Hello World";
4951 let needle = b"World";
4952 let pos = find_byte_pattern(haystack, needle);
4953 assert_eq!(pos, Some(6), "Must find 'World' at position 6");
4954 }
4955
4956 #[test]
4957 fn test_find_byte_pattern_not_found() {
4958 let haystack = b"Hello World";
4959 let needle = b"Rust";
4960 let pos = find_byte_pattern(haystack, needle);
4961 assert_eq!(pos, None, "Must return None when not found");
4962 }
4963
4964 #[test]
4965 fn test_find_byte_pattern_at_start() {
4966 let haystack = b"Hello World";
4967 let needle = b"Hello";
4968 let pos = find_byte_pattern(haystack, needle);
4969 assert_eq!(pos, Some(0), "Must find at position 0");
4970 }
4971
4972 #[test]
4973 fn test_is_immediate_stream_start_with_stream() {
4974 let data = b"stream\ndata";
4975 assert!(
4976 is_immediate_stream_start(data),
4977 "Must detect 'stream' at start"
4978 );
4979 }
4980
4981 #[test]
4982 fn test_is_immediate_stream_start_with_whitespace() {
4983 let data = b" \n\tstream\ndata";
4984 assert!(
4985 is_immediate_stream_start(data),
4986 "Must detect 'stream' after whitespace"
4987 );
4988 }
4989
4990 #[test]
4991 fn test_is_immediate_stream_start_no_stream() {
4992 let data = b"endobj";
4993 assert!(
4994 !is_immediate_stream_start(data),
4995 "Must return false when 'stream' absent"
4996 );
4997 }
4998 }
4999}