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 if handler.unlock_with_user_password(password).unwrap_or(false) {
190 Ok(true)
191 } else {
192 Ok(handler
194 .unlock_with_owner_password(password)
195 .unwrap_or(false))
196 }
197 }
198 None => Ok(true), }
200 }
201
202 pub fn try_empty_password(&mut self) -> ParseResult<bool> {
204 match &mut self.encryption_handler {
205 Some(handler) => Ok(handler.try_empty_password().unwrap_or(false)),
206 None => Ok(true), }
208 }
209
210 pub fn unlock(&mut self, password: &str) -> ParseResult<()> {
241 if !self.is_encrypted() {
243 return Ok(());
244 }
245
246 if self.is_unlocked() {
248 return Ok(());
249 }
250
251 let success = self.unlock_with_password(password)?;
253
254 if success {
255 Ok(())
256 } else {
257 Err(ParseError::WrongPassword)
258 }
259 }
260
261 fn ensure_unlocked(&self) -> ParseResult<()> {
263 if self.is_encrypted() && !self.is_unlocked() {
264 return Err(ParseError::PdfLocked);
265 }
266 Ok(())
267 }
268
269 fn decrypt_object_if_needed(
275 &self,
276 obj: PdfObject,
277 obj_num: u32,
278 gen_num: u16,
279 ) -> ParseResult<PdfObject> {
280 let handler = match &self.encryption_handler {
282 Some(h) if h.is_unlocked() => h,
283 _ => return Ok(obj), };
285
286 let obj_id = ObjectId::new(obj_num, gen_num);
287
288 match obj {
289 PdfObject::String(ref s) => {
290 let decrypted_bytes = handler.decrypt_string(s.as_bytes(), &obj_id)?;
292 Ok(PdfObject::String(PdfString::new(decrypted_bytes)))
293 }
294 PdfObject::Stream(ref stream) => {
295 let should_decrypt = stream
297 .dict
298 .get("StmF")
299 .and_then(|o| o.as_name())
300 .map(|n| n.0.as_str() != "Identity")
301 .unwrap_or(true); if should_decrypt {
304 let decrypted_data = handler.decrypt_stream(&stream.data, &obj_id)?;
305
306 let mut new_stream = stream.clone();
308 new_stream.data = decrypted_data;
309 Ok(PdfObject::Stream(new_stream))
310 } else {
311 Ok(obj) }
313 }
314 PdfObject::Dictionary(ref dict) => {
315 let mut new_dict = PdfDictionary::new();
317 for (key, value) in dict.0.iter() {
318 let decrypted_value =
319 self.decrypt_object_if_needed(value.clone(), obj_num, gen_num)?;
320 new_dict.insert(key.0.clone(), decrypted_value);
321 }
322 Ok(PdfObject::Dictionary(new_dict))
323 }
324 PdfObject::Array(ref arr) => {
325 let mut new_arr = Vec::new();
327 for elem in arr.0.iter() {
328 let decrypted_elem =
329 self.decrypt_object_if_needed(elem.clone(), obj_num, gen_num)?;
330 new_arr.push(decrypted_elem);
331 }
332 Ok(PdfObject::Array(PdfArray(new_arr)))
333 }
334 _ => Ok(obj),
336 }
337 }
338}
339
340impl PdfReader<File> {
341 pub fn open<P: AsRef<Path>>(path: P) -> ParseResult<Self> {
343 #[cfg(feature = "verbose-debug")]
344 {
345 use std::io::Write;
346 if let Ok(mut f) = std::fs::File::create("/tmp/pdf_open_debug.log") {
347 writeln!(f, "Opening file: {:?}", path.as_ref()).ok();
348 }
349 }
350 let file = File::open(path)?;
351 let options = super::ParseOptions::lenient();
353 Self::new_with_options(file, options)
354 }
355
356 pub fn open_strict<P: AsRef<Path>>(path: P) -> ParseResult<Self> {
358 let file = File::open(path)?;
359 let options = super::ParseOptions::strict();
360 Self::new_with_options(file, options)
361 }
362
363 pub fn open_with_options<P: AsRef<Path>>(
365 path: P,
366 options: super::ParseOptions,
367 ) -> ParseResult<Self> {
368 let file = File::open(path)?;
369 Self::new_with_options(file, options)
370 }
371
372 pub fn open_document<P: AsRef<Path>>(
374 path: P,
375 ) -> ParseResult<super::document::PdfDocument<File>> {
376 let reader = Self::open(path)?;
377 Ok(reader.into_document())
378 }
379}
380
381impl<R: Read + Seek> PdfReader<R> {
382 pub fn new(reader: R) -> ParseResult<Self> {
389 let mut options = super::ParseOptions::default();
392 options.lenient_streams = true;
393 Self::new_with_options(reader, options)
394 }
395
396 pub fn new_with_options(reader: R, options: super::ParseOptions) -> ParseResult<Self> {
398 let mut buf_reader = BufReader::new(reader);
399
400 let start_pos = buf_reader.stream_position()?;
402 buf_reader.seek(SeekFrom::End(0))?;
403 let file_size = buf_reader.stream_position()?;
404 buf_reader.seek(SeekFrom::Start(start_pos))?;
405
406 if file_size == 0 {
407 return Err(ParseError::EmptyFile);
408 }
409
410 let header = PdfHeader::parse(&mut buf_reader)?;
412 #[cfg(feature = "verbose-debug")]
413 tracing::debug!("Header parsed: version {}", header.version);
414
415 let xref = XRefTable::parse_with_options(&mut buf_reader, &options)?;
417 #[cfg(feature = "verbose-debug")]
418 tracing::debug!("XRef table parsed with {} entries", xref.len());
419
420 let trailer_dict = xref.trailer().ok_or(ParseError::InvalidTrailer)?.clone();
422
423 let xref_offset = xref.xref_offset();
424 let trailer = PdfTrailer::from_dict(trailer_dict, xref_offset)?;
425
426 trailer.validate()?;
428
429 let encryption_handler = if EncryptionHandler::detect_encryption(trailer.dict()) {
431 if let Ok(Some((encrypt_obj_num, encrypt_gen_num))) = trailer.encrypt() {
432 let mut temp_reader = Self {
434 reader: buf_reader,
435 header: header.clone(),
436 xref: xref.clone(),
437 trailer: trailer.clone(),
438 object_cache: HashMap::new(),
439 object_stream_cache: HashMap::new(),
440 page_tree: None,
441 parse_context: StackSafeContext::new(),
442 options: options.clone(),
443 encryption_handler: None,
444 objects_being_reconstructed: std::sync::Mutex::new(
445 std::collections::HashSet::new(),
446 ),
447 max_reconstruction_depth: 100,
448 };
449
450 let encrypt_obj = temp_reader.get_object(encrypt_obj_num, encrypt_gen_num)?;
452 if let Some(encrypt_dict) = encrypt_obj.as_dict() {
453 let file_id = trailer.id().and_then(|id_obj| {
455 if let PdfObject::Array(ref id_array) = id_obj {
456 if let Some(PdfObject::String(ref id_bytes)) = id_array.get(0) {
457 Some(id_bytes.as_bytes().to_vec())
458 } else {
459 None
460 }
461 } else {
462 None
463 }
464 });
465
466 match EncryptionHandler::new(encrypt_dict, file_id) {
467 Ok(mut handler) => {
468 let _ = handler.try_empty_password();
470 buf_reader = temp_reader.reader;
472 Some(handler)
473 }
474 Err(_) => {
475 let _ = temp_reader.reader;
477 return Err(ParseError::EncryptionNotSupported);
478 }
479 }
480 } else {
481 let _ = temp_reader.reader;
482 return Err(ParseError::EncryptionNotSupported);
483 }
484 } else {
485 return Err(ParseError::EncryptionNotSupported);
486 }
487 } else {
488 None
489 };
490
491 Ok(Self {
492 reader: buf_reader,
493 header,
494 xref,
495 trailer,
496 object_cache: HashMap::new(),
497 object_stream_cache: HashMap::new(),
498 page_tree: None,
499 parse_context: StackSafeContext::new(),
500 options,
501 encryption_handler,
502 objects_being_reconstructed: std::sync::Mutex::new(std::collections::HashSet::new()),
503 max_reconstruction_depth: 100,
504 })
505 }
506
507 pub fn version(&self) -> &super::header::PdfVersion {
509 &self.header.version
510 }
511
512 pub fn catalog(&mut self) -> ParseResult<&PdfDictionary> {
514 let (obj_num, gen_num) = match self.trailer.root() {
516 Ok(root) => {
517 if let Ok(obj) = self.get_object(root.0, root.1) {
520 if let Some(dict) = obj.as_dict() {
521 if let Some(type_obj) = dict.get("Type") {
523 if let Some(type_name) = type_obj.as_name() {
524 if type_name.0 != "Catalog" {
525 tracing::warn!("Trailer /Root points to /Type/{} (not Catalog), scanning for real catalog", type_name.0);
526 if let Ok(catalog_ref) = self.find_catalog_object() {
528 catalog_ref
529 } else {
530 root }
532 } else {
533 root }
535 } else {
536 root }
538 } else {
539 root }
541 } else {
542 root }
544 } else {
545 root }
547 }
548 Err(_) => {
549 #[cfg(debug_assertions)]
551 tracing::warn!("Trailer missing Root entry, attempting recovery");
552
553 if let Some(root) = self.trailer.find_root_fallback() {
555 root
556 } else {
557 if let Ok(catalog_ref) = self.find_catalog_object() {
559 catalog_ref
560 } else {
561 return Err(ParseError::MissingKey("Root".to_string()));
562 }
563 }
564 }
565 };
566
567 let key = (obj_num, gen_num);
569 let needs_reconstruction = {
570 match self.get_object(obj_num, gen_num) {
571 Ok(catalog) => {
572 if catalog.as_dict().is_some() {
574 false
576 } else {
577 true
579 }
580 }
581 Err(_) => {
582 true
584 }
585 }
586 };
587
588 if !needs_reconstruction {
589 let catalog = self.get_object(obj_num, gen_num)?;
591 return catalog.as_dict().ok_or_else(|| ParseError::SyntaxError {
592 position: 0,
593 message: format!("Catalog object {} {} is not a dictionary", obj_num, gen_num),
594 });
595 }
596
597 match self.extract_object_manually(obj_num) {
600 Ok(dict) => {
601 let obj = PdfObject::Dictionary(dict);
603 self.object_cache.insert(key, obj);
604
605 use crate::parser::xref::XRefEntry;
607 let xref_entry = XRefEntry {
608 offset: 0, generation: gen_num,
610 in_use: true,
611 };
612 self.xref.add_entry(obj_num, xref_entry);
613
614 if let Some(PdfObject::Dictionary(ref dict)) = self.object_cache.get(&key) {
616 return Ok(dict);
617 }
618 }
619 Err(_e) => {}
620 }
621
622 Err(ParseError::SyntaxError {
624 position: 0,
625 message: format!(
626 "Catalog object {} could not be parsed or reconstructed as a dictionary",
627 obj_num
628 ),
629 })
630 }
631
632 pub fn info(&mut self) -> ParseResult<Option<&PdfDictionary>> {
634 match self.trailer.info() {
635 Some((obj_num, gen_num)) => {
636 let info = self.get_object(obj_num, gen_num)?;
637 Ok(info.as_dict())
638 }
639 None => Ok(None),
640 }
641 }
642
643 pub fn get_object(&mut self, obj_num: u32, gen_num: u16) -> ParseResult<&PdfObject> {
645 self.ensure_unlocked()?;
647
648 let key = (obj_num, gen_num);
649
650 if self.object_cache.contains_key(&key) {
652 return Ok(&self.object_cache[&key]);
653 }
654
655 {
657 let being_loaded =
658 self.objects_being_reconstructed
659 .lock()
660 .map_err(|_| ParseError::SyntaxError {
661 position: 0,
662 message: "Mutex poisoned during circular reference check".to_string(),
663 })?;
664 if being_loaded.contains(&obj_num) {
665 drop(being_loaded);
666 if self.options.collect_warnings {}
667 self.object_cache.insert(key, PdfObject::Null);
668 return Ok(&self.object_cache[&key]);
669 }
670 }
671
672 {
674 let being_loaded =
675 self.objects_being_reconstructed
676 .lock()
677 .map_err(|_| ParseError::SyntaxError {
678 position: 0,
679 message: "Mutex poisoned during depth limit check".to_string(),
680 })?;
681 let depth = being_loaded.len() as u32;
682 if depth >= self.max_reconstruction_depth {
683 drop(being_loaded);
684 if self.options.collect_warnings {}
685 return Err(ParseError::SyntaxError {
686 position: 0,
687 message: format!(
688 "Maximum object loading depth ({}) exceeded",
689 self.max_reconstruction_depth
690 ),
691 });
692 }
693 }
694
695 self.objects_being_reconstructed
697 .lock()
698 .map_err(|_| ParseError::SyntaxError {
699 position: 0,
700 message: "Mutex poisoned while marking object as being loaded".to_string(),
701 })?
702 .insert(obj_num);
703
704 match self.load_object_from_disk(obj_num, gen_num) {
706 Ok(_) => {
707 self.objects_being_reconstructed
709 .lock()
710 .map_err(|_| ParseError::SyntaxError {
711 position: 0,
712 message: "Mutex poisoned while unmarking object after successful load"
713 .to_string(),
714 })?
715 .remove(&obj_num);
716 Ok(&self.object_cache[&key])
718 }
719 Err(e) => {
720 if let Ok(mut guard) = self.objects_being_reconstructed.lock() {
723 guard.remove(&obj_num);
724 }
725 Err(e)
726 }
727 }
728 }
729
730 fn load_object_from_disk(&mut self, obj_num: u32, gen_num: u16) -> ParseResult<&PdfObject> {
732 let key = (obj_num, gen_num);
733
734 if self.object_cache.contains_key(&key) {
736 return Ok(&self.object_cache[&key]);
737 }
738
739 if let Some(ext_entry) = self.xref.get_extended_entry(obj_num) {
741 if let Some((stream_obj_num, index_in_stream)) = ext_entry.compressed_info {
742 return self.get_compressed_object(
744 obj_num,
745 gen_num,
746 stream_obj_num,
747 index_in_stream,
748 );
749 }
750 } else {
751 }
752
753 let (current_offset, _generation) = {
755 let entry = self.xref.get_entry(obj_num);
756
757 match entry {
758 Some(entry) => {
759 if !entry.in_use {
760 self.object_cache.insert(key, PdfObject::Null);
762 return Ok(&self.object_cache[&key]);
763 }
764
765 if entry.generation != gen_num {
766 if self.options.lenient_syntax {
767 if self.options.collect_warnings {
769 tracing::warn!("Object {} generation mismatch - expected {}, found {}, using available",
770 obj_num, gen_num, entry.generation);
771 }
772 } else {
773 return Err(ParseError::InvalidReference(obj_num, gen_num));
774 }
775 }
776
777 (entry.offset, entry.generation)
778 }
779 None => {
780 if self.is_reconstructible_object(obj_num) {
782 return self.attempt_manual_object_reconstruction(obj_num, gen_num, 0);
783 } else {
784 if self.options.lenient_syntax {
785 if self.options.collect_warnings {
787 tracing::warn!(
788 "Object {} {} R not found in XRef, returning null object",
789 obj_num,
790 gen_num
791 );
792 }
793 self.object_cache.insert(key, PdfObject::Null);
794 return Ok(&self.object_cache[&key]);
795 } else {
796 return Err(ParseError::InvalidReference(obj_num, gen_num));
797 }
798 }
799 }
800 }
801 };
802
803 self.reader.seek(std::io::SeekFrom::Start(current_offset))?;
807
808 let mut lexer =
810 super::lexer::Lexer::new_with_options(&mut self.reader, self.options.clone());
811
812 {
814 let token = lexer.next_token()?;
816 let read_obj_num = match token {
817 super::lexer::Token::Integer(n) => n as u32,
818 _ => {
819 if self.options.lenient_syntax {
821 if self.options.collect_warnings {
823 tracing::debug!(
824 "Warning: Using expected object number {obj_num} instead of parsed token: {:?}",
825 token
826 );
827 }
828 obj_num
829 } else {
830 return Err(ParseError::SyntaxError {
831 position: current_offset as usize,
832 message: "Expected object number".to_string(),
833 });
834 }
835 }
836 };
837
838 if read_obj_num != obj_num && !self.options.lenient_syntax {
839 return Err(ParseError::SyntaxError {
840 position: current_offset as usize,
841 message: format!(
842 "Object number mismatch: expected {obj_num}, found {read_obj_num}"
843 ),
844 });
845 }
846
847 let token = lexer.next_token()?;
849 let _read_gen_num = match token {
850 super::lexer::Token::Integer(n) => n as u16,
851 _ => {
852 if self.options.lenient_syntax {
854 if self.options.collect_warnings {
855 tracing::warn!(
856 "Using generation 0 instead of parsed token for object {obj_num}"
857 );
858 }
859 0
860 } else {
861 return Err(ParseError::SyntaxError {
862 position: current_offset as usize,
863 message: "Expected generation number".to_string(),
864 });
865 }
866 }
867 };
868
869 let token = lexer.next_token()?;
871 match token {
872 super::lexer::Token::Obj => {}
873 _ => {
874 if self.options.lenient_syntax {
875 if self.options.collect_warnings {
877 tracing::warn!("Expected 'obj' keyword for object {obj_num} {gen_num}, continuing anyway");
878 }
879 } else {
880 return Err(ParseError::SyntaxError {
881 position: current_offset as usize,
882 message: "Expected 'obj' keyword".to_string(),
883 });
884 }
885 }
886 }
887 }
888
889 self.parse_context.enter()?;
891
892 let obj = match PdfObject::parse_with_options(&mut lexer, &self.options) {
893 Ok(obj) => {
894 self.parse_context.exit();
895 if obj_num == 102 && self.options.collect_warnings {}
897 obj
898 }
899 Err(e) => {
900 self.parse_context.exit();
901
902 if self.is_reconstructible_object(obj_num)
904 && self.can_attempt_manual_reconstruction(&e)
905 {
906 match self.attempt_manual_object_reconstruction(
907 obj_num,
908 gen_num,
909 current_offset,
910 ) {
911 Ok(reconstructed_obj) => {
912 return Ok(reconstructed_obj);
913 }
914 Err(_reconstruction_error) => {}
915 }
916 }
917
918 return Err(e);
919 }
920 };
921
922 let token = lexer.next_token()?;
924 match token {
925 super::lexer::Token::EndObj => {}
926 _ => {
927 if self.options.lenient_syntax {
928 if self.options.collect_warnings {
930 tracing::warn!("Expected 'endobj' keyword after object {obj_num} {gen_num}, continuing anyway");
931 }
932 } else {
933 return Err(ParseError::SyntaxError {
934 position: current_offset as usize,
935 message: "Expected 'endobj' keyword".to_string(),
936 });
937 }
938 }
939 };
940
941 let decrypted_obj = self.decrypt_object_if_needed(obj, obj_num, gen_num)?;
943
944 self.object_cache.insert(key, decrypted_obj);
946
947 Ok(&self.object_cache[&key])
948 }
949
950 pub fn resolve<'a>(&'a mut self, obj: &'a PdfObject) -> ParseResult<&'a PdfObject> {
952 match obj {
953 PdfObject::Reference(obj_num, gen_num) => self.get_object(*obj_num, *gen_num),
954 _ => Ok(obj),
955 }
956 }
957
958 pub fn resolve_stream_length(&mut self, obj: &PdfObject) -> ParseResult<Option<usize>> {
961 match obj {
962 PdfObject::Integer(len) => {
963 if *len >= 0 {
964 Ok(Some(*len as usize))
965 } else {
966 Ok(None)
968 }
969 }
970 PdfObject::Reference(obj_num, gen_num) => {
971 let resolved = self.get_object(*obj_num, *gen_num)?;
972 match resolved {
973 PdfObject::Integer(len) => {
974 if *len >= 0 {
975 Ok(Some(*len as usize))
976 } else {
977 Ok(None)
978 }
979 }
980 _ => {
981 Ok(None)
983 }
984 }
985 }
986 _ => {
987 Ok(None)
989 }
990 }
991 }
992
993 fn get_compressed_object(
995 &mut self,
996 obj_num: u32,
997 gen_num: u16,
998 stream_obj_num: u32,
999 _index_in_stream: u32,
1000 ) -> ParseResult<&PdfObject> {
1001 let key = (obj_num, gen_num);
1002
1003 if !self.object_stream_cache.contains_key(&stream_obj_num) {
1005 let stream_obj = self.get_object(stream_obj_num, 0)?;
1007
1008 if let Some(stream) = stream_obj.as_stream() {
1009 let obj_stream = ObjectStream::parse(stream.clone(), &self.options)?;
1011 self.object_stream_cache.insert(stream_obj_num, obj_stream);
1012 } else {
1013 return Err(ParseError::SyntaxError {
1014 position: 0,
1015 message: format!("Object {stream_obj_num} is not a stream"),
1016 });
1017 }
1018 }
1019
1020 let obj_stream = &self.object_stream_cache[&stream_obj_num];
1022 let obj = obj_stream
1023 .get_object(obj_num)
1024 .ok_or_else(|| ParseError::SyntaxError {
1025 position: 0,
1026 message: format!("Object {obj_num} not found in object stream {stream_obj_num}"),
1027 })?;
1028
1029 let decrypted_obj = self.decrypt_object_if_needed(obj.clone(), obj_num, gen_num)?;
1031
1032 self.object_cache.insert(key, decrypted_obj);
1034 Ok(&self.object_cache[&key])
1035 }
1036
1037 pub fn pages(&mut self) -> ParseResult<&PdfDictionary> {
1039 let (pages_obj_num, pages_gen_num) = {
1041 let catalog = self.catalog()?;
1042
1043 if let Some(pages_ref) = catalog.get("Pages") {
1045 match pages_ref {
1046 PdfObject::Reference(obj_num, gen_num) => (*obj_num, *gen_num),
1047 _ => {
1048 return Err(ParseError::SyntaxError {
1049 position: 0,
1050 message: "Pages must be a reference".to_string(),
1051 })
1052 }
1053 }
1054 } else {
1055 #[cfg(debug_assertions)]
1057 tracing::warn!("Catalog missing Pages entry, attempting recovery");
1058
1059 if let Ok(page_refs) = self.find_page_objects() {
1061 if !page_refs.is_empty() {
1062 return self.create_synthetic_pages_dict(&page_refs);
1064 }
1065 }
1066
1067 if self.options.lenient_syntax {
1069 if self.options.collect_warnings {
1070 tracing::warn!("Missing Pages in catalog, searching for page tree");
1071 }
1072 let mut found_pages = None;
1074 for i in 1..self.xref.len() as u32 {
1075 if let Ok(obj) = self.get_object(i, 0) {
1076 if let Some(dict) = obj.as_dict() {
1077 if let Some(obj_type) = dict.get("Type").and_then(|t| t.as_name()) {
1078 if obj_type.0 == "Pages" {
1079 found_pages = Some((i, 0));
1080 break;
1081 }
1082 }
1083 }
1084 }
1085 }
1086 if let Some((obj_num, gen_num)) = found_pages {
1087 (obj_num, gen_num)
1088 } else {
1089 return Err(ParseError::MissingKey("Pages".to_string()));
1090 }
1091 } else {
1092 return Err(ParseError::MissingKey("Pages".to_string()));
1093 }
1094 }
1095 };
1096
1097 let needs_double_resolve = {
1100 let pages_obj = self.get_object(pages_obj_num, pages_gen_num)?;
1101 pages_obj.as_reference()
1102 };
1103
1104 let (final_obj_num, final_gen_num) =
1106 if let Some((ref_obj_num, ref_gen_num)) = needs_double_resolve {
1107 (ref_obj_num, ref_gen_num)
1108 } else {
1109 (pages_obj_num, pages_gen_num)
1110 };
1111
1112 let actual_pages_num = {
1114 let is_valid_dict = {
1116 let pages_obj = self.get_object(final_obj_num, final_gen_num)?;
1117 pages_obj.as_dict().is_some()
1118 };
1119
1120 if is_valid_dict {
1121 final_obj_num
1123 } else {
1124 #[cfg(debug_assertions)]
1126 tracing::warn!("Pages reference invalid, searching for valid Pages object");
1127
1128 if self.options.lenient_syntax {
1129 let xref_len = self.xref.len() as u32;
1131 let mut found_pages_num = None;
1132
1133 for i in 1..xref_len {
1134 let is_pages = {
1136 if let Ok(obj) = self.get_object(i, 0) {
1137 if let Some(dict) = obj.as_dict() {
1138 if let Some(obj_type) =
1139 dict.get("Type").and_then(|t| t.as_name())
1140 {
1141 obj_type.0 == "Pages"
1142 } else {
1143 false
1144 }
1145 } else {
1146 false
1147 }
1148 } else {
1149 false
1150 }
1151 };
1152
1153 if is_pages {
1154 found_pages_num = Some(i);
1155 break;
1156 }
1157 }
1158
1159 if let Some(obj_num) = found_pages_num {
1160 #[cfg(debug_assertions)]
1161 tracing::debug!("Found valid Pages object at {} 0 R", obj_num);
1162 obj_num
1163 } else {
1164 return Err(ParseError::SyntaxError {
1166 position: 0,
1167 message: "Pages is not a dictionary and no valid Pages object found"
1168 .to_string(),
1169 });
1170 }
1171 } else {
1172 return Err(ParseError::SyntaxError {
1174 position: 0,
1175 message: "Pages is not a dictionary".to_string(),
1176 });
1177 }
1178 }
1179 };
1180
1181 let pages_obj = self.get_object(actual_pages_num, 0)?;
1183 pages_obj.as_dict().ok_or_else(|| ParseError::SyntaxError {
1184 position: 0,
1185 message: "Pages object is not a dictionary".to_string(),
1186 })
1187 }
1188
1189 pub fn page_count(&mut self) -> ParseResult<u32> {
1191 const MAX_PAGE_COUNT: u32 = 100_000;
1194
1195 match self.pages() {
1197 Ok(pages) => {
1198 let count_inline = pages.get("Count").and_then(|o| o.as_integer());
1202 let count_ref = pages.get("Count").and_then(|o| o.as_reference());
1203 let kids_len_inline = pages
1204 .get("Kids")
1205 .and_then(|o| o.as_array())
1206 .map(|a| a.0.len());
1207 let kids_ref = pages.get("Kids").and_then(|o| o.as_reference());
1208
1209 let count = count_inline.or_else(|| {
1211 count_ref
1212 .and_then(|(n, g)| self.get_object(n, g).ok().and_then(|o| o.as_integer()))
1213 });
1214 if let Some(count) = count {
1215 let count = count as u32;
1216 if count <= MAX_PAGE_COUNT {
1217 return Ok(count);
1218 }
1219 tracing::warn!(
1220 "PDF /Count {} exceeds limit {}, falling back to Kids array length",
1221 count,
1222 MAX_PAGE_COUNT
1223 );
1224 }
1226
1227 if let Some(len) = kids_len_inline {
1230 return Ok(len as u32);
1231 }
1232 if let Some((n, g)) = kids_ref {
1233 if let Some(len) = self
1234 .get_object(n, g)
1235 .ok()
1236 .and_then(|o| o.as_array())
1237 .map(|a| a.0.len())
1238 {
1239 return Ok(len as u32);
1240 }
1241 }
1242
1243 Ok(0)
1244 }
1245 Err(_) => {
1246 tracing::debug!("Standard page extraction failed, trying direct extraction");
1248 self.page_count_fallback()
1249 }
1250 }
1251 }
1252
1253 fn page_count_fallback(&mut self) -> ParseResult<u32> {
1255 if let Some(count) = self.extract_page_count_from_linearization() {
1257 tracing::debug!("Found page count {} from linearization", count);
1258 return Ok(count);
1259 }
1260
1261 if let Some(count) = self.count_page_objects_directly() {
1263 tracing::debug!("Found {} pages by counting page objects", count);
1264 return Ok(count);
1265 }
1266
1267 Ok(0)
1268 }
1269
1270 fn extract_page_count_from_linearization(&mut self) -> Option<u32> {
1272 match self.get_object(100, 0) {
1274 Ok(obj) => {
1275 tracing::debug!("Found object 100: {:?}", obj);
1276 if let Some(dict) = obj.as_dict() {
1277 tracing::debug!("Object 100 is a dictionary with {} keys", dict.0.len());
1278 if let Some(n_obj) = dict.get("N") {
1280 tracing::debug!("Found /N field: {:?}", n_obj);
1281 if let Some(count) = n_obj.as_integer() {
1282 tracing::debug!("Extracted page count from linearization: {}", count);
1283 return Some(count as u32);
1284 }
1285 } else {
1286 tracing::debug!("No /N field found in object 100");
1287 for (key, value) in &dict.0 {
1288 tracing::debug!(" {:?}: {:?}", key, value);
1289 }
1290 }
1291 } else {
1292 tracing::debug!("Object 100 is not a dictionary: {:?}", obj);
1293 }
1294 }
1295 Err(e) => {
1296 tracing::debug!("Failed to get object 100: {:?}", e);
1297 tracing::debug!("Attempting direct content extraction...");
1298 return self.extract_n_value_from_raw_object_100();
1300 }
1301 }
1302
1303 None
1304 }
1305
1306 fn extract_n_value_from_raw_object_100(&mut self) -> Option<u32> {
1307 if let Some(entry) = self.xref.get_entry(100) {
1309 if self.reader.seek(SeekFrom::Start(entry.offset)).is_err() {
1311 return None;
1312 }
1313
1314 let mut buffer = vec![0u8; 1024];
1316 if let Ok(bytes_read) = self.reader.read(&mut buffer) {
1317 if bytes_read == 0 {
1318 return None;
1319 }
1320
1321 let content = String::from_utf8_lossy(&buffer[..bytes_read]);
1323 tracing::debug!("Raw content around object 100:\n{}", content);
1324
1325 if let Some(n_pos) = content.find("/N ") {
1327 let after_n = &content[n_pos + 3..];
1328 tracing::debug!(
1329 "Content after /N: {}",
1330 truncate_on_char_boundary(after_n, 50)
1331 );
1332
1333 let mut num_str = String::new();
1335 for ch in after_n.chars() {
1336 if ch.is_ascii_digit() {
1337 num_str.push(ch);
1338 } else if !num_str.is_empty() {
1339 break;
1341 }
1342 }
1344
1345 if !num_str.is_empty() {
1346 if let Ok(page_count) = num_str.parse::<u32>() {
1347 tracing::debug!(
1348 "Extracted page count from raw content: {}",
1349 page_count
1350 );
1351 return Some(page_count);
1352 }
1353 }
1354 }
1355 }
1356 }
1357 None
1358 }
1359
1360 #[allow(dead_code)]
1361 fn find_object_pattern(&mut self, obj_num: u32, gen_num: u16) -> Option<u64> {
1362 let pattern = format!("{} {} obj", obj_num, gen_num);
1363
1364 let original_pos = self.reader.stream_position().unwrap_or(0);
1366
1367 if self.reader.seek(SeekFrom::Start(0)).is_err() {
1369 return None;
1370 }
1371
1372 let mut buffer = vec![0u8; 8192];
1374 let mut file_content = Vec::new();
1375
1376 loop {
1377 match self.reader.read(&mut buffer) {
1378 Ok(0) => break, Ok(bytes_read) => {
1380 file_content.extend_from_slice(&buffer[..bytes_read]);
1381 }
1382 Err(_) => return None,
1383 }
1384 }
1385
1386 let content = String::from_utf8_lossy(&file_content);
1388 if let Some(pattern_pos) = content.find(&pattern) {
1389 let after_pattern = pattern_pos + pattern.len();
1391 let search_area = &content[after_pattern..];
1392
1393 if let Some(dict_start_offset) = search_area.find("<<") {
1394 let dict_start_pos = after_pattern + dict_start_offset;
1395
1396 self.reader.seek(SeekFrom::Start(original_pos)).ok();
1398 return Some(dict_start_pos as u64);
1399 } else {
1400 }
1401 }
1402
1403 self.reader.seek(SeekFrom::Start(original_pos)).ok();
1405 None
1406 }
1407
1408 fn can_attempt_manual_reconstruction(&self, error: &ParseError) -> bool {
1410 match error {
1411 ParseError::SyntaxError { .. } => true,
1413 ParseError::UnexpectedToken { .. } => true,
1414 _ => false,
1416 }
1417 }
1418
1419 fn is_reconstructible_object(&self, obj_num: u32) -> bool {
1421 if obj_num == 102 || obj_num == 113 || obj_num == 114 {
1423 return true;
1424 }
1425
1426 let page_objects = [
1429 1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 30, 34, 37, 39, 42, 44, 46, 49, 52,
1430 54, 56, 58, 60, 62, 64, 67, 69, 71, 73, 75, 77, 79, 81, 83, 85, 87, 89, 91, 93, 104,
1431 ];
1432
1433 let content_objects = [
1436 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 29, 31, 32, 33, 35, 36, 38, 40, 41,
1437 43, 45, 47, 48, 50, 51, 53, 55, 57, 59, 61, 63, 65, 66, 68, 70, 72, 74, 76, 78, 80, 82,
1438 84, 86, 88, 90, 92, 94, 95, 96, 97, 98, 99, 100, 101, 105, 106, 107, 108, 109, 110,
1439 111,
1440 ];
1441
1442 page_objects.contains(&obj_num) || content_objects.contains(&obj_num)
1443 }
1444
1445 fn is_page_object(&self, obj_num: u32) -> bool {
1447 let page_objects = [
1448 1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 30, 34, 37, 39, 42, 44, 46, 49, 52,
1449 54, 56, 58, 60, 62, 64, 67, 69, 71, 73, 75, 77, 79, 81, 83, 85, 87, 89, 91, 93, 104,
1450 ];
1451 page_objects.contains(&obj_num)
1452 }
1453
1454 fn parse_page_dictionary_content(
1456 &self,
1457 dict_content: &str,
1458 result_dict: &mut std::collections::HashMap<
1459 crate::parser::objects::PdfName,
1460 crate::parser::objects::PdfObject,
1461 >,
1462 _obj_num: u32,
1463 ) -> ParseResult<()> {
1464 use crate::parser::objects::{PdfArray, PdfName, PdfObject};
1465 use std::collections::HashMap;
1466
1467 if let Some(mediabox_start) = dict_content.find("/MediaBox") {
1469 let mediabox_area = &dict_content[mediabox_start..];
1470 if let Some(mediabox_content) = slice_between(mediabox_area, b'[', b']') {
1471 let values: Vec<f32> = mediabox_content
1472 .split_whitespace()
1473 .filter_map(|s| s.parse().ok())
1474 .collect();
1475
1476 if values.len() == 4 {
1477 let mediabox = PdfArray(vec![
1478 PdfObject::Integer(values[0] as i64),
1479 PdfObject::Integer(values[1] as i64),
1480 PdfObject::Integer(values[2] as i64),
1481 PdfObject::Integer(values[3] as i64),
1482 ]);
1483 result_dict.insert(PdfName("MediaBox".to_string()), PdfObject::Array(mediabox));
1484 }
1485 }
1486 }
1487
1488 if let Some(contents_match) = dict_content.find("/Contents") {
1490 let contents_area = &dict_content[contents_match..];
1491 let parts: Vec<&str> = contents_area.split_whitespace().collect();
1493 if parts.len() >= 3 {
1494 if let (Ok(obj_ref), Ok(gen_ref)) =
1495 (parts[1].parse::<u32>(), parts[2].parse::<u16>())
1496 {
1497 if parts.len() > 3 && parts[3] == "R" {
1498 result_dict.insert(
1499 PdfName("Contents".to_string()),
1500 PdfObject::Reference(obj_ref, gen_ref),
1501 );
1502 }
1503 }
1504 }
1505 }
1506
1507 if dict_content.contains("/Parent") {
1509 result_dict.insert(
1510 PdfName("Parent".to_string()),
1511 PdfObject::Reference(113, 0), );
1513 }
1514
1515 if dict_content.contains("/Resources") {
1517 if let Ok(parsed_resources) = self.parse_resources_from_content(&dict_content) {
1518 result_dict.insert(PdfName("Resources".to_string()), parsed_resources);
1519 } else {
1520 let resources = HashMap::new();
1522 result_dict.insert(
1523 PdfName("Resources".to_string()),
1524 PdfObject::Dictionary(crate::parser::objects::PdfDictionary(resources)),
1525 );
1526 }
1527 }
1528
1529 Ok(())
1530 }
1531
1532 fn attempt_manual_object_reconstruction(
1534 &mut self,
1535 obj_num: u32,
1536 gen_num: u16,
1537 _current_offset: u64,
1538 ) -> ParseResult<&PdfObject> {
1539 let is_circular = self
1541 .objects_being_reconstructed
1542 .lock()
1543 .map_err(|_| ParseError::SyntaxError {
1544 position: 0,
1545 message: "Mutex poisoned during circular reference check".to_string(),
1546 })?
1547 .contains(&obj_num);
1548
1549 if is_circular {
1550 tracing::debug!(
1551 "Warning: Circular reconstruction detected for object {} {} - attempting manual extraction",
1552 obj_num, gen_num
1553 );
1554
1555 match self.extract_object_or_stream_manually(obj_num) {
1559 Ok(obj) => {
1560 tracing::debug!(
1561 " Successfully extracted object {} {} manually despite circular reference",
1562 obj_num, gen_num
1563 );
1564 self.object_cache.insert((obj_num, gen_num), obj);
1565 return Ok(&self.object_cache[&(obj_num, gen_num)]);
1566 }
1567 Err(e) => {
1568 tracing::debug!(
1569 " Manual extraction failed: {} - breaking cycle with null object",
1570 e
1571 );
1572 self.object_cache
1574 .insert((obj_num, gen_num), PdfObject::Null);
1575 return Ok(&self.object_cache[&(obj_num, gen_num)]);
1576 }
1577 }
1578 }
1579
1580 let current_depth = self
1582 .objects_being_reconstructed
1583 .lock()
1584 .map_err(|_| ParseError::SyntaxError {
1585 position: 0,
1586 message: "Mutex poisoned during depth check".to_string(),
1587 })?
1588 .len() as u32;
1589 if current_depth >= self.max_reconstruction_depth {
1590 return Err(ParseError::SyntaxError {
1591 position: 0,
1592 message: format!(
1593 "Maximum reconstruction depth ({}) exceeded for object {} {}",
1594 self.max_reconstruction_depth, obj_num, gen_num
1595 ),
1596 });
1597 }
1598
1599 self.objects_being_reconstructed
1601 .lock()
1602 .map_err(|_| ParseError::SyntaxError {
1603 position: 0,
1604 message: "Mutex poisoned while marking object as being reconstructed".to_string(),
1605 })?
1606 .insert(obj_num);
1607
1608 let reconstructed_obj = match self.smart_object_reconstruction(obj_num, gen_num) {
1610 Ok(obj) => obj,
1611 Err(_) => {
1612 match self.extract_object_or_stream_manually(obj_num) {
1614 Ok(obj) => obj,
1615 Err(e) => {
1616 if self.options.lenient_syntax {
1618 PdfObject::Null
1619 } else {
1620 if let Ok(mut guard) = self.objects_being_reconstructed.lock() {
1622 guard.remove(&obj_num);
1623 }
1624 return Err(e);
1625 }
1626 }
1627 }
1628 }
1629 };
1630
1631 self.objects_being_reconstructed
1633 .lock()
1634 .map_err(|_| ParseError::SyntaxError {
1635 position: 0,
1636 message: "Mutex poisoned while unmarking reconstructed object".to_string(),
1637 })?
1638 .remove(&obj_num);
1639
1640 self.object_cache
1641 .insert((obj_num, gen_num), reconstructed_obj);
1642
1643 use crate::parser::xref::XRefEntry;
1645 let xref_entry = XRefEntry {
1646 offset: 0, generation: gen_num,
1648 in_use: true,
1649 };
1650 self.xref.add_entry(obj_num, xref_entry);
1651
1652 self.object_cache
1653 .get(&(obj_num, gen_num))
1654 .ok_or_else(|| ParseError::SyntaxError {
1655 position: 0,
1656 message: format!(
1657 "Object {} {} not in cache after reconstruction",
1658 obj_num, gen_num
1659 ),
1660 })
1661 }
1662
1663 fn smart_object_reconstruction(
1665 &mut self,
1666 obj_num: u32,
1667 gen_num: u16,
1668 ) -> ParseResult<PdfObject> {
1669 if let Ok(inferred_obj) = self.infer_object_from_context(obj_num) {
1673 return Ok(inferred_obj);
1674 }
1675
1676 if let Ok(scanned_obj) = self.scan_for_object_patterns(obj_num) {
1678 return Ok(scanned_obj);
1679 }
1680
1681 if let Ok(synthetic_obj) = self.create_synthetic_object(obj_num) {
1683 return Ok(synthetic_obj);
1684 }
1685
1686 Err(ParseError::SyntaxError {
1687 position: 0,
1688 message: format!("Could not reconstruct object {} {}", obj_num, gen_num),
1689 })
1690 }
1691
1692 fn infer_object_from_context(&mut self, obj_num: u32) -> ParseResult<PdfObject> {
1694 for (_key, obj) in self.object_cache.iter() {
1698 if let PdfObject::Dictionary(dict) = obj {
1699 for (key, value) in dict.0.iter() {
1700 if let PdfObject::Reference(ref_num, _) = value {
1701 if *ref_num == obj_num {
1702 match key.as_str() {
1704 "Font" | "F1" | "F2" | "F3" => {
1705 return Ok(self.create_font_object(obj_num));
1706 }
1707 "XObject" | "Image" | "Im1" => {
1708 return Ok(self.create_xobject(obj_num));
1709 }
1710 "Contents" => {
1711 return Ok(self.create_content_stream(obj_num));
1712 }
1713 "Resources" => {
1714 return Ok(self.create_resources_dict(obj_num));
1715 }
1716 _ => continue,
1717 }
1718 }
1719 }
1720 }
1721 }
1722 }
1723
1724 Err(ParseError::SyntaxError {
1725 position: 0,
1726 message: "Cannot infer object type from context".to_string(),
1727 })
1728 }
1729
1730 fn scan_for_object_patterns(&mut self, obj_num: u32) -> ParseResult<PdfObject> {
1732 self.extract_object_or_stream_manually(obj_num)
1735 }
1736
1737 fn create_synthetic_object(&mut self, obj_num: u32) -> ParseResult<PdfObject> {
1739 use super::objects::{PdfDictionary, PdfName, PdfObject};
1740
1741 match obj_num {
1743 1..=10 => {
1744 let mut dict = PdfDictionary::new();
1746 dict.insert(
1747 "Type".to_string(),
1748 PdfObject::Name(PdfName("Null".to_string())),
1749 );
1750 Ok(PdfObject::Dictionary(dict))
1751 }
1752 _ => {
1753 Ok(PdfObject::Null)
1755 }
1756 }
1757 }
1758
1759 fn create_font_object(&self, _obj_num: u32) -> PdfObject {
1760 use super::objects::{PdfDictionary, PdfName, PdfObject};
1761 let mut font_dict = PdfDictionary::new();
1762 font_dict.insert(
1763 "Type".to_string(),
1764 PdfObject::Name(PdfName("Font".to_string())),
1765 );
1766 font_dict.insert(
1767 "Subtype".to_string(),
1768 PdfObject::Name(PdfName("Type1".to_string())),
1769 );
1770 font_dict.insert(
1771 "BaseFont".to_string(),
1772 PdfObject::Name(PdfName("Helvetica".to_string())),
1773 );
1774 PdfObject::Dictionary(font_dict)
1775 }
1776
1777 fn create_xobject(&self, _obj_num: u32) -> PdfObject {
1778 use super::objects::{PdfDictionary, PdfName, PdfObject};
1779 let mut xobj_dict = PdfDictionary::new();
1780 xobj_dict.insert(
1781 "Type".to_string(),
1782 PdfObject::Name(PdfName("XObject".to_string())),
1783 );
1784 xobj_dict.insert(
1785 "Subtype".to_string(),
1786 PdfObject::Name(PdfName("Form".to_string())),
1787 );
1788 PdfObject::Dictionary(xobj_dict)
1789 }
1790
1791 fn create_content_stream(&self, _obj_num: u32) -> PdfObject {
1792 use super::objects::{PdfDictionary, PdfObject, PdfStream};
1793 let mut stream_dict = PdfDictionary::new();
1794 stream_dict.insert("Length".to_string(), PdfObject::Integer(0));
1795
1796 let stream = PdfStream {
1797 dict: stream_dict,
1798 data: Vec::new(),
1799 };
1800 PdfObject::Stream(stream)
1801 }
1802
1803 fn create_resources_dict(&self, _obj_num: u32) -> PdfObject {
1804 use super::objects::{PdfArray, PdfDictionary, PdfObject};
1805 let mut res_dict = PdfDictionary::new();
1806 res_dict.insert("ProcSet".to_string(), PdfObject::Array(PdfArray::new()));
1807 PdfObject::Dictionary(res_dict)
1808 }
1809
1810 fn extract_object_manually(
1811 &mut self,
1812 obj_num: u32,
1813 ) -> ParseResult<crate::parser::objects::PdfDictionary> {
1814 use crate::parser::objects::{PdfArray, PdfDictionary, PdfName, PdfObject};
1815 use std::collections::HashMap;
1816
1817 let original_pos = self.reader.stream_position().unwrap_or(0);
1819
1820 let window = match read_object_window(&mut self.reader, obj_num, MANUAL_DICT_WINDOW) {
1824 Ok(Some((_, w))) => w,
1825 Ok(None) => {
1826 self.reader.seek(SeekFrom::Start(original_pos)).ok();
1827 return Err(ParseError::SyntaxError {
1828 position: 0,
1829 message: format!("Object {obj_num} not found in manual extraction"),
1830 });
1831 }
1832 Err(_) => {
1833 self.reader.seek(SeekFrom::Start(original_pos)).ok();
1834 return Err(ParseError::SyntaxError {
1835 position: 0,
1836 message: "Failed to read file for manual extraction".to_string(),
1837 });
1838 }
1839 };
1840
1841 let content = String::from_utf8_lossy(&window);
1842
1843 let pattern = format!("{} 0 obj", obj_num);
1845 if let Some(start) = content.find(&pattern) {
1846 let search_area = &content[start..];
1847 if let Some(dict_start) = search_area.find("<<") {
1848 let after_bracket = &search_area[dict_start + 2..];
1849
1850 if let Some(dict_end) = find_dict_end(after_bracket) {
1851 let dict_content = &after_bracket[..dict_end];
1852
1853 let mut result_dict = HashMap::new();
1855
1856 if dict_content.contains("/Type/Catalog")
1859 || dict_content.contains("/Type /Catalog")
1860 {
1861 result_dict.insert(
1862 PdfName("Type".to_string()),
1863 PdfObject::Name(PdfName("Catalog".to_string())),
1864 );
1865
1866 if let Some(pages_start) = dict_content.find("/Pages") {
1870 let after_pages = &dict_content[pages_start + 6..]; let trimmed = after_pages.trim_start();
1873 let parts: Vec<&str> = trimmed.split_whitespace().collect();
1875 if parts.len() >= 3 {
1876 if let (Ok(obj), Ok(gen)) =
1880 (parts[0].parse::<u32>(), parts[1].parse::<u16>())
1881 {
1882 if parts[2] == "R" || parts[2].starts_with('R') {
1883 result_dict.insert(
1884 PdfName("Pages".to_string()),
1885 PdfObject::Reference(obj, gen),
1886 );
1887 }
1888 }
1889 }
1890 }
1891
1892 if let Some(ver_start) = dict_content.find("/Version") {
1895 let after_ver = &dict_content[ver_start + 8..];
1896 if let Some(ver_end) = after_ver.find(|c: char| c == '/' || c == '>') {
1897 let version_str = after_ver[..ver_end].trim();
1898 result_dict.insert(
1899 PdfName("Version".to_string()),
1900 PdfObject::Name(PdfName(
1901 version_str.trim_start_matches('/').to_string(),
1902 )),
1903 );
1904 }
1905 }
1906
1907 if let Some(meta_start) = dict_content.find("/Metadata") {
1909 let after_meta = &dict_content[meta_start + 9..];
1910 let parts: Vec<&str> = after_meta.split_whitespace().collect();
1911 if parts.len() >= 3 {
1912 if let (Ok(obj), Ok(gen)) =
1913 (parts[0].parse::<u32>(), parts[1].parse::<u16>())
1914 {
1915 if parts[2] == "R" {
1916 result_dict.insert(
1917 PdfName("Metadata".to_string()),
1918 PdfObject::Reference(obj, gen),
1919 );
1920 }
1921 }
1922 }
1923 }
1924
1925 if let Some(acro_start) = dict_content.find("/AcroForm") {
1927 let after_acro = &dict_content[acro_start + 9..];
1928 if after_acro.trim_start().starts_with("<<") {
1930 } else {
1932 let parts: Vec<&str> = after_acro.split_whitespace().collect();
1933 if parts.len() >= 3 {
1934 if let (Ok(obj), Ok(gen)) =
1935 (parts[0].parse::<u32>(), parts[1].parse::<u16>())
1936 {
1937 if parts[2] == "R" {
1938 result_dict.insert(
1939 PdfName("AcroForm".to_string()),
1940 PdfObject::Reference(obj, gen),
1941 );
1942 }
1943 }
1944 }
1945 }
1946 }
1947 } else if obj_num == 102 {
1948 if dict_content.contains("/Type /Catalog") {
1950 result_dict.insert(
1952 PdfName("Type".to_string()),
1953 PdfObject::Name(PdfName("Catalog".to_string())),
1954 );
1955
1956 if dict_content.contains("/Dests 139 0 R") {
1958 result_dict.insert(
1959 PdfName("Dests".to_string()),
1960 PdfObject::Reference(139, 0),
1961 );
1962 }
1963
1964 if dict_content.contains("/Pages 113 0 R") {
1966 result_dict.insert(
1967 PdfName("Pages".to_string()),
1968 PdfObject::Reference(113, 0),
1969 );
1970 }
1971 } else {
1972 self.reader.seek(SeekFrom::Start(original_pos)).ok();
1975 return Err(ParseError::SyntaxError {
1976 position: 0,
1977 message:
1978 "Object 102 is not a corrupted catalog, cannot reconstruct"
1979 .to_string(),
1980 });
1981 }
1982 } else if obj_num == 113 {
1983 result_dict.insert(
1986 PdfName("Type".to_string()),
1987 PdfObject::Name(PdfName("Pages".to_string())),
1988 );
1989
1990 let page_refs = match self.find_page_objects() {
1992 Ok(refs) => refs,
1993 Err(_e) => {
1994 vec![]
1995 }
1996 };
1997
1998 let page_count = if page_refs.is_empty() {
2000 44
2001 } else {
2002 page_refs.len() as i64
2003 };
2004 result_dict
2005 .insert(PdfName("Count".to_string()), PdfObject::Integer(page_count));
2006
2007 let kids_array: Vec<PdfObject> = page_refs
2009 .into_iter()
2010 .map(|(obj_num, gen_num)| PdfObject::Reference(obj_num, gen_num))
2011 .collect();
2012
2013 result_dict.insert(
2014 PdfName("Kids".to_string()),
2015 PdfObject::Array(PdfArray(kids_array)),
2016 );
2017 } else if obj_num == 114 {
2018 result_dict.insert(
2021 PdfName("Type".to_string()),
2022 PdfObject::Name(PdfName("Pages".to_string())),
2023 );
2024
2025 let page_refs = match self.find_page_objects() {
2027 Ok(refs) => refs,
2028 Err(_e) => {
2029 vec![]
2030 }
2031 };
2032
2033 let page_count = if page_refs.is_empty() {
2035 44
2036 } else {
2037 page_refs.len() as i64
2038 };
2039 result_dict
2040 .insert(PdfName("Count".to_string()), PdfObject::Integer(page_count));
2041
2042 let kids_array: Vec<PdfObject> = page_refs
2044 .into_iter()
2045 .map(|(obj_num, gen_num)| PdfObject::Reference(obj_num, gen_num))
2046 .collect();
2047
2048 result_dict.insert(
2049 PdfName("Kids".to_string()),
2050 PdfObject::Array(PdfArray(kids_array)),
2051 );
2052 } else if self.is_page_object(obj_num) {
2053 result_dict.insert(
2056 PdfName("Type".to_string()),
2057 PdfObject::Name(PdfName("Page".to_string())),
2058 );
2059
2060 self.parse_page_dictionary_content(
2062 &dict_content,
2063 &mut result_dict,
2064 obj_num,
2065 )?;
2066 }
2067
2068 self.reader.seek(SeekFrom::Start(original_pos)).ok();
2070
2071 return Ok(PdfDictionary(result_dict));
2072 }
2073 }
2074 }
2075
2076 self.reader.seek(SeekFrom::Start(original_pos)).ok();
2078
2079 if obj_num == 113 {
2081 let mut result_dict = HashMap::new();
2082 result_dict.insert(
2083 PdfName("Type".to_string()),
2084 PdfObject::Name(PdfName("Pages".to_string())),
2085 );
2086
2087 let page_refs = match self.find_page_objects() {
2089 Ok(refs) => refs,
2090 Err(_e) => {
2091 vec![]
2092 }
2093 };
2094
2095 let page_count = if page_refs.is_empty() {
2097 44
2098 } else {
2099 page_refs.len() as i64
2100 };
2101 result_dict.insert(PdfName("Count".to_string()), PdfObject::Integer(page_count));
2102
2103 let kids_array: Vec<PdfObject> = page_refs
2105 .into_iter()
2106 .map(|(obj_num, gen_num)| PdfObject::Reference(obj_num, gen_num))
2107 .collect();
2108
2109 result_dict.insert(
2110 PdfName("Kids".to_string()),
2111 PdfObject::Array(PdfArray(kids_array)),
2112 );
2113
2114 return Ok(PdfDictionary(result_dict));
2115 } else if obj_num == 114 {
2116 let mut result_dict = HashMap::new();
2117 result_dict.insert(
2118 PdfName("Type".to_string()),
2119 PdfObject::Name(PdfName("Pages".to_string())),
2120 );
2121
2122 let page_refs = match self.find_page_objects() {
2124 Ok(refs) => refs,
2125 Err(_e) => {
2126 vec![]
2127 }
2128 };
2129
2130 let page_count = if page_refs.is_empty() {
2132 44
2133 } else {
2134 page_refs.len() as i64
2135 };
2136 result_dict.insert(PdfName("Count".to_string()), PdfObject::Integer(page_count));
2137
2138 let kids_array: Vec<PdfObject> = page_refs
2140 .into_iter()
2141 .map(|(obj_num, gen_num)| PdfObject::Reference(obj_num, gen_num))
2142 .collect();
2143
2144 result_dict.insert(
2145 PdfName("Kids".to_string()),
2146 PdfObject::Array(PdfArray(kids_array)),
2147 );
2148
2149 return Ok(PdfDictionary(result_dict));
2150 }
2151
2152 Err(ParseError::SyntaxError {
2153 position: 0,
2154 message: "Could not find catalog dictionary in manual extraction".to_string(),
2155 })
2156 }
2157
2158 fn extract_object_or_stream_manually(&mut self, obj_num: u32) -> ParseResult<PdfObject> {
2160 use crate::parser::objects::PdfObject;
2161
2162 let original_pos = self.reader.stream_position().unwrap_or(0);
2164
2165 let (obj_offset, window) =
2170 match read_object_window(&mut self.reader, obj_num, MANUAL_DICT_WINDOW) {
2171 Ok(Some(v)) => v,
2172 Ok(None) => {
2173 self.reader.seek(SeekFrom::Start(original_pos)).ok();
2174 return Err(ParseError::SyntaxError {
2175 position: 0,
2176 message: format!("Could not manually extract object {obj_num}"),
2177 });
2178 }
2179 Err(_) => {
2180 self.reader.seek(SeekFrom::Start(original_pos)).ok();
2181 return Err(ParseError::SyntaxError {
2182 position: 0,
2183 message: "Failed to read file for manual extraction".to_string(),
2184 });
2185 }
2186 };
2187
2188 if let Some(dict_start) = find_byte_pattern(&window, b"<<") {
2191 let mut bracket_count = 1;
2193 let mut pos = dict_start + 2;
2194 let mut dict_end = None;
2195
2196 while pos < window.len().saturating_sub(1) && bracket_count > 0 {
2197 if window[pos] == b'<' && window[pos + 1] == b'<' {
2198 bracket_count += 1;
2199 pos += 2;
2200 } else if window[pos] == b'>' && window[pos + 1] == b'>' {
2201 bracket_count -= 1;
2202 if bracket_count == 0 {
2203 dict_end = Some(pos);
2204 break;
2205 }
2206 pos += 2;
2207 } else {
2208 pos += 1;
2209 }
2210 }
2211
2212 if let Some(dict_end_pos) = dict_end {
2213 let dict_content = String::from_utf8_lossy(&window[dict_start + 2..dict_end_pos]);
2214 let dict_bytes = &window[dict_start..dict_end_pos + 2];
2216
2217 let after_dict = &window[dict_end_pos + 2..];
2219 if is_immediate_stream_start(after_dict) {
2220 let after_dict_abs = obj_offset + (dict_end_pos + 2) as u64;
2222 return self.reconstruct_stream_object_bounded(
2223 obj_num,
2224 dict_bytes,
2225 &dict_content,
2226 after_dict_abs,
2227 after_dict,
2228 );
2229 } else {
2230 self.reader.seek(SeekFrom::Start(original_pos)).ok();
2232 return self
2233 .extract_object_manually(obj_num)
2234 .map(PdfObject::Dictionary);
2235 }
2236 }
2237 }
2238
2239 self.reader.seek(SeekFrom::Start(original_pos)).ok();
2241
2242 Err(ParseError::SyntaxError {
2243 position: 0,
2244 message: format!("Could not manually extract object {obj_num}"),
2245 })
2246 }
2247
2248 fn reconstruct_stream_object_bounded(
2256 &mut self,
2257 obj_num: u32,
2258 dict_bytes: &[u8],
2259 dict_content: &str,
2260 after_dict_abs: u64,
2261 after_dict: &[u8],
2262 ) -> ParseResult<PdfObject> {
2263 use crate::parser::objects::{PdfDictionary, PdfName, PdfObject, PdfStream};
2264 use std::collections::HashMap;
2265
2266 let opts = self.options.clone();
2275 let mut dict: HashMap<PdfName, PdfObject> = {
2276 let mut lexer = super::lexer::Lexer::new_with_options(
2277 std::io::Cursor::new(dict_bytes),
2278 opts.clone(),
2279 );
2280 match PdfObject::parse_with_options(&mut lexer, &opts) {
2281 Ok(PdfObject::Dictionary(d)) => d.0,
2282 Ok(PdfObject::Stream(s)) => s.dict.0,
2285 _ => {
2288 let mut d = HashMap::new();
2289 if dict_content.contains("/Filter /FlateDecode") {
2290 d.insert(
2291 PdfName("Filter".to_string()),
2292 PdfObject::Name(PdfName("FlateDecode".to_string())),
2293 );
2294 }
2295 d
2296 }
2297 }
2298 };
2299
2300 let length = self.parse_stream_length(dict_content)?;
2302
2303 let Some(stream_kw) = find_byte_pattern(after_dict, b"stream") else {
2305 return Err(ParseError::SyntaxError {
2306 position: 0,
2307 message: format!("Could not reconstruct stream for object {obj_num}"),
2308 });
2309 };
2310 let after_kw = stream_kw + 6; let data_rel = match after_dict.get(after_kw) {
2312 Some(b'\r') if after_dict.get(after_kw + 1) == Some(&b'\n') => after_kw + 2,
2313 Some(b'\r') | Some(b'\n') => after_kw + 1,
2314 _ => after_kw,
2315 };
2316 let data_abs = after_dict_abs + data_rel as u64;
2317
2318 let data = match length {
2320 Some(len) => {
2321 let body = read_window_at(&mut self.reader, data_abs, len)?;
2328 dict.insert(
2329 PdfName("Length".to_string()),
2330 PdfObject::Integer(len as i64),
2331 );
2332 body
2333 }
2334 None => {
2335 let body = self.read_stream_until_endstream(data_abs)?;
2338 dict.insert(
2343 PdfName("Length".to_string()),
2344 PdfObject::Integer(body.len() as i64),
2345 );
2346 body
2347 }
2348 };
2349
2350 Ok(PdfObject::Stream(PdfStream {
2351 dict: PdfDictionary(dict),
2352 data,
2353 }))
2354 }
2355
2356 fn parse_stream_length(&mut self, dict_content: &str) -> ParseResult<Option<usize>> {
2360 let Some(idx) = dict_content.find("/Length") else {
2361 return Ok(None);
2362 };
2363 let rest = dict_content[idx + "/Length".len()..].trim_start();
2364 let tokens: Vec<&str> = rest.split_whitespace().collect();
2365
2366 if tokens.len() >= 3 && tokens[2] == "R" {
2368 if let Ok(len_obj) = tokens[0].parse::<u32>() {
2369 return self.resolve_length_object(len_obj);
2370 }
2371 }
2372
2373 if let Some(tok) = tokens.first() {
2375 if let Ok(n) = tok.parse::<i64>() {
2376 if n >= 0 {
2377 return Ok(Some(n as usize));
2378 }
2379 }
2380 }
2381 Ok(None)
2382 }
2383
2384 fn resolve_length_object(&mut self, len_obj: u32) -> ParseResult<Option<usize>> {
2389 let Some((_, window)) = read_object_window(&mut self.reader, len_obj, 4096)? else {
2390 return Ok(None);
2391 };
2392 let text = String::from_utf8_lossy(&window);
2393 if let Some(obj_pos) = text.find("obj") {
2395 let after = text[obj_pos + 3..].trim_start();
2396 let digits: String = after.chars().take_while(|c| c.is_ascii_digit()).collect();
2397 if let Ok(n) = digits.parse::<usize>() {
2398 return Ok(Some(n));
2399 }
2400 }
2401 Ok(None)
2402 }
2403
2404 fn read_stream_until_endstream(&mut self, data_abs: u64) -> ParseResult<Vec<u8>> {
2408 const WIN: usize = 64 * 1024;
2409 const MAX_STREAM: usize = 64 * 1024 * 1024; let mut acc: Vec<u8> = Vec::new();
2412 let mut offset = data_abs;
2413 let mut searched = 0usize;
2414 loop {
2415 let chunk = read_window_at(&mut self.reader, offset, WIN)?;
2416 if chunk.is_empty() {
2417 break; }
2419 acc.extend_from_slice(&chunk);
2420
2421 let from = searched.saturating_sub(b"endstream".len() - 1);
2424 if let Some(rel) = find_byte_pattern(&acc[from..], b"endstream") {
2425 acc.truncate(from + rel);
2426 break;
2427 }
2428 searched = acc.len();
2429 offset += chunk.len() as u64;
2430
2431 if chunk.len() < WIN {
2432 break; }
2434 if acc.len() > MAX_STREAM {
2435 tracing::warn!(
2439 "stream body exceeds {} MiB runaway guard without endstream; truncating",
2440 MAX_STREAM / (1024 * 1024)
2441 );
2442 break;
2443 }
2444 }
2445
2446 if acc.last() == Some(&b'\n') {
2448 acc.pop();
2449 if acc.last() == Some(&b'\r') {
2450 acc.pop();
2451 }
2452 } else if acc.last() == Some(&b'\r') {
2453 acc.pop();
2454 }
2455 Ok(acc)
2456 }
2457
2458 fn parse_resources_from_content(&self, dict_content: &str) -> ParseResult<PdfObject> {
2460 use crate::parser::objects::{PdfDictionary, PdfName, PdfObject};
2461 use std::collections::HashMap;
2462
2463 if let Some(resources_start) = dict_content.find("/Resources") {
2465 if let Some(bracket_start) = dict_content[resources_start..].find("<<") {
2467 let abs_bracket_start = resources_start + bracket_start + 2;
2468
2469 let after_bracket = &dict_content[abs_bracket_start..];
2472 if let Some(end_rel) = find_dict_end(after_bracket) {
2473 let resources_content = &after_bracket[..end_rel];
2474
2475 let mut resources_dict = HashMap::new();
2477
2478 if let Some(font_start) = resources_content.find("/Font") {
2480 if let Some(font_bracket) = resources_content[font_start..].find("<<") {
2481 let abs_font_start = font_start + font_bracket + 2;
2482
2483 let mut font_dict = HashMap::new();
2485
2486 let font_section = &resources_content[abs_font_start..];
2488 let mut pos = 0;
2489 while let Some(f_pos) = font_section[pos..].find("/F") {
2490 let abs_f_pos = pos + f_pos;
2491 if let Some(space_pos) = font_section[abs_f_pos..].find(" ") {
2492 let font_name = &font_section[abs_f_pos..abs_f_pos + space_pos];
2493
2494 let after_name = &font_section[abs_f_pos + space_pos..];
2496 if let Some(r_pos) = after_name.find(" R") {
2497 let ref_part = after_name[..r_pos].trim();
2498 if let Some(parts) = ref_part
2499 .split_whitespace()
2500 .collect::<Vec<&str>>()
2501 .get(0..2)
2502 {
2503 if let (Ok(obj_num), Ok(gen_num)) =
2504 (parts[0].parse::<u32>(), parts[1].parse::<u16>())
2505 {
2506 font_dict.insert(
2507 PdfName(font_name[1..].to_string()), PdfObject::Reference(obj_num, gen_num),
2509 );
2510 }
2511 }
2512 }
2513 }
2514 pos = abs_f_pos + 1;
2515 }
2516
2517 if !font_dict.is_empty() {
2518 resources_dict.insert(
2519 PdfName("Font".to_string()),
2520 PdfObject::Dictionary(PdfDictionary(font_dict)),
2521 );
2522 }
2523 }
2524 }
2525
2526 return Ok(PdfObject::Dictionary(PdfDictionary(resources_dict)));
2527 }
2528 }
2529 }
2530
2531 Err(ParseError::SyntaxError {
2532 position: 0,
2533 message: "Could not parse Resources".to_string(),
2534 })
2535 }
2536
2537 #[allow(dead_code)]
2538 fn extract_catalog_directly(
2539 &mut self,
2540 obj_num: u32,
2541 gen_num: u16,
2542 ) -> ParseResult<&PdfDictionary> {
2543 if let Some(entry) = self.xref.get_entry(obj_num) {
2545 if self.reader.seek(SeekFrom::Start(entry.offset)).is_err() {
2547 return Err(ParseError::SyntaxError {
2548 position: 0,
2549 message: "Failed to seek to catalog object".to_string(),
2550 });
2551 }
2552
2553 let mut buffer = vec![0u8; 2048];
2555 if let Ok(bytes_read) = self.reader.read(&mut buffer) {
2556 let content = String::from_utf8_lossy(&buffer[..bytes_read]);
2557 tracing::debug!("Raw catalog content:\n{}", content);
2558
2559 if let Some(dict_start) = content.find("<<") {
2561 if let Some(dict_end) = content[dict_start..].find(">>") {
2562 let dict_content = &content[dict_start..dict_start + dict_end + 2];
2563 tracing::debug!("Found dictionary content: {}", dict_content);
2564
2565 if let Ok(dict) = self.parse_dictionary_from_string(dict_content) {
2567 let key = (obj_num, gen_num);
2569 self.object_cache.insert(key, PdfObject::Dictionary(dict));
2570
2571 if let Some(PdfObject::Dictionary(ref dict)) =
2573 self.object_cache.get(&key)
2574 {
2575 return Ok(dict);
2576 }
2577 }
2578 }
2579 }
2580 }
2581 }
2582
2583 Err(ParseError::SyntaxError {
2584 position: 0,
2585 message: "Failed to extract catalog directly".to_string(),
2586 })
2587 }
2588
2589 #[allow(dead_code)]
2590 fn parse_dictionary_from_string(&self, dict_str: &str) -> ParseResult<PdfDictionary> {
2591 use crate::parser::lexer::{Lexer, Token};
2592
2593 let mut cursor = std::io::Cursor::new(dict_str.as_bytes());
2595 let mut lexer = Lexer::new_with_options(&mut cursor, self.options.clone());
2596
2597 match lexer.next_token()? {
2599 Token::DictStart => {
2600 let mut dict = std::collections::HashMap::new();
2601
2602 loop {
2603 let token = lexer.next_token()?;
2604 match token {
2605 Token::DictEnd => break,
2606 Token::Name(key) => {
2607 let value = PdfObject::parse_with_options(&mut lexer, &self.options)?;
2609 dict.insert(crate::parser::objects::PdfName(key), value);
2610 }
2611 _ => {
2612 return Err(ParseError::SyntaxError {
2613 position: 0,
2614 message: "Invalid dictionary format".to_string(),
2615 });
2616 }
2617 }
2618 }
2619
2620 Ok(PdfDictionary(dict))
2621 }
2622 _ => Err(ParseError::SyntaxError {
2623 position: 0,
2624 message: "Expected dictionary start".to_string(),
2625 }),
2626 }
2627 }
2628
2629 fn count_page_objects_directly(&mut self) -> Option<u32> {
2631 let mut page_count = 0;
2632
2633 for obj_num in 1..self.xref.len() as u32 {
2635 if let Ok(obj) = self.get_object(obj_num, 0) {
2636 if let Some(dict) = obj.as_dict() {
2637 if let Some(obj_type) = dict.get("Type").and_then(|t| t.as_name()) {
2638 if obj_type.0 == "Page" {
2639 page_count += 1;
2640 }
2641 }
2642 }
2643 }
2644 }
2645
2646 if page_count > 0 {
2647 Some(page_count)
2648 } else {
2649 None
2650 }
2651 }
2652
2653 pub fn metadata(&mut self) -> ParseResult<DocumentMetadata> {
2655 let mut metadata = DocumentMetadata::default();
2656
2657 if let Some(info_dict) = self.info()? {
2658 if let Some(title) = info_dict.get("Title").and_then(|o| o.as_string()) {
2659 metadata.title = title.as_str().ok().map(|s| s.to_string());
2660 }
2661 if let Some(author) = info_dict.get("Author").and_then(|o| o.as_string()) {
2662 metadata.author = author.as_str().ok().map(|s| s.to_string());
2663 }
2664 if let Some(subject) = info_dict.get("Subject").and_then(|o| o.as_string()) {
2665 metadata.subject = subject.as_str().ok().map(|s| s.to_string());
2666 }
2667 if let Some(keywords) = info_dict.get("Keywords").and_then(|o| o.as_string()) {
2668 metadata.keywords = keywords.as_str().ok().map(|s| s.to_string());
2669 }
2670 if let Some(creator) = info_dict.get("Creator").and_then(|o| o.as_string()) {
2671 metadata.creator = creator.as_str().ok().map(|s| s.to_string());
2672 }
2673 if let Some(producer) = info_dict.get("Producer").and_then(|o| o.as_string()) {
2674 metadata.producer = producer.as_str().ok().map(|s| s.to_string());
2675 }
2676 }
2677
2678 metadata.version = self.version().to_string();
2679 metadata.page_count = self.page_count().ok();
2680
2681 Ok(metadata)
2682 }
2683
2684 fn ensure_page_tree(&mut self) -> ParseResult<()> {
2686 if self.page_tree.is_none() {
2687 let page_count = self.page_count()?;
2688 self.page_tree = Some(super::page_tree::PageTree::new(page_count));
2689 }
2690 Ok(())
2691 }
2692
2693 pub fn get_page(&mut self, _index: u32) -> ParseResult<&super::page_tree::ParsedPage> {
2699 self.ensure_page_tree()?;
2700
2701 Err(ParseError::SyntaxError {
2705 position: 0,
2706 message: "get_page not implemented due to borrow checker constraints. Use PdfDocument instead.".to_string(),
2707 })
2708 }
2709
2710 pub fn get_all_pages(&mut self) -> ParseResult<Vec<super::page_tree::ParsedPage>> {
2712 let page_count = self.page_count()?;
2713 let mut pages = Vec::with_capacity(page_count as usize);
2714
2715 for i in 0..page_count {
2716 let page = self.get_page(i)?.clone();
2717 pages.push(page);
2718 }
2719
2720 Ok(pages)
2721 }
2722
2723 pub fn into_document(self) -> super::document::PdfDocument<R> {
2725 super::document::PdfDocument::new(self)
2726 }
2727
2728 pub fn clear_parse_context(&mut self) {
2730 self.parse_context = StackSafeContext::new();
2731 }
2732
2733 pub fn parse_context_mut(&mut self) -> &mut StackSafeContext {
2735 &mut self.parse_context
2736 }
2737
2738 fn find_page_objects(&mut self) -> ParseResult<Vec<(u32, u16)>> {
2745 let original_pos = self.reader.stream_position().unwrap_or(0);
2746 let result = scan_page_object_refs(&mut self.reader);
2747 self.reader.seek(SeekFrom::Start(original_pos)).ok();
2748 Ok(result.unwrap_or_default())
2749 }
2750
2751 fn find_catalog_object(&mut self) -> ParseResult<(u32, u16)> {
2753 let obj_numbers: Vec<u32> = self.xref.entries().keys().copied().collect();
2758
2759 for obj_num in obj_numbers {
2761 if let Ok(obj) = self.get_object(obj_num, 0) {
2763 if let Some(dict) = obj.as_dict() {
2764 if let Some(type_obj) = dict.get("Type") {
2766 if let Some(type_name) = type_obj.as_name() {
2767 if type_name.0 == "Catalog" {
2768 return Ok((obj_num, 0));
2769 }
2770 if type_name.0 == "Sig"
2772 || type_name.0 == "Pages"
2773 || type_name.0 == "Page"
2774 {
2775 continue;
2776 }
2777 }
2778 }
2779 }
2780 }
2781 }
2782
2783 for obj_num in [1, 2, 3, 4, 5] {
2785 if let Ok(obj) = self.get_object(obj_num, 0) {
2786 if let Some(dict) = obj.as_dict() {
2787 if dict.contains_key("Pages") {
2789 return Ok((obj_num, 0));
2790 }
2791 }
2792 }
2793 }
2794
2795 Err(ParseError::MissingKey(
2796 "Could not find Catalog object".to_string(),
2797 ))
2798 }
2799
2800 fn create_synthetic_pages_dict(
2802 &mut self,
2803 page_refs: &[(u32, u16)],
2804 ) -> ParseResult<&PdfDictionary> {
2805 use super::objects::{PdfArray, PdfName};
2806
2807 let mut valid_page_refs = Vec::new();
2809 for (obj_num, gen_num) in page_refs {
2810 if let Ok(page_obj) = self.get_object(*obj_num, *gen_num) {
2811 if let Some(page_dict) = page_obj.as_dict() {
2812 if let Some(obj_type) = page_dict.get("Type").and_then(|t| t.as_name()) {
2814 if obj_type.0 == "Page" {
2815 valid_page_refs.push((*obj_num, *gen_num));
2816 continue;
2817 }
2818 }
2819
2820 if page_dict.contains_key("MediaBox") || page_dict.contains_key("Contents") {
2822 valid_page_refs.push((*obj_num, *gen_num));
2823 }
2824 }
2825 }
2826 }
2827
2828 if valid_page_refs.is_empty() {
2829 return Err(ParseError::SyntaxError {
2830 position: 0,
2831 message: "No valid page objects found for synthetic Pages tree".to_string(),
2832 });
2833 }
2834
2835 if valid_page_refs.len() > 10 {
2837 return self.create_hierarchical_pages_tree(&valid_page_refs);
2838 }
2839
2840 let mut kids = PdfArray::new();
2842 for (obj_num, gen_num) in &valid_page_refs {
2843 kids.push(PdfObject::Reference(*obj_num, *gen_num));
2844 }
2845
2846 let mut pages_dict = PdfDictionary::new();
2848 pages_dict.insert(
2849 "Type".to_string(),
2850 PdfObject::Name(PdfName("Pages".to_string())),
2851 );
2852 pages_dict.insert("Kids".to_string(), PdfObject::Array(kids));
2853 pages_dict.insert(
2854 "Count".to_string(),
2855 PdfObject::Integer(valid_page_refs.len() as i64),
2856 );
2857
2858 let mut media_box = None;
2860 for (obj_num, gen_num) in valid_page_refs.iter().take(3) {
2861 if let Ok(page_obj) = self.get_object(*obj_num, *gen_num) {
2862 if let Some(page_dict) = page_obj.as_dict() {
2863 if let Some(mb) = page_dict.get("MediaBox") {
2864 media_box = Some(mb.clone());
2865 }
2866 }
2867 }
2868 }
2869
2870 if let Some(mb) = media_box {
2872 pages_dict.insert("MediaBox".to_string(), mb);
2873 } else {
2874 let mut mb_array = PdfArray::new();
2875 mb_array.push(PdfObject::Integer(0));
2876 mb_array.push(PdfObject::Integer(0));
2877 mb_array.push(PdfObject::Integer(612));
2878 mb_array.push(PdfObject::Integer(792));
2879 pages_dict.insert("MediaBox".to_string(), PdfObject::Array(mb_array));
2880 }
2881
2882 let synthetic_key = (u32::MAX - 1, 0);
2884 self.object_cache
2885 .insert(synthetic_key, PdfObject::Dictionary(pages_dict));
2886
2887 if let PdfObject::Dictionary(dict) = &self.object_cache[&synthetic_key] {
2889 Ok(dict)
2890 } else {
2891 unreachable!("Just inserted dictionary")
2892 }
2893 }
2894
2895 fn create_hierarchical_pages_tree(
2897 &mut self,
2898 page_refs: &[(u32, u16)],
2899 ) -> ParseResult<&PdfDictionary> {
2900 use super::objects::{PdfArray, PdfName};
2901
2902 const PAGES_PER_NODE: usize = 10; let chunks: Vec<&[(u32, u16)]> = page_refs.chunks(PAGES_PER_NODE).collect();
2906 let mut intermediate_nodes = Vec::new();
2907
2908 for (chunk_idx, chunk) in chunks.iter().enumerate() {
2910 let mut kids = PdfArray::new();
2911 for (obj_num, gen_num) in chunk.iter() {
2912 kids.push(PdfObject::Reference(*obj_num, *gen_num));
2913 }
2914
2915 let mut intermediate_dict = PdfDictionary::new();
2916 intermediate_dict.insert(
2917 "Type".to_string(),
2918 PdfObject::Name(PdfName("Pages".to_string())),
2919 );
2920 intermediate_dict.insert("Kids".to_string(), PdfObject::Array(kids));
2921 intermediate_dict.insert("Count".to_string(), PdfObject::Integer(chunk.len() as i64));
2922
2923 let intermediate_key = (u32::MAX - 2 - chunk_idx as u32, 0);
2925 self.object_cache
2926 .insert(intermediate_key, PdfObject::Dictionary(intermediate_dict));
2927
2928 intermediate_nodes.push(intermediate_key);
2929 }
2930
2931 let mut root_kids = PdfArray::new();
2933 for (obj_num, gen_num) in &intermediate_nodes {
2934 root_kids.push(PdfObject::Reference(*obj_num, *gen_num));
2935 }
2936
2937 let mut root_pages_dict = PdfDictionary::new();
2938 root_pages_dict.insert(
2939 "Type".to_string(),
2940 PdfObject::Name(PdfName("Pages".to_string())),
2941 );
2942 root_pages_dict.insert("Kids".to_string(), PdfObject::Array(root_kids));
2943 root_pages_dict.insert(
2944 "Count".to_string(),
2945 PdfObject::Integer(page_refs.len() as i64),
2946 );
2947
2948 if let Some((obj_num, gen_num)) = page_refs.first() {
2950 if let Ok(page_obj) = self.get_object(*obj_num, *gen_num) {
2951 if let Some(page_dict) = page_obj.as_dict() {
2952 if let Some(mb) = page_dict.get("MediaBox") {
2953 root_pages_dict.insert("MediaBox".to_string(), mb.clone());
2954 }
2955 }
2956 }
2957 }
2958
2959 let root_key = (u32::MAX - 1, 0);
2961 self.object_cache
2962 .insert(root_key, PdfObject::Dictionary(root_pages_dict));
2963
2964 if let PdfObject::Dictionary(dict) = &self.object_cache[&root_key] {
2966 Ok(dict)
2967 } else {
2968 unreachable!("Just inserted dictionary")
2969 }
2970 }
2971
2972 pub fn signatures(&mut self) -> ParseResult<Vec<crate::signatures::SignatureField>> {
3002 crate::signatures::detect_signature_fields(self).map_err(|e| ParseError::SyntaxError {
3003 position: 0,
3004 message: format!("Failed to detect signatures: {}", e),
3005 })
3006 }
3007
3008 pub fn verify_signatures(
3043 &mut self,
3044 ) -> ParseResult<Vec<crate::signatures::FullSignatureValidationResult>> {
3045 self.verify_signatures_with_trust_store(crate::signatures::TrustStore::default())
3046 }
3047
3048 pub fn verify_signatures_with_trust_store(
3080 &mut self,
3081 trust_store: crate::signatures::TrustStore,
3082 ) -> ParseResult<Vec<crate::signatures::FullSignatureValidationResult>> {
3083 use crate::signatures::{
3084 has_incremental_update, parse_pkcs7_signature, validate_certificate, verify_signature,
3085 FullSignatureValidationResult,
3086 };
3087
3088 let original_pos = self.reader.stream_position().unwrap_or(0);
3090 self.reader.seek(SeekFrom::Start(0))?;
3091
3092 let mut pdf_bytes = Vec::new();
3093 self.reader.read_to_end(&mut pdf_bytes)?;
3094
3095 self.reader.seek(SeekFrom::Start(original_pos)).ok();
3097
3098 let signature_fields = self.signatures()?;
3100
3101 let mut results = Vec::new();
3102
3103 for field in signature_fields {
3104 let mut result = FullSignatureValidationResult {
3105 field: field.clone(),
3106 signer_name: None,
3107 signing_time: None,
3108 hash_valid: false,
3109 signature_valid: false,
3110 certificate_result: None,
3111 has_modifications_after_signing: false,
3112 errors: Vec::new(),
3113 warnings: Vec::new(),
3114 };
3115
3116 result.has_modifications_after_signing =
3118 has_incremental_update(&pdf_bytes, &field.byte_range);
3119
3120 let parsed_sig = match parse_pkcs7_signature(&field.contents) {
3122 Ok(sig) => sig,
3123 Err(e) => {
3124 result
3125 .errors
3126 .push(format!("Failed to parse signature: {}", e));
3127 results.push(result);
3128 continue;
3129 }
3130 };
3131
3132 result.signing_time = parsed_sig.signing_time.clone();
3134 result.signer_name = parsed_sig.signer_common_name().ok();
3135
3136 match verify_signature(&pdf_bytes, &parsed_sig, &field.byte_range) {
3138 Ok(verification) => {
3139 result.hash_valid = verification.hash_valid;
3140 result.signature_valid = verification.signature_valid;
3141 if let Some(details) = verification.details {
3142 result.warnings.push(details);
3143 }
3144 }
3145 Err(e) => {
3146 result
3147 .errors
3148 .push(format!("Signature verification failed: {}", e));
3149 }
3150 }
3151
3152 match validate_certificate(&parsed_sig.signer_certificate_der, &trust_store) {
3154 Ok(cert_result) => {
3155 result.certificate_result = Some(cert_result);
3156 }
3157 Err(e) => {
3158 result
3159 .warnings
3160 .push(format!("Certificate validation failed: {}", e));
3161 }
3162 }
3163
3164 results.push(result);
3165 }
3166
3167 Ok(results)
3168 }
3169}
3170
3171#[derive(Debug, Default, Clone)]
3173pub struct DocumentMetadata {
3174 pub title: Option<String>,
3175 pub author: Option<String>,
3176 pub subject: Option<String>,
3177 pub keywords: Option<String>,
3178 pub creator: Option<String>,
3179 pub producer: Option<String>,
3180 pub creation_date: Option<String>,
3181 pub modification_date: Option<String>,
3182 pub version: String,
3183 pub page_count: Option<u32>,
3184}
3185
3186pub struct EOLIter<'s> {
3187 remainder: &'s str,
3188}
3189impl<'s> Iterator for EOLIter<'s> {
3190 type Item = &'s str;
3191
3192 fn next(&mut self) -> Option<Self::Item> {
3193 if self.remainder.is_empty() {
3194 return None;
3195 }
3196
3197 if let Some((i, sep)) = ["\r\n", "\n", "\r"]
3198 .iter()
3199 .filter_map(|&sep| self.remainder.find(sep).map(|i| (i, sep)))
3200 .min_by_key(|(i, _)| *i)
3201 {
3202 let (line, rest) = self.remainder.split_at(i);
3203 self.remainder = &rest[sep.len()..];
3204 Some(line)
3205 } else {
3206 let line = self.remainder;
3207 self.remainder = "";
3208 Some(line)
3209 }
3210 }
3211}
3212pub trait PDFLines: AsRef<str> {
3213 fn pdf_lines(&self) -> EOLIter<'_> {
3214 EOLIter {
3215 remainder: self.as_ref(),
3216 }
3217 }
3218}
3219impl PDFLines for &str {}
3220impl<'a> PDFLines for std::borrow::Cow<'a, str> {}
3221impl PDFLines for String {}
3222
3223#[cfg(test)]
3224mod tests {
3225
3226 use super::*;
3227 use crate::parser::objects::{PdfName, PdfString};
3228 use crate::parser::test_helpers::*;
3229 use crate::parser::ParseOptions;
3230 use std::io::Cursor;
3231
3232 #[test]
3233 fn test_reader_construction() {
3234 let pdf_data = create_minimal_pdf();
3235 let cursor = Cursor::new(pdf_data);
3236 let result = PdfReader::new(cursor);
3237 assert!(result.is_ok());
3238 }
3239
3240 #[test]
3248 fn slice_between_extracts_delimited_content() {
3249 assert_eq!(
3250 slice_between("/MediaBox [0 0 612 792] /X", b'[', b']'),
3251 Some("0 0 612 792")
3252 );
3253 assert_eq!(slice_between("[]", b'[', b']'), Some(""));
3254 }
3255
3256 #[test]
3257 fn slice_between_searches_the_closer_after_the_opener() {
3258 assert_eq!(slice_between("/MediaBox ][", b'[', b']'), None);
3263 assert_eq!(slice_between("] a [ b ] c", b'[', b']'), Some(" b "));
3264 }
3265
3266 #[test]
3267 fn slice_between_returns_none_when_a_delimiter_is_missing() {
3268 assert_eq!(slice_between("/MediaBox 0 0 612 792", b'[', b']'), None);
3269 assert_eq!(slice_between("/MediaBox [0 0 612 792", b'[', b']'), None);
3270 assert_eq!(slice_between("", b'[', b']'), None);
3271 }
3272
3273 #[test]
3274 fn slice_between_never_splits_a_multibyte_char() {
3275 assert_eq!(
3278 slice_between("[\u{FFFD}\u{20AC}]", b'[', b']'),
3279 Some("\u{FFFD}\u{20AC}")
3280 );
3281 assert_eq!(slice_between("\u{20AC}[x]\u{20AC}", b'[', b']'), Some("x"));
3284 }
3285
3286 #[test]
3287 fn find_dict_end_locates_the_closing_marker() {
3288 assert_eq!(find_dict_end(" /A 1 >>"), Some(6));
3291 assert_eq!(find_dict_end(">>"), Some(0));
3292 }
3293
3294 #[test]
3295 fn find_dict_end_matches_nesting() {
3296 assert_eq!(find_dict_end(" /A << /B 1 >> >> tail"), Some(15));
3297 }
3298
3299 #[test]
3300 fn find_dict_end_returns_none_when_unbalanced() {
3301 assert_eq!(find_dict_end(" /A 1"), None);
3302 assert_eq!(find_dict_end(" /A << /B 1 >>"), None);
3303 assert_eq!(find_dict_end(""), None);
3304 assert_eq!(find_dict_end(">"), None);
3305 }
3306
3307 #[test]
3308 fn find_dict_end_offset_is_a_char_boundary_with_multibyte_content() {
3309 let after = " /Font \u{FFFD} >> tail";
3312 let end = find_dict_end(after).expect("closes");
3313 assert_eq!(&after[..end], " /Font \u{FFFD} ");
3314 }
3315
3316 #[test]
3317 fn truncate_on_char_boundary_cuts_without_splitting() {
3318 assert_eq!(truncate_on_char_boundary("abc", 10), "abc");
3319 assert_eq!(truncate_on_char_boundary("abcdef", 3), "abc");
3320 assert_eq!(truncate_on_char_boundary("\u{FFFD}x", 1), "");
3322 assert_eq!(truncate_on_char_boundary("\u{FFFD}x", 3), "\u{FFFD}");
3323 assert_eq!(truncate_on_char_boundary("", 5), "");
3324 }
3325
3326 struct CountingReader<R> {
3333 inner: R,
3334 total_read: std::sync::Arc<std::sync::atomic::AtomicUsize>,
3335 max_read: std::sync::Arc<std::sync::atomic::AtomicUsize>,
3336 }
3337 impl<R: Read> Read for CountingReader<R> {
3338 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
3339 self.max_read
3340 .fetch_max(buf.len(), std::sync::atomic::Ordering::SeqCst);
3341 let n = self.inner.read(buf)?;
3342 self.total_read
3343 .fetch_add(n, std::sync::atomic::Ordering::SeqCst);
3344 Ok(n)
3345 }
3346 }
3347 impl<R: Seek> Seek for CountingReader<R> {
3348 fn seek(&mut self, p: SeekFrom) -> std::io::Result<u64> {
3349 self.inner.seek(p)
3350 }
3351 }
3352
3353 #[test]
3354 fn test_extract_stream_manually_bounded_honors_length() {
3355 use crate::parser::objects::PdfObject;
3356 use std::sync::atomic::{AtomicUsize, Ordering};
3357 use std::sync::Arc;
3358
3359 const L: usize = 300 * 1024; const FILLER: usize = 4 * 1024 * 1024; let payload: Vec<u8> = (0..L).map(|i| (i % 251) as u8).collect();
3371 let filler: Vec<u8> = (0..FILLER).map(|i| ((i + 7) % 251) as u8).collect();
3372
3373 let header = b"%PDF-1.4\n";
3374 let obj1 = b"1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n";
3375 let obj2 = b"2 0 obj\n<< /Type /Pages /Kids [] /Count 0 >>\nendobj\n";
3376
3377 let mut data = Vec::new();
3378 data.extend_from_slice(header);
3379 let obj1_start = data.len();
3380 data.extend_from_slice(obj1);
3381 let obj2_start = data.len();
3382 data.extend_from_slice(obj2);
3383
3384 data.extend_from_slice(
3387 b"4 0 obj\n<< /Type /Metadata /Subtype /XML /Length 307200 >>\nstream\n",
3388 );
3389 data.extend_from_slice(&payload);
3390 data.extend_from_slice(b"\nendstream\nendobj\n");
3391
3392 data.extend_from_slice(b"5 0 obj\n<< /Length 4194304 >>\nstream\n");
3394 data.extend_from_slice(&filler);
3395 data.extend_from_slice(b"\nendstream\nendobj\n");
3396
3397 let xref_start = data.len();
3399 let xref = format!(
3400 "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"
3401 );
3402 data.extend_from_slice(xref.as_bytes());
3403 let file_len = data.len();
3404
3405 let counter = Arc::new(AtomicUsize::new(0));
3406 let reader = CountingReader {
3407 inner: Cursor::new(data),
3408 total_read: counter.clone(),
3409 max_read: Arc::new(AtomicUsize::new(0)),
3410 };
3411 let mut pdf = PdfReader::new_with_options(reader, ParseOptions::tolerant())
3412 .expect("minimal PDF must parse");
3413
3414 counter.store(0, Ordering::SeqCst);
3416 let obj = pdf
3417 .extract_object_or_stream_manually(4)
3418 .expect("stream object 4 must be extracted");
3419
3420 let stream = match &obj {
3422 PdfObject::Stream(s) => s,
3423 other => panic!("expected a stream, got {other:?}"),
3424 };
3425 assert_eq!(
3426 stream.data.len(),
3427 L,
3428 "stream body must equal /Length (no truncation)"
3429 );
3430 assert_eq!(stream.data, payload, "stream body content must be intact");
3431
3432 let total_read = counter.load(Ordering::SeqCst);
3436 assert!(
3437 total_read <= L + 2 * MANUAL_DICT_WINDOW,
3438 "manual stream extraction read {total_read} bytes total (file={file_len}); not bounded by object size"
3439 );
3440 }
3441
3442 #[cfg(test)]
3446 fn build_manual_stream_pdf(obj4_dict_body: &str, payload: &[u8]) -> Vec<u8> {
3447 let header = b"%PDF-1.4\n";
3448 let obj1 = b"1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n";
3449 let obj2 = b"2 0 obj\n<< /Type /Pages /Kids [] /Count 0 >>\nendobj\n";
3450
3451 let mut data = Vec::new();
3452 data.extend_from_slice(header);
3453 let obj1_start = data.len();
3454 data.extend_from_slice(obj1);
3455 let obj2_start = data.len();
3456 data.extend_from_slice(obj2);
3457
3458 data.extend_from_slice(format!("4 0 obj\n<<{obj4_dict_body}>>\nstream\n").as_bytes());
3460 data.extend_from_slice(payload);
3461 data.extend_from_slice(b"\nendstream\nendobj\n");
3462
3463 let xref_start = data.len();
3465 let xref = format!(
3466 "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"
3467 );
3468 data.extend_from_slice(xref.as_bytes());
3469 data
3470 }
3471
3472 #[test]
3473 fn test_reconstruct_preserves_non_flate_filter() {
3474 use crate::parser::objects::PdfObject;
3475
3476 let payload = b"\xff\xd8\xff\xe0not-a-real-jpeg\xff\xd9";
3482 let len = payload.len();
3483 let dict_body =
3484 format!(" /Type /XObject /Subtype /Image /Filter /DCTDecode /Length {len} ");
3485 let data = build_manual_stream_pdf(&dict_body, payload);
3486
3487 let mut pdf = PdfReader::new_with_options(Cursor::new(data), ParseOptions::tolerant())
3488 .expect("minimal PDF must parse");
3489 let obj = pdf
3490 .extract_object_or_stream_manually(4)
3491 .expect("stream object 4 must be extracted");
3492 let stream = match &obj {
3493 PdfObject::Stream(s) => s,
3494 other => panic!("expected a stream, got {other:?}"),
3495 };
3496
3497 assert_eq!(
3498 stream
3499 .dict
3500 .get("Filter")
3501 .and_then(|o| o.as_name())
3502 .map(|n| n.0.as_str()),
3503 Some("DCTDecode"),
3504 "non-Flate /Filter must be preserved in reconstructed stream dict"
3505 );
3506 assert_eq!(
3507 stream
3508 .dict
3509 .get("Subtype")
3510 .and_then(|o| o.as_name())
3511 .map(|n| n.0.as_str()),
3512 Some("Image"),
3513 "/Subtype must be preserved, not dropped"
3514 );
3515 assert_eq!(stream.data, payload, "stream body must be intact");
3516 }
3517
3518 #[test]
3519 fn test_reconstruct_preserves_filter_array_and_decodeparms() {
3520 use crate::parser::objects::PdfObject;
3521
3522 let payload = b"compressed-bytes-placeholder";
3527 let len = payload.len();
3528 let dict_body = format!(
3529 " /Filter[/LZWDecode /FlateDecode] /DecodeParms[null<< /Predictor 12 /Columns 4 >>] /Length {len} "
3530 );
3531 let data = build_manual_stream_pdf(&dict_body, payload);
3532
3533 let mut pdf = PdfReader::new_with_options(Cursor::new(data), ParseOptions::tolerant())
3534 .expect("minimal PDF must parse");
3535 let obj = pdf
3536 .extract_object_or_stream_manually(4)
3537 .expect("stream object 4 must be extracted");
3538 let stream = match &obj {
3539 PdfObject::Stream(s) => s,
3540 other => panic!("expected a stream, got {other:?}"),
3541 };
3542
3543 let filter = stream
3544 .dict
3545 .get("Filter")
3546 .and_then(|o| o.as_array())
3547 .expect("/Filter array must be preserved");
3548 let names: Vec<&str> = filter
3549 .0
3550 .iter()
3551 .filter_map(|o| o.as_name())
3552 .map(|n| n.0.as_str())
3553 .collect();
3554 assert_eq!(
3555 names,
3556 vec!["LZWDecode", "FlateDecode"],
3557 "filter array entries must be preserved in order"
3558 );
3559 assert!(
3560 stream.dict.get("DecodeParms").is_some(),
3561 "/DecodeParms must be preserved alongside the filter chain"
3562 );
3563 assert_eq!(stream.data, payload, "stream body must be intact");
3564 }
3565
3566 #[test]
3567 fn test_reconstruct_malformed_dict_falls_back_without_panic() {
3568 use crate::parser::objects::PdfObject;
3569
3570 let payload = b"0123456789";
3576 let len = payload.len();
3577 let dict_body = format!(" /Length {len} /BadKey ");
3579 let data = build_manual_stream_pdf(&dict_body, payload);
3580
3581 let mut pdf = PdfReader::new_with_options(Cursor::new(data), ParseOptions::tolerant())
3582 .expect("minimal PDF must parse");
3583 let obj = pdf
3584 .extract_object_or_stream_manually(4)
3585 .expect("malformed-dict stream must still be reconstructed via fallback");
3586 let stream = match &obj {
3587 PdfObject::Stream(s) => s,
3588 other => panic!("expected a stream, got {other:?}"),
3589 };
3590
3591 assert_eq!(
3592 stream.data, payload,
3593 "fallback path must still read the bounded body intact"
3594 );
3595 assert!(
3597 stream.dict.get("Filter").is_none(),
3598 "fallback dict must not invent a /Filter"
3599 );
3600 }
3601
3602 #[test]
3603 fn test_find_page_objects_bounded() {
3604 use std::sync::atomic::{AtomicUsize, Ordering};
3605 use std::sync::Arc;
3606
3607 const FILLER: usize = 2 * 1024 * 1024;
3613 let filler: Vec<u8> = (0..FILLER).map(|i| (i % 251) as u8).collect();
3614
3615 let header = b"%PDF-1.4\n";
3616 let obj1 = b"1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n";
3617 let obj2 = b"2 0 obj\n<< /Type /Pages /Kids [4 0 R 5 0 R 6 0 R] /Count 3 >>\nendobj\n";
3618
3619 let mut data = Vec::new();
3620 data.extend_from_slice(header);
3621 let obj1_start = data.len();
3622 data.extend_from_slice(obj1);
3623 let obj2_start = data.len();
3624 data.extend_from_slice(obj2);
3625
3626 for n in [4u32, 5, 6] {
3628 data.extend_from_slice(
3629 format!(
3630 "{n} 0 obj\n<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] >>\nendobj\n"
3631 )
3632 .as_bytes(),
3633 );
3634 }
3635
3636 data.extend_from_slice(b"7 0 obj\n<< /Length 2097152 >>\nstream\n");
3638 data.extend_from_slice(&filler);
3639 data.extend_from_slice(b"\nendstream\nendobj\n");
3640
3641 let xref_start = data.len();
3642 let xref = format!(
3643 "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"
3644 );
3645 data.extend_from_slice(xref.as_bytes());
3646 let file_len = data.len();
3647
3648 let max_read = Arc::new(AtomicUsize::new(0));
3649 let reader = CountingReader {
3650 inner: Cursor::new(data),
3651 total_read: Arc::new(AtomicUsize::new(0)),
3652 max_read: max_read.clone(),
3653 };
3654 let mut pdf = PdfReader::new_with_options(reader, ParseOptions::tolerant())
3655 .expect("minimal PDF must parse");
3656
3657 max_read.store(0, Ordering::SeqCst);
3659 let pages = pdf.find_page_objects().expect("page scan must succeed");
3660
3661 assert_eq!(
3663 pages,
3664 vec![(4, 0), (5, 0), (6, 0)],
3665 "must find exactly the three /Type /Page objects"
3666 );
3667
3668 let peak = max_read.load(Ordering::SeqCst);
3670 assert!(
3671 peak <= 128 * 1024,
3672 "page scan requested {peak} bytes in one read (file={file_len}); not bounded"
3673 );
3674 }
3675
3676 #[test]
3677 fn test_signature_verification_reads_full_file_intentionally() {
3678 use std::sync::atomic::{AtomicUsize, Ordering};
3679 use std::sync::Arc;
3680
3681 let data = create_minimal_pdf();
3686 let file_len = data.len();
3687
3688 let total = Arc::new(AtomicUsize::new(0));
3689 let reader = CountingReader {
3690 inner: Cursor::new(data),
3691 total_read: total.clone(),
3692 max_read: Arc::new(AtomicUsize::new(0)),
3693 };
3694 let mut pdf = PdfReader::new_with_options(reader, ParseOptions::tolerant())
3695 .expect("minimal PDF must parse");
3696
3697 total.store(0, Ordering::SeqCst);
3698 let _ = pdf.verify_signatures();
3700 let read = total.load(Ordering::SeqCst);
3701 assert!(
3702 read >= file_len,
3703 "signature verification must read the whole file (read {read}, file {file_len})"
3704 );
3705 }
3706
3707 #[test]
3708 fn test_reader_version() {
3709 let pdf_data = create_minimal_pdf();
3710 let cursor = Cursor::new(pdf_data);
3711 let reader = PdfReader::new(cursor).unwrap();
3712 assert_eq!(reader.version().major, 1);
3713 assert_eq!(reader.version().minor, 4);
3714 }
3715
3716 #[test]
3717 fn test_reader_different_versions() {
3718 let versions = vec![
3719 "1.0", "1.1", "1.2", "1.3", "1.4", "1.5", "1.6", "1.7", "2.0",
3720 ];
3721
3722 for version in versions {
3723 let pdf_data = create_pdf_with_version(version);
3724 let cursor = Cursor::new(pdf_data);
3725 let reader = PdfReader::new(cursor).unwrap();
3726
3727 let parts: Vec<&str> = version.split('.').collect();
3728 assert_eq!(reader.version().major, parts[0].parse::<u8>().unwrap());
3729 assert_eq!(reader.version().minor, parts[1].parse::<u8>().unwrap());
3730 }
3731 }
3732
3733 #[test]
3734 fn test_reader_catalog() {
3735 let pdf_data = create_minimal_pdf();
3736 let cursor = Cursor::new(pdf_data);
3737 let mut reader = PdfReader::new(cursor).unwrap();
3738
3739 let catalog = reader.catalog();
3740 assert!(catalog.is_ok());
3741
3742 let catalog_dict = catalog.unwrap();
3743 assert_eq!(
3744 catalog_dict.get("Type"),
3745 Some(&PdfObject::Name(PdfName("Catalog".to_string())))
3746 );
3747 }
3748
3749 #[test]
3750 fn test_reader_info_none() {
3751 let pdf_data = create_minimal_pdf();
3752 let cursor = Cursor::new(pdf_data);
3753 let mut reader = PdfReader::new(cursor).unwrap();
3754
3755 let info = reader.info().unwrap();
3756 assert!(info.is_none());
3757 }
3758
3759 #[test]
3760 fn test_reader_info_present() {
3761 let pdf_data = create_pdf_with_info();
3762 let cursor = Cursor::new(pdf_data);
3763 let mut reader = PdfReader::new(cursor).unwrap();
3764
3765 let info = reader.info().unwrap();
3766 assert!(info.is_some());
3767
3768 let info_dict = info.unwrap();
3769 assert_eq!(
3770 info_dict.get("Title"),
3771 Some(&PdfObject::String(PdfString(
3772 "Test PDF".to_string().into_bytes()
3773 )))
3774 );
3775 assert_eq!(
3776 info_dict.get("Author"),
3777 Some(&PdfObject::String(PdfString(
3778 "Test Author".to_string().into_bytes()
3779 )))
3780 );
3781 }
3782
3783 #[test]
3784 fn test_reader_get_object() {
3785 let pdf_data = create_minimal_pdf();
3786 let cursor = Cursor::new(pdf_data);
3787 let mut reader = PdfReader::new(cursor).unwrap();
3788
3789 let obj = reader.get_object(1, 0);
3791 assert!(obj.is_ok());
3792
3793 let catalog = obj.unwrap();
3794 assert!(catalog.as_dict().is_some());
3795 }
3796
3797 #[test]
3798 fn test_reader_get_invalid_object() {
3799 let pdf_data = create_minimal_pdf();
3800 let cursor = Cursor::new(pdf_data);
3801 let mut reader = PdfReader::new(cursor).unwrap();
3802
3803 let obj = reader.get_object(999, 0);
3805 assert!(obj.is_err());
3806 }
3807
3808 #[test]
3809 fn test_reader_get_free_object() {
3810 let pdf_data = create_minimal_pdf();
3811 let cursor = Cursor::new(pdf_data);
3812 let mut reader = PdfReader::new(cursor).unwrap();
3813
3814 let obj = reader.get_object(0, 65535);
3816 assert!(obj.is_ok());
3817 assert_eq!(obj.unwrap(), &PdfObject::Null);
3818 }
3819
3820 #[test]
3821 fn test_reader_resolve_reference() {
3822 let pdf_data = create_minimal_pdf();
3823 let cursor = Cursor::new(pdf_data);
3824 let mut reader = PdfReader::new(cursor).unwrap();
3825
3826 let ref_obj = PdfObject::Reference(1, 0);
3828 let resolved = reader.resolve(&ref_obj);
3829
3830 assert!(resolved.is_ok());
3831 assert!(resolved.unwrap().as_dict().is_some());
3832 }
3833
3834 #[test]
3835 fn test_reader_resolve_non_reference() {
3836 let pdf_data = create_minimal_pdf();
3837 let cursor = Cursor::new(pdf_data);
3838 let mut reader = PdfReader::new(cursor).unwrap();
3839
3840 let int_obj = PdfObject::Integer(42);
3842 let resolved = reader.resolve(&int_obj).unwrap();
3843
3844 assert_eq!(resolved, &PdfObject::Integer(42));
3845 }
3846
3847 #[test]
3848 fn test_reader_cache_behavior() {
3849 let pdf_data = create_minimal_pdf();
3850 let cursor = Cursor::new(pdf_data);
3851 let mut reader = PdfReader::new(cursor).unwrap();
3852
3853 let obj1 = reader.get_object(1, 0).unwrap();
3855 assert!(obj1.as_dict().is_some());
3856
3857 let obj2 = reader.get_object(1, 0).unwrap();
3859 assert!(obj2.as_dict().is_some());
3860 }
3861
3862 #[test]
3863 fn test_reader_wrong_generation() {
3864 let pdf_data = create_minimal_pdf();
3865 let cursor = Cursor::new(pdf_data);
3866 let mut reader = PdfReader::new(cursor).unwrap();
3867
3868 let obj = reader.get_object(1, 99);
3870 assert!(obj.is_err());
3871 }
3872
3873 #[test]
3874 fn test_reader_invalid_pdf() {
3875 let invalid_data = b"This is not a PDF file";
3876 let cursor = Cursor::new(invalid_data.to_vec());
3877 let result = PdfReader::new(cursor);
3878
3879 assert!(result.is_err());
3880 }
3881
3882 #[test]
3883 fn test_reader_corrupt_xref() {
3884 let corrupt_pdf = b"%PDF-1.4
38851 0 obj
3886<< /Type /Catalog >>
3887endobj
3888xref
3889corrupted xref table
3890trailer
3891<< /Size 2 /Root 1 0 R >>
3892startxref
389324
3894%%EOF"
3895 .to_vec();
3896
3897 let cursor = Cursor::new(corrupt_pdf.clone());
3901 let mut reader = PdfReader::new(cursor).expect("corrupt xref is reconstructed by scan");
3902 let catalog = reader
3903 .catalog()
3904 .expect("catalog resolves after reconstruction");
3905 assert_eq!(
3906 catalog.get("Type"),
3907 Some(&PdfObject::Name(PdfName("Catalog".to_string()))),
3908 );
3909
3910 let cursor = Cursor::new(corrupt_pdf);
3912 assert!(PdfReader::new_with_options(cursor, ParseOptions::strict()).is_err());
3913 }
3914
3915 #[test]
3916 fn test_reader_missing_trailer() {
3917 let pdf_no_trailer = b"%PDF-1.4
39181 0 obj
3919<< /Type /Catalog >>
3920endobj
3921xref
39220 2
39230000000000 65535 f
39240000000009 00000 n
3925startxref
392624
3927%%EOF"
3928 .to_vec();
3929
3930 let cursor = Cursor::new(pdf_no_trailer.clone());
3934 let mut reader = PdfReader::new(cursor).expect("missing trailer recovered by object scan");
3935 let catalog = reader
3936 .catalog()
3937 .expect("catalog resolved from scanned /Root");
3938 assert_eq!(
3939 catalog.get("Type"),
3940 Some(&PdfObject::Name(PdfName("Catalog".to_string()))),
3941 );
3942
3943 let cursor = Cursor::new(pdf_no_trailer);
3945 assert!(PdfReader::new_with_options(cursor, ParseOptions::strict()).is_err());
3946 }
3947
3948 #[test]
3949 fn test_reader_empty_pdf() {
3950 let cursor = Cursor::new(Vec::new());
3951 let result = PdfReader::new(cursor);
3952 assert!(result.is_err());
3953 }
3954
3955 #[test]
3956 fn test_reader_page_count() {
3957 let pdf_data = create_minimal_pdf();
3958 let cursor = Cursor::new(pdf_data);
3959 let mut reader = PdfReader::new(cursor).unwrap();
3960
3961 let count = reader.page_count();
3962 assert!(count.is_ok());
3963 assert_eq!(count.unwrap(), 0); }
3965
3966 #[test]
3967 fn test_reader_into_document() {
3968 let pdf_data = create_minimal_pdf();
3969 let cursor = Cursor::new(pdf_data);
3970 let reader = PdfReader::new(cursor).unwrap();
3971
3972 let document = reader.into_document();
3973 let page_count = document.page_count();
3975 assert!(page_count.is_ok());
3976 }
3977
3978 #[test]
3979 fn test_reader_pages_dict() {
3980 let pdf_data = create_minimal_pdf();
3981 let cursor = Cursor::new(pdf_data);
3982 let mut reader = PdfReader::new(cursor).unwrap();
3983
3984 let pages = reader.pages();
3985 assert!(pages.is_ok());
3986 let pages_dict = pages.unwrap();
3987 assert_eq!(
3988 pages_dict.get("Type"),
3989 Some(&PdfObject::Name(PdfName("Pages".to_string())))
3990 );
3991 }
3992
3993 #[test]
3994 fn test_reader_pdf_with_binary_data() {
3995 let pdf_data = create_pdf_with_binary_marker();
3996
3997 let cursor = Cursor::new(pdf_data);
3998 let result = PdfReader::new(cursor);
3999 assert!(result.is_ok());
4000 }
4001
4002 #[test]
4003 fn test_reader_metadata() {
4004 let pdf_data = create_pdf_with_info();
4005 let cursor = Cursor::new(pdf_data);
4006 let mut reader = PdfReader::new(cursor).unwrap();
4007
4008 let metadata = reader.metadata().unwrap();
4009 assert_eq!(metadata.title, Some("Test PDF".to_string()));
4010 assert_eq!(metadata.author, Some("Test Author".to_string()));
4011 assert_eq!(metadata.subject, Some("Testing".to_string()));
4012 assert_eq!(metadata.version, "1.4".to_string());
4013 }
4014
4015 #[test]
4016 fn test_reader_metadata_empty() {
4017 let pdf_data = create_minimal_pdf();
4018 let cursor = Cursor::new(pdf_data);
4019 let mut reader = PdfReader::new(cursor).unwrap();
4020
4021 let metadata = reader.metadata().unwrap();
4022 assert!(metadata.title.is_none());
4023 assert!(metadata.author.is_none());
4024 assert_eq!(metadata.version, "1.4".to_string());
4025 assert_eq!(metadata.page_count, Some(0));
4026 }
4027
4028 #[test]
4029 fn test_reader_object_number_mismatch() {
4030 let pdf_data = create_minimal_pdf();
4034 let cursor = Cursor::new(pdf_data);
4035 let mut reader = PdfReader::new(cursor).unwrap();
4036
4037 let result = reader.get_object(1, 99);
4040 assert!(result.is_err());
4041
4042 let result2 = reader.get_object(999, 0);
4044 assert!(result2.is_err());
4045 }
4046
4047 #[test]
4048 fn test_document_metadata_struct() {
4049 let metadata = DocumentMetadata {
4050 title: Some("Title".to_string()),
4051 author: Some("Author".to_string()),
4052 subject: Some("Subject".to_string()),
4053 keywords: Some("Keywords".to_string()),
4054 creator: Some("Creator".to_string()),
4055 producer: Some("Producer".to_string()),
4056 creation_date: Some("D:20240101".to_string()),
4057 modification_date: Some("D:20240102".to_string()),
4058 version: "1.5".to_string(),
4059 page_count: Some(10),
4060 };
4061
4062 assert_eq!(metadata.title, Some("Title".to_string()));
4063 assert_eq!(metadata.page_count, Some(10));
4064 }
4065
4066 #[test]
4067 fn test_document_metadata_default() {
4068 let metadata = DocumentMetadata::default();
4069 assert!(metadata.title.is_none());
4070 assert!(metadata.author.is_none());
4071 assert!(metadata.subject.is_none());
4072 assert!(metadata.keywords.is_none());
4073 assert!(metadata.creator.is_none());
4074 assert!(metadata.producer.is_none());
4075 assert!(metadata.creation_date.is_none());
4076 assert!(metadata.modification_date.is_none());
4077 assert_eq!(metadata.version, "".to_string());
4078 assert!(metadata.page_count.is_none());
4079 }
4080
4081 #[test]
4082 fn test_document_metadata_clone() {
4083 let metadata = DocumentMetadata {
4084 title: Some("Test".to_string()),
4085 version: "1.4".to_string(),
4086 ..Default::default()
4087 };
4088
4089 let cloned = metadata;
4090 assert_eq!(cloned.title, Some("Test".to_string()));
4091 assert_eq!(cloned.version, "1.4".to_string());
4092 }
4093
4094 #[test]
4095 fn test_reader_trailer_validation_error() {
4096 let bad_pdf = b"%PDF-1.4
40981 0 obj
4099<< /Type /Catalog >>
4100endobj
4101xref
41020 2
41030000000000 65535 f
41040000000009 00000 n
4105trailer
4106<< /Size 2 >>
4107startxref
410846
4109%%EOF"
4110 .to_vec();
4111
4112 let cursor = Cursor::new(bad_pdf.clone());
4116 let mut reader = PdfReader::new(cursor).expect("missing /Root recovered by object scan");
4117 let catalog = reader
4118 .catalog()
4119 .expect("catalog resolved from scanned /Root");
4120 assert_eq!(
4121 catalog.get("Type"),
4122 Some(&PdfObject::Name(PdfName("Catalog".to_string()))),
4123 );
4124
4125 let cursor = Cursor::new(bad_pdf);
4127 assert!(PdfReader::new_with_options(cursor, ParseOptions::strict()).is_err());
4128 }
4129
4130 #[test]
4131 fn test_reader_with_options() {
4132 let pdf_data = create_minimal_pdf();
4133 let cursor = Cursor::new(pdf_data);
4134 let mut options = ParseOptions::default();
4135 options.lenient_streams = true;
4136 options.max_recovery_bytes = 2000;
4137 options.collect_warnings = true;
4138
4139 let reader = PdfReader::new_with_options(cursor, options);
4140 assert!(reader.is_ok());
4141 }
4142
4143 #[test]
4144 fn test_lenient_stream_parsing() {
4145 let pdf_data = b"%PDF-1.4
41471 0 obj
4148<< /Type /Catalog /Pages 2 0 R >>
4149endobj
41502 0 obj
4151<< /Type /Pages /Kids [3 0 R] /Count 1 >>
4152endobj
41533 0 obj
4154<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] /Contents 4 0 R >>
4155endobj
41564 0 obj
4157<< /Length 10 >>
4158stream
4159This is a longer stream than 10 bytes
4160endstream
4161endobj
4162xref
41630 5
41640000000000 65535 f
41650000000009 00000 n
41660000000058 00000 n
41670000000116 00000 n
41680000000219 00000 n
4169trailer
4170<< /Size 5 /Root 1 0 R >>
4171startxref
4172299
4173%%EOF"
4174 .to_vec();
4175
4176 let cursor = Cursor::new(pdf_data.clone());
4178 let strict_options = ParseOptions::strict();
4179 let strict_reader = PdfReader::new_with_options(cursor, strict_options);
4180 assert!(strict_reader.is_err());
4182
4183 let cursor = Cursor::new(pdf_data);
4187 let mut options = ParseOptions::default();
4188 options.lenient_streams = true;
4189 options.max_recovery_bytes = 1000;
4190 options.collect_warnings = false;
4191 let mut reader =
4192 PdfReader::new_with_options(cursor, options).expect("mismatched xref reconstructed");
4193 let catalog = reader
4194 .catalog()
4195 .expect("catalog resolves after reconstruction");
4196 assert_eq!(
4197 catalog.get("Type"),
4198 Some(&PdfObject::Name(PdfName("Catalog".to_string()))),
4199 );
4200 }
4201
4202 #[test]
4203 fn test_parse_options_default() {
4204 let options = ParseOptions::default();
4205 assert!(!options.lenient_streams);
4206 assert_eq!(options.max_recovery_bytes, 1000);
4207 assert!(!options.collect_warnings);
4208 }
4209
4210 #[test]
4211 fn test_parse_options_clone() {
4212 let mut options = ParseOptions::default();
4213 options.lenient_streams = true;
4214 options.max_recovery_bytes = 2000;
4215 options.collect_warnings = true;
4216 let cloned = options;
4217 assert!(cloned.lenient_streams);
4218 assert_eq!(cloned.max_recovery_bytes, 2000);
4219 assert!(cloned.collect_warnings);
4220 }
4221
4222 #[allow(dead_code)]
4225 fn create_encrypted_pdf_dict() -> PdfDictionary {
4226 let mut dict = PdfDictionary::new();
4227 dict.insert(
4228 "Filter".to_string(),
4229 PdfObject::Name(PdfName("Standard".to_string())),
4230 );
4231 dict.insert("V".to_string(), PdfObject::Integer(1));
4232 dict.insert("R".to_string(), PdfObject::Integer(2));
4233 dict.insert("O".to_string(), PdfObject::String(PdfString(vec![0u8; 32])));
4234 dict.insert("U".to_string(), PdfObject::String(PdfString(vec![0u8; 32])));
4235 dict.insert("P".to_string(), PdfObject::Integer(-4));
4236 dict
4237 }
4238
4239 fn create_pdf_with_encryption() -> Vec<u8> {
4240 b"%PDF-1.4
42421 0 obj
4243<< /Type /Catalog /Pages 2 0 R >>
4244endobj
42452 0 obj
4246<< /Type /Pages /Kids [3 0 R] /Count 1 >>
4247endobj
42483 0 obj
4249<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] >>
4250endobj
42514 0 obj
4252<< /Filter /Standard /V 1 /R 2 /O (32 bytes of owner password hash data) /U (32 bytes of user password hash data) /P -4 >>
4253endobj
4254xref
42550 5
42560000000000 65535 f
42570000000009 00000 n
42580000000058 00000 n
42590000000116 00000 n
42600000000201 00000 n
4261trailer
4262<< /Size 5 /Root 1 0 R /Encrypt 4 0 R /ID [(file id)] >>
4263startxref
4264295
4265%%EOF"
4266 .to_vec()
4267 }
4268
4269 #[test]
4270 fn test_reader_encryption_detection() {
4271 let unencrypted_pdf = create_minimal_pdf();
4273 let cursor = Cursor::new(unencrypted_pdf);
4274 let reader = PdfReader::new(cursor).unwrap();
4275 assert!(!reader.is_encrypted());
4276 assert!(reader.is_unlocked()); let encrypted_pdf = create_pdf_with_encryption();
4284 let cursor = Cursor::new(encrypted_pdf);
4285 let reader = PdfReader::new(cursor).expect("encrypted PDF opens as locked, not rejected");
4286 assert!(
4287 reader.is_encrypted(),
4288 "must report encrypted, not plaintext"
4289 );
4290 assert!(
4291 !reader.is_unlocked(),
4292 "must stay locked without a valid password"
4293 );
4294 }
4295
4296 #[test]
4297 fn test_reader_encryption_methods_unencrypted() {
4298 let pdf_data = create_minimal_pdf();
4299 let cursor = Cursor::new(pdf_data);
4300 let mut reader = PdfReader::new(cursor).unwrap();
4301
4302 assert!(!reader.is_encrypted());
4304 assert!(reader.is_unlocked());
4305 assert!(reader.encryption_handler().is_none());
4306 assert!(reader.encryption_handler_mut().is_none());
4307
4308 assert!(reader.unlock_with_password("any_password").unwrap());
4310 assert!(reader.try_empty_password().unwrap());
4311 }
4312
4313 #[test]
4314 fn test_reader_encryption_handler_access() {
4315 let pdf_data = create_minimal_pdf();
4316 let cursor = Cursor::new(pdf_data);
4317 let mut reader = PdfReader::new(cursor).unwrap();
4318
4319 assert!(reader.encryption_handler().is_none());
4321 assert!(reader.encryption_handler_mut().is_none());
4322
4323 assert!(!reader.is_encrypted());
4325 assert!(reader.is_unlocked());
4326 }
4327
4328 #[test]
4329 fn test_reader_multiple_password_attempts() {
4330 let pdf_data = create_minimal_pdf();
4331 let cursor = Cursor::new(pdf_data);
4332 let mut reader = PdfReader::new(cursor).unwrap();
4333
4334 let passwords = vec!["test1", "test2", "admin", "", "password"];
4336 for password in passwords {
4337 assert!(reader.unlock_with_password(password).unwrap());
4338 }
4339
4340 for _ in 0..5 {
4342 assert!(reader.try_empty_password().unwrap());
4343 }
4344 }
4345
4346 #[test]
4347 fn test_reader_encryption_state_consistency() {
4348 let pdf_data = create_minimal_pdf();
4349 let cursor = Cursor::new(pdf_data);
4350 let mut reader = PdfReader::new(cursor).unwrap();
4351
4352 assert!(!reader.is_encrypted());
4354 assert!(reader.is_unlocked());
4355 assert!(reader.encryption_handler().is_none());
4356
4357 let _ = reader.unlock_with_password("test");
4359 assert!(!reader.is_encrypted());
4360 assert!(reader.is_unlocked());
4361 assert!(reader.encryption_handler().is_none());
4362
4363 let _ = reader.try_empty_password();
4364 assert!(!reader.is_encrypted());
4365 assert!(reader.is_unlocked());
4366 assert!(reader.encryption_handler().is_none());
4367 }
4368
4369 #[test]
4370 fn test_reader_encryption_error_handling() {
4371 let encrypted_pdf = create_pdf_with_encryption();
4376 let cursor = Cursor::new(encrypted_pdf);
4377
4378 match PdfReader::new(cursor) {
4379 Err(_) => {}
4381 Ok(reader) => {
4384 assert!(
4385 reader.is_encrypted() && !reader.is_unlocked(),
4386 "recovered encrypted PDF must stay encrypted+locked, not open as plaintext"
4387 );
4388 }
4389 }
4390 }
4391
4392 #[test]
4393 fn test_reader_encryption_with_options() {
4394 let pdf_data = create_minimal_pdf();
4395 let cursor = Cursor::new(pdf_data);
4396
4397 let strict_options = ParseOptions::strict();
4399 let strict_reader = PdfReader::new_with_options(cursor, strict_options).unwrap();
4400 assert!(!strict_reader.is_encrypted());
4401 assert!(strict_reader.is_unlocked());
4402
4403 let pdf_data = create_minimal_pdf();
4404 let cursor = Cursor::new(pdf_data);
4405 let lenient_options = ParseOptions::lenient();
4406 let lenient_reader = PdfReader::new_with_options(cursor, lenient_options).unwrap();
4407 assert!(!lenient_reader.is_encrypted());
4408 assert!(lenient_reader.is_unlocked());
4409 }
4410
4411 #[test]
4412 fn test_reader_encryption_integration_edge_cases() {
4413 let pdf_data = create_minimal_pdf();
4414 let cursor = Cursor::new(pdf_data);
4415 let mut reader = PdfReader::new(cursor).unwrap();
4416
4417 assert!(reader.unlock_with_password("").unwrap());
4419 assert!(reader.unlock_with_password(" ").unwrap()); assert!(reader
4421 .unlock_with_password("very_long_password_that_exceeds_normal_length")
4422 .unwrap());
4423 assert!(reader.unlock_with_password("unicode_test_ñáéíóú").unwrap());
4424
4425 assert!(reader.unlock_with_password("pass@#$%^&*()").unwrap());
4427 assert!(reader.unlock_with_password("pass\nwith\nnewlines").unwrap());
4428 assert!(reader.unlock_with_password("pass\twith\ttabs").unwrap());
4429 }
4430
4431 mod rigorous {
4432 use super::*;
4433
4434 #[test]
4439 fn test_reader_invalid_pdf_header() {
4440 let invalid_data = b"This is not a PDF file";
4442 let cursor = Cursor::new(invalid_data.to_vec());
4443 let result = PdfReader::new(cursor);
4444
4445 assert!(result.is_err(), "Should fail on invalid PDF header");
4446 }
4447
4448 #[test]
4449 fn test_reader_truncated_header() {
4450 let truncated = b"%PDF";
4452 let cursor = Cursor::new(truncated.to_vec());
4453 let result = PdfReader::new(cursor);
4454
4455 assert!(result.is_err(), "Should fail on truncated header");
4456 }
4457
4458 #[test]
4459 fn test_reader_empty_file() {
4460 let empty = Vec::new();
4461 let cursor = Cursor::new(empty);
4462 let result = PdfReader::new(cursor);
4463
4464 assert!(result.is_err(), "Should fail on empty file");
4465 }
4466
4467 #[test]
4468 fn test_reader_malformed_version() {
4469 let malformed = b"%PDF-X.Y\n%%\xE2\xE3\xCF\xD3\n";
4471 let cursor = Cursor::new(malformed.to_vec());
4472 let result = PdfReader::new(cursor);
4473
4474 if let Ok(reader) = result {
4476 let _version = reader.version();
4478 }
4479 }
4480
4481 #[test]
4482 fn test_reader_get_nonexistent_object() {
4483 let pdf_data = create_minimal_pdf();
4484 let cursor = Cursor::new(pdf_data);
4485 let mut reader = PdfReader::new(cursor).unwrap();
4486
4487 let result = reader.get_object(999, 0);
4489
4490 assert!(result.is_err(), "Should fail when object doesn't exist");
4491 }
4492
4493 #[test]
4494 fn test_reader_get_object_wrong_generation() {
4495 let pdf_data = create_minimal_pdf();
4496 let cursor = Cursor::new(pdf_data);
4497 let mut reader = PdfReader::new(cursor).unwrap();
4498
4499 let result = reader.get_object(1, 99);
4501
4502 if let Err(e) = result {
4504 let _ = e;
4506 }
4507 }
4508
4509 #[test]
4514 fn test_resolve_direct_object() {
4515 let pdf_data = create_minimal_pdf();
4516 let cursor = Cursor::new(pdf_data);
4517 let mut reader = PdfReader::new(cursor).unwrap();
4518
4519 let direct_obj = PdfObject::Integer(42);
4521
4522 let resolved = reader.resolve(&direct_obj).unwrap();
4523
4524 assert_eq!(resolved, &PdfObject::Integer(42));
4526 }
4527
4528 #[test]
4529 fn test_resolve_reference() {
4530 let pdf_data = create_minimal_pdf();
4531 let cursor = Cursor::new(pdf_data);
4532 let mut reader = PdfReader::new(cursor).unwrap();
4533
4534 let pages_ref = {
4536 let catalog = reader.catalog().unwrap();
4537 if let Some(PdfObject::Reference(obj_num, gen_num)) = catalog.get("Pages") {
4538 PdfObject::Reference(*obj_num, *gen_num)
4539 } else {
4540 panic!("Catalog /Pages must be a Reference");
4541 }
4542 };
4543
4544 let resolved = reader.resolve(&pages_ref).unwrap();
4546
4547 if let PdfObject::Dictionary(dict) = resolved {
4549 assert_eq!(
4550 dict.get("Type"),
4551 Some(&PdfObject::Name(PdfName("Pages".to_string())))
4552 );
4553 } else {
4554 panic!("Expected dictionary, got: {:?}", resolved);
4555 }
4556 }
4557
4558 #[test]
4563 fn test_is_encrypted_on_unencrypted() {
4564 let pdf_data = create_minimal_pdf();
4565 let cursor = Cursor::new(pdf_data);
4566 let reader = PdfReader::new(cursor).unwrap();
4567
4568 assert!(
4569 !reader.is_encrypted(),
4570 "Minimal PDF should not be encrypted"
4571 );
4572 }
4573
4574 #[test]
4575 fn test_is_unlocked_on_unencrypted() {
4576 let pdf_data = create_minimal_pdf();
4577 let cursor = Cursor::new(pdf_data);
4578 let reader = PdfReader::new(cursor).unwrap();
4579
4580 assert!(reader.is_unlocked(), "Unencrypted PDF should be unlocked");
4582 }
4583
4584 #[test]
4585 fn test_try_empty_password_on_unencrypted() {
4586 let pdf_data = create_minimal_pdf();
4587 let cursor = Cursor::new(pdf_data);
4588 let mut reader = PdfReader::new(cursor).unwrap();
4589
4590 let result = reader.try_empty_password();
4592 assert!(result.is_ok());
4593 }
4594
4595 #[test]
4600 fn test_reader_with_strict_options() {
4601 let pdf_data = create_minimal_pdf();
4602 let cursor = Cursor::new(pdf_data);
4603
4604 let options = ParseOptions::strict();
4605 let result = PdfReader::new_with_options(cursor, options);
4606
4607 assert!(result.is_ok(), "Minimal PDF should parse in strict mode");
4608 }
4609
4610 #[test]
4611 fn test_reader_with_lenient_options() {
4612 let pdf_data = create_minimal_pdf();
4613 let cursor = Cursor::new(pdf_data);
4614
4615 let options = ParseOptions::lenient();
4616 let result = PdfReader::new_with_options(cursor, options);
4617
4618 assert!(result.is_ok(), "Minimal PDF should parse in lenient mode");
4619 }
4620
4621 #[test]
4622 fn test_reader_options_accessible() {
4623 let pdf_data = create_minimal_pdf();
4624 let cursor = Cursor::new(pdf_data);
4625
4626 let options = ParseOptions::lenient();
4627 let reader = PdfReader::new_with_options(cursor, options.clone()).unwrap();
4628
4629 let reader_options = reader.options();
4631 assert_eq!(reader_options.strict_mode, options.strict_mode);
4632 }
4633
4634 #[test]
4639 fn test_catalog_has_required_fields() {
4640 let pdf_data = create_minimal_pdf();
4641 let cursor = Cursor::new(pdf_data);
4642 let mut reader = PdfReader::new(cursor).unwrap();
4643
4644 let catalog = reader.catalog().unwrap();
4645
4646 assert_eq!(
4648 catalog.get("Type"),
4649 Some(&PdfObject::Name(PdfName("Catalog".to_string()))),
4650 "Catalog must have /Type /Catalog"
4651 );
4652
4653 assert!(
4655 catalog.contains_key("Pages"),
4656 "Catalog must have /Pages entry"
4657 );
4658 }
4659
4660 #[test]
4661 fn test_info_fields_when_present() {
4662 let pdf_data = create_pdf_with_info();
4663 let cursor = Cursor::new(pdf_data);
4664 let mut reader = PdfReader::new(cursor).unwrap();
4665
4666 let info = reader.info().unwrap();
4667 assert!(info.is_some(), "PDF should have Info dictionary");
4668
4669 let info_dict = info.unwrap();
4670
4671 assert!(info_dict.contains_key("Title"), "Info should have Title");
4673 assert!(info_dict.contains_key("Author"), "Info should have Author");
4674 }
4675
4676 #[test]
4677 fn test_info_none_when_absent() {
4678 let pdf_data = create_minimal_pdf();
4679 let cursor = Cursor::new(pdf_data);
4680 let mut reader = PdfReader::new(cursor).unwrap();
4681
4682 let info = reader.info().unwrap();
4683 assert!(info.is_none(), "Minimal PDF should not have Info");
4684 }
4685
4686 #[test]
4691 fn test_version_exact_values() {
4692 let pdf_data = create_pdf_with_version("1.7");
4693 let cursor = Cursor::new(pdf_data);
4694 let reader = PdfReader::new(cursor).unwrap();
4695
4696 let version = reader.version();
4697 assert_eq!(version.major, 1, "Major version must be exact");
4698 assert_eq!(version.minor, 7, "Minor version must be exact");
4699 }
4700
4701 #[test]
4702 fn test_version_pdf_20() {
4703 let pdf_data = create_pdf_with_version("2.0");
4704 let cursor = Cursor::new(pdf_data);
4705 let reader = PdfReader::new(cursor).unwrap();
4706
4707 let version = reader.version();
4708 assert_eq!(version.major, 2, "PDF 2.0 major version");
4709 assert_eq!(version.minor, 0, "PDF 2.0 minor version");
4710 }
4711
4712 #[test]
4717 fn test_pages_returns_pages_dict() {
4718 let pdf_data = create_minimal_pdf();
4719 let cursor = Cursor::new(pdf_data);
4720 let mut reader = PdfReader::new(cursor).unwrap();
4721
4722 let pages_dict = reader
4723 .pages()
4724 .expect("pages() must return Pages dictionary");
4725
4726 assert_eq!(
4727 pages_dict.get("Type"),
4728 Some(&PdfObject::Name(PdfName("Pages".to_string()))),
4729 "Pages dict must have /Type /Pages"
4730 );
4731 }
4732
4733 #[test]
4734 fn test_page_count_minimal_pdf() {
4735 let pdf_data = create_minimal_pdf();
4736 let cursor = Cursor::new(pdf_data);
4737 let mut reader = PdfReader::new(cursor).unwrap();
4738
4739 let count = reader.page_count().expect("page_count() must succeed");
4740 assert_eq!(count, 0, "Minimal PDF has 0 pages");
4741 }
4742
4743 #[test]
4744 fn test_page_count_with_info_pdf() {
4745 let pdf_data = create_pdf_with_info();
4746 let cursor = Cursor::new(pdf_data);
4747 let mut reader = PdfReader::new(cursor).unwrap();
4748
4749 let count = reader.page_count().expect("page_count() must succeed");
4750 assert_eq!(count, 0, "create_pdf_with_info() has Count 0 in Pages dict");
4751 }
4752
4753 #[test]
4758 fn test_metadata_minimal_pdf() {
4759 let pdf_data = create_minimal_pdf();
4760 let cursor = Cursor::new(pdf_data);
4761 let mut reader = PdfReader::new(cursor).unwrap();
4762
4763 let meta = reader.metadata().expect("metadata() must succeed");
4764
4765 assert!(meta.title.is_none(), "Minimal PDF has no title");
4767 assert!(meta.author.is_none(), "Minimal PDF has no author");
4768 }
4769
4770 #[test]
4771 fn test_metadata_with_info() {
4772 let pdf_data = create_pdf_with_info();
4773 let cursor = Cursor::new(pdf_data);
4774 let mut reader = PdfReader::new(cursor).unwrap();
4775
4776 let meta = reader.metadata().expect("metadata() must succeed");
4777
4778 assert!(meta.title.is_some(), "PDF with Info has title");
4779 assert_eq!(meta.title.unwrap(), "Test PDF", "Title must match");
4780 assert!(meta.author.is_some(), "PDF with Info has author");
4781 assert_eq!(meta.author.unwrap(), "Test Author", "Author must match");
4782 }
4783
4784 #[test]
4789 fn test_resolve_stream_length_direct_integer() {
4790 let pdf_data = create_minimal_pdf();
4791 let cursor = Cursor::new(pdf_data);
4792 let mut reader = PdfReader::new(cursor).unwrap();
4793
4794 let length_obj = PdfObject::Integer(100);
4796
4797 let length = reader
4798 .resolve_stream_length(&length_obj)
4799 .expect("resolve_stream_length must succeed");
4800 assert_eq!(length, Some(100), "Direct integer must be resolved");
4801 }
4802
4803 #[test]
4804 fn test_resolve_stream_length_negative_integer() {
4805 let pdf_data = create_minimal_pdf();
4806 let cursor = Cursor::new(pdf_data);
4807 let mut reader = PdfReader::new(cursor).unwrap();
4808
4809 let length_obj = PdfObject::Integer(-10);
4811
4812 let length = reader
4813 .resolve_stream_length(&length_obj)
4814 .expect("resolve_stream_length must succeed");
4815 assert_eq!(length, None, "Negative integer returns None");
4816 }
4817
4818 #[test]
4819 fn test_resolve_stream_length_non_integer() {
4820 let pdf_data = create_minimal_pdf();
4821 let cursor = Cursor::new(pdf_data);
4822 let mut reader = PdfReader::new(cursor).unwrap();
4823
4824 let name_obj = PdfObject::Name(PdfName("Test".to_string()));
4826
4827 let length = reader
4828 .resolve_stream_length(&name_obj)
4829 .expect("resolve_stream_length must succeed");
4830 assert_eq!(length, None, "Non-integer object returns None");
4831 }
4832
4833 #[test]
4838 fn test_get_all_pages_empty_pdf() {
4839 let pdf_data = create_minimal_pdf();
4840 let cursor = Cursor::new(pdf_data);
4841 let mut reader = PdfReader::new(cursor).unwrap();
4842
4843 let pages = reader
4844 .get_all_pages()
4845 .expect("get_all_pages() must succeed");
4846 assert_eq!(pages.len(), 0, "Minimal PDF has 0 pages");
4847 }
4848
4849 #[test]
4850 fn test_get_all_pages_with_info() {
4851 let pdf_data = create_pdf_with_info();
4852 let cursor = Cursor::new(pdf_data);
4853 let mut reader = PdfReader::new(cursor).unwrap();
4854
4855 let pages = reader
4856 .get_all_pages()
4857 .expect("get_all_pages() must succeed");
4858 assert_eq!(
4859 pages.len(),
4860 0,
4861 "create_pdf_with_info() has 0 pages (Count 0)"
4862 );
4863 }
4864
4865 #[test]
4870 fn test_into_document_consumes_reader() {
4871 let pdf_data = create_minimal_pdf();
4872 let cursor = Cursor::new(pdf_data);
4873 let reader = PdfReader::new(cursor).unwrap();
4874
4875 let document = reader.into_document();
4876
4877 let version = document.version().expect("Document must have version");
4879 assert!(
4880 version.starts_with("1."),
4881 "Document must have PDF 1.x version, got: {}",
4882 version
4883 );
4884
4885 let page_count = document
4887 .page_count()
4888 .expect("Document must allow page_count()");
4889 assert_eq!(
4890 page_count, 0,
4891 "Minimal PDF has 0 pages (Count 0 in test helper)"
4892 );
4893 }
4894
4895 #[test]
4900 fn test_clear_parse_context() {
4901 let pdf_data = create_minimal_pdf();
4902 let cursor = Cursor::new(pdf_data);
4903 let mut reader = PdfReader::new(cursor).unwrap();
4904
4905 reader.clear_parse_context();
4907
4908 let version = reader.version();
4910 assert_eq!(version.major, 1, "Reader must still work after clear");
4911 }
4912
4913 #[test]
4914 fn test_parse_context_mut_accessible() {
4915 let pdf_data = create_minimal_pdf();
4916 let cursor = Cursor::new(pdf_data);
4917 let mut reader = PdfReader::new(cursor).unwrap();
4918
4919 let context = reader.parse_context_mut();
4920
4921 let initial_depth = context.depth;
4923 assert_eq!(initial_depth, 0, "Parse context must start with depth 0");
4924
4925 assert!(
4927 context.max_depth > 0,
4928 "Parse context must have positive max_depth"
4929 );
4930 }
4931
4932 #[test]
4937 fn test_find_byte_pattern_basic() {
4938 let haystack = b"Hello World";
4939 let needle = b"World";
4940 let pos = find_byte_pattern(haystack, needle);
4941 assert_eq!(pos, Some(6), "Must find 'World' at position 6");
4942 }
4943
4944 #[test]
4945 fn test_find_byte_pattern_not_found() {
4946 let haystack = b"Hello World";
4947 let needle = b"Rust";
4948 let pos = find_byte_pattern(haystack, needle);
4949 assert_eq!(pos, None, "Must return None when not found");
4950 }
4951
4952 #[test]
4953 fn test_find_byte_pattern_at_start() {
4954 let haystack = b"Hello World";
4955 let needle = b"Hello";
4956 let pos = find_byte_pattern(haystack, needle);
4957 assert_eq!(pos, Some(0), "Must find at position 0");
4958 }
4959
4960 #[test]
4961 fn test_is_immediate_stream_start_with_stream() {
4962 let data = b"stream\ndata";
4963 assert!(
4964 is_immediate_stream_start(data),
4965 "Must detect 'stream' at start"
4966 );
4967 }
4968
4969 #[test]
4970 fn test_is_immediate_stream_start_with_whitespace() {
4971 let data = b" \n\tstream\ndata";
4972 assert!(
4973 is_immediate_stream_start(data),
4974 "Must detect 'stream' after whitespace"
4975 );
4976 }
4977
4978 #[test]
4979 fn test_is_immediate_stream_start_no_stream() {
4980 let data = b"endobj";
4981 assert!(
4982 !is_immediate_stream_start(data),
4983 "Must return false when 'stream' absent"
4984 );
4985 }
4986 }
4987}