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_detailed, verify_signature_detailed,
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_detailed(&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_detailed(&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 crate::signatures::validate_certificate_chain(
3166 &parsed_sig.signer_certificate_der,
3167 &parsed_sig.certificates_der,
3168 &trust_store,
3169 None,
3170 ) {
3171 Ok(cert_result) => {
3172 result.certificate_result =
3173 Some(cert_result.into_certificate_result_fail_closed());
3174 }
3175 Err(e) => {
3176 result
3177 .errors
3178 .push(format!("Certificate validation failed: {}", e));
3179 }
3180 }
3181
3182 results.push(result);
3183 }
3184
3185 Ok(results)
3186 }
3187}
3188
3189#[derive(Debug, Default, Clone)]
3191pub struct DocumentMetadata {
3192 pub title: Option<String>,
3193 pub author: Option<String>,
3194 pub subject: Option<String>,
3195 pub keywords: Option<String>,
3196 pub creator: Option<String>,
3197 pub producer: Option<String>,
3198 pub creation_date: Option<String>,
3199 pub modification_date: Option<String>,
3200 pub version: String,
3201 pub page_count: Option<u32>,
3202}
3203
3204pub struct EOLIter<'s> {
3205 remainder: &'s str,
3206}
3207impl<'s> Iterator for EOLIter<'s> {
3208 type Item = &'s str;
3209
3210 fn next(&mut self) -> Option<Self::Item> {
3211 if self.remainder.is_empty() {
3212 return None;
3213 }
3214
3215 if let Some((i, sep)) = ["\r\n", "\n", "\r"]
3216 .iter()
3217 .filter_map(|&sep| self.remainder.find(sep).map(|i| (i, sep)))
3218 .min_by_key(|(i, _)| *i)
3219 {
3220 let (line, rest) = self.remainder.split_at(i);
3221 self.remainder = &rest[sep.len()..];
3222 Some(line)
3223 } else {
3224 let line = self.remainder;
3225 self.remainder = "";
3226 Some(line)
3227 }
3228 }
3229}
3230pub trait PDFLines: AsRef<str> {
3231 fn pdf_lines(&self) -> EOLIter<'_> {
3232 EOLIter {
3233 remainder: self.as_ref(),
3234 }
3235 }
3236}
3237impl PDFLines for &str {}
3238impl<'a> PDFLines for std::borrow::Cow<'a, str> {}
3239impl PDFLines for String {}
3240
3241#[cfg(test)]
3242mod tests {
3243
3244 use super::*;
3245 use crate::parser::objects::{PdfName, PdfString};
3246 use crate::parser::test_helpers::*;
3247 use crate::parser::ParseOptions;
3248 use std::io::Cursor;
3249
3250 #[test]
3251 fn test_reader_construction() {
3252 let pdf_data = create_minimal_pdf();
3253 let cursor = Cursor::new(pdf_data);
3254 let result = PdfReader::new(cursor);
3255 assert!(result.is_ok());
3256 }
3257
3258 #[test]
3266 fn slice_between_extracts_delimited_content() {
3267 assert_eq!(
3268 slice_between("/MediaBox [0 0 612 792] /X", b'[', b']'),
3269 Some("0 0 612 792")
3270 );
3271 assert_eq!(slice_between("[]", b'[', b']'), Some(""));
3272 }
3273
3274 #[test]
3275 fn slice_between_searches_the_closer_after_the_opener() {
3276 assert_eq!(slice_between("/MediaBox ][", b'[', b']'), None);
3281 assert_eq!(slice_between("] a [ b ] c", b'[', b']'), Some(" b "));
3282 }
3283
3284 #[test]
3285 fn slice_between_returns_none_when_a_delimiter_is_missing() {
3286 assert_eq!(slice_between("/MediaBox 0 0 612 792", b'[', b']'), None);
3287 assert_eq!(slice_between("/MediaBox [0 0 612 792", b'[', b']'), None);
3288 assert_eq!(slice_between("", b'[', b']'), None);
3289 }
3290
3291 #[test]
3292 fn slice_between_never_splits_a_multibyte_char() {
3293 assert_eq!(
3296 slice_between("[\u{FFFD}\u{20AC}]", b'[', b']'),
3297 Some("\u{FFFD}\u{20AC}")
3298 );
3299 assert_eq!(slice_between("\u{20AC}[x]\u{20AC}", b'[', b']'), Some("x"));
3302 }
3303
3304 #[test]
3305 fn find_dict_end_locates_the_closing_marker() {
3306 assert_eq!(find_dict_end(" /A 1 >>"), Some(6));
3309 assert_eq!(find_dict_end(">>"), Some(0));
3310 }
3311
3312 #[test]
3313 fn find_dict_end_matches_nesting() {
3314 assert_eq!(find_dict_end(" /A << /B 1 >> >> tail"), Some(15));
3315 }
3316
3317 #[test]
3318 fn find_dict_end_returns_none_when_unbalanced() {
3319 assert_eq!(find_dict_end(" /A 1"), None);
3320 assert_eq!(find_dict_end(" /A << /B 1 >>"), None);
3321 assert_eq!(find_dict_end(""), None);
3322 assert_eq!(find_dict_end(">"), None);
3323 }
3324
3325 #[test]
3326 fn find_dict_end_offset_is_a_char_boundary_with_multibyte_content() {
3327 let after = " /Font \u{FFFD} >> tail";
3330 let end = find_dict_end(after).expect("closes");
3331 assert_eq!(&after[..end], " /Font \u{FFFD} ");
3332 }
3333
3334 #[test]
3335 fn truncate_on_char_boundary_cuts_without_splitting() {
3336 assert_eq!(truncate_on_char_boundary("abc", 10), "abc");
3337 assert_eq!(truncate_on_char_boundary("abcdef", 3), "abc");
3338 assert_eq!(truncate_on_char_boundary("\u{FFFD}x", 1), "");
3340 assert_eq!(truncate_on_char_boundary("\u{FFFD}x", 3), "\u{FFFD}");
3341 assert_eq!(truncate_on_char_boundary("", 5), "");
3342 }
3343
3344 struct CountingReader<R> {
3351 inner: R,
3352 total_read: std::sync::Arc<std::sync::atomic::AtomicUsize>,
3353 max_read: std::sync::Arc<std::sync::atomic::AtomicUsize>,
3354 }
3355 impl<R: Read> Read for CountingReader<R> {
3356 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
3357 self.max_read
3358 .fetch_max(buf.len(), std::sync::atomic::Ordering::SeqCst);
3359 let n = self.inner.read(buf)?;
3360 self.total_read
3361 .fetch_add(n, std::sync::atomic::Ordering::SeqCst);
3362 Ok(n)
3363 }
3364 }
3365 impl<R: Seek> Seek for CountingReader<R> {
3366 fn seek(&mut self, p: SeekFrom) -> std::io::Result<u64> {
3367 self.inner.seek(p)
3368 }
3369 }
3370
3371 #[test]
3372 fn test_extract_stream_manually_bounded_honors_length() {
3373 use crate::parser::objects::PdfObject;
3374 use std::sync::atomic::{AtomicUsize, Ordering};
3375 use std::sync::Arc;
3376
3377 const L: usize = 300 * 1024; const FILLER: usize = 4 * 1024 * 1024; let payload: Vec<u8> = (0..L).map(|i| (i % 251) as u8).collect();
3389 let filler: Vec<u8> = (0..FILLER).map(|i| ((i + 7) % 251) as u8).collect();
3390
3391 let header = b"%PDF-1.4\n";
3392 let obj1 = b"1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n";
3393 let obj2 = b"2 0 obj\n<< /Type /Pages /Kids [] /Count 0 >>\nendobj\n";
3394
3395 let mut data = Vec::new();
3396 data.extend_from_slice(header);
3397 let obj1_start = data.len();
3398 data.extend_from_slice(obj1);
3399 let obj2_start = data.len();
3400 data.extend_from_slice(obj2);
3401
3402 data.extend_from_slice(
3405 b"4 0 obj\n<< /Type /Metadata /Subtype /XML /Length 307200 >>\nstream\n",
3406 );
3407 data.extend_from_slice(&payload);
3408 data.extend_from_slice(b"\nendstream\nendobj\n");
3409
3410 data.extend_from_slice(b"5 0 obj\n<< /Length 4194304 >>\nstream\n");
3412 data.extend_from_slice(&filler);
3413 data.extend_from_slice(b"\nendstream\nendobj\n");
3414
3415 let xref_start = data.len();
3417 let xref = format!(
3418 "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"
3419 );
3420 data.extend_from_slice(xref.as_bytes());
3421 let file_len = data.len();
3422
3423 let counter = Arc::new(AtomicUsize::new(0));
3424 let reader = CountingReader {
3425 inner: Cursor::new(data),
3426 total_read: counter.clone(),
3427 max_read: Arc::new(AtomicUsize::new(0)),
3428 };
3429 let mut pdf = PdfReader::new_with_options(reader, ParseOptions::tolerant())
3430 .expect("minimal PDF must parse");
3431
3432 counter.store(0, Ordering::SeqCst);
3434 let obj = pdf
3435 .extract_object_or_stream_manually(4)
3436 .expect("stream object 4 must be extracted");
3437
3438 let stream = match &obj {
3440 PdfObject::Stream(s) => s,
3441 other => panic!("expected a stream, got {other:?}"),
3442 };
3443 assert_eq!(
3444 stream.data.len(),
3445 L,
3446 "stream body must equal /Length (no truncation)"
3447 );
3448 assert_eq!(stream.data, payload, "stream body content must be intact");
3449
3450 let total_read = counter.load(Ordering::SeqCst);
3454 assert!(
3455 total_read <= L + 2 * MANUAL_DICT_WINDOW,
3456 "manual stream extraction read {total_read} bytes total (file={file_len}); not bounded by object size"
3457 );
3458 }
3459
3460 #[cfg(test)]
3464 fn build_manual_stream_pdf(obj4_dict_body: &str, payload: &[u8]) -> Vec<u8> {
3465 let header = b"%PDF-1.4\n";
3466 let obj1 = b"1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n";
3467 let obj2 = b"2 0 obj\n<< /Type /Pages /Kids [] /Count 0 >>\nendobj\n";
3468
3469 let mut data = Vec::new();
3470 data.extend_from_slice(header);
3471 let obj1_start = data.len();
3472 data.extend_from_slice(obj1);
3473 let obj2_start = data.len();
3474 data.extend_from_slice(obj2);
3475
3476 data.extend_from_slice(format!("4 0 obj\n<<{obj4_dict_body}>>\nstream\n").as_bytes());
3478 data.extend_from_slice(payload);
3479 data.extend_from_slice(b"\nendstream\nendobj\n");
3480
3481 let xref_start = data.len();
3483 let xref = format!(
3484 "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"
3485 );
3486 data.extend_from_slice(xref.as_bytes());
3487 data
3488 }
3489
3490 #[test]
3491 fn test_reconstruct_preserves_non_flate_filter() {
3492 use crate::parser::objects::PdfObject;
3493
3494 let payload = b"\xff\xd8\xff\xe0not-a-real-jpeg\xff\xd9";
3500 let len = payload.len();
3501 let dict_body =
3502 format!(" /Type /XObject /Subtype /Image /Filter /DCTDecode /Length {len} ");
3503 let data = build_manual_stream_pdf(&dict_body, payload);
3504
3505 let mut pdf = PdfReader::new_with_options(Cursor::new(data), ParseOptions::tolerant())
3506 .expect("minimal PDF must parse");
3507 let obj = pdf
3508 .extract_object_or_stream_manually(4)
3509 .expect("stream object 4 must be extracted");
3510 let stream = match &obj {
3511 PdfObject::Stream(s) => s,
3512 other => panic!("expected a stream, got {other:?}"),
3513 };
3514
3515 assert_eq!(
3516 stream
3517 .dict
3518 .get("Filter")
3519 .and_then(|o| o.as_name())
3520 .map(|n| n.0.as_str()),
3521 Some("DCTDecode"),
3522 "non-Flate /Filter must be preserved in reconstructed stream dict"
3523 );
3524 assert_eq!(
3525 stream
3526 .dict
3527 .get("Subtype")
3528 .and_then(|o| o.as_name())
3529 .map(|n| n.0.as_str()),
3530 Some("Image"),
3531 "/Subtype must be preserved, not dropped"
3532 );
3533 assert_eq!(stream.data, payload, "stream body must be intact");
3534 }
3535
3536 #[test]
3537 fn test_reconstruct_preserves_filter_array_and_decodeparms() {
3538 use crate::parser::objects::PdfObject;
3539
3540 let payload = b"compressed-bytes-placeholder";
3545 let len = payload.len();
3546 let dict_body = format!(
3547 " /Filter[/LZWDecode /FlateDecode] /DecodeParms[null<< /Predictor 12 /Columns 4 >>] /Length {len} "
3548 );
3549 let data = build_manual_stream_pdf(&dict_body, payload);
3550
3551 let mut pdf = PdfReader::new_with_options(Cursor::new(data), ParseOptions::tolerant())
3552 .expect("minimal PDF must parse");
3553 let obj = pdf
3554 .extract_object_or_stream_manually(4)
3555 .expect("stream object 4 must be extracted");
3556 let stream = match &obj {
3557 PdfObject::Stream(s) => s,
3558 other => panic!("expected a stream, got {other:?}"),
3559 };
3560
3561 let filter = stream
3562 .dict
3563 .get("Filter")
3564 .and_then(|o| o.as_array())
3565 .expect("/Filter array must be preserved");
3566 let names: Vec<&str> = filter
3567 .0
3568 .iter()
3569 .filter_map(|o| o.as_name())
3570 .map(|n| n.0.as_str())
3571 .collect();
3572 assert_eq!(
3573 names,
3574 vec!["LZWDecode", "FlateDecode"],
3575 "filter array entries must be preserved in order"
3576 );
3577 assert!(
3578 stream.dict.get("DecodeParms").is_some(),
3579 "/DecodeParms must be preserved alongside the filter chain"
3580 );
3581 assert_eq!(stream.data, payload, "stream body must be intact");
3582 }
3583
3584 #[test]
3585 fn test_reconstruct_malformed_dict_falls_back_without_panic() {
3586 use crate::parser::objects::PdfObject;
3587
3588 let payload = b"0123456789";
3594 let len = payload.len();
3595 let dict_body = format!(" /Length {len} /BadKey ");
3597 let data = build_manual_stream_pdf(&dict_body, payload);
3598
3599 let mut pdf = PdfReader::new_with_options(Cursor::new(data), ParseOptions::tolerant())
3600 .expect("minimal PDF must parse");
3601 let obj = pdf
3602 .extract_object_or_stream_manually(4)
3603 .expect("malformed-dict stream must still be reconstructed via fallback");
3604 let stream = match &obj {
3605 PdfObject::Stream(s) => s,
3606 other => panic!("expected a stream, got {other:?}"),
3607 };
3608
3609 assert_eq!(
3610 stream.data, payload,
3611 "fallback path must still read the bounded body intact"
3612 );
3613 assert!(
3615 stream.dict.get("Filter").is_none(),
3616 "fallback dict must not invent a /Filter"
3617 );
3618 }
3619
3620 #[test]
3621 fn test_find_page_objects_bounded() {
3622 use std::sync::atomic::{AtomicUsize, Ordering};
3623 use std::sync::Arc;
3624
3625 const FILLER: usize = 2 * 1024 * 1024;
3631 let filler: Vec<u8> = (0..FILLER).map(|i| (i % 251) as u8).collect();
3632
3633 let header = b"%PDF-1.4\n";
3634 let obj1 = b"1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n";
3635 let obj2 = b"2 0 obj\n<< /Type /Pages /Kids [4 0 R 5 0 R 6 0 R] /Count 3 >>\nendobj\n";
3636
3637 let mut data = Vec::new();
3638 data.extend_from_slice(header);
3639 let obj1_start = data.len();
3640 data.extend_from_slice(obj1);
3641 let obj2_start = data.len();
3642 data.extend_from_slice(obj2);
3643
3644 for n in [4u32, 5, 6] {
3646 data.extend_from_slice(
3647 format!(
3648 "{n} 0 obj\n<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] >>\nendobj\n"
3649 )
3650 .as_bytes(),
3651 );
3652 }
3653
3654 data.extend_from_slice(b"7 0 obj\n<< /Length 2097152 >>\nstream\n");
3656 data.extend_from_slice(&filler);
3657 data.extend_from_slice(b"\nendstream\nendobj\n");
3658
3659 let xref_start = data.len();
3660 let xref = format!(
3661 "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"
3662 );
3663 data.extend_from_slice(xref.as_bytes());
3664 let file_len = data.len();
3665
3666 let max_read = Arc::new(AtomicUsize::new(0));
3667 let reader = CountingReader {
3668 inner: Cursor::new(data),
3669 total_read: Arc::new(AtomicUsize::new(0)),
3670 max_read: max_read.clone(),
3671 };
3672 let mut pdf = PdfReader::new_with_options(reader, ParseOptions::tolerant())
3673 .expect("minimal PDF must parse");
3674
3675 max_read.store(0, Ordering::SeqCst);
3677 let pages = pdf.find_page_objects().expect("page scan must succeed");
3678
3679 assert_eq!(
3681 pages,
3682 vec![(4, 0), (5, 0), (6, 0)],
3683 "must find exactly the three /Type /Page objects"
3684 );
3685
3686 let peak = max_read.load(Ordering::SeqCst);
3688 assert!(
3689 peak <= 128 * 1024,
3690 "page scan requested {peak} bytes in one read (file={file_len}); not bounded"
3691 );
3692 }
3693
3694 #[test]
3695 fn test_signature_verification_reads_full_file_intentionally() {
3696 use std::sync::atomic::{AtomicUsize, Ordering};
3697 use std::sync::Arc;
3698
3699 let data = create_minimal_pdf();
3704 let file_len = data.len();
3705
3706 let total = Arc::new(AtomicUsize::new(0));
3707 let reader = CountingReader {
3708 inner: Cursor::new(data),
3709 total_read: total.clone(),
3710 max_read: Arc::new(AtomicUsize::new(0)),
3711 };
3712 let mut pdf = PdfReader::new_with_options(reader, ParseOptions::tolerant())
3713 .expect("minimal PDF must parse");
3714
3715 total.store(0, Ordering::SeqCst);
3716 let _ = pdf.verify_signatures();
3718 let read = total.load(Ordering::SeqCst);
3719 assert!(
3720 read >= file_len,
3721 "signature verification must read the whole file (read {read}, file {file_len})"
3722 );
3723 }
3724
3725 #[test]
3726 fn test_reader_version() {
3727 let pdf_data = create_minimal_pdf();
3728 let cursor = Cursor::new(pdf_data);
3729 let reader = PdfReader::new(cursor).unwrap();
3730 assert_eq!(reader.version().major, 1);
3731 assert_eq!(reader.version().minor, 4);
3732 }
3733
3734 #[test]
3735 fn test_reader_different_versions() {
3736 let versions = vec![
3737 "1.0", "1.1", "1.2", "1.3", "1.4", "1.5", "1.6", "1.7", "2.0",
3738 ];
3739
3740 for version in versions {
3741 let pdf_data = create_pdf_with_version(version);
3742 let cursor = Cursor::new(pdf_data);
3743 let reader = PdfReader::new(cursor).unwrap();
3744
3745 let parts: Vec<&str> = version.split('.').collect();
3746 assert_eq!(reader.version().major, parts[0].parse::<u8>().unwrap());
3747 assert_eq!(reader.version().minor, parts[1].parse::<u8>().unwrap());
3748 }
3749 }
3750
3751 #[test]
3752 fn test_reader_catalog() {
3753 let pdf_data = create_minimal_pdf();
3754 let cursor = Cursor::new(pdf_data);
3755 let mut reader = PdfReader::new(cursor).unwrap();
3756
3757 let catalog = reader.catalog();
3758 assert!(catalog.is_ok());
3759
3760 let catalog_dict = catalog.unwrap();
3761 assert_eq!(
3762 catalog_dict.get("Type"),
3763 Some(&PdfObject::Name(PdfName("Catalog".to_string())))
3764 );
3765 }
3766
3767 #[test]
3768 fn test_reader_info_none() {
3769 let pdf_data = create_minimal_pdf();
3770 let cursor = Cursor::new(pdf_data);
3771 let mut reader = PdfReader::new(cursor).unwrap();
3772
3773 let info = reader.info().unwrap();
3774 assert!(info.is_none());
3775 }
3776
3777 #[test]
3778 fn test_reader_info_present() {
3779 let pdf_data = create_pdf_with_info();
3780 let cursor = Cursor::new(pdf_data);
3781 let mut reader = PdfReader::new(cursor).unwrap();
3782
3783 let info = reader.info().unwrap();
3784 assert!(info.is_some());
3785
3786 let info_dict = info.unwrap();
3787 assert_eq!(
3788 info_dict.get("Title"),
3789 Some(&PdfObject::String(PdfString(
3790 "Test PDF".to_string().into_bytes()
3791 )))
3792 );
3793 assert_eq!(
3794 info_dict.get("Author"),
3795 Some(&PdfObject::String(PdfString(
3796 "Test Author".to_string().into_bytes()
3797 )))
3798 );
3799 }
3800
3801 #[test]
3802 fn test_reader_get_object() {
3803 let pdf_data = create_minimal_pdf();
3804 let cursor = Cursor::new(pdf_data);
3805 let mut reader = PdfReader::new(cursor).unwrap();
3806
3807 let obj = reader.get_object(1, 0);
3809 assert!(obj.is_ok());
3810
3811 let catalog = obj.unwrap();
3812 assert!(catalog.as_dict().is_some());
3813 }
3814
3815 #[test]
3816 fn test_reader_get_invalid_object() {
3817 let pdf_data = create_minimal_pdf();
3818 let cursor = Cursor::new(pdf_data);
3819 let mut reader = PdfReader::new(cursor).unwrap();
3820
3821 let obj = reader.get_object(999, 0);
3823 assert!(obj.is_err());
3824 }
3825
3826 #[test]
3827 fn test_reader_get_free_object() {
3828 let pdf_data = create_minimal_pdf();
3829 let cursor = Cursor::new(pdf_data);
3830 let mut reader = PdfReader::new(cursor).unwrap();
3831
3832 let obj = reader.get_object(0, 65535);
3834 assert!(obj.is_ok());
3835 assert_eq!(obj.unwrap(), &PdfObject::Null);
3836 }
3837
3838 #[test]
3839 fn test_reader_resolve_reference() {
3840 let pdf_data = create_minimal_pdf();
3841 let cursor = Cursor::new(pdf_data);
3842 let mut reader = PdfReader::new(cursor).unwrap();
3843
3844 let ref_obj = PdfObject::Reference(1, 0);
3846 let resolved = reader.resolve(&ref_obj);
3847
3848 assert!(resolved.is_ok());
3849 assert!(resolved.unwrap().as_dict().is_some());
3850 }
3851
3852 #[test]
3853 fn test_reader_resolve_non_reference() {
3854 let pdf_data = create_minimal_pdf();
3855 let cursor = Cursor::new(pdf_data);
3856 let mut reader = PdfReader::new(cursor).unwrap();
3857
3858 let int_obj = PdfObject::Integer(42);
3860 let resolved = reader.resolve(&int_obj).unwrap();
3861
3862 assert_eq!(resolved, &PdfObject::Integer(42));
3863 }
3864
3865 #[test]
3866 fn test_reader_cache_behavior() {
3867 let pdf_data = create_minimal_pdf();
3868 let cursor = Cursor::new(pdf_data);
3869 let mut reader = PdfReader::new(cursor).unwrap();
3870
3871 let obj1 = reader.get_object(1, 0).unwrap();
3873 assert!(obj1.as_dict().is_some());
3874
3875 let obj2 = reader.get_object(1, 0).unwrap();
3877 assert!(obj2.as_dict().is_some());
3878 }
3879
3880 #[test]
3881 fn test_reader_wrong_generation() {
3882 let pdf_data = create_minimal_pdf();
3883 let cursor = Cursor::new(pdf_data);
3884 let mut reader = PdfReader::new(cursor).unwrap();
3885
3886 let obj = reader.get_object(1, 99);
3888 assert!(obj.is_err());
3889 }
3890
3891 #[test]
3892 fn test_reader_invalid_pdf() {
3893 let invalid_data = b"This is not a PDF file";
3894 let cursor = Cursor::new(invalid_data.to_vec());
3895 let result = PdfReader::new(cursor);
3896
3897 assert!(result.is_err());
3898 }
3899
3900 #[test]
3901 fn test_reader_corrupt_xref() {
3902 let corrupt_pdf = b"%PDF-1.4
39031 0 obj
3904<< /Type /Catalog >>
3905endobj
3906xref
3907corrupted xref table
3908trailer
3909<< /Size 2 /Root 1 0 R >>
3910startxref
391124
3912%%EOF"
3913 .to_vec();
3914
3915 let cursor = Cursor::new(corrupt_pdf.clone());
3919 let mut reader = PdfReader::new(cursor).expect("corrupt xref is reconstructed by scan");
3920 let catalog = reader
3921 .catalog()
3922 .expect("catalog resolves after reconstruction");
3923 assert_eq!(
3924 catalog.get("Type"),
3925 Some(&PdfObject::Name(PdfName("Catalog".to_string()))),
3926 );
3927
3928 let cursor = Cursor::new(corrupt_pdf);
3930 assert!(PdfReader::new_with_options(cursor, ParseOptions::strict()).is_err());
3931 }
3932
3933 #[test]
3934 fn test_reader_missing_trailer() {
3935 let pdf_no_trailer = b"%PDF-1.4
39361 0 obj
3937<< /Type /Catalog >>
3938endobj
3939xref
39400 2
39410000000000 65535 f
39420000000009 00000 n
3943startxref
394424
3945%%EOF"
3946 .to_vec();
3947
3948 let cursor = Cursor::new(pdf_no_trailer.clone());
3952 let mut reader = PdfReader::new(cursor).expect("missing trailer recovered by object scan");
3953 let catalog = reader
3954 .catalog()
3955 .expect("catalog resolved from scanned /Root");
3956 assert_eq!(
3957 catalog.get("Type"),
3958 Some(&PdfObject::Name(PdfName("Catalog".to_string()))),
3959 );
3960
3961 let cursor = Cursor::new(pdf_no_trailer);
3963 assert!(PdfReader::new_with_options(cursor, ParseOptions::strict()).is_err());
3964 }
3965
3966 #[test]
3967 fn test_reader_empty_pdf() {
3968 let cursor = Cursor::new(Vec::new());
3969 let result = PdfReader::new(cursor);
3970 assert!(result.is_err());
3971 }
3972
3973 #[test]
3974 fn test_reader_page_count() {
3975 let pdf_data = create_minimal_pdf();
3976 let cursor = Cursor::new(pdf_data);
3977 let mut reader = PdfReader::new(cursor).unwrap();
3978
3979 let count = reader.page_count();
3980 assert!(count.is_ok());
3981 assert_eq!(count.unwrap(), 0); }
3983
3984 #[test]
3985 fn test_reader_into_document() {
3986 let pdf_data = create_minimal_pdf();
3987 let cursor = Cursor::new(pdf_data);
3988 let reader = PdfReader::new(cursor).unwrap();
3989
3990 let document = reader.into_document();
3991 let page_count = document.page_count();
3993 assert!(page_count.is_ok());
3994 }
3995
3996 #[test]
3997 fn test_reader_pages_dict() {
3998 let pdf_data = create_minimal_pdf();
3999 let cursor = Cursor::new(pdf_data);
4000 let mut reader = PdfReader::new(cursor).unwrap();
4001
4002 let pages = reader.pages();
4003 assert!(pages.is_ok());
4004 let pages_dict = pages.unwrap();
4005 assert_eq!(
4006 pages_dict.get("Type"),
4007 Some(&PdfObject::Name(PdfName("Pages".to_string())))
4008 );
4009 }
4010
4011 #[test]
4012 fn test_reader_pdf_with_binary_data() {
4013 let pdf_data = create_pdf_with_binary_marker();
4014
4015 let cursor = Cursor::new(pdf_data);
4016 let result = PdfReader::new(cursor);
4017 assert!(result.is_ok());
4018 }
4019
4020 #[test]
4021 fn test_reader_metadata() {
4022 let pdf_data = create_pdf_with_info();
4023 let cursor = Cursor::new(pdf_data);
4024 let mut reader = PdfReader::new(cursor).unwrap();
4025
4026 let metadata = reader.metadata().unwrap();
4027 assert_eq!(metadata.title, Some("Test PDF".to_string()));
4028 assert_eq!(metadata.author, Some("Test Author".to_string()));
4029 assert_eq!(metadata.subject, Some("Testing".to_string()));
4030 assert_eq!(metadata.version, "1.4".to_string());
4031 }
4032
4033 #[test]
4034 fn test_reader_metadata_empty() {
4035 let pdf_data = create_minimal_pdf();
4036 let cursor = Cursor::new(pdf_data);
4037 let mut reader = PdfReader::new(cursor).unwrap();
4038
4039 let metadata = reader.metadata().unwrap();
4040 assert!(metadata.title.is_none());
4041 assert!(metadata.author.is_none());
4042 assert_eq!(metadata.version, "1.4".to_string());
4043 assert_eq!(metadata.page_count, Some(0));
4044 }
4045
4046 #[test]
4047 fn test_reader_object_number_mismatch() {
4048 let pdf_data = create_minimal_pdf();
4052 let cursor = Cursor::new(pdf_data);
4053 let mut reader = PdfReader::new(cursor).unwrap();
4054
4055 let result = reader.get_object(1, 99);
4058 assert!(result.is_err());
4059
4060 let result2 = reader.get_object(999, 0);
4062 assert!(result2.is_err());
4063 }
4064
4065 #[test]
4066 fn test_document_metadata_struct() {
4067 let metadata = DocumentMetadata {
4068 title: Some("Title".to_string()),
4069 author: Some("Author".to_string()),
4070 subject: Some("Subject".to_string()),
4071 keywords: Some("Keywords".to_string()),
4072 creator: Some("Creator".to_string()),
4073 producer: Some("Producer".to_string()),
4074 creation_date: Some("D:20240101".to_string()),
4075 modification_date: Some("D:20240102".to_string()),
4076 version: "1.5".to_string(),
4077 page_count: Some(10),
4078 };
4079
4080 assert_eq!(metadata.title, Some("Title".to_string()));
4081 assert_eq!(metadata.page_count, Some(10));
4082 }
4083
4084 #[test]
4085 fn test_document_metadata_default() {
4086 let metadata = DocumentMetadata::default();
4087 assert!(metadata.title.is_none());
4088 assert!(metadata.author.is_none());
4089 assert!(metadata.subject.is_none());
4090 assert!(metadata.keywords.is_none());
4091 assert!(metadata.creator.is_none());
4092 assert!(metadata.producer.is_none());
4093 assert!(metadata.creation_date.is_none());
4094 assert!(metadata.modification_date.is_none());
4095 assert_eq!(metadata.version, "".to_string());
4096 assert!(metadata.page_count.is_none());
4097 }
4098
4099 #[test]
4100 fn test_document_metadata_clone() {
4101 let metadata = DocumentMetadata {
4102 title: Some("Test".to_string()),
4103 version: "1.4".to_string(),
4104 ..Default::default()
4105 };
4106
4107 let cloned = metadata;
4108 assert_eq!(cloned.title, Some("Test".to_string()));
4109 assert_eq!(cloned.version, "1.4".to_string());
4110 }
4111
4112 #[test]
4113 fn test_reader_trailer_validation_error() {
4114 let bad_pdf = b"%PDF-1.4
41161 0 obj
4117<< /Type /Catalog >>
4118endobj
4119xref
41200 2
41210000000000 65535 f
41220000000009 00000 n
4123trailer
4124<< /Size 2 >>
4125startxref
412646
4127%%EOF"
4128 .to_vec();
4129
4130 let cursor = Cursor::new(bad_pdf.clone());
4134 let mut reader = PdfReader::new(cursor).expect("missing /Root recovered by object scan");
4135 let catalog = reader
4136 .catalog()
4137 .expect("catalog resolved from scanned /Root");
4138 assert_eq!(
4139 catalog.get("Type"),
4140 Some(&PdfObject::Name(PdfName("Catalog".to_string()))),
4141 );
4142
4143 let cursor = Cursor::new(bad_pdf);
4145 assert!(PdfReader::new_with_options(cursor, ParseOptions::strict()).is_err());
4146 }
4147
4148 #[test]
4149 fn test_reader_with_options() {
4150 let pdf_data = create_minimal_pdf();
4151 let cursor = Cursor::new(pdf_data);
4152 let mut options = ParseOptions::default();
4153 options.lenient_streams = true;
4154 options.max_recovery_bytes = 2000;
4155 options.collect_warnings = true;
4156
4157 let reader = PdfReader::new_with_options(cursor, options);
4158 assert!(reader.is_ok());
4159 }
4160
4161 #[test]
4162 fn test_lenient_stream_parsing() {
4163 let pdf_data = b"%PDF-1.4
41651 0 obj
4166<< /Type /Catalog /Pages 2 0 R >>
4167endobj
41682 0 obj
4169<< /Type /Pages /Kids [3 0 R] /Count 1 >>
4170endobj
41713 0 obj
4172<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] /Contents 4 0 R >>
4173endobj
41744 0 obj
4175<< /Length 10 >>
4176stream
4177This is a longer stream than 10 bytes
4178endstream
4179endobj
4180xref
41810 5
41820000000000 65535 f
41830000000009 00000 n
41840000000058 00000 n
41850000000116 00000 n
41860000000219 00000 n
4187trailer
4188<< /Size 5 /Root 1 0 R >>
4189startxref
4190299
4191%%EOF"
4192 .to_vec();
4193
4194 let cursor = Cursor::new(pdf_data.clone());
4196 let strict_options = ParseOptions::strict();
4197 let strict_reader = PdfReader::new_with_options(cursor, strict_options);
4198 assert!(strict_reader.is_err());
4200
4201 let cursor = Cursor::new(pdf_data);
4205 let mut options = ParseOptions::default();
4206 options.lenient_streams = true;
4207 options.max_recovery_bytes = 1000;
4208 options.collect_warnings = false;
4209 let mut reader =
4210 PdfReader::new_with_options(cursor, options).expect("mismatched xref reconstructed");
4211 let catalog = reader
4212 .catalog()
4213 .expect("catalog resolves after reconstruction");
4214 assert_eq!(
4215 catalog.get("Type"),
4216 Some(&PdfObject::Name(PdfName("Catalog".to_string()))),
4217 );
4218 }
4219
4220 #[test]
4221 fn test_parse_options_default() {
4222 let options = ParseOptions::default();
4223 assert!(!options.lenient_streams);
4224 assert_eq!(options.max_recovery_bytes, 1000);
4225 assert!(!options.collect_warnings);
4226 }
4227
4228 #[test]
4229 fn test_parse_options_clone() {
4230 let mut options = ParseOptions::default();
4231 options.lenient_streams = true;
4232 options.max_recovery_bytes = 2000;
4233 options.collect_warnings = true;
4234 let cloned = options;
4235 assert!(cloned.lenient_streams);
4236 assert_eq!(cloned.max_recovery_bytes, 2000);
4237 assert!(cloned.collect_warnings);
4238 }
4239
4240 #[allow(dead_code)]
4243 fn create_encrypted_pdf_dict() -> PdfDictionary {
4244 let mut dict = PdfDictionary::new();
4245 dict.insert(
4246 "Filter".to_string(),
4247 PdfObject::Name(PdfName("Standard".to_string())),
4248 );
4249 dict.insert("V".to_string(), PdfObject::Integer(1));
4250 dict.insert("R".to_string(), PdfObject::Integer(2));
4251 dict.insert("O".to_string(), PdfObject::String(PdfString(vec![0u8; 32])));
4252 dict.insert("U".to_string(), PdfObject::String(PdfString(vec![0u8; 32])));
4253 dict.insert("P".to_string(), PdfObject::Integer(-4));
4254 dict
4255 }
4256
4257 fn create_pdf_with_encryption() -> Vec<u8> {
4258 b"%PDF-1.4
42601 0 obj
4261<< /Type /Catalog /Pages 2 0 R >>
4262endobj
42632 0 obj
4264<< /Type /Pages /Kids [3 0 R] /Count 1 >>
4265endobj
42663 0 obj
4267<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] >>
4268endobj
42694 0 obj
4270<< /Filter /Standard /V 1 /R 2 /O (32 bytes of owner password hash data) /U (32 bytes of user password hash data) /P -4 >>
4271endobj
4272xref
42730 5
42740000000000 65535 f
42750000000009 00000 n
42760000000058 00000 n
42770000000116 00000 n
42780000000201 00000 n
4279trailer
4280<< /Size 5 /Root 1 0 R /Encrypt 4 0 R /ID [(file id)] >>
4281startxref
4282295
4283%%EOF"
4284 .to_vec()
4285 }
4286
4287 #[test]
4288 fn test_reader_encryption_detection() {
4289 let unencrypted_pdf = create_minimal_pdf();
4291 let cursor = Cursor::new(unencrypted_pdf);
4292 let reader = PdfReader::new(cursor).unwrap();
4293 assert!(!reader.is_encrypted());
4294 assert!(reader.is_unlocked()); let encrypted_pdf = create_pdf_with_encryption();
4302 let cursor = Cursor::new(encrypted_pdf);
4303 let reader = PdfReader::new(cursor).expect("encrypted PDF opens as locked, not rejected");
4304 assert!(
4305 reader.is_encrypted(),
4306 "must report encrypted, not plaintext"
4307 );
4308 assert!(
4309 !reader.is_unlocked(),
4310 "must stay locked without a valid password"
4311 );
4312 }
4313
4314 #[test]
4315 fn test_reader_encryption_methods_unencrypted() {
4316 let pdf_data = create_minimal_pdf();
4317 let cursor = Cursor::new(pdf_data);
4318 let mut reader = PdfReader::new(cursor).unwrap();
4319
4320 assert!(!reader.is_encrypted());
4322 assert!(reader.is_unlocked());
4323 assert!(reader.encryption_handler().is_none());
4324 assert!(reader.encryption_handler_mut().is_none());
4325
4326 assert!(reader.unlock_with_password("any_password").unwrap());
4328 assert!(reader.try_empty_password().unwrap());
4329 }
4330
4331 #[test]
4332 fn test_reader_encryption_handler_access() {
4333 let pdf_data = create_minimal_pdf();
4334 let cursor = Cursor::new(pdf_data);
4335 let mut reader = PdfReader::new(cursor).unwrap();
4336
4337 assert!(reader.encryption_handler().is_none());
4339 assert!(reader.encryption_handler_mut().is_none());
4340
4341 assert!(!reader.is_encrypted());
4343 assert!(reader.is_unlocked());
4344 }
4345
4346 #[test]
4347 fn test_reader_multiple_password_attempts() {
4348 let pdf_data = create_minimal_pdf();
4349 let cursor = Cursor::new(pdf_data);
4350 let mut reader = PdfReader::new(cursor).unwrap();
4351
4352 let passwords = vec!["test1", "test2", "admin", "", "password"];
4354 for password in passwords {
4355 assert!(reader.unlock_with_password(password).unwrap());
4356 }
4357
4358 for _ in 0..5 {
4360 assert!(reader.try_empty_password().unwrap());
4361 }
4362 }
4363
4364 #[test]
4365 fn test_reader_encryption_state_consistency() {
4366 let pdf_data = create_minimal_pdf();
4367 let cursor = Cursor::new(pdf_data);
4368 let mut reader = PdfReader::new(cursor).unwrap();
4369
4370 assert!(!reader.is_encrypted());
4372 assert!(reader.is_unlocked());
4373 assert!(reader.encryption_handler().is_none());
4374
4375 let _ = reader.unlock_with_password("test");
4377 assert!(!reader.is_encrypted());
4378 assert!(reader.is_unlocked());
4379 assert!(reader.encryption_handler().is_none());
4380
4381 let _ = reader.try_empty_password();
4382 assert!(!reader.is_encrypted());
4383 assert!(reader.is_unlocked());
4384 assert!(reader.encryption_handler().is_none());
4385 }
4386
4387 #[test]
4388 fn test_reader_encryption_error_handling() {
4389 let encrypted_pdf = create_pdf_with_encryption();
4394 let cursor = Cursor::new(encrypted_pdf);
4395
4396 match PdfReader::new(cursor) {
4397 Err(_) => {}
4399 Ok(reader) => {
4402 assert!(
4403 reader.is_encrypted() && !reader.is_unlocked(),
4404 "recovered encrypted PDF must stay encrypted+locked, not open as plaintext"
4405 );
4406 }
4407 }
4408 }
4409
4410 #[test]
4411 fn test_reader_encryption_with_options() {
4412 let pdf_data = create_minimal_pdf();
4413 let cursor = Cursor::new(pdf_data);
4414
4415 let strict_options = ParseOptions::strict();
4417 let strict_reader = PdfReader::new_with_options(cursor, strict_options).unwrap();
4418 assert!(!strict_reader.is_encrypted());
4419 assert!(strict_reader.is_unlocked());
4420
4421 let pdf_data = create_minimal_pdf();
4422 let cursor = Cursor::new(pdf_data);
4423 let lenient_options = ParseOptions::lenient();
4424 let lenient_reader = PdfReader::new_with_options(cursor, lenient_options).unwrap();
4425 assert!(!lenient_reader.is_encrypted());
4426 assert!(lenient_reader.is_unlocked());
4427 }
4428
4429 #[test]
4430 fn test_reader_encryption_integration_edge_cases() {
4431 let pdf_data = create_minimal_pdf();
4432 let cursor = Cursor::new(pdf_data);
4433 let mut reader = PdfReader::new(cursor).unwrap();
4434
4435 assert!(reader.unlock_with_password("").unwrap());
4437 assert!(reader.unlock_with_password(" ").unwrap()); assert!(reader
4439 .unlock_with_password("very_long_password_that_exceeds_normal_length")
4440 .unwrap());
4441 assert!(reader.unlock_with_password("unicode_test_ñáéíóú").unwrap());
4442
4443 assert!(reader.unlock_with_password("pass@#$%^&*()").unwrap());
4445 assert!(reader.unlock_with_password("pass\nwith\nnewlines").unwrap());
4446 assert!(reader.unlock_with_password("pass\twith\ttabs").unwrap());
4447 }
4448
4449 mod rigorous {
4450 use super::*;
4451
4452 #[test]
4457 fn test_reader_invalid_pdf_header() {
4458 let invalid_data = b"This is not a PDF file";
4460 let cursor = Cursor::new(invalid_data.to_vec());
4461 let result = PdfReader::new(cursor);
4462
4463 assert!(result.is_err(), "Should fail on invalid PDF header");
4464 }
4465
4466 #[test]
4467 fn test_reader_truncated_header() {
4468 let truncated = b"%PDF";
4470 let cursor = Cursor::new(truncated.to_vec());
4471 let result = PdfReader::new(cursor);
4472
4473 assert!(result.is_err(), "Should fail on truncated header");
4474 }
4475
4476 #[test]
4477 fn test_reader_empty_file() {
4478 let empty = Vec::new();
4479 let cursor = Cursor::new(empty);
4480 let result = PdfReader::new(cursor);
4481
4482 assert!(result.is_err(), "Should fail on empty file");
4483 }
4484
4485 #[test]
4486 fn test_reader_malformed_version() {
4487 let malformed = b"%PDF-X.Y\n%%\xE2\xE3\xCF\xD3\n";
4489 let cursor = Cursor::new(malformed.to_vec());
4490 let result = PdfReader::new(cursor);
4491
4492 if let Ok(reader) = result {
4494 let _version = reader.version();
4496 }
4497 }
4498
4499 #[test]
4500 fn test_reader_get_nonexistent_object() {
4501 let pdf_data = create_minimal_pdf();
4502 let cursor = Cursor::new(pdf_data);
4503 let mut reader = PdfReader::new(cursor).unwrap();
4504
4505 let result = reader.get_object(999, 0);
4507
4508 assert!(result.is_err(), "Should fail when object doesn't exist");
4509 }
4510
4511 #[test]
4512 fn test_reader_get_object_wrong_generation() {
4513 let pdf_data = create_minimal_pdf();
4514 let cursor = Cursor::new(pdf_data);
4515 let mut reader = PdfReader::new(cursor).unwrap();
4516
4517 let result = reader.get_object(1, 99);
4519
4520 if let Err(e) = result {
4522 let _ = e;
4524 }
4525 }
4526
4527 #[test]
4532 fn test_resolve_direct_object() {
4533 let pdf_data = create_minimal_pdf();
4534 let cursor = Cursor::new(pdf_data);
4535 let mut reader = PdfReader::new(cursor).unwrap();
4536
4537 let direct_obj = PdfObject::Integer(42);
4539
4540 let resolved = reader.resolve(&direct_obj).unwrap();
4541
4542 assert_eq!(resolved, &PdfObject::Integer(42));
4544 }
4545
4546 #[test]
4547 fn test_resolve_reference() {
4548 let pdf_data = create_minimal_pdf();
4549 let cursor = Cursor::new(pdf_data);
4550 let mut reader = PdfReader::new(cursor).unwrap();
4551
4552 let pages_ref = {
4554 let catalog = reader.catalog().unwrap();
4555 if let Some(PdfObject::Reference(obj_num, gen_num)) = catalog.get("Pages") {
4556 PdfObject::Reference(*obj_num, *gen_num)
4557 } else {
4558 panic!("Catalog /Pages must be a Reference");
4559 }
4560 };
4561
4562 let resolved = reader.resolve(&pages_ref).unwrap();
4564
4565 if let PdfObject::Dictionary(dict) = resolved {
4567 assert_eq!(
4568 dict.get("Type"),
4569 Some(&PdfObject::Name(PdfName("Pages".to_string())))
4570 );
4571 } else {
4572 panic!("Expected dictionary, got: {:?}", resolved);
4573 }
4574 }
4575
4576 #[test]
4581 fn test_is_encrypted_on_unencrypted() {
4582 let pdf_data = create_minimal_pdf();
4583 let cursor = Cursor::new(pdf_data);
4584 let reader = PdfReader::new(cursor).unwrap();
4585
4586 assert!(
4587 !reader.is_encrypted(),
4588 "Minimal PDF should not be encrypted"
4589 );
4590 }
4591
4592 #[test]
4593 fn test_is_unlocked_on_unencrypted() {
4594 let pdf_data = create_minimal_pdf();
4595 let cursor = Cursor::new(pdf_data);
4596 let reader = PdfReader::new(cursor).unwrap();
4597
4598 assert!(reader.is_unlocked(), "Unencrypted PDF should be unlocked");
4600 }
4601
4602 #[test]
4603 fn test_try_empty_password_on_unencrypted() {
4604 let pdf_data = create_minimal_pdf();
4605 let cursor = Cursor::new(pdf_data);
4606 let mut reader = PdfReader::new(cursor).unwrap();
4607
4608 let result = reader.try_empty_password();
4610 assert!(result.is_ok());
4611 }
4612
4613 #[test]
4618 fn test_reader_with_strict_options() {
4619 let pdf_data = create_minimal_pdf();
4620 let cursor = Cursor::new(pdf_data);
4621
4622 let options = ParseOptions::strict();
4623 let result = PdfReader::new_with_options(cursor, options);
4624
4625 assert!(result.is_ok(), "Minimal PDF should parse in strict mode");
4626 }
4627
4628 #[test]
4629 fn test_reader_with_lenient_options() {
4630 let pdf_data = create_minimal_pdf();
4631 let cursor = Cursor::new(pdf_data);
4632
4633 let options = ParseOptions::lenient();
4634 let result = PdfReader::new_with_options(cursor, options);
4635
4636 assert!(result.is_ok(), "Minimal PDF should parse in lenient mode");
4637 }
4638
4639 #[test]
4640 fn test_reader_options_accessible() {
4641 let pdf_data = create_minimal_pdf();
4642 let cursor = Cursor::new(pdf_data);
4643
4644 let options = ParseOptions::lenient();
4645 let reader = PdfReader::new_with_options(cursor, options.clone()).unwrap();
4646
4647 let reader_options = reader.options();
4649 assert_eq!(reader_options.strict_mode, options.strict_mode);
4650 }
4651
4652 #[test]
4657 fn test_catalog_has_required_fields() {
4658 let pdf_data = create_minimal_pdf();
4659 let cursor = Cursor::new(pdf_data);
4660 let mut reader = PdfReader::new(cursor).unwrap();
4661
4662 let catalog = reader.catalog().unwrap();
4663
4664 assert_eq!(
4666 catalog.get("Type"),
4667 Some(&PdfObject::Name(PdfName("Catalog".to_string()))),
4668 "Catalog must have /Type /Catalog"
4669 );
4670
4671 assert!(
4673 catalog.contains_key("Pages"),
4674 "Catalog must have /Pages entry"
4675 );
4676 }
4677
4678 #[test]
4679 fn test_info_fields_when_present() {
4680 let pdf_data = create_pdf_with_info();
4681 let cursor = Cursor::new(pdf_data);
4682 let mut reader = PdfReader::new(cursor).unwrap();
4683
4684 let info = reader.info().unwrap();
4685 assert!(info.is_some(), "PDF should have Info dictionary");
4686
4687 let info_dict = info.unwrap();
4688
4689 assert!(info_dict.contains_key("Title"), "Info should have Title");
4691 assert!(info_dict.contains_key("Author"), "Info should have Author");
4692 }
4693
4694 #[test]
4695 fn test_info_none_when_absent() {
4696 let pdf_data = create_minimal_pdf();
4697 let cursor = Cursor::new(pdf_data);
4698 let mut reader = PdfReader::new(cursor).unwrap();
4699
4700 let info = reader.info().unwrap();
4701 assert!(info.is_none(), "Minimal PDF should not have Info");
4702 }
4703
4704 #[test]
4709 fn test_version_exact_values() {
4710 let pdf_data = create_pdf_with_version("1.7");
4711 let cursor = Cursor::new(pdf_data);
4712 let reader = PdfReader::new(cursor).unwrap();
4713
4714 let version = reader.version();
4715 assert_eq!(version.major, 1, "Major version must be exact");
4716 assert_eq!(version.minor, 7, "Minor version must be exact");
4717 }
4718
4719 #[test]
4720 fn test_version_pdf_20() {
4721 let pdf_data = create_pdf_with_version("2.0");
4722 let cursor = Cursor::new(pdf_data);
4723 let reader = PdfReader::new(cursor).unwrap();
4724
4725 let version = reader.version();
4726 assert_eq!(version.major, 2, "PDF 2.0 major version");
4727 assert_eq!(version.minor, 0, "PDF 2.0 minor version");
4728 }
4729
4730 #[test]
4735 fn test_pages_returns_pages_dict() {
4736 let pdf_data = create_minimal_pdf();
4737 let cursor = Cursor::new(pdf_data);
4738 let mut reader = PdfReader::new(cursor).unwrap();
4739
4740 let pages_dict = reader
4741 .pages()
4742 .expect("pages() must return Pages dictionary");
4743
4744 assert_eq!(
4745 pages_dict.get("Type"),
4746 Some(&PdfObject::Name(PdfName("Pages".to_string()))),
4747 "Pages dict must have /Type /Pages"
4748 );
4749 }
4750
4751 #[test]
4752 fn test_page_count_minimal_pdf() {
4753 let pdf_data = create_minimal_pdf();
4754 let cursor = Cursor::new(pdf_data);
4755 let mut reader = PdfReader::new(cursor).unwrap();
4756
4757 let count = reader.page_count().expect("page_count() must succeed");
4758 assert_eq!(count, 0, "Minimal PDF has 0 pages");
4759 }
4760
4761 #[test]
4762 fn test_page_count_with_info_pdf() {
4763 let pdf_data = create_pdf_with_info();
4764 let cursor = Cursor::new(pdf_data);
4765 let mut reader = PdfReader::new(cursor).unwrap();
4766
4767 let count = reader.page_count().expect("page_count() must succeed");
4768 assert_eq!(count, 0, "create_pdf_with_info() has Count 0 in Pages dict");
4769 }
4770
4771 #[test]
4776 fn test_metadata_minimal_pdf() {
4777 let pdf_data = create_minimal_pdf();
4778 let cursor = Cursor::new(pdf_data);
4779 let mut reader = PdfReader::new(cursor).unwrap();
4780
4781 let meta = reader.metadata().expect("metadata() must succeed");
4782
4783 assert!(meta.title.is_none(), "Minimal PDF has no title");
4785 assert!(meta.author.is_none(), "Minimal PDF has no author");
4786 }
4787
4788 #[test]
4789 fn test_metadata_with_info() {
4790 let pdf_data = create_pdf_with_info();
4791 let cursor = Cursor::new(pdf_data);
4792 let mut reader = PdfReader::new(cursor).unwrap();
4793
4794 let meta = reader.metadata().expect("metadata() must succeed");
4795
4796 assert!(meta.title.is_some(), "PDF with Info has title");
4797 assert_eq!(meta.title.unwrap(), "Test PDF", "Title must match");
4798 assert!(meta.author.is_some(), "PDF with Info has author");
4799 assert_eq!(meta.author.unwrap(), "Test Author", "Author must match");
4800 }
4801
4802 #[test]
4807 fn test_resolve_stream_length_direct_integer() {
4808 let pdf_data = create_minimal_pdf();
4809 let cursor = Cursor::new(pdf_data);
4810 let mut reader = PdfReader::new(cursor).unwrap();
4811
4812 let length_obj = PdfObject::Integer(100);
4814
4815 let length = reader
4816 .resolve_stream_length(&length_obj)
4817 .expect("resolve_stream_length must succeed");
4818 assert_eq!(length, Some(100), "Direct integer must be resolved");
4819 }
4820
4821 #[test]
4822 fn test_resolve_stream_length_negative_integer() {
4823 let pdf_data = create_minimal_pdf();
4824 let cursor = Cursor::new(pdf_data);
4825 let mut reader = PdfReader::new(cursor).unwrap();
4826
4827 let length_obj = PdfObject::Integer(-10);
4829
4830 let length = reader
4831 .resolve_stream_length(&length_obj)
4832 .expect("resolve_stream_length must succeed");
4833 assert_eq!(length, None, "Negative integer returns None");
4834 }
4835
4836 #[test]
4837 fn test_resolve_stream_length_non_integer() {
4838 let pdf_data = create_minimal_pdf();
4839 let cursor = Cursor::new(pdf_data);
4840 let mut reader = PdfReader::new(cursor).unwrap();
4841
4842 let name_obj = PdfObject::Name(PdfName("Test".to_string()));
4844
4845 let length = reader
4846 .resolve_stream_length(&name_obj)
4847 .expect("resolve_stream_length must succeed");
4848 assert_eq!(length, None, "Non-integer object returns None");
4849 }
4850
4851 #[test]
4856 fn test_get_all_pages_empty_pdf() {
4857 let pdf_data = create_minimal_pdf();
4858 let cursor = Cursor::new(pdf_data);
4859 let mut reader = PdfReader::new(cursor).unwrap();
4860
4861 let pages = reader
4862 .get_all_pages()
4863 .expect("get_all_pages() must succeed");
4864 assert_eq!(pages.len(), 0, "Minimal PDF has 0 pages");
4865 }
4866
4867 #[test]
4868 fn test_get_all_pages_with_info() {
4869 let pdf_data = create_pdf_with_info();
4870 let cursor = Cursor::new(pdf_data);
4871 let mut reader = PdfReader::new(cursor).unwrap();
4872
4873 let pages = reader
4874 .get_all_pages()
4875 .expect("get_all_pages() must succeed");
4876 assert_eq!(
4877 pages.len(),
4878 0,
4879 "create_pdf_with_info() has 0 pages (Count 0)"
4880 );
4881 }
4882
4883 #[test]
4888 fn test_into_document_consumes_reader() {
4889 let pdf_data = create_minimal_pdf();
4890 let cursor = Cursor::new(pdf_data);
4891 let reader = PdfReader::new(cursor).unwrap();
4892
4893 let document = reader.into_document();
4894
4895 let version = document.version().expect("Document must have version");
4897 assert!(
4898 version.starts_with("1."),
4899 "Document must have PDF 1.x version, got: {}",
4900 version
4901 );
4902
4903 let page_count = document
4905 .page_count()
4906 .expect("Document must allow page_count()");
4907 assert_eq!(
4908 page_count, 0,
4909 "Minimal PDF has 0 pages (Count 0 in test helper)"
4910 );
4911 }
4912
4913 #[test]
4918 fn test_clear_parse_context() {
4919 let pdf_data = create_minimal_pdf();
4920 let cursor = Cursor::new(pdf_data);
4921 let mut reader = PdfReader::new(cursor).unwrap();
4922
4923 reader.clear_parse_context();
4925
4926 let version = reader.version();
4928 assert_eq!(version.major, 1, "Reader must still work after clear");
4929 }
4930
4931 #[test]
4932 fn test_parse_context_mut_accessible() {
4933 let pdf_data = create_minimal_pdf();
4934 let cursor = Cursor::new(pdf_data);
4935 let mut reader = PdfReader::new(cursor).unwrap();
4936
4937 let context = reader.parse_context_mut();
4938
4939 let initial_depth = context.depth;
4941 assert_eq!(initial_depth, 0, "Parse context must start with depth 0");
4942
4943 assert!(
4945 context.max_depth > 0,
4946 "Parse context must have positive max_depth"
4947 );
4948 }
4949
4950 #[test]
4955 fn test_find_byte_pattern_basic() {
4956 let haystack = b"Hello World";
4957 let needle = b"World";
4958 let pos = find_byte_pattern(haystack, needle);
4959 assert_eq!(pos, Some(6), "Must find 'World' at position 6");
4960 }
4961
4962 #[test]
4963 fn test_find_byte_pattern_not_found() {
4964 let haystack = b"Hello World";
4965 let needle = b"Rust";
4966 let pos = find_byte_pattern(haystack, needle);
4967 assert_eq!(pos, None, "Must return None when not found");
4968 }
4969
4970 #[test]
4971 fn test_find_byte_pattern_at_start() {
4972 let haystack = b"Hello World";
4973 let needle = b"Hello";
4974 let pos = find_byte_pattern(haystack, needle);
4975 assert_eq!(pos, Some(0), "Must find at position 0");
4976 }
4977
4978 #[test]
4979 fn test_is_immediate_stream_start_with_stream() {
4980 let data = b"stream\ndata";
4981 assert!(
4982 is_immediate_stream_start(data),
4983 "Must detect 'stream' at start"
4984 );
4985 }
4986
4987 #[test]
4988 fn test_is_immediate_stream_start_with_whitespace() {
4989 let data = b" \n\tstream\ndata";
4990 assert!(
4991 is_immediate_stream_start(data),
4992 "Must detect 'stream' after whitespace"
4993 );
4994 }
4995
4996 #[test]
4997 fn test_is_immediate_stream_start_no_stream() {
4998 let data = b"endobj";
4999 assert!(
5000 !is_immediate_stream_start(data),
5001 "Must return false when 'stream' absent"
5002 );
5003 }
5004 }
5005}