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(crate) fn object_references(&self) -> Vec<(u32, u16)> {
168 self.xref.in_use_references()
169 }
170
171 pub(crate) fn object_storage_offset(&self, object_number: u32) -> Option<u64> {
173 self.xref.object_storage_offset(object_number)
174 }
175
176 pub(crate) fn xref_revisions(&self) -> Vec<super::xref::XRefRevision> {
178 self.xref.revisions_oldest_first()
179 }
180
181 pub fn is_unlocked(&self) -> bool {
183 match &self.encryption_handler {
184 Some(handler) => handler.is_unlocked(),
185 None => true, }
187 }
188
189 pub fn encryption_handler_mut(&mut self) -> Option<&mut EncryptionHandler> {
191 self.encryption_handler.as_mut()
192 }
193
194 pub fn encryption_handler(&self) -> Option<&EncryptionHandler> {
196 self.encryption_handler.as_ref()
197 }
198
199 pub fn unlock_with_password(&mut self, password: &str) -> ParseResult<bool> {
201 match &mut self.encryption_handler {
202 Some(handler) => {
203 let user = handler.unlock_with_user_password(password);
213 if matches!(user, Ok(true)) {
214 return Ok(true);
215 }
216 let owner = handler.unlock_with_owner_password(password);
217 if matches!(owner, Ok(true)) {
218 return Ok(true);
219 }
220 match (user, owner) {
221 (Err(e), _) | (Ok(_), Err(e)) => Err(e),
222 (Ok(_), Ok(_)) => Ok(false),
223 }
224 }
225 None => Ok(true), }
227 }
228
229 pub fn try_empty_password(&mut self) -> ParseResult<bool> {
231 match &mut self.encryption_handler {
232 Some(handler) => Ok(handler.try_empty_password().unwrap_or(false)),
233 None => Ok(true), }
235 }
236
237 pub fn unlock(&mut self, password: &str) -> ParseResult<()> {
268 if !self.is_encrypted() {
270 return Ok(());
271 }
272
273 if self.is_unlocked() {
275 return Ok(());
276 }
277
278 let success = self.unlock_with_password(password)?;
280
281 if success {
282 Ok(())
283 } else {
284 Err(ParseError::WrongPassword)
285 }
286 }
287
288 fn ensure_unlocked(&self) -> ParseResult<()> {
290 if self.is_encrypted() && !self.is_unlocked() {
291 return Err(ParseError::PdfLocked);
292 }
293 Ok(())
294 }
295
296 fn decrypt_object_if_needed(
302 &self,
303 obj: PdfObject,
304 obj_num: u32,
305 gen_num: u16,
306 ) -> ParseResult<PdfObject> {
307 let handler = match &self.encryption_handler {
309 Some(h) if h.is_unlocked() => h,
310 _ => return Ok(obj), };
312
313 let obj_id = ObjectId::new(obj_num, gen_num);
314
315 match obj {
316 PdfObject::String(ref s) => {
317 let decrypted_bytes = handler.decrypt_string(s.as_bytes(), &obj_id)?;
319 Ok(PdfObject::String(PdfString::new(decrypted_bytes)))
320 }
321 PdfObject::Stream(ref stream) => {
322 let should_decrypt = stream
324 .dict
325 .get("StmF")
326 .and_then(|o| o.as_name())
327 .map(|n| n.0.as_str() != "Identity")
328 .unwrap_or(true); if should_decrypt {
331 let decrypted_data = handler.decrypt_stream(&stream.data, &obj_id)?;
332
333 let mut new_stream = stream.clone();
335 new_stream.data = decrypted_data;
336 Ok(PdfObject::Stream(new_stream))
337 } else {
338 Ok(obj) }
340 }
341 PdfObject::Dictionary(ref dict) => {
342 let mut new_dict = PdfDictionary::new();
344 for (key, value) in dict.0.iter() {
345 let decrypted_value =
346 self.decrypt_object_if_needed(value.clone(), obj_num, gen_num)?;
347 new_dict.insert(key.0.clone(), decrypted_value);
348 }
349 Ok(PdfObject::Dictionary(new_dict))
350 }
351 PdfObject::Array(ref arr) => {
352 let mut new_arr = Vec::new();
354 for elem in arr.0.iter() {
355 let decrypted_elem =
356 self.decrypt_object_if_needed(elem.clone(), obj_num, gen_num)?;
357 new_arr.push(decrypted_elem);
358 }
359 Ok(PdfObject::Array(PdfArray(new_arr)))
360 }
361 _ => Ok(obj),
363 }
364 }
365}
366
367impl PdfReader<File> {
368 pub fn open<P: AsRef<Path>>(path: P) -> ParseResult<Self> {
370 #[cfg(feature = "verbose-debug")]
371 {
372 use std::io::Write;
373 if let Ok(mut f) = std::fs::File::create("/tmp/pdf_open_debug.log") {
374 writeln!(f, "Opening file: {:?}", path.as_ref()).ok();
375 }
376 }
377 let file = File::open(path)?;
378 let options = super::ParseOptions::lenient();
380 Self::new_with_options(file, options)
381 }
382
383 pub fn open_strict<P: AsRef<Path>>(path: P) -> ParseResult<Self> {
385 let file = File::open(path)?;
386 let options = super::ParseOptions::strict();
387 Self::new_with_options(file, options)
388 }
389
390 pub fn open_with_options<P: AsRef<Path>>(
392 path: P,
393 options: super::ParseOptions,
394 ) -> ParseResult<Self> {
395 let file = File::open(path)?;
396 Self::new_with_options(file, options)
397 }
398
399 pub fn open_document<P: AsRef<Path>>(
401 path: P,
402 ) -> ParseResult<super::document::PdfDocument<File>> {
403 let reader = Self::open(path)?;
404 Ok(reader.into_document())
405 }
406}
407
408impl<R: Read + Seek> PdfReader<R> {
409 pub fn new(reader: R) -> ParseResult<Self> {
416 let mut options = super::ParseOptions::default();
419 options.lenient_streams = true;
420 Self::new_with_options(reader, options)
421 }
422
423 pub fn new_with_options(reader: R, options: super::ParseOptions) -> ParseResult<Self> {
425 let mut buf_reader = BufReader::new(reader);
426
427 let start_pos = buf_reader.stream_position()?;
429 buf_reader.seek(SeekFrom::End(0))?;
430 let file_size = buf_reader.stream_position()?;
431 buf_reader.seek(SeekFrom::Start(start_pos))?;
432
433 if file_size == 0 {
434 return Err(ParseError::EmptyFile);
435 }
436
437 let header = PdfHeader::parse(&mut buf_reader)?;
439 #[cfg(feature = "verbose-debug")]
440 tracing::debug!("Header parsed: version {}", header.version);
441
442 let xref = XRefTable::parse_with_options(&mut buf_reader, &options)?;
444 #[cfg(feature = "verbose-debug")]
445 tracing::debug!("XRef table parsed with {} entries", xref.len());
446
447 let trailer_dict = xref.trailer().ok_or(ParseError::InvalidTrailer)?.clone();
449
450 let xref_offset = xref.xref_offset();
451 let trailer = PdfTrailer::from_dict(trailer_dict, xref_offset)?;
452
453 trailer.validate()?;
455
456 let encryption_handler = if EncryptionHandler::detect_encryption(trailer.dict()) {
458 if let Ok(Some((encrypt_obj_num, encrypt_gen_num))) = trailer.encrypt() {
459 let mut temp_reader = Self {
461 reader: buf_reader,
462 header: header.clone(),
463 xref: xref.clone(),
464 trailer: trailer.clone(),
465 object_cache: HashMap::new(),
466 object_stream_cache: HashMap::new(),
467 page_tree: None,
468 parse_context: StackSafeContext::new(),
469 options: options.clone(),
470 encryption_handler: None,
471 objects_being_reconstructed: std::sync::Mutex::new(
472 std::collections::HashSet::new(),
473 ),
474 max_reconstruction_depth: 100,
475 };
476
477 let encrypt_obj = temp_reader.get_object(encrypt_obj_num, encrypt_gen_num)?;
479 if let Some(encrypt_dict) = encrypt_obj.as_dict() {
480 let file_id = trailer.id().and_then(|id_obj| {
482 if let PdfObject::Array(ref id_array) = id_obj {
483 if let Some(PdfObject::String(ref id_bytes)) = id_array.get(0) {
484 Some(id_bytes.as_bytes().to_vec())
485 } else {
486 None
487 }
488 } else {
489 None
490 }
491 });
492
493 match EncryptionHandler::new(encrypt_dict, file_id) {
494 Ok(mut handler) => {
495 let _ = handler.try_empty_password();
497 buf_reader = temp_reader.reader;
499 Some(handler)
500 }
501 Err(_) => {
502 let _ = temp_reader.reader;
504 return Err(ParseError::EncryptionNotSupported);
505 }
506 }
507 } else {
508 let _ = temp_reader.reader;
509 return Err(ParseError::EncryptionNotSupported);
510 }
511 } else {
512 return Err(ParseError::EncryptionNotSupported);
513 }
514 } else {
515 None
516 };
517
518 Ok(Self {
519 reader: buf_reader,
520 header,
521 xref,
522 trailer,
523 object_cache: HashMap::new(),
524 object_stream_cache: HashMap::new(),
525 page_tree: None,
526 parse_context: StackSafeContext::new(),
527 options,
528 encryption_handler,
529 objects_being_reconstructed: std::sync::Mutex::new(std::collections::HashSet::new()),
530 max_reconstruction_depth: 100,
531 })
532 }
533
534 pub fn version(&self) -> &super::header::PdfVersion {
536 &self.header.version
537 }
538
539 pub fn catalog(&mut self) -> ParseResult<&PdfDictionary> {
541 let (obj_num, gen_num) = match self.trailer.root() {
543 Ok(root) => {
544 if let Ok(obj) = self.get_object(root.0, root.1) {
547 if let Some(dict) = obj.as_dict() {
548 if let Some(type_obj) = dict.get("Type") {
550 if let Some(type_name) = type_obj.as_name() {
551 if type_name.0 != "Catalog" {
552 tracing::warn!("Trailer /Root points to /Type/{} (not Catalog), scanning for real catalog", type_name.0);
553 if let Ok(catalog_ref) = self.find_catalog_object() {
555 catalog_ref
556 } else {
557 root }
559 } else {
560 root }
562 } else {
563 root }
565 } else {
566 root }
568 } else {
569 root }
571 } else {
572 root }
574 }
575 Err(_) => {
576 #[cfg(debug_assertions)]
578 tracing::warn!("Trailer missing Root entry, attempting recovery");
579
580 if let Some(root) = self.trailer.find_root_fallback() {
582 root
583 } else {
584 if let Ok(catalog_ref) = self.find_catalog_object() {
586 catalog_ref
587 } else {
588 return Err(ParseError::MissingKey("Root".to_string()));
589 }
590 }
591 }
592 };
593
594 let key = (obj_num, gen_num);
596 let needs_reconstruction = {
597 match self.get_object(obj_num, gen_num) {
598 Ok(catalog) => {
599 if catalog.as_dict().is_some() {
601 false
603 } else {
604 true
606 }
607 }
608 Err(_) => {
609 true
611 }
612 }
613 };
614
615 if !needs_reconstruction {
616 let catalog = self.get_object(obj_num, gen_num)?;
618 return catalog.as_dict().ok_or_else(|| ParseError::SyntaxError {
619 position: 0,
620 message: format!("Catalog object {} {} is not a dictionary", obj_num, gen_num),
621 });
622 }
623
624 match self.extract_object_manually(obj_num) {
627 Ok(dict) => {
628 let obj = PdfObject::Dictionary(dict);
630 self.object_cache.insert(key, obj);
631
632 use crate::parser::xref::XRefEntry;
634 let xref_entry = XRefEntry {
635 offset: 0, generation: gen_num,
637 in_use: true,
638 };
639 self.xref.add_entry(obj_num, xref_entry);
640
641 if let Some(PdfObject::Dictionary(ref dict)) = self.object_cache.get(&key) {
643 return Ok(dict);
644 }
645 }
646 Err(_e) => {}
647 }
648
649 Err(ParseError::SyntaxError {
651 position: 0,
652 message: format!(
653 "Catalog object {} could not be parsed or reconstructed as a dictionary",
654 obj_num
655 ),
656 })
657 }
658
659 pub fn info(&mut self) -> ParseResult<Option<&PdfDictionary>> {
661 match self.trailer.info() {
662 Some((obj_num, gen_num)) => {
663 let info = self.get_object(obj_num, gen_num)?;
664 Ok(info.as_dict())
665 }
666 None => Ok(None),
667 }
668 }
669
670 pub fn get_object(&mut self, obj_num: u32, gen_num: u16) -> ParseResult<&PdfObject> {
672 self.ensure_unlocked()?;
674
675 let key = (obj_num, gen_num);
676
677 if self.object_cache.contains_key(&key) {
679 return Ok(&self.object_cache[&key]);
680 }
681
682 {
684 let being_loaded =
685 self.objects_being_reconstructed
686 .lock()
687 .map_err(|_| ParseError::SyntaxError {
688 position: 0,
689 message: "Mutex poisoned during circular reference check".to_string(),
690 })?;
691 if being_loaded.contains(&obj_num) {
692 drop(being_loaded);
693 if self.options.collect_warnings {}
694 self.object_cache.insert(key, PdfObject::Null);
695 return Ok(&self.object_cache[&key]);
696 }
697 }
698
699 {
701 let being_loaded =
702 self.objects_being_reconstructed
703 .lock()
704 .map_err(|_| ParseError::SyntaxError {
705 position: 0,
706 message: "Mutex poisoned during depth limit check".to_string(),
707 })?;
708 let depth = being_loaded.len() as u32;
709 if depth >= self.max_reconstruction_depth {
710 drop(being_loaded);
711 if self.options.collect_warnings {}
712 return Err(ParseError::SyntaxError {
713 position: 0,
714 message: format!(
715 "Maximum object loading depth ({}) exceeded",
716 self.max_reconstruction_depth
717 ),
718 });
719 }
720 }
721
722 self.objects_being_reconstructed
724 .lock()
725 .map_err(|_| ParseError::SyntaxError {
726 position: 0,
727 message: "Mutex poisoned while marking object as being loaded".to_string(),
728 })?
729 .insert(obj_num);
730
731 match self.load_object_from_disk(obj_num, gen_num) {
733 Ok(_) => {
734 self.objects_being_reconstructed
736 .lock()
737 .map_err(|_| ParseError::SyntaxError {
738 position: 0,
739 message: "Mutex poisoned while unmarking object after successful load"
740 .to_string(),
741 })?
742 .remove(&obj_num);
743 Ok(&self.object_cache[&key])
745 }
746 Err(e) => {
747 if let Ok(mut guard) = self.objects_being_reconstructed.lock() {
750 guard.remove(&obj_num);
751 }
752 Err(e)
753 }
754 }
755 }
756
757 fn load_object_from_disk(&mut self, obj_num: u32, gen_num: u16) -> ParseResult<&PdfObject> {
759 let key = (obj_num, gen_num);
760
761 if self.object_cache.contains_key(&key) {
763 return Ok(&self.object_cache[&key]);
764 }
765
766 if let Some(ext_entry) = self.xref.get_extended_entry(obj_num) {
768 if let Some((stream_obj_num, index_in_stream)) = ext_entry.compressed_info {
769 return self.get_compressed_object(
771 obj_num,
772 gen_num,
773 stream_obj_num,
774 index_in_stream,
775 );
776 }
777 } else {
778 }
779
780 let (current_offset, _generation) = {
782 let entry = self.xref.get_entry(obj_num);
783
784 match entry {
785 Some(entry) => {
786 if !entry.in_use {
787 self.object_cache.insert(key, PdfObject::Null);
789 return Ok(&self.object_cache[&key]);
790 }
791
792 if entry.generation != gen_num {
793 if self.options.lenient_syntax {
794 if self.options.collect_warnings {
796 tracing::warn!("Object {} generation mismatch - expected {}, found {}, using available",
797 obj_num, gen_num, entry.generation);
798 }
799 } else {
800 return Err(ParseError::InvalidReference(obj_num, gen_num));
801 }
802 }
803
804 (entry.offset, entry.generation)
805 }
806 None => {
807 if self.is_reconstructible_object(obj_num) {
809 return self.attempt_manual_object_reconstruction(obj_num, gen_num, 0);
810 } else {
811 if self.options.lenient_syntax {
812 if self.options.collect_warnings {
814 tracing::warn!(
815 "Object {} {} R not found in XRef, returning null object",
816 obj_num,
817 gen_num
818 );
819 }
820 self.object_cache.insert(key, PdfObject::Null);
821 return Ok(&self.object_cache[&key]);
822 } else {
823 return Err(ParseError::InvalidReference(obj_num, gen_num));
824 }
825 }
826 }
827 }
828 };
829
830 self.reader.seek(std::io::SeekFrom::Start(current_offset))?;
834
835 let mut lexer =
837 super::lexer::Lexer::new_with_options(&mut self.reader, self.options.clone());
838
839 {
841 let token = lexer.next_token()?;
843 let read_obj_num = match token {
844 super::lexer::Token::Integer(n) => n as u32,
845 _ => {
846 if self.options.lenient_syntax {
848 if self.options.collect_warnings {
850 tracing::debug!(
851 "Warning: Using expected object number {obj_num} instead of parsed token: {:?}",
852 token
853 );
854 }
855 obj_num
856 } else {
857 return Err(ParseError::SyntaxError {
858 position: current_offset as usize,
859 message: "Expected object number".to_string(),
860 });
861 }
862 }
863 };
864
865 if read_obj_num != obj_num && !self.options.lenient_syntax {
866 return Err(ParseError::SyntaxError {
867 position: current_offset as usize,
868 message: format!(
869 "Object number mismatch: expected {obj_num}, found {read_obj_num}"
870 ),
871 });
872 }
873
874 let token = lexer.next_token()?;
876 let _read_gen_num = match token {
877 super::lexer::Token::Integer(n) => n as u16,
878 _ => {
879 if self.options.lenient_syntax {
881 if self.options.collect_warnings {
882 tracing::warn!(
883 "Using generation 0 instead of parsed token for object {obj_num}"
884 );
885 }
886 0
887 } else {
888 return Err(ParseError::SyntaxError {
889 position: current_offset as usize,
890 message: "Expected generation number".to_string(),
891 });
892 }
893 }
894 };
895
896 let token = lexer.next_token()?;
898 match token {
899 super::lexer::Token::Obj => {}
900 _ => {
901 if self.options.lenient_syntax {
902 if self.options.collect_warnings {
904 tracing::warn!("Expected 'obj' keyword for object {obj_num} {gen_num}, continuing anyway");
905 }
906 } else {
907 return Err(ParseError::SyntaxError {
908 position: current_offset as usize,
909 message: "Expected 'obj' keyword".to_string(),
910 });
911 }
912 }
913 }
914 }
915
916 self.parse_context.enter()?;
918
919 let obj = match PdfObject::parse_with_options(&mut lexer, &self.options) {
920 Ok(obj) => {
921 self.parse_context.exit();
922 if obj_num == 102 && self.options.collect_warnings {}
924 obj
925 }
926 Err(e) => {
927 self.parse_context.exit();
928
929 if self.is_reconstructible_object(obj_num)
931 && self.can_attempt_manual_reconstruction(&e)
932 {
933 match self.attempt_manual_object_reconstruction(
934 obj_num,
935 gen_num,
936 current_offset,
937 ) {
938 Ok(reconstructed_obj) => {
939 return Ok(reconstructed_obj);
940 }
941 Err(_reconstruction_error) => {}
942 }
943 }
944
945 return Err(e);
946 }
947 };
948
949 let token = lexer.next_token()?;
951 match token {
952 super::lexer::Token::EndObj => {}
953 _ => {
954 if self.options.lenient_syntax {
955 if self.options.collect_warnings {
957 tracing::warn!("Expected 'endobj' keyword after object {obj_num} {gen_num}, continuing anyway");
958 }
959 } else {
960 return Err(ParseError::SyntaxError {
961 position: current_offset as usize,
962 message: "Expected 'endobj' keyword".to_string(),
963 });
964 }
965 }
966 };
967
968 let decrypted_obj = self.decrypt_object_if_needed(obj, obj_num, gen_num)?;
970
971 self.object_cache.insert(key, decrypted_obj);
973
974 Ok(&self.object_cache[&key])
975 }
976
977 pub fn resolve<'a>(&'a mut self, obj: &'a PdfObject) -> ParseResult<&'a PdfObject> {
979 match obj {
980 PdfObject::Reference(obj_num, gen_num) => self.get_object(*obj_num, *gen_num),
981 _ => Ok(obj),
982 }
983 }
984
985 pub fn resolve_stream_length(&mut self, obj: &PdfObject) -> ParseResult<Option<usize>> {
988 match obj {
989 PdfObject::Integer(len) => {
990 if *len >= 0 {
991 Ok(Some(*len as usize))
992 } else {
993 Ok(None)
995 }
996 }
997 PdfObject::Reference(obj_num, gen_num) => {
998 let resolved = self.get_object(*obj_num, *gen_num)?;
999 match resolved {
1000 PdfObject::Integer(len) => {
1001 if *len >= 0 {
1002 Ok(Some(*len as usize))
1003 } else {
1004 Ok(None)
1005 }
1006 }
1007 _ => {
1008 Ok(None)
1010 }
1011 }
1012 }
1013 _ => {
1014 Ok(None)
1016 }
1017 }
1018 }
1019
1020 fn get_compressed_object(
1022 &mut self,
1023 obj_num: u32,
1024 gen_num: u16,
1025 stream_obj_num: u32,
1026 _index_in_stream: u32,
1027 ) -> ParseResult<&PdfObject> {
1028 let key = (obj_num, gen_num);
1029
1030 if !self.object_stream_cache.contains_key(&stream_obj_num) {
1032 let stream_obj = self.get_object(stream_obj_num, 0)?;
1034
1035 if let Some(stream) = stream_obj.as_stream() {
1036 let obj_stream = ObjectStream::parse(stream.clone(), &self.options)?;
1038 self.object_stream_cache.insert(stream_obj_num, obj_stream);
1039 } else {
1040 return Err(ParseError::SyntaxError {
1041 position: 0,
1042 message: format!("Object {stream_obj_num} is not a stream"),
1043 });
1044 }
1045 }
1046
1047 let obj_stream = &self.object_stream_cache[&stream_obj_num];
1049 let obj = obj_stream
1050 .get_object(obj_num)
1051 .ok_or_else(|| ParseError::SyntaxError {
1052 position: 0,
1053 message: format!("Object {obj_num} not found in object stream {stream_obj_num}"),
1054 })?;
1055
1056 let decrypted_obj = self.decrypt_object_if_needed(obj.clone(), obj_num, gen_num)?;
1058
1059 self.object_cache.insert(key, decrypted_obj);
1061 Ok(&self.object_cache[&key])
1062 }
1063
1064 pub fn pages(&mut self) -> ParseResult<&PdfDictionary> {
1066 let (pages_obj_num, pages_gen_num) = {
1068 let catalog = self.catalog()?;
1069
1070 if let Some(pages_ref) = catalog.get("Pages") {
1072 match pages_ref {
1073 PdfObject::Reference(obj_num, gen_num) => (*obj_num, *gen_num),
1074 _ => {
1075 return Err(ParseError::SyntaxError {
1076 position: 0,
1077 message: "Pages must be a reference".to_string(),
1078 })
1079 }
1080 }
1081 } else {
1082 #[cfg(debug_assertions)]
1084 tracing::warn!("Catalog missing Pages entry, attempting recovery");
1085
1086 if let Ok(page_refs) = self.find_page_objects() {
1088 if !page_refs.is_empty() {
1089 return self.create_synthetic_pages_dict(&page_refs);
1091 }
1092 }
1093
1094 if self.options.lenient_syntax {
1096 if self.options.collect_warnings {
1097 tracing::warn!("Missing Pages in catalog, searching for page tree");
1098 }
1099 let mut found_pages = None;
1101 for i in 1..self.xref.len() as u32 {
1102 if let Ok(obj) = self.get_object(i, 0) {
1103 if let Some(dict) = obj.as_dict() {
1104 if let Some(obj_type) = dict.get("Type").and_then(|t| t.as_name()) {
1105 if obj_type.0 == "Pages" {
1106 found_pages = Some((i, 0));
1107 break;
1108 }
1109 }
1110 }
1111 }
1112 }
1113 if let Some((obj_num, gen_num)) = found_pages {
1114 (obj_num, gen_num)
1115 } else {
1116 return Err(ParseError::MissingKey("Pages".to_string()));
1117 }
1118 } else {
1119 return Err(ParseError::MissingKey("Pages".to_string()));
1120 }
1121 }
1122 };
1123
1124 let needs_double_resolve = {
1127 let pages_obj = self.get_object(pages_obj_num, pages_gen_num)?;
1128 pages_obj.as_reference()
1129 };
1130
1131 let (final_obj_num, final_gen_num) =
1133 if let Some((ref_obj_num, ref_gen_num)) = needs_double_resolve {
1134 (ref_obj_num, ref_gen_num)
1135 } else {
1136 (pages_obj_num, pages_gen_num)
1137 };
1138
1139 let actual_pages_num = {
1141 let is_valid_dict = {
1143 let pages_obj = self.get_object(final_obj_num, final_gen_num)?;
1144 pages_obj.as_dict().is_some()
1145 };
1146
1147 if is_valid_dict {
1148 final_obj_num
1150 } else {
1151 #[cfg(debug_assertions)]
1153 tracing::warn!("Pages reference invalid, searching for valid Pages object");
1154
1155 if self.options.lenient_syntax {
1156 let xref_len = self.xref.len() as u32;
1158 let mut found_pages_num = None;
1159
1160 for i in 1..xref_len {
1161 let is_pages = {
1163 if let Ok(obj) = self.get_object(i, 0) {
1164 if let Some(dict) = obj.as_dict() {
1165 if let Some(obj_type) =
1166 dict.get("Type").and_then(|t| t.as_name())
1167 {
1168 obj_type.0 == "Pages"
1169 } else {
1170 false
1171 }
1172 } else {
1173 false
1174 }
1175 } else {
1176 false
1177 }
1178 };
1179
1180 if is_pages {
1181 found_pages_num = Some(i);
1182 break;
1183 }
1184 }
1185
1186 if let Some(obj_num) = found_pages_num {
1187 #[cfg(debug_assertions)]
1188 tracing::debug!("Found valid Pages object at {} 0 R", obj_num);
1189 obj_num
1190 } else {
1191 return Err(ParseError::SyntaxError {
1193 position: 0,
1194 message: "Pages is not a dictionary and no valid Pages object found"
1195 .to_string(),
1196 });
1197 }
1198 } else {
1199 return Err(ParseError::SyntaxError {
1201 position: 0,
1202 message: "Pages is not a dictionary".to_string(),
1203 });
1204 }
1205 }
1206 };
1207
1208 let pages_obj = self.get_object(actual_pages_num, 0)?;
1210 pages_obj.as_dict().ok_or_else(|| ParseError::SyntaxError {
1211 position: 0,
1212 message: "Pages object is not a dictionary".to_string(),
1213 })
1214 }
1215
1216 pub fn page_count(&mut self) -> ParseResult<u32> {
1218 const MAX_PAGE_COUNT: u32 = 100_000;
1221
1222 match self.pages() {
1224 Ok(pages) => {
1225 let count_inline = pages.get("Count").and_then(|o| o.as_integer());
1229 let count_ref = pages.get("Count").and_then(|o| o.as_reference());
1230 let kids_len_inline = pages
1231 .get("Kids")
1232 .and_then(|o| o.as_array())
1233 .map(|a| a.0.len());
1234 let kids_ref = pages.get("Kids").and_then(|o| o.as_reference());
1235
1236 let count = count_inline.or_else(|| {
1238 count_ref
1239 .and_then(|(n, g)| self.get_object(n, g).ok().and_then(|o| o.as_integer()))
1240 });
1241 if let Some(count) = count {
1242 let count = count as u32;
1243 if count <= MAX_PAGE_COUNT {
1244 return Ok(count);
1245 }
1246 tracing::warn!(
1247 "PDF /Count {} exceeds limit {}, falling back to Kids array length",
1248 count,
1249 MAX_PAGE_COUNT
1250 );
1251 }
1253
1254 if let Some(len) = kids_len_inline {
1257 return Ok(len as u32);
1258 }
1259 if let Some((n, g)) = kids_ref {
1260 if let Some(len) = self
1261 .get_object(n, g)
1262 .ok()
1263 .and_then(|o| o.as_array())
1264 .map(|a| a.0.len())
1265 {
1266 return Ok(len as u32);
1267 }
1268 }
1269
1270 Ok(0)
1271 }
1272 Err(_) => {
1273 tracing::debug!("Standard page extraction failed, trying direct extraction");
1275 self.page_count_fallback()
1276 }
1277 }
1278 }
1279
1280 fn page_count_fallback(&mut self) -> ParseResult<u32> {
1282 if let Some(count) = self.extract_page_count_from_linearization() {
1284 tracing::debug!("Found page count {} from linearization", count);
1285 return Ok(count);
1286 }
1287
1288 if let Some(count) = self.count_page_objects_directly() {
1290 tracing::debug!("Found {} pages by counting page objects", count);
1291 return Ok(count);
1292 }
1293
1294 Ok(0)
1295 }
1296
1297 fn extract_page_count_from_linearization(&mut self) -> Option<u32> {
1299 match self.get_object(100, 0) {
1301 Ok(obj) => {
1302 tracing::debug!("Found object 100: {:?}", obj);
1303 if let Some(dict) = obj.as_dict() {
1304 tracing::debug!("Object 100 is a dictionary with {} keys", dict.0.len());
1305 if let Some(n_obj) = dict.get("N") {
1307 tracing::debug!("Found /N field: {:?}", n_obj);
1308 if let Some(count) = n_obj.as_integer() {
1309 tracing::debug!("Extracted page count from linearization: {}", count);
1310 return Some(count as u32);
1311 }
1312 } else {
1313 tracing::debug!("No /N field found in object 100");
1314 for (key, value) in &dict.0 {
1315 tracing::debug!(" {:?}: {:?}", key, value);
1316 }
1317 }
1318 } else {
1319 tracing::debug!("Object 100 is not a dictionary: {:?}", obj);
1320 }
1321 }
1322 Err(e) => {
1323 tracing::debug!("Failed to get object 100: {:?}", e);
1324 tracing::debug!("Attempting direct content extraction...");
1325 return self.extract_n_value_from_raw_object_100();
1327 }
1328 }
1329
1330 None
1331 }
1332
1333 fn extract_n_value_from_raw_object_100(&mut self) -> Option<u32> {
1334 if let Some(entry) = self.xref.get_entry(100) {
1336 if self.reader.seek(SeekFrom::Start(entry.offset)).is_err() {
1338 return None;
1339 }
1340
1341 let mut buffer = vec![0u8; 1024];
1343 if let Ok(bytes_read) = self.reader.read(&mut buffer) {
1344 if bytes_read == 0 {
1345 return None;
1346 }
1347
1348 let content = String::from_utf8_lossy(&buffer[..bytes_read]);
1350 tracing::debug!("Raw content around object 100:\n{}", content);
1351
1352 if let Some(n_pos) = content.find("/N ") {
1354 let after_n = &content[n_pos + 3..];
1355 tracing::debug!(
1356 "Content after /N: {}",
1357 truncate_on_char_boundary(after_n, 50)
1358 );
1359
1360 let mut num_str = String::new();
1362 for ch in after_n.chars() {
1363 if ch.is_ascii_digit() {
1364 num_str.push(ch);
1365 } else if !num_str.is_empty() {
1366 break;
1368 }
1369 }
1371
1372 if !num_str.is_empty() {
1373 if let Ok(page_count) = num_str.parse::<u32>() {
1374 tracing::debug!(
1375 "Extracted page count from raw content: {}",
1376 page_count
1377 );
1378 return Some(page_count);
1379 }
1380 }
1381 }
1382 }
1383 }
1384 None
1385 }
1386
1387 #[allow(dead_code)]
1388 fn find_object_pattern(&mut self, obj_num: u32, gen_num: u16) -> Option<u64> {
1389 let pattern = format!("{} {} obj", obj_num, gen_num);
1390
1391 let original_pos = self.reader.stream_position().unwrap_or(0);
1393
1394 if self.reader.seek(SeekFrom::Start(0)).is_err() {
1396 return None;
1397 }
1398
1399 let mut buffer = vec![0u8; 8192];
1401 let mut file_content = Vec::new();
1402
1403 loop {
1404 match self.reader.read(&mut buffer) {
1405 Ok(0) => break, Ok(bytes_read) => {
1407 file_content.extend_from_slice(&buffer[..bytes_read]);
1408 }
1409 Err(_) => return None,
1410 }
1411 }
1412
1413 let content = String::from_utf8_lossy(&file_content);
1415 if let Some(pattern_pos) = content.find(&pattern) {
1416 let after_pattern = pattern_pos + pattern.len();
1418 let search_area = &content[after_pattern..];
1419
1420 if let Some(dict_start_offset) = search_area.find("<<") {
1421 let dict_start_pos = after_pattern + dict_start_offset;
1422
1423 self.reader.seek(SeekFrom::Start(original_pos)).ok();
1425 return Some(dict_start_pos as u64);
1426 } else {
1427 }
1428 }
1429
1430 self.reader.seek(SeekFrom::Start(original_pos)).ok();
1432 None
1433 }
1434
1435 fn can_attempt_manual_reconstruction(&self, error: &ParseError) -> bool {
1437 match error {
1438 ParseError::SyntaxError { .. } => true,
1440 ParseError::UnexpectedToken { .. } => true,
1441 _ => false,
1443 }
1444 }
1445
1446 fn is_reconstructible_object(&self, obj_num: u32) -> bool {
1448 if obj_num == 102 || obj_num == 113 || obj_num == 114 {
1450 return true;
1451 }
1452
1453 let page_objects = [
1456 1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 30, 34, 37, 39, 42, 44, 46, 49, 52,
1457 54, 56, 58, 60, 62, 64, 67, 69, 71, 73, 75, 77, 79, 81, 83, 85, 87, 89, 91, 93, 104,
1458 ];
1459
1460 let content_objects = [
1463 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 29, 31, 32, 33, 35, 36, 38, 40, 41,
1464 43, 45, 47, 48, 50, 51, 53, 55, 57, 59, 61, 63, 65, 66, 68, 70, 72, 74, 76, 78, 80, 82,
1465 84, 86, 88, 90, 92, 94, 95, 96, 97, 98, 99, 100, 101, 105, 106, 107, 108, 109, 110,
1466 111,
1467 ];
1468
1469 page_objects.contains(&obj_num) || content_objects.contains(&obj_num)
1470 }
1471
1472 fn is_page_object(&self, obj_num: u32) -> bool {
1474 let page_objects = [
1475 1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 30, 34, 37, 39, 42, 44, 46, 49, 52,
1476 54, 56, 58, 60, 62, 64, 67, 69, 71, 73, 75, 77, 79, 81, 83, 85, 87, 89, 91, 93, 104,
1477 ];
1478 page_objects.contains(&obj_num)
1479 }
1480
1481 fn parse_page_dictionary_content(
1483 &self,
1484 dict_content: &str,
1485 result_dict: &mut std::collections::HashMap<
1486 crate::parser::objects::PdfName,
1487 crate::parser::objects::PdfObject,
1488 >,
1489 _obj_num: u32,
1490 ) -> ParseResult<()> {
1491 use crate::parser::objects::{PdfArray, PdfName, PdfObject};
1492 use std::collections::HashMap;
1493
1494 if let Some(mediabox_start) = dict_content.find("/MediaBox") {
1496 let mediabox_area = &dict_content[mediabox_start..];
1497 if let Some(mediabox_content) = slice_between(mediabox_area, b'[', b']') {
1498 let values: Vec<f32> = mediabox_content
1499 .split_whitespace()
1500 .filter_map(|s| s.parse().ok())
1501 .collect();
1502
1503 if values.len() == 4 {
1504 let mediabox = PdfArray(vec![
1505 PdfObject::Integer(values[0] as i64),
1506 PdfObject::Integer(values[1] as i64),
1507 PdfObject::Integer(values[2] as i64),
1508 PdfObject::Integer(values[3] as i64),
1509 ]);
1510 result_dict.insert(PdfName("MediaBox".to_string()), PdfObject::Array(mediabox));
1511 }
1512 }
1513 }
1514
1515 if let Some(contents_match) = dict_content.find("/Contents") {
1517 let contents_area = &dict_content[contents_match..];
1518 let parts: Vec<&str> = contents_area.split_whitespace().collect();
1520 if parts.len() >= 3 {
1521 if let (Ok(obj_ref), Ok(gen_ref)) =
1522 (parts[1].parse::<u32>(), parts[2].parse::<u16>())
1523 {
1524 if parts.len() > 3 && parts[3] == "R" {
1525 result_dict.insert(
1526 PdfName("Contents".to_string()),
1527 PdfObject::Reference(obj_ref, gen_ref),
1528 );
1529 }
1530 }
1531 }
1532 }
1533
1534 if dict_content.contains("/Parent") {
1536 result_dict.insert(
1537 PdfName("Parent".to_string()),
1538 PdfObject::Reference(113, 0), );
1540 }
1541
1542 if dict_content.contains("/Resources") {
1544 if let Ok(parsed_resources) = self.parse_resources_from_content(&dict_content) {
1545 result_dict.insert(PdfName("Resources".to_string()), parsed_resources);
1546 } else {
1547 let resources = HashMap::new();
1549 result_dict.insert(
1550 PdfName("Resources".to_string()),
1551 PdfObject::Dictionary(crate::parser::objects::PdfDictionary(resources)),
1552 );
1553 }
1554 }
1555
1556 Ok(())
1557 }
1558
1559 fn attempt_manual_object_reconstruction(
1561 &mut self,
1562 obj_num: u32,
1563 gen_num: u16,
1564 _current_offset: u64,
1565 ) -> ParseResult<&PdfObject> {
1566 let is_circular = self
1568 .objects_being_reconstructed
1569 .lock()
1570 .map_err(|_| ParseError::SyntaxError {
1571 position: 0,
1572 message: "Mutex poisoned during circular reference check".to_string(),
1573 })?
1574 .contains(&obj_num);
1575
1576 if is_circular {
1577 tracing::debug!(
1578 "Warning: Circular reconstruction detected for object {} {} - attempting manual extraction",
1579 obj_num, gen_num
1580 );
1581
1582 match self.extract_object_or_stream_manually(obj_num) {
1586 Ok(obj) => {
1587 tracing::debug!(
1588 " Successfully extracted object {} {} manually despite circular reference",
1589 obj_num, gen_num
1590 );
1591 self.object_cache.insert((obj_num, gen_num), obj);
1592 return Ok(&self.object_cache[&(obj_num, gen_num)]);
1593 }
1594 Err(e) => {
1595 tracing::debug!(
1596 " Manual extraction failed: {} - breaking cycle with null object",
1597 e
1598 );
1599 self.object_cache
1601 .insert((obj_num, gen_num), PdfObject::Null);
1602 return Ok(&self.object_cache[&(obj_num, gen_num)]);
1603 }
1604 }
1605 }
1606
1607 let current_depth = self
1609 .objects_being_reconstructed
1610 .lock()
1611 .map_err(|_| ParseError::SyntaxError {
1612 position: 0,
1613 message: "Mutex poisoned during depth check".to_string(),
1614 })?
1615 .len() as u32;
1616 if current_depth >= self.max_reconstruction_depth {
1617 return Err(ParseError::SyntaxError {
1618 position: 0,
1619 message: format!(
1620 "Maximum reconstruction depth ({}) exceeded for object {} {}",
1621 self.max_reconstruction_depth, obj_num, gen_num
1622 ),
1623 });
1624 }
1625
1626 self.objects_being_reconstructed
1628 .lock()
1629 .map_err(|_| ParseError::SyntaxError {
1630 position: 0,
1631 message: "Mutex poisoned while marking object as being reconstructed".to_string(),
1632 })?
1633 .insert(obj_num);
1634
1635 let reconstructed_obj = match self.smart_object_reconstruction(obj_num, gen_num) {
1637 Ok(obj) => obj,
1638 Err(_) => {
1639 match self.extract_object_or_stream_manually(obj_num) {
1641 Ok(obj) => obj,
1642 Err(e) => {
1643 if self.options.lenient_syntax {
1645 PdfObject::Null
1646 } else {
1647 if let Ok(mut guard) = self.objects_being_reconstructed.lock() {
1649 guard.remove(&obj_num);
1650 }
1651 return Err(e);
1652 }
1653 }
1654 }
1655 }
1656 };
1657
1658 self.objects_being_reconstructed
1660 .lock()
1661 .map_err(|_| ParseError::SyntaxError {
1662 position: 0,
1663 message: "Mutex poisoned while unmarking reconstructed object".to_string(),
1664 })?
1665 .remove(&obj_num);
1666
1667 self.object_cache
1668 .insert((obj_num, gen_num), reconstructed_obj);
1669
1670 use crate::parser::xref::XRefEntry;
1672 let xref_entry = XRefEntry {
1673 offset: 0, generation: gen_num,
1675 in_use: true,
1676 };
1677 self.xref.add_entry(obj_num, xref_entry);
1678
1679 self.object_cache
1680 .get(&(obj_num, gen_num))
1681 .ok_or_else(|| ParseError::SyntaxError {
1682 position: 0,
1683 message: format!(
1684 "Object {} {} not in cache after reconstruction",
1685 obj_num, gen_num
1686 ),
1687 })
1688 }
1689
1690 fn smart_object_reconstruction(
1692 &mut self,
1693 obj_num: u32,
1694 gen_num: u16,
1695 ) -> ParseResult<PdfObject> {
1696 if let Ok(inferred_obj) = self.infer_object_from_context(obj_num) {
1700 return Ok(inferred_obj);
1701 }
1702
1703 if let Ok(scanned_obj) = self.scan_for_object_patterns(obj_num) {
1705 return Ok(scanned_obj);
1706 }
1707
1708 if let Ok(synthetic_obj) = self.create_synthetic_object(obj_num) {
1710 return Ok(synthetic_obj);
1711 }
1712
1713 Err(ParseError::SyntaxError {
1714 position: 0,
1715 message: format!("Could not reconstruct object {} {}", obj_num, gen_num),
1716 })
1717 }
1718
1719 fn infer_object_from_context(&mut self, obj_num: u32) -> ParseResult<PdfObject> {
1721 for (_key, obj) in self.object_cache.iter() {
1725 if let PdfObject::Dictionary(dict) = obj {
1726 for (key, value) in dict.0.iter() {
1727 if let PdfObject::Reference(ref_num, _) = value {
1728 if *ref_num == obj_num {
1729 match key.as_str() {
1731 "Font" | "F1" | "F2" | "F3" => {
1732 return Ok(self.create_font_object(obj_num));
1733 }
1734 "XObject" | "Image" | "Im1" => {
1735 return Ok(self.create_xobject(obj_num));
1736 }
1737 "Contents" => {
1738 return Ok(self.create_content_stream(obj_num));
1739 }
1740 "Resources" => {
1741 return Ok(self.create_resources_dict(obj_num));
1742 }
1743 _ => continue,
1744 }
1745 }
1746 }
1747 }
1748 }
1749 }
1750
1751 Err(ParseError::SyntaxError {
1752 position: 0,
1753 message: "Cannot infer object type from context".to_string(),
1754 })
1755 }
1756
1757 fn scan_for_object_patterns(&mut self, obj_num: u32) -> ParseResult<PdfObject> {
1759 self.extract_object_or_stream_manually(obj_num)
1762 }
1763
1764 fn create_synthetic_object(&mut self, obj_num: u32) -> ParseResult<PdfObject> {
1766 use super::objects::{PdfDictionary, PdfName, PdfObject};
1767
1768 match obj_num {
1770 1..=10 => {
1771 let mut dict = PdfDictionary::new();
1773 dict.insert(
1774 "Type".to_string(),
1775 PdfObject::Name(PdfName("Null".to_string())),
1776 );
1777 Ok(PdfObject::Dictionary(dict))
1778 }
1779 _ => {
1780 Ok(PdfObject::Null)
1782 }
1783 }
1784 }
1785
1786 fn create_font_object(&self, _obj_num: u32) -> PdfObject {
1787 use super::objects::{PdfDictionary, PdfName, PdfObject};
1788 let mut font_dict = PdfDictionary::new();
1789 font_dict.insert(
1790 "Type".to_string(),
1791 PdfObject::Name(PdfName("Font".to_string())),
1792 );
1793 font_dict.insert(
1794 "Subtype".to_string(),
1795 PdfObject::Name(PdfName("Type1".to_string())),
1796 );
1797 font_dict.insert(
1798 "BaseFont".to_string(),
1799 PdfObject::Name(PdfName("Helvetica".to_string())),
1800 );
1801 PdfObject::Dictionary(font_dict)
1802 }
1803
1804 fn create_xobject(&self, _obj_num: u32) -> PdfObject {
1805 use super::objects::{PdfDictionary, PdfName, PdfObject};
1806 let mut xobj_dict = PdfDictionary::new();
1807 xobj_dict.insert(
1808 "Type".to_string(),
1809 PdfObject::Name(PdfName("XObject".to_string())),
1810 );
1811 xobj_dict.insert(
1812 "Subtype".to_string(),
1813 PdfObject::Name(PdfName("Form".to_string())),
1814 );
1815 PdfObject::Dictionary(xobj_dict)
1816 }
1817
1818 fn create_content_stream(&self, _obj_num: u32) -> PdfObject {
1819 use super::objects::{PdfDictionary, PdfObject, PdfStream};
1820 let mut stream_dict = PdfDictionary::new();
1821 stream_dict.insert("Length".to_string(), PdfObject::Integer(0));
1822
1823 let stream = PdfStream {
1824 dict: stream_dict,
1825 data: Vec::new(),
1826 };
1827 PdfObject::Stream(stream)
1828 }
1829
1830 fn create_resources_dict(&self, _obj_num: u32) -> PdfObject {
1831 use super::objects::{PdfArray, PdfDictionary, PdfObject};
1832 let mut res_dict = PdfDictionary::new();
1833 res_dict.insert("ProcSet".to_string(), PdfObject::Array(PdfArray::new()));
1834 PdfObject::Dictionary(res_dict)
1835 }
1836
1837 fn extract_object_manually(
1838 &mut self,
1839 obj_num: u32,
1840 ) -> ParseResult<crate::parser::objects::PdfDictionary> {
1841 use crate::parser::objects::{PdfArray, PdfDictionary, PdfName, PdfObject};
1842 use std::collections::HashMap;
1843
1844 let original_pos = self.reader.stream_position().unwrap_or(0);
1846
1847 let window = match read_object_window(&mut self.reader, obj_num, MANUAL_DICT_WINDOW) {
1851 Ok(Some((_, w))) => w,
1852 Ok(None) => {
1853 self.reader.seek(SeekFrom::Start(original_pos)).ok();
1854 return Err(ParseError::SyntaxError {
1855 position: 0,
1856 message: format!("Object {obj_num} not found in manual extraction"),
1857 });
1858 }
1859 Err(_) => {
1860 self.reader.seek(SeekFrom::Start(original_pos)).ok();
1861 return Err(ParseError::SyntaxError {
1862 position: 0,
1863 message: "Failed to read file for manual extraction".to_string(),
1864 });
1865 }
1866 };
1867
1868 let content = String::from_utf8_lossy(&window);
1869
1870 let pattern = format!("{} 0 obj", obj_num);
1872 if let Some(start) = content.find(&pattern) {
1873 let search_area = &content[start..];
1874 if let Some(dict_start) = search_area.find("<<") {
1875 let after_bracket = &search_area[dict_start + 2..];
1876
1877 if let Some(dict_end) = find_dict_end(after_bracket) {
1878 let dict_content = &after_bracket[..dict_end];
1879
1880 let mut result_dict = HashMap::new();
1882
1883 if dict_content.contains("/Type/Catalog")
1886 || dict_content.contains("/Type /Catalog")
1887 {
1888 result_dict.insert(
1889 PdfName("Type".to_string()),
1890 PdfObject::Name(PdfName("Catalog".to_string())),
1891 );
1892
1893 if let Some(pages_start) = dict_content.find("/Pages") {
1897 let after_pages = &dict_content[pages_start + 6..]; let trimmed = after_pages.trim_start();
1900 let parts: Vec<&str> = trimmed.split_whitespace().collect();
1902 if parts.len() >= 3 {
1903 if let (Ok(obj), Ok(gen)) =
1907 (parts[0].parse::<u32>(), parts[1].parse::<u16>())
1908 {
1909 if parts[2] == "R" || parts[2].starts_with('R') {
1910 result_dict.insert(
1911 PdfName("Pages".to_string()),
1912 PdfObject::Reference(obj, gen),
1913 );
1914 }
1915 }
1916 }
1917 }
1918
1919 if let Some(ver_start) = dict_content.find("/Version") {
1922 let after_ver = &dict_content[ver_start + 8..];
1923 if let Some(ver_end) = after_ver.find(|c: char| c == '/' || c == '>') {
1924 let version_str = after_ver[..ver_end].trim();
1925 result_dict.insert(
1926 PdfName("Version".to_string()),
1927 PdfObject::Name(PdfName(
1928 version_str.trim_start_matches('/').to_string(),
1929 )),
1930 );
1931 }
1932 }
1933
1934 if let Some(meta_start) = dict_content.find("/Metadata") {
1936 let after_meta = &dict_content[meta_start + 9..];
1937 let parts: Vec<&str> = after_meta.split_whitespace().collect();
1938 if parts.len() >= 3 {
1939 if let (Ok(obj), Ok(gen)) =
1940 (parts[0].parse::<u32>(), parts[1].parse::<u16>())
1941 {
1942 if parts[2] == "R" {
1943 result_dict.insert(
1944 PdfName("Metadata".to_string()),
1945 PdfObject::Reference(obj, gen),
1946 );
1947 }
1948 }
1949 }
1950 }
1951
1952 if let Some(acro_start) = dict_content.find("/AcroForm") {
1954 let after_acro = &dict_content[acro_start + 9..];
1955 if after_acro.trim_start().starts_with("<<") {
1957 } else {
1959 let parts: Vec<&str> = after_acro.split_whitespace().collect();
1960 if parts.len() >= 3 {
1961 if let (Ok(obj), Ok(gen)) =
1962 (parts[0].parse::<u32>(), parts[1].parse::<u16>())
1963 {
1964 if parts[2] == "R" {
1965 result_dict.insert(
1966 PdfName("AcroForm".to_string()),
1967 PdfObject::Reference(obj, gen),
1968 );
1969 }
1970 }
1971 }
1972 }
1973 }
1974 } else if obj_num == 102 {
1975 if dict_content.contains("/Type /Catalog") {
1977 result_dict.insert(
1979 PdfName("Type".to_string()),
1980 PdfObject::Name(PdfName("Catalog".to_string())),
1981 );
1982
1983 if dict_content.contains("/Dests 139 0 R") {
1985 result_dict.insert(
1986 PdfName("Dests".to_string()),
1987 PdfObject::Reference(139, 0),
1988 );
1989 }
1990
1991 if dict_content.contains("/Pages 113 0 R") {
1993 result_dict.insert(
1994 PdfName("Pages".to_string()),
1995 PdfObject::Reference(113, 0),
1996 );
1997 }
1998 } else {
1999 self.reader.seek(SeekFrom::Start(original_pos)).ok();
2002 return Err(ParseError::SyntaxError {
2003 position: 0,
2004 message:
2005 "Object 102 is not a corrupted catalog, cannot reconstruct"
2006 .to_string(),
2007 });
2008 }
2009 } else if obj_num == 113 {
2010 result_dict.insert(
2013 PdfName("Type".to_string()),
2014 PdfObject::Name(PdfName("Pages".to_string())),
2015 );
2016
2017 let page_refs = match self.find_page_objects() {
2019 Ok(refs) => refs,
2020 Err(_e) => {
2021 vec![]
2022 }
2023 };
2024
2025 let page_count = if page_refs.is_empty() {
2027 44
2028 } else {
2029 page_refs.len() as i64
2030 };
2031 result_dict
2032 .insert(PdfName("Count".to_string()), PdfObject::Integer(page_count));
2033
2034 let kids_array: Vec<PdfObject> = page_refs
2036 .into_iter()
2037 .map(|(obj_num, gen_num)| PdfObject::Reference(obj_num, gen_num))
2038 .collect();
2039
2040 result_dict.insert(
2041 PdfName("Kids".to_string()),
2042 PdfObject::Array(PdfArray(kids_array)),
2043 );
2044 } else if obj_num == 114 {
2045 result_dict.insert(
2048 PdfName("Type".to_string()),
2049 PdfObject::Name(PdfName("Pages".to_string())),
2050 );
2051
2052 let page_refs = match self.find_page_objects() {
2054 Ok(refs) => refs,
2055 Err(_e) => {
2056 vec![]
2057 }
2058 };
2059
2060 let page_count = if page_refs.is_empty() {
2062 44
2063 } else {
2064 page_refs.len() as i64
2065 };
2066 result_dict
2067 .insert(PdfName("Count".to_string()), PdfObject::Integer(page_count));
2068
2069 let kids_array: Vec<PdfObject> = page_refs
2071 .into_iter()
2072 .map(|(obj_num, gen_num)| PdfObject::Reference(obj_num, gen_num))
2073 .collect();
2074
2075 result_dict.insert(
2076 PdfName("Kids".to_string()),
2077 PdfObject::Array(PdfArray(kids_array)),
2078 );
2079 } else if self.is_page_object(obj_num) {
2080 result_dict.insert(
2083 PdfName("Type".to_string()),
2084 PdfObject::Name(PdfName("Page".to_string())),
2085 );
2086
2087 self.parse_page_dictionary_content(
2089 &dict_content,
2090 &mut result_dict,
2091 obj_num,
2092 )?;
2093 }
2094
2095 self.reader.seek(SeekFrom::Start(original_pos)).ok();
2097
2098 return Ok(PdfDictionary(result_dict));
2099 }
2100 }
2101 }
2102
2103 self.reader.seek(SeekFrom::Start(original_pos)).ok();
2105
2106 if obj_num == 113 {
2108 let mut result_dict = HashMap::new();
2109 result_dict.insert(
2110 PdfName("Type".to_string()),
2111 PdfObject::Name(PdfName("Pages".to_string())),
2112 );
2113
2114 let page_refs = match self.find_page_objects() {
2116 Ok(refs) => refs,
2117 Err(_e) => {
2118 vec![]
2119 }
2120 };
2121
2122 let page_count = if page_refs.is_empty() {
2124 44
2125 } else {
2126 page_refs.len() as i64
2127 };
2128 result_dict.insert(PdfName("Count".to_string()), PdfObject::Integer(page_count));
2129
2130 let kids_array: Vec<PdfObject> = page_refs
2132 .into_iter()
2133 .map(|(obj_num, gen_num)| PdfObject::Reference(obj_num, gen_num))
2134 .collect();
2135
2136 result_dict.insert(
2137 PdfName("Kids".to_string()),
2138 PdfObject::Array(PdfArray(kids_array)),
2139 );
2140
2141 return Ok(PdfDictionary(result_dict));
2142 } else if obj_num == 114 {
2143 let mut result_dict = HashMap::new();
2144 result_dict.insert(
2145 PdfName("Type".to_string()),
2146 PdfObject::Name(PdfName("Pages".to_string())),
2147 );
2148
2149 let page_refs = match self.find_page_objects() {
2151 Ok(refs) => refs,
2152 Err(_e) => {
2153 vec![]
2154 }
2155 };
2156
2157 let page_count = if page_refs.is_empty() {
2159 44
2160 } else {
2161 page_refs.len() as i64
2162 };
2163 result_dict.insert(PdfName("Count".to_string()), PdfObject::Integer(page_count));
2164
2165 let kids_array: Vec<PdfObject> = page_refs
2167 .into_iter()
2168 .map(|(obj_num, gen_num)| PdfObject::Reference(obj_num, gen_num))
2169 .collect();
2170
2171 result_dict.insert(
2172 PdfName("Kids".to_string()),
2173 PdfObject::Array(PdfArray(kids_array)),
2174 );
2175
2176 return Ok(PdfDictionary(result_dict));
2177 }
2178
2179 Err(ParseError::SyntaxError {
2180 position: 0,
2181 message: "Could not find catalog dictionary in manual extraction".to_string(),
2182 })
2183 }
2184
2185 fn extract_object_or_stream_manually(&mut self, obj_num: u32) -> ParseResult<PdfObject> {
2187 use crate::parser::objects::PdfObject;
2188
2189 let original_pos = self.reader.stream_position().unwrap_or(0);
2191
2192 let (obj_offset, window) =
2197 match read_object_window(&mut self.reader, obj_num, MANUAL_DICT_WINDOW) {
2198 Ok(Some(v)) => v,
2199 Ok(None) => {
2200 self.reader.seek(SeekFrom::Start(original_pos)).ok();
2201 return Err(ParseError::SyntaxError {
2202 position: 0,
2203 message: format!("Could not manually extract object {obj_num}"),
2204 });
2205 }
2206 Err(_) => {
2207 self.reader.seek(SeekFrom::Start(original_pos)).ok();
2208 return Err(ParseError::SyntaxError {
2209 position: 0,
2210 message: "Failed to read file for manual extraction".to_string(),
2211 });
2212 }
2213 };
2214
2215 if let Some(dict_start) = find_byte_pattern(&window, b"<<") {
2218 let mut bracket_count = 1;
2220 let mut pos = dict_start + 2;
2221 let mut dict_end = None;
2222
2223 while pos < window.len().saturating_sub(1) && bracket_count > 0 {
2224 if window[pos] == b'<' && window[pos + 1] == b'<' {
2225 bracket_count += 1;
2226 pos += 2;
2227 } else if window[pos] == b'>' && window[pos + 1] == b'>' {
2228 bracket_count -= 1;
2229 if bracket_count == 0 {
2230 dict_end = Some(pos);
2231 break;
2232 }
2233 pos += 2;
2234 } else {
2235 pos += 1;
2236 }
2237 }
2238
2239 if let Some(dict_end_pos) = dict_end {
2240 let dict_content = String::from_utf8_lossy(&window[dict_start + 2..dict_end_pos]);
2241 let dict_bytes = &window[dict_start..dict_end_pos + 2];
2243
2244 let after_dict = &window[dict_end_pos + 2..];
2246 if is_immediate_stream_start(after_dict) {
2247 let after_dict_abs = obj_offset + (dict_end_pos + 2) as u64;
2249 return self.reconstruct_stream_object_bounded(
2250 obj_num,
2251 dict_bytes,
2252 &dict_content,
2253 after_dict_abs,
2254 after_dict,
2255 );
2256 } else {
2257 self.reader.seek(SeekFrom::Start(original_pos)).ok();
2259 return self
2260 .extract_object_manually(obj_num)
2261 .map(PdfObject::Dictionary);
2262 }
2263 }
2264 }
2265
2266 self.reader.seek(SeekFrom::Start(original_pos)).ok();
2268
2269 Err(ParseError::SyntaxError {
2270 position: 0,
2271 message: format!("Could not manually extract object {obj_num}"),
2272 })
2273 }
2274
2275 fn reconstruct_stream_object_bounded(
2283 &mut self,
2284 obj_num: u32,
2285 dict_bytes: &[u8],
2286 dict_content: &str,
2287 after_dict_abs: u64,
2288 after_dict: &[u8],
2289 ) -> ParseResult<PdfObject> {
2290 use crate::parser::objects::{PdfDictionary, PdfName, PdfObject, PdfStream};
2291 use std::collections::HashMap;
2292
2293 let opts = self.options.clone();
2302 let mut dict: HashMap<PdfName, PdfObject> = {
2303 let mut lexer = super::lexer::Lexer::new_with_options(
2304 std::io::Cursor::new(dict_bytes),
2305 opts.clone(),
2306 );
2307 match PdfObject::parse_with_options(&mut lexer, &opts) {
2308 Ok(PdfObject::Dictionary(d)) => d.0,
2309 Ok(PdfObject::Stream(s)) => s.dict.0,
2312 _ => {
2315 let mut d = HashMap::new();
2316 if dict_content.contains("/Filter /FlateDecode") {
2317 d.insert(
2318 PdfName("Filter".to_string()),
2319 PdfObject::Name(PdfName("FlateDecode".to_string())),
2320 );
2321 }
2322 d
2323 }
2324 }
2325 };
2326
2327 let length = self.parse_stream_length(dict_content)?;
2329
2330 let Some(stream_kw) = find_byte_pattern(after_dict, b"stream") else {
2332 return Err(ParseError::SyntaxError {
2333 position: 0,
2334 message: format!("Could not reconstruct stream for object {obj_num}"),
2335 });
2336 };
2337 let after_kw = stream_kw + 6; let data_rel = match after_dict.get(after_kw) {
2339 Some(b'\r') if after_dict.get(after_kw + 1) == Some(&b'\n') => after_kw + 2,
2340 Some(b'\r') | Some(b'\n') => after_kw + 1,
2341 _ => after_kw,
2342 };
2343 let data_abs = after_dict_abs + data_rel as u64;
2344
2345 let data = match length {
2347 Some(len) => {
2348 let body = read_window_at(&mut self.reader, data_abs, len)?;
2355 dict.insert(
2356 PdfName("Length".to_string()),
2357 PdfObject::Integer(len as i64),
2358 );
2359 body
2360 }
2361 None => {
2362 let body = self.read_stream_until_endstream(data_abs)?;
2365 dict.insert(
2370 PdfName("Length".to_string()),
2371 PdfObject::Integer(body.len() as i64),
2372 );
2373 body
2374 }
2375 };
2376
2377 Ok(PdfObject::Stream(PdfStream {
2378 dict: PdfDictionary(dict),
2379 data,
2380 }))
2381 }
2382
2383 fn parse_stream_length(&mut self, dict_content: &str) -> ParseResult<Option<usize>> {
2387 let Some(idx) = dict_content.find("/Length") else {
2388 return Ok(None);
2389 };
2390 let rest = dict_content[idx + "/Length".len()..].trim_start();
2391 let tokens: Vec<&str> = rest.split_whitespace().collect();
2392
2393 if tokens.len() >= 3 && tokens[2] == "R" {
2395 if let Ok(len_obj) = tokens[0].parse::<u32>() {
2396 return self.resolve_length_object(len_obj);
2397 }
2398 }
2399
2400 if let Some(tok) = tokens.first() {
2402 if let Ok(n) = tok.parse::<i64>() {
2403 if n >= 0 {
2404 return Ok(Some(n as usize));
2405 }
2406 }
2407 }
2408 Ok(None)
2409 }
2410
2411 fn resolve_length_object(&mut self, len_obj: u32) -> ParseResult<Option<usize>> {
2416 let Some((_, window)) = read_object_window(&mut self.reader, len_obj, 4096)? else {
2417 return Ok(None);
2418 };
2419 let text = String::from_utf8_lossy(&window);
2420 if let Some(obj_pos) = text.find("obj") {
2422 let after = text[obj_pos + 3..].trim_start();
2423 let digits: String = after.chars().take_while(|c| c.is_ascii_digit()).collect();
2424 if let Ok(n) = digits.parse::<usize>() {
2425 return Ok(Some(n));
2426 }
2427 }
2428 Ok(None)
2429 }
2430
2431 fn read_stream_until_endstream(&mut self, data_abs: u64) -> ParseResult<Vec<u8>> {
2435 const WIN: usize = 64 * 1024;
2436 const MAX_STREAM: usize = 64 * 1024 * 1024; let mut acc: Vec<u8> = Vec::new();
2439 let mut offset = data_abs;
2440 let mut searched = 0usize;
2441 loop {
2442 let chunk = read_window_at(&mut self.reader, offset, WIN)?;
2443 if chunk.is_empty() {
2444 break; }
2446 acc.extend_from_slice(&chunk);
2447
2448 let from = searched.saturating_sub(b"endstream".len() - 1);
2451 if let Some(rel) = find_byte_pattern(&acc[from..], b"endstream") {
2452 acc.truncate(from + rel);
2453 break;
2454 }
2455 searched = acc.len();
2456 offset += chunk.len() as u64;
2457
2458 if chunk.len() < WIN {
2459 break; }
2461 if acc.len() > MAX_STREAM {
2462 tracing::warn!(
2466 "stream body exceeds {} MiB runaway guard without endstream; truncating",
2467 MAX_STREAM / (1024 * 1024)
2468 );
2469 break;
2470 }
2471 }
2472
2473 if acc.last() == Some(&b'\n') {
2475 acc.pop();
2476 if acc.last() == Some(&b'\r') {
2477 acc.pop();
2478 }
2479 } else if acc.last() == Some(&b'\r') {
2480 acc.pop();
2481 }
2482 Ok(acc)
2483 }
2484
2485 fn parse_resources_from_content(&self, dict_content: &str) -> ParseResult<PdfObject> {
2487 use crate::parser::objects::{PdfDictionary, PdfName, PdfObject};
2488 use std::collections::HashMap;
2489
2490 if let Some(resources_start) = dict_content.find("/Resources") {
2492 if let Some(bracket_start) = dict_content[resources_start..].find("<<") {
2494 let abs_bracket_start = resources_start + bracket_start + 2;
2495
2496 let after_bracket = &dict_content[abs_bracket_start..];
2499 if let Some(end_rel) = find_dict_end(after_bracket) {
2500 let resources_content = &after_bracket[..end_rel];
2501
2502 let mut resources_dict = HashMap::new();
2504
2505 if let Some(font_start) = resources_content.find("/Font") {
2507 if let Some(font_bracket) = resources_content[font_start..].find("<<") {
2508 let abs_font_start = font_start + font_bracket + 2;
2509
2510 let mut font_dict = HashMap::new();
2512
2513 let font_section = &resources_content[abs_font_start..];
2515 let mut pos = 0;
2516 while let Some(f_pos) = font_section[pos..].find("/F") {
2517 let abs_f_pos = pos + f_pos;
2518 if let Some(space_pos) = font_section[abs_f_pos..].find(" ") {
2519 let font_name = &font_section[abs_f_pos..abs_f_pos + space_pos];
2520
2521 let after_name = &font_section[abs_f_pos + space_pos..];
2523 if let Some(r_pos) = after_name.find(" R") {
2524 let ref_part = after_name[..r_pos].trim();
2525 if let Some(parts) = ref_part
2526 .split_whitespace()
2527 .collect::<Vec<&str>>()
2528 .get(0..2)
2529 {
2530 if let (Ok(obj_num), Ok(gen_num)) =
2531 (parts[0].parse::<u32>(), parts[1].parse::<u16>())
2532 {
2533 font_dict.insert(
2534 PdfName(font_name[1..].to_string()), PdfObject::Reference(obj_num, gen_num),
2536 );
2537 }
2538 }
2539 }
2540 }
2541 pos = abs_f_pos + 1;
2542 }
2543
2544 if !font_dict.is_empty() {
2545 resources_dict.insert(
2546 PdfName("Font".to_string()),
2547 PdfObject::Dictionary(PdfDictionary(font_dict)),
2548 );
2549 }
2550 }
2551 }
2552
2553 return Ok(PdfObject::Dictionary(PdfDictionary(resources_dict)));
2554 }
2555 }
2556 }
2557
2558 Err(ParseError::SyntaxError {
2559 position: 0,
2560 message: "Could not parse Resources".to_string(),
2561 })
2562 }
2563
2564 #[allow(dead_code)]
2565 fn extract_catalog_directly(
2566 &mut self,
2567 obj_num: u32,
2568 gen_num: u16,
2569 ) -> ParseResult<&PdfDictionary> {
2570 if let Some(entry) = self.xref.get_entry(obj_num) {
2572 if self.reader.seek(SeekFrom::Start(entry.offset)).is_err() {
2574 return Err(ParseError::SyntaxError {
2575 position: 0,
2576 message: "Failed to seek to catalog object".to_string(),
2577 });
2578 }
2579
2580 let mut buffer = vec![0u8; 2048];
2582 if let Ok(bytes_read) = self.reader.read(&mut buffer) {
2583 let content = String::from_utf8_lossy(&buffer[..bytes_read]);
2584 tracing::debug!("Raw catalog content:\n{}", content);
2585
2586 if let Some(dict_start) = content.find("<<") {
2588 if let Some(dict_end) = content[dict_start..].find(">>") {
2589 let dict_content = &content[dict_start..dict_start + dict_end + 2];
2590 tracing::debug!("Found dictionary content: {}", dict_content);
2591
2592 if let Ok(dict) = self.parse_dictionary_from_string(dict_content) {
2594 let key = (obj_num, gen_num);
2596 self.object_cache.insert(key, PdfObject::Dictionary(dict));
2597
2598 if let Some(PdfObject::Dictionary(ref dict)) =
2600 self.object_cache.get(&key)
2601 {
2602 return Ok(dict);
2603 }
2604 }
2605 }
2606 }
2607 }
2608 }
2609
2610 Err(ParseError::SyntaxError {
2611 position: 0,
2612 message: "Failed to extract catalog directly".to_string(),
2613 })
2614 }
2615
2616 #[allow(dead_code)]
2617 fn parse_dictionary_from_string(&self, dict_str: &str) -> ParseResult<PdfDictionary> {
2618 use crate::parser::lexer::{Lexer, Token};
2619
2620 let mut cursor = std::io::Cursor::new(dict_str.as_bytes());
2622 let mut lexer = Lexer::new_with_options(&mut cursor, self.options.clone());
2623
2624 match lexer.next_token()? {
2626 Token::DictStart => {
2627 let mut dict = std::collections::HashMap::new();
2628
2629 loop {
2630 let token = lexer.next_token()?;
2631 match token {
2632 Token::DictEnd => break,
2633 Token::Name(key) => {
2634 let value = PdfObject::parse_with_options(&mut lexer, &self.options)?;
2636 dict.insert(crate::parser::objects::PdfName(key), value);
2637 }
2638 _ => {
2639 return Err(ParseError::SyntaxError {
2640 position: 0,
2641 message: "Invalid dictionary format".to_string(),
2642 });
2643 }
2644 }
2645 }
2646
2647 Ok(PdfDictionary(dict))
2648 }
2649 _ => Err(ParseError::SyntaxError {
2650 position: 0,
2651 message: "Expected dictionary start".to_string(),
2652 }),
2653 }
2654 }
2655
2656 fn count_page_objects_directly(&mut self) -> Option<u32> {
2658 let mut page_count = 0;
2659
2660 for obj_num in 1..self.xref.len() as u32 {
2662 if let Ok(obj) = self.get_object(obj_num, 0) {
2663 if let Some(dict) = obj.as_dict() {
2664 if let Some(obj_type) = dict.get("Type").and_then(|t| t.as_name()) {
2665 if obj_type.0 == "Page" {
2666 page_count += 1;
2667 }
2668 }
2669 }
2670 }
2671 }
2672
2673 if page_count > 0 {
2674 Some(page_count)
2675 } else {
2676 None
2677 }
2678 }
2679
2680 pub fn metadata(&mut self) -> ParseResult<DocumentMetadata> {
2682 let mut metadata = DocumentMetadata::default();
2683
2684 if let Some(info_dict) = self.info()? {
2685 if let Some(title) = info_dict.get("Title").and_then(|o| o.as_string()) {
2686 metadata.title = Some(title.to_text());
2687 }
2688 if let Some(author) = info_dict.get("Author").and_then(|o| o.as_string()) {
2689 metadata.author = Some(author.to_text());
2690 }
2691 if let Some(subject) = info_dict.get("Subject").and_then(|o| o.as_string()) {
2692 metadata.subject = Some(subject.to_text());
2693 }
2694 if let Some(keywords) = info_dict.get("Keywords").and_then(|o| o.as_string()) {
2695 metadata.keywords = Some(keywords.to_text());
2696 }
2697 if let Some(creator) = info_dict.get("Creator").and_then(|o| o.as_string()) {
2698 metadata.creator = Some(creator.to_text());
2699 }
2700 if let Some(producer) = info_dict.get("Producer").and_then(|o| o.as_string()) {
2701 metadata.producer = Some(producer.to_text());
2702 }
2703 }
2704
2705 metadata.version = self.version().to_string();
2706 metadata.page_count = self.page_count().ok();
2707
2708 Ok(metadata)
2709 }
2710
2711 fn ensure_page_tree(&mut self) -> ParseResult<()> {
2713 if self.page_tree.is_none() {
2714 let page_count = self.page_count()?;
2715 self.page_tree = Some(super::page_tree::PageTree::new(page_count));
2716 }
2717 Ok(())
2718 }
2719
2720 pub fn get_page(&mut self, _index: u32) -> ParseResult<&super::page_tree::ParsedPage> {
2726 self.ensure_page_tree()?;
2727
2728 Err(ParseError::SyntaxError {
2732 position: 0,
2733 message: "get_page not implemented due to borrow checker constraints. Use PdfDocument instead.".to_string(),
2734 })
2735 }
2736
2737 pub fn get_all_pages(&mut self) -> ParseResult<Vec<super::page_tree::ParsedPage>> {
2739 let page_count = self.page_count()?;
2740 let mut pages = Vec::with_capacity(page_count as usize);
2741
2742 for i in 0..page_count {
2743 let page = self.get_page(i)?.clone();
2744 pages.push(page);
2745 }
2746
2747 Ok(pages)
2748 }
2749
2750 pub fn into_document(self) -> super::document::PdfDocument<R> {
2752 super::document::PdfDocument::new(self)
2753 }
2754
2755 pub fn clear_parse_context(&mut self) {
2757 self.parse_context = StackSafeContext::new();
2758 }
2759
2760 pub fn parse_context_mut(&mut self) -> &mut StackSafeContext {
2762 &mut self.parse_context
2763 }
2764
2765 fn find_page_objects(&mut self) -> ParseResult<Vec<(u32, u16)>> {
2772 let original_pos = self.reader.stream_position().unwrap_or(0);
2773 let result = scan_page_object_refs(&mut self.reader);
2774 self.reader.seek(SeekFrom::Start(original_pos)).ok();
2775 Ok(result.unwrap_or_default())
2776 }
2777
2778 fn find_catalog_object(&mut self) -> ParseResult<(u32, u16)> {
2780 let obj_numbers: Vec<u32> = self.xref.entries().keys().copied().collect();
2785
2786 for obj_num in obj_numbers {
2788 if let Ok(obj) = self.get_object(obj_num, 0) {
2790 if let Some(dict) = obj.as_dict() {
2791 if let Some(type_obj) = dict.get("Type") {
2793 if let Some(type_name) = type_obj.as_name() {
2794 if type_name.0 == "Catalog" {
2795 return Ok((obj_num, 0));
2796 }
2797 if type_name.0 == "Sig"
2799 || type_name.0 == "Pages"
2800 || type_name.0 == "Page"
2801 {
2802 continue;
2803 }
2804 }
2805 }
2806 }
2807 }
2808 }
2809
2810 for obj_num in [1, 2, 3, 4, 5] {
2812 if let Ok(obj) = self.get_object(obj_num, 0) {
2813 if let Some(dict) = obj.as_dict() {
2814 if dict.contains_key("Pages") {
2816 return Ok((obj_num, 0));
2817 }
2818 }
2819 }
2820 }
2821
2822 Err(ParseError::MissingKey(
2823 "Could not find Catalog object".to_string(),
2824 ))
2825 }
2826
2827 fn create_synthetic_pages_dict(
2829 &mut self,
2830 page_refs: &[(u32, u16)],
2831 ) -> ParseResult<&PdfDictionary> {
2832 use super::objects::{PdfArray, PdfName};
2833
2834 let mut valid_page_refs = Vec::new();
2836 for (obj_num, gen_num) in page_refs {
2837 if let Ok(page_obj) = self.get_object(*obj_num, *gen_num) {
2838 if let Some(page_dict) = page_obj.as_dict() {
2839 if let Some(obj_type) = page_dict.get("Type").and_then(|t| t.as_name()) {
2841 if obj_type.0 == "Page" {
2842 valid_page_refs.push((*obj_num, *gen_num));
2843 continue;
2844 }
2845 }
2846
2847 if page_dict.contains_key("MediaBox") || page_dict.contains_key("Contents") {
2849 valid_page_refs.push((*obj_num, *gen_num));
2850 }
2851 }
2852 }
2853 }
2854
2855 if valid_page_refs.is_empty() {
2856 return Err(ParseError::SyntaxError {
2857 position: 0,
2858 message: "No valid page objects found for synthetic Pages tree".to_string(),
2859 });
2860 }
2861
2862 if valid_page_refs.len() > 10 {
2864 return self.create_hierarchical_pages_tree(&valid_page_refs);
2865 }
2866
2867 let mut kids = PdfArray::new();
2869 for (obj_num, gen_num) in &valid_page_refs {
2870 kids.push(PdfObject::Reference(*obj_num, *gen_num));
2871 }
2872
2873 let mut pages_dict = PdfDictionary::new();
2875 pages_dict.insert(
2876 "Type".to_string(),
2877 PdfObject::Name(PdfName("Pages".to_string())),
2878 );
2879 pages_dict.insert("Kids".to_string(), PdfObject::Array(kids));
2880 pages_dict.insert(
2881 "Count".to_string(),
2882 PdfObject::Integer(valid_page_refs.len() as i64),
2883 );
2884
2885 let mut media_box = None;
2887 for (obj_num, gen_num) in valid_page_refs.iter().take(3) {
2888 if let Ok(page_obj) = self.get_object(*obj_num, *gen_num) {
2889 if let Some(page_dict) = page_obj.as_dict() {
2890 if let Some(mb) = page_dict.get("MediaBox") {
2891 media_box = Some(mb.clone());
2892 }
2893 }
2894 }
2895 }
2896
2897 if let Some(mb) = media_box {
2899 pages_dict.insert("MediaBox".to_string(), mb);
2900 } else {
2901 let mut mb_array = PdfArray::new();
2902 mb_array.push(PdfObject::Integer(0));
2903 mb_array.push(PdfObject::Integer(0));
2904 mb_array.push(PdfObject::Integer(612));
2905 mb_array.push(PdfObject::Integer(792));
2906 pages_dict.insert("MediaBox".to_string(), PdfObject::Array(mb_array));
2907 }
2908
2909 let synthetic_key = (u32::MAX - 1, 0);
2911 self.object_cache
2912 .insert(synthetic_key, PdfObject::Dictionary(pages_dict));
2913
2914 if let PdfObject::Dictionary(dict) = &self.object_cache[&synthetic_key] {
2916 Ok(dict)
2917 } else {
2918 unreachable!("Just inserted dictionary")
2919 }
2920 }
2921
2922 fn create_hierarchical_pages_tree(
2924 &mut self,
2925 page_refs: &[(u32, u16)],
2926 ) -> ParseResult<&PdfDictionary> {
2927 use super::objects::{PdfArray, PdfName};
2928
2929 const PAGES_PER_NODE: usize = 10; let chunks: Vec<&[(u32, u16)]> = page_refs.chunks(PAGES_PER_NODE).collect();
2933 let mut intermediate_nodes = Vec::new();
2934
2935 for (chunk_idx, chunk) in chunks.iter().enumerate() {
2937 let mut kids = PdfArray::new();
2938 for (obj_num, gen_num) in chunk.iter() {
2939 kids.push(PdfObject::Reference(*obj_num, *gen_num));
2940 }
2941
2942 let mut intermediate_dict = PdfDictionary::new();
2943 intermediate_dict.insert(
2944 "Type".to_string(),
2945 PdfObject::Name(PdfName("Pages".to_string())),
2946 );
2947 intermediate_dict.insert("Kids".to_string(), PdfObject::Array(kids));
2948 intermediate_dict.insert("Count".to_string(), PdfObject::Integer(chunk.len() as i64));
2949
2950 let intermediate_key = (u32::MAX - 2 - chunk_idx as u32, 0);
2952 self.object_cache
2953 .insert(intermediate_key, PdfObject::Dictionary(intermediate_dict));
2954
2955 intermediate_nodes.push(intermediate_key);
2956 }
2957
2958 let mut root_kids = PdfArray::new();
2960 for (obj_num, gen_num) in &intermediate_nodes {
2961 root_kids.push(PdfObject::Reference(*obj_num, *gen_num));
2962 }
2963
2964 let mut root_pages_dict = PdfDictionary::new();
2965 root_pages_dict.insert(
2966 "Type".to_string(),
2967 PdfObject::Name(PdfName("Pages".to_string())),
2968 );
2969 root_pages_dict.insert("Kids".to_string(), PdfObject::Array(root_kids));
2970 root_pages_dict.insert(
2971 "Count".to_string(),
2972 PdfObject::Integer(page_refs.len() as i64),
2973 );
2974
2975 if let Some((obj_num, gen_num)) = page_refs.first() {
2977 if let Ok(page_obj) = self.get_object(*obj_num, *gen_num) {
2978 if let Some(page_dict) = page_obj.as_dict() {
2979 if let Some(mb) = page_dict.get("MediaBox") {
2980 root_pages_dict.insert("MediaBox".to_string(), mb.clone());
2981 }
2982 }
2983 }
2984 }
2985
2986 let root_key = (u32::MAX - 1, 0);
2988 self.object_cache
2989 .insert(root_key, PdfObject::Dictionary(root_pages_dict));
2990
2991 if let PdfObject::Dictionary(dict) = &self.object_cache[&root_key] {
2993 Ok(dict)
2994 } else {
2995 unreachable!("Just inserted dictionary")
2996 }
2997 }
2998
2999 pub fn signatures(&mut self) -> ParseResult<Vec<crate::signatures::SignatureField>> {
3029 crate::signatures::detect_signature_fields(self).map_err(|e| ParseError::SyntaxError {
3030 position: 0,
3031 message: format!("Failed to detect signatures: {}", e),
3032 })
3033 }
3034
3035 pub fn verify_signatures(
3070 &mut self,
3071 ) -> ParseResult<Vec<crate::signatures::FullSignatureValidationResult>> {
3072 self.verify_signatures_with_trust_store(crate::signatures::TrustStore::default())
3073 }
3074
3075 pub fn verify_signatures_with_trust_store(
3107 &mut self,
3108 trust_store: crate::signatures::TrustStore,
3109 ) -> ParseResult<Vec<crate::signatures::FullSignatureValidationResult>> {
3110 use crate::signatures::{
3111 has_incremental_update, parse_pkcs7_signature_detailed, verify_signature_detailed,
3112 FullSignatureValidationResult,
3113 };
3114
3115 let original_pos = self.reader.stream_position().unwrap_or(0);
3117 self.reader.seek(SeekFrom::Start(0))?;
3118
3119 let mut pdf_bytes = Vec::new();
3120 self.reader.read_to_end(&mut pdf_bytes)?;
3121
3122 self.reader.seek(SeekFrom::Start(original_pos)).ok();
3124
3125 let signature_fields = self.signatures()?;
3127
3128 let mut results = Vec::new();
3129
3130 for field in signature_fields {
3131 let mut result = FullSignatureValidationResult {
3132 field: field.clone(),
3133 signer_name: None,
3134 signing_time: None,
3135 hash_valid: false,
3136 signature_valid: false,
3137 certificate_result: None,
3138 has_modifications_after_signing: false,
3139 errors: Vec::new(),
3140 warnings: Vec::new(),
3141 };
3142
3143 result.has_modifications_after_signing =
3145 has_incremental_update(&pdf_bytes, &field.byte_range);
3146
3147 let parsed_sig = match parse_pkcs7_signature_detailed(&field.contents) {
3149 Ok(sig) => sig,
3150 Err(e) => {
3151 result
3152 .errors
3153 .push(format!("Failed to parse signature: {}", e));
3154 results.push(result);
3155 continue;
3156 }
3157 };
3158
3159 result.signing_time = parsed_sig.signing_time.clone();
3161 result.signer_name = parsed_sig.signer_common_name().ok();
3162
3163 match verify_signature_detailed(&pdf_bytes, &parsed_sig, &field.byte_range) {
3165 Ok(verification) => {
3166 result.hash_valid = verification.hash_valid;
3167 result.signature_valid = verification.signature_valid;
3168 if let Some(details) = verification.details {
3169 result.warnings.push(details);
3170 }
3171 }
3172 Err(e) => {
3173 result
3174 .errors
3175 .push(format!("Signature verification failed: {}", e));
3176 }
3177 }
3178
3179 match crate::signatures::validate_certificate_chain(
3181 &parsed_sig.signer_certificate_der,
3182 &parsed_sig.certificates_der,
3183 &trust_store,
3184 None,
3185 ) {
3186 Ok(cert_result) => {
3187 result.certificate_result =
3188 Some(cert_result.into_certificate_result_fail_closed());
3189 }
3190 Err(e) => {
3191 result
3192 .errors
3193 .push(format!("Certificate validation failed: {}", e));
3194 }
3195 }
3196
3197 results.push(result);
3198 }
3199
3200 Ok(results)
3201 }
3202}
3203
3204#[derive(Debug, Default, Clone)]
3206pub struct DocumentMetadata {
3207 pub title: Option<String>,
3208 pub author: Option<String>,
3209 pub subject: Option<String>,
3210 pub keywords: Option<String>,
3211 pub creator: Option<String>,
3212 pub producer: Option<String>,
3213 pub creation_date: Option<String>,
3214 pub modification_date: Option<String>,
3215 pub version: String,
3216 pub page_count: Option<u32>,
3217}
3218
3219pub struct EOLIter<'s> {
3220 remainder: &'s str,
3221}
3222impl<'s> Iterator for EOLIter<'s> {
3223 type Item = &'s str;
3224
3225 fn next(&mut self) -> Option<Self::Item> {
3226 if self.remainder.is_empty() {
3227 return None;
3228 }
3229
3230 if let Some((i, sep)) = ["\r\n", "\n", "\r"]
3231 .iter()
3232 .filter_map(|&sep| self.remainder.find(sep).map(|i| (i, sep)))
3233 .min_by_key(|(i, _)| *i)
3234 {
3235 let (line, rest) = self.remainder.split_at(i);
3236 self.remainder = &rest[sep.len()..];
3237 Some(line)
3238 } else {
3239 let line = self.remainder;
3240 self.remainder = "";
3241 Some(line)
3242 }
3243 }
3244}
3245pub trait PDFLines: AsRef<str> {
3246 fn pdf_lines(&self) -> EOLIter<'_> {
3247 EOLIter {
3248 remainder: self.as_ref(),
3249 }
3250 }
3251}
3252impl PDFLines for &str {}
3253impl<'a> PDFLines for std::borrow::Cow<'a, str> {}
3254impl PDFLines for String {}
3255
3256#[cfg(test)]
3257mod tests {
3258
3259 use super::*;
3260 use crate::parser::objects::{PdfName, PdfString};
3261 use crate::parser::test_helpers::*;
3262 use crate::parser::ParseOptions;
3263 use std::io::Cursor;
3264
3265 #[test]
3266 fn test_reader_construction() {
3267 let pdf_data = create_minimal_pdf();
3268 let cursor = Cursor::new(pdf_data);
3269 let result = PdfReader::new(cursor);
3270 assert!(result.is_ok());
3271 }
3272
3273 #[test]
3281 fn slice_between_extracts_delimited_content() {
3282 assert_eq!(
3283 slice_between("/MediaBox [0 0 612 792] /X", b'[', b']'),
3284 Some("0 0 612 792")
3285 );
3286 assert_eq!(slice_between("[]", b'[', b']'), Some(""));
3287 }
3288
3289 #[test]
3290 fn slice_between_searches_the_closer_after_the_opener() {
3291 assert_eq!(slice_between("/MediaBox ][", b'[', b']'), None);
3296 assert_eq!(slice_between("] a [ b ] c", b'[', b']'), Some(" b "));
3297 }
3298
3299 #[test]
3300 fn slice_between_returns_none_when_a_delimiter_is_missing() {
3301 assert_eq!(slice_between("/MediaBox 0 0 612 792", b'[', b']'), None);
3302 assert_eq!(slice_between("/MediaBox [0 0 612 792", b'[', b']'), None);
3303 assert_eq!(slice_between("", b'[', b']'), None);
3304 }
3305
3306 #[test]
3307 fn slice_between_never_splits_a_multibyte_char() {
3308 assert_eq!(
3311 slice_between("[\u{FFFD}\u{20AC}]", b'[', b']'),
3312 Some("\u{FFFD}\u{20AC}")
3313 );
3314 assert_eq!(slice_between("\u{20AC}[x]\u{20AC}", b'[', b']'), Some("x"));
3317 }
3318
3319 #[test]
3320 fn find_dict_end_locates_the_closing_marker() {
3321 assert_eq!(find_dict_end(" /A 1 >>"), Some(6));
3324 assert_eq!(find_dict_end(">>"), Some(0));
3325 }
3326
3327 #[test]
3328 fn find_dict_end_matches_nesting() {
3329 assert_eq!(find_dict_end(" /A << /B 1 >> >> tail"), Some(15));
3330 }
3331
3332 #[test]
3333 fn find_dict_end_returns_none_when_unbalanced() {
3334 assert_eq!(find_dict_end(" /A 1"), None);
3335 assert_eq!(find_dict_end(" /A << /B 1 >>"), None);
3336 assert_eq!(find_dict_end(""), None);
3337 assert_eq!(find_dict_end(">"), None);
3338 }
3339
3340 #[test]
3341 fn find_dict_end_offset_is_a_char_boundary_with_multibyte_content() {
3342 let after = " /Font \u{FFFD} >> tail";
3345 let end = find_dict_end(after).expect("closes");
3346 assert_eq!(&after[..end], " /Font \u{FFFD} ");
3347 }
3348
3349 #[test]
3350 fn truncate_on_char_boundary_cuts_without_splitting() {
3351 assert_eq!(truncate_on_char_boundary("abc", 10), "abc");
3352 assert_eq!(truncate_on_char_boundary("abcdef", 3), "abc");
3353 assert_eq!(truncate_on_char_boundary("\u{FFFD}x", 1), "");
3355 assert_eq!(truncate_on_char_boundary("\u{FFFD}x", 3), "\u{FFFD}");
3356 assert_eq!(truncate_on_char_boundary("", 5), "");
3357 }
3358
3359 struct CountingReader<R> {
3366 inner: R,
3367 total_read: std::sync::Arc<std::sync::atomic::AtomicUsize>,
3368 max_read: std::sync::Arc<std::sync::atomic::AtomicUsize>,
3369 }
3370 impl<R: Read> Read for CountingReader<R> {
3371 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
3372 self.max_read
3373 .fetch_max(buf.len(), std::sync::atomic::Ordering::SeqCst);
3374 let n = self.inner.read(buf)?;
3375 self.total_read
3376 .fetch_add(n, std::sync::atomic::Ordering::SeqCst);
3377 Ok(n)
3378 }
3379 }
3380 impl<R: Seek> Seek for CountingReader<R> {
3381 fn seek(&mut self, p: SeekFrom) -> std::io::Result<u64> {
3382 self.inner.seek(p)
3383 }
3384 }
3385
3386 #[test]
3387 fn test_extract_stream_manually_bounded_honors_length() {
3388 use crate::parser::objects::PdfObject;
3389 use std::sync::atomic::{AtomicUsize, Ordering};
3390 use std::sync::Arc;
3391
3392 const L: usize = 300 * 1024; const FILLER: usize = 4 * 1024 * 1024; let payload: Vec<u8> = (0..L).map(|i| (i % 251) as u8).collect();
3404 let filler: Vec<u8> = (0..FILLER).map(|i| ((i + 7) % 251) as u8).collect();
3405
3406 let header = b"%PDF-1.4\n";
3407 let obj1 = b"1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n";
3408 let obj2 = b"2 0 obj\n<< /Type /Pages /Kids [] /Count 0 >>\nendobj\n";
3409
3410 let mut data = Vec::new();
3411 data.extend_from_slice(header);
3412 let obj1_start = data.len();
3413 data.extend_from_slice(obj1);
3414 let obj2_start = data.len();
3415 data.extend_from_slice(obj2);
3416
3417 data.extend_from_slice(
3420 b"4 0 obj\n<< /Type /Metadata /Subtype /XML /Length 307200 >>\nstream\n",
3421 );
3422 data.extend_from_slice(&payload);
3423 data.extend_from_slice(b"\nendstream\nendobj\n");
3424
3425 data.extend_from_slice(b"5 0 obj\n<< /Length 4194304 >>\nstream\n");
3427 data.extend_from_slice(&filler);
3428 data.extend_from_slice(b"\nendstream\nendobj\n");
3429
3430 let xref_start = data.len();
3432 let xref = format!(
3433 "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"
3434 );
3435 data.extend_from_slice(xref.as_bytes());
3436 let file_len = data.len();
3437
3438 let counter = Arc::new(AtomicUsize::new(0));
3439 let reader = CountingReader {
3440 inner: Cursor::new(data),
3441 total_read: counter.clone(),
3442 max_read: Arc::new(AtomicUsize::new(0)),
3443 };
3444 let mut pdf = PdfReader::new_with_options(reader, ParseOptions::tolerant())
3445 .expect("minimal PDF must parse");
3446
3447 counter.store(0, Ordering::SeqCst);
3449 let obj = pdf
3450 .extract_object_or_stream_manually(4)
3451 .expect("stream object 4 must be extracted");
3452
3453 let stream = match &obj {
3455 PdfObject::Stream(s) => s,
3456 other => panic!("expected a stream, got {other:?}"),
3457 };
3458 assert_eq!(
3459 stream.data.len(),
3460 L,
3461 "stream body must equal /Length (no truncation)"
3462 );
3463 assert_eq!(stream.data, payload, "stream body content must be intact");
3464
3465 let total_read = counter.load(Ordering::SeqCst);
3469 assert!(
3470 total_read <= L + 2 * MANUAL_DICT_WINDOW,
3471 "manual stream extraction read {total_read} bytes total (file={file_len}); not bounded by object size"
3472 );
3473 }
3474
3475 #[cfg(test)]
3479 fn build_manual_stream_pdf(obj4_dict_body: &str, payload: &[u8]) -> Vec<u8> {
3480 let header = b"%PDF-1.4\n";
3481 let obj1 = b"1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n";
3482 let obj2 = b"2 0 obj\n<< /Type /Pages /Kids [] /Count 0 >>\nendobj\n";
3483
3484 let mut data = Vec::new();
3485 data.extend_from_slice(header);
3486 let obj1_start = data.len();
3487 data.extend_from_slice(obj1);
3488 let obj2_start = data.len();
3489 data.extend_from_slice(obj2);
3490
3491 data.extend_from_slice(format!("4 0 obj\n<<{obj4_dict_body}>>\nstream\n").as_bytes());
3493 data.extend_from_slice(payload);
3494 data.extend_from_slice(b"\nendstream\nendobj\n");
3495
3496 let xref_start = data.len();
3498 let xref = format!(
3499 "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"
3500 );
3501 data.extend_from_slice(xref.as_bytes());
3502 data
3503 }
3504
3505 #[test]
3506 fn test_reconstruct_preserves_non_flate_filter() {
3507 use crate::parser::objects::PdfObject;
3508
3509 let payload = b"\xff\xd8\xff\xe0not-a-real-jpeg\xff\xd9";
3515 let len = payload.len();
3516 let dict_body =
3517 format!(" /Type /XObject /Subtype /Image /Filter /DCTDecode /Length {len} ");
3518 let data = build_manual_stream_pdf(&dict_body, payload);
3519
3520 let mut pdf = PdfReader::new_with_options(Cursor::new(data), ParseOptions::tolerant())
3521 .expect("minimal PDF must parse");
3522 let obj = pdf
3523 .extract_object_or_stream_manually(4)
3524 .expect("stream object 4 must be extracted");
3525 let stream = match &obj {
3526 PdfObject::Stream(s) => s,
3527 other => panic!("expected a stream, got {other:?}"),
3528 };
3529
3530 assert_eq!(
3531 stream
3532 .dict
3533 .get("Filter")
3534 .and_then(|o| o.as_name())
3535 .map(|n| n.0.as_str()),
3536 Some("DCTDecode"),
3537 "non-Flate /Filter must be preserved in reconstructed stream dict"
3538 );
3539 assert_eq!(
3540 stream
3541 .dict
3542 .get("Subtype")
3543 .and_then(|o| o.as_name())
3544 .map(|n| n.0.as_str()),
3545 Some("Image"),
3546 "/Subtype must be preserved, not dropped"
3547 );
3548 assert_eq!(stream.data, payload, "stream body must be intact");
3549 }
3550
3551 #[test]
3552 fn test_reconstruct_preserves_filter_array_and_decodeparms() {
3553 use crate::parser::objects::PdfObject;
3554
3555 let payload = b"compressed-bytes-placeholder";
3560 let len = payload.len();
3561 let dict_body = format!(
3562 " /Filter[/LZWDecode /FlateDecode] /DecodeParms[null<< /Predictor 12 /Columns 4 >>] /Length {len} "
3563 );
3564 let data = build_manual_stream_pdf(&dict_body, payload);
3565
3566 let mut pdf = PdfReader::new_with_options(Cursor::new(data), ParseOptions::tolerant())
3567 .expect("minimal PDF must parse");
3568 let obj = pdf
3569 .extract_object_or_stream_manually(4)
3570 .expect("stream object 4 must be extracted");
3571 let stream = match &obj {
3572 PdfObject::Stream(s) => s,
3573 other => panic!("expected a stream, got {other:?}"),
3574 };
3575
3576 let filter = stream
3577 .dict
3578 .get("Filter")
3579 .and_then(|o| o.as_array())
3580 .expect("/Filter array must be preserved");
3581 let names: Vec<&str> = filter
3582 .0
3583 .iter()
3584 .filter_map(|o| o.as_name())
3585 .map(|n| n.0.as_str())
3586 .collect();
3587 assert_eq!(
3588 names,
3589 vec!["LZWDecode", "FlateDecode"],
3590 "filter array entries must be preserved in order"
3591 );
3592 assert!(
3593 stream.dict.get("DecodeParms").is_some(),
3594 "/DecodeParms must be preserved alongside the filter chain"
3595 );
3596 assert_eq!(stream.data, payload, "stream body must be intact");
3597 }
3598
3599 #[test]
3600 fn test_reconstruct_malformed_dict_falls_back_without_panic() {
3601 use crate::parser::objects::PdfObject;
3602
3603 let payload = b"0123456789";
3609 let len = payload.len();
3610 let dict_body = format!(" /Length {len} /BadKey ");
3612 let data = build_manual_stream_pdf(&dict_body, payload);
3613
3614 let mut pdf = PdfReader::new_with_options(Cursor::new(data), ParseOptions::tolerant())
3615 .expect("minimal PDF must parse");
3616 let obj = pdf
3617 .extract_object_or_stream_manually(4)
3618 .expect("malformed-dict stream must still be reconstructed via fallback");
3619 let stream = match &obj {
3620 PdfObject::Stream(s) => s,
3621 other => panic!("expected a stream, got {other:?}"),
3622 };
3623
3624 assert_eq!(
3625 stream.data, payload,
3626 "fallback path must still read the bounded body intact"
3627 );
3628 assert!(
3630 stream.dict.get("Filter").is_none(),
3631 "fallback dict must not invent a /Filter"
3632 );
3633 }
3634
3635 #[test]
3636 fn test_find_page_objects_bounded() {
3637 use std::sync::atomic::{AtomicUsize, Ordering};
3638 use std::sync::Arc;
3639
3640 const FILLER: usize = 2 * 1024 * 1024;
3646 let filler: Vec<u8> = (0..FILLER).map(|i| (i % 251) as u8).collect();
3647
3648 let header = b"%PDF-1.4\n";
3649 let obj1 = b"1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n";
3650 let obj2 = b"2 0 obj\n<< /Type /Pages /Kids [4 0 R 5 0 R 6 0 R] /Count 3 >>\nendobj\n";
3651
3652 let mut data = Vec::new();
3653 data.extend_from_slice(header);
3654 let obj1_start = data.len();
3655 data.extend_from_slice(obj1);
3656 let obj2_start = data.len();
3657 data.extend_from_slice(obj2);
3658
3659 for n in [4u32, 5, 6] {
3661 data.extend_from_slice(
3662 format!(
3663 "{n} 0 obj\n<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] >>\nendobj\n"
3664 )
3665 .as_bytes(),
3666 );
3667 }
3668
3669 data.extend_from_slice(b"7 0 obj\n<< /Length 2097152 >>\nstream\n");
3671 data.extend_from_slice(&filler);
3672 data.extend_from_slice(b"\nendstream\nendobj\n");
3673
3674 let xref_start = data.len();
3675 let xref = format!(
3676 "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"
3677 );
3678 data.extend_from_slice(xref.as_bytes());
3679 let file_len = data.len();
3680
3681 let max_read = Arc::new(AtomicUsize::new(0));
3682 let reader = CountingReader {
3683 inner: Cursor::new(data),
3684 total_read: Arc::new(AtomicUsize::new(0)),
3685 max_read: max_read.clone(),
3686 };
3687 let mut pdf = PdfReader::new_with_options(reader, ParseOptions::tolerant())
3688 .expect("minimal PDF must parse");
3689
3690 max_read.store(0, Ordering::SeqCst);
3692 let pages = pdf.find_page_objects().expect("page scan must succeed");
3693
3694 assert_eq!(
3696 pages,
3697 vec![(4, 0), (5, 0), (6, 0)],
3698 "must find exactly the three /Type /Page objects"
3699 );
3700
3701 let peak = max_read.load(Ordering::SeqCst);
3703 assert!(
3704 peak <= 128 * 1024,
3705 "page scan requested {peak} bytes in one read (file={file_len}); not bounded"
3706 );
3707 }
3708
3709 #[test]
3710 fn test_signature_verification_reads_full_file_intentionally() {
3711 use std::sync::atomic::{AtomicUsize, Ordering};
3712 use std::sync::Arc;
3713
3714 let data = create_minimal_pdf();
3719 let file_len = data.len();
3720
3721 let total = Arc::new(AtomicUsize::new(0));
3722 let reader = CountingReader {
3723 inner: Cursor::new(data),
3724 total_read: total.clone(),
3725 max_read: Arc::new(AtomicUsize::new(0)),
3726 };
3727 let mut pdf = PdfReader::new_with_options(reader, ParseOptions::tolerant())
3728 .expect("minimal PDF must parse");
3729
3730 total.store(0, Ordering::SeqCst);
3731 let _ = pdf.verify_signatures();
3733 let read = total.load(Ordering::SeqCst);
3734 assert!(
3735 read >= file_len,
3736 "signature verification must read the whole file (read {read}, file {file_len})"
3737 );
3738 }
3739
3740 #[test]
3741 fn test_reader_version() {
3742 let pdf_data = create_minimal_pdf();
3743 let cursor = Cursor::new(pdf_data);
3744 let reader = PdfReader::new(cursor).unwrap();
3745 assert_eq!(reader.version().major, 1);
3746 assert_eq!(reader.version().minor, 4);
3747 }
3748
3749 #[test]
3750 fn test_reader_different_versions() {
3751 let versions = vec![
3752 "1.0", "1.1", "1.2", "1.3", "1.4", "1.5", "1.6", "1.7", "2.0",
3753 ];
3754
3755 for version in versions {
3756 let pdf_data = create_pdf_with_version(version);
3757 let cursor = Cursor::new(pdf_data);
3758 let reader = PdfReader::new(cursor).unwrap();
3759
3760 let parts: Vec<&str> = version.split('.').collect();
3761 assert_eq!(reader.version().major, parts[0].parse::<u8>().unwrap());
3762 assert_eq!(reader.version().minor, parts[1].parse::<u8>().unwrap());
3763 }
3764 }
3765
3766 #[test]
3767 fn test_reader_catalog() {
3768 let pdf_data = create_minimal_pdf();
3769 let cursor = Cursor::new(pdf_data);
3770 let mut reader = PdfReader::new(cursor).unwrap();
3771
3772 let catalog = reader.catalog();
3773 assert!(catalog.is_ok());
3774
3775 let catalog_dict = catalog.unwrap();
3776 assert_eq!(
3777 catalog_dict.get("Type"),
3778 Some(&PdfObject::Name(PdfName("Catalog".to_string())))
3779 );
3780 }
3781
3782 #[test]
3783 fn test_reader_info_none() {
3784 let pdf_data = create_minimal_pdf();
3785 let cursor = Cursor::new(pdf_data);
3786 let mut reader = PdfReader::new(cursor).unwrap();
3787
3788 let info = reader.info().unwrap();
3789 assert!(info.is_none());
3790 }
3791
3792 #[test]
3793 fn test_reader_info_present() {
3794 let pdf_data = create_pdf_with_info();
3795 let cursor = Cursor::new(pdf_data);
3796 let mut reader = PdfReader::new(cursor).unwrap();
3797
3798 let info = reader.info().unwrap();
3799 assert!(info.is_some());
3800
3801 let info_dict = info.unwrap();
3802 assert_eq!(
3803 info_dict.get("Title"),
3804 Some(&PdfObject::String(PdfString(
3805 "Test PDF".to_string().into_bytes()
3806 )))
3807 );
3808 assert_eq!(
3809 info_dict.get("Author"),
3810 Some(&PdfObject::String(PdfString(
3811 "Test Author".to_string().into_bytes()
3812 )))
3813 );
3814 }
3815
3816 #[test]
3817 fn test_reader_get_object() {
3818 let pdf_data = create_minimal_pdf();
3819 let cursor = Cursor::new(pdf_data);
3820 let mut reader = PdfReader::new(cursor).unwrap();
3821
3822 let obj = reader.get_object(1, 0);
3824 assert!(obj.is_ok());
3825
3826 let catalog = obj.unwrap();
3827 assert!(catalog.as_dict().is_some());
3828 }
3829
3830 #[test]
3831 fn test_reader_get_invalid_object() {
3832 let pdf_data = create_minimal_pdf();
3833 let cursor = Cursor::new(pdf_data);
3834 let mut reader = PdfReader::new(cursor).unwrap();
3835
3836 let obj = reader.get_object(999, 0);
3838 assert!(obj.is_err());
3839 }
3840
3841 #[test]
3842 fn test_reader_get_free_object() {
3843 let pdf_data = create_minimal_pdf();
3844 let cursor = Cursor::new(pdf_data);
3845 let mut reader = PdfReader::new(cursor).unwrap();
3846
3847 let obj = reader.get_object(0, 65535);
3849 assert!(obj.is_ok());
3850 assert_eq!(obj.unwrap(), &PdfObject::Null);
3851 }
3852
3853 #[test]
3854 fn test_reader_resolve_reference() {
3855 let pdf_data = create_minimal_pdf();
3856 let cursor = Cursor::new(pdf_data);
3857 let mut reader = PdfReader::new(cursor).unwrap();
3858
3859 let ref_obj = PdfObject::Reference(1, 0);
3861 let resolved = reader.resolve(&ref_obj);
3862
3863 assert!(resolved.is_ok());
3864 assert!(resolved.unwrap().as_dict().is_some());
3865 }
3866
3867 #[test]
3868 fn test_reader_resolve_non_reference() {
3869 let pdf_data = create_minimal_pdf();
3870 let cursor = Cursor::new(pdf_data);
3871 let mut reader = PdfReader::new(cursor).unwrap();
3872
3873 let int_obj = PdfObject::Integer(42);
3875 let resolved = reader.resolve(&int_obj).unwrap();
3876
3877 assert_eq!(resolved, &PdfObject::Integer(42));
3878 }
3879
3880 #[test]
3881 fn test_reader_cache_behavior() {
3882 let pdf_data = create_minimal_pdf();
3883 let cursor = Cursor::new(pdf_data);
3884 let mut reader = PdfReader::new(cursor).unwrap();
3885
3886 let obj1 = reader.get_object(1, 0).unwrap();
3888 assert!(obj1.as_dict().is_some());
3889
3890 let obj2 = reader.get_object(1, 0).unwrap();
3892 assert!(obj2.as_dict().is_some());
3893 }
3894
3895 #[test]
3896 fn test_reader_wrong_generation() {
3897 let pdf_data = create_minimal_pdf();
3898 let cursor = Cursor::new(pdf_data);
3899 let mut reader = PdfReader::new(cursor).unwrap();
3900
3901 let obj = reader.get_object(1, 99);
3903 assert!(obj.is_err());
3904 }
3905
3906 #[test]
3907 fn test_reader_invalid_pdf() {
3908 let invalid_data = b"This is not a PDF file";
3909 let cursor = Cursor::new(invalid_data.to_vec());
3910 let result = PdfReader::new(cursor);
3911
3912 assert!(result.is_err());
3913 }
3914
3915 #[test]
3916 fn test_reader_corrupt_xref() {
3917 let corrupt_pdf = b"%PDF-1.4
39181 0 obj
3919<< /Type /Catalog >>
3920endobj
3921xref
3922corrupted xref table
3923trailer
3924<< /Size 2 /Root 1 0 R >>
3925startxref
392624
3927%%EOF"
3928 .to_vec();
3929
3930 let cursor = Cursor::new(corrupt_pdf.clone());
3934 let mut reader = PdfReader::new(cursor).expect("corrupt xref is reconstructed by scan");
3935 let catalog = reader
3936 .catalog()
3937 .expect("catalog resolves after reconstruction");
3938 assert_eq!(
3939 catalog.get("Type"),
3940 Some(&PdfObject::Name(PdfName("Catalog".to_string()))),
3941 );
3942
3943 let cursor = Cursor::new(corrupt_pdf);
3945 assert!(PdfReader::new_with_options(cursor, ParseOptions::strict()).is_err());
3946 }
3947
3948 #[test]
3949 fn test_reader_missing_trailer() {
3950 let pdf_no_trailer = b"%PDF-1.4
39511 0 obj
3952<< /Type /Catalog >>
3953endobj
3954xref
39550 2
39560000000000 65535 f
39570000000009 00000 n
3958startxref
395924
3960%%EOF"
3961 .to_vec();
3962
3963 let cursor = Cursor::new(pdf_no_trailer.clone());
3967 let mut reader = PdfReader::new(cursor).expect("missing trailer recovered by object scan");
3968 let catalog = reader
3969 .catalog()
3970 .expect("catalog resolved from scanned /Root");
3971 assert_eq!(
3972 catalog.get("Type"),
3973 Some(&PdfObject::Name(PdfName("Catalog".to_string()))),
3974 );
3975
3976 let cursor = Cursor::new(pdf_no_trailer);
3978 assert!(PdfReader::new_with_options(cursor, ParseOptions::strict()).is_err());
3979 }
3980
3981 #[test]
3982 fn test_reader_empty_pdf() {
3983 let cursor = Cursor::new(Vec::new());
3984 let result = PdfReader::new(cursor);
3985 assert!(result.is_err());
3986 }
3987
3988 #[test]
3989 fn test_reader_page_count() {
3990 let pdf_data = create_minimal_pdf();
3991 let cursor = Cursor::new(pdf_data);
3992 let mut reader = PdfReader::new(cursor).unwrap();
3993
3994 let count = reader.page_count();
3995 assert!(count.is_ok());
3996 assert_eq!(count.unwrap(), 0); }
3998
3999 #[test]
4000 fn test_reader_into_document() {
4001 let pdf_data = create_minimal_pdf();
4002 let cursor = Cursor::new(pdf_data);
4003 let reader = PdfReader::new(cursor).unwrap();
4004
4005 let document = reader.into_document();
4006 let page_count = document.page_count();
4008 assert!(page_count.is_ok());
4009 }
4010
4011 #[test]
4012 fn test_reader_pages_dict() {
4013 let pdf_data = create_minimal_pdf();
4014 let cursor = Cursor::new(pdf_data);
4015 let mut reader = PdfReader::new(cursor).unwrap();
4016
4017 let pages = reader.pages();
4018 assert!(pages.is_ok());
4019 let pages_dict = pages.unwrap();
4020 assert_eq!(
4021 pages_dict.get("Type"),
4022 Some(&PdfObject::Name(PdfName("Pages".to_string())))
4023 );
4024 }
4025
4026 #[test]
4027 fn test_reader_pdf_with_binary_data() {
4028 let pdf_data = create_pdf_with_binary_marker();
4029
4030 let cursor = Cursor::new(pdf_data);
4031 let result = PdfReader::new(cursor);
4032 assert!(result.is_ok());
4033 }
4034
4035 #[test]
4036 fn test_reader_metadata() {
4037 let pdf_data = create_pdf_with_info();
4038 let cursor = Cursor::new(pdf_data);
4039 let mut reader = PdfReader::new(cursor).unwrap();
4040
4041 let metadata = reader.metadata().unwrap();
4042 assert_eq!(metadata.title, Some("Test PDF".to_string()));
4043 assert_eq!(metadata.author, Some("Test Author".to_string()));
4044 assert_eq!(metadata.subject, Some("Testing".to_string()));
4045 assert_eq!(metadata.version, "1.4".to_string());
4046 }
4047
4048 #[test]
4049 fn test_reader_metadata_empty() {
4050 let pdf_data = create_minimal_pdf();
4051 let cursor = Cursor::new(pdf_data);
4052 let mut reader = PdfReader::new(cursor).unwrap();
4053
4054 let metadata = reader.metadata().unwrap();
4055 assert!(metadata.title.is_none());
4056 assert!(metadata.author.is_none());
4057 assert_eq!(metadata.version, "1.4".to_string());
4058 assert_eq!(metadata.page_count, Some(0));
4059 }
4060
4061 #[test]
4062 fn test_reader_object_number_mismatch() {
4063 let pdf_data = create_minimal_pdf();
4067 let cursor = Cursor::new(pdf_data);
4068 let mut reader = PdfReader::new(cursor).unwrap();
4069
4070 let result = reader.get_object(1, 99);
4073 assert!(result.is_err());
4074
4075 let result2 = reader.get_object(999, 0);
4077 assert!(result2.is_err());
4078 }
4079
4080 #[test]
4081 fn test_document_metadata_struct() {
4082 let metadata = DocumentMetadata {
4083 title: Some("Title".to_string()),
4084 author: Some("Author".to_string()),
4085 subject: Some("Subject".to_string()),
4086 keywords: Some("Keywords".to_string()),
4087 creator: Some("Creator".to_string()),
4088 producer: Some("Producer".to_string()),
4089 creation_date: Some("D:20240101".to_string()),
4090 modification_date: Some("D:20240102".to_string()),
4091 version: "1.5".to_string(),
4092 page_count: Some(10),
4093 };
4094
4095 assert_eq!(metadata.title, Some("Title".to_string()));
4096 assert_eq!(metadata.page_count, Some(10));
4097 }
4098
4099 #[test]
4100 fn test_document_metadata_default() {
4101 let metadata = DocumentMetadata::default();
4102 assert!(metadata.title.is_none());
4103 assert!(metadata.author.is_none());
4104 assert!(metadata.subject.is_none());
4105 assert!(metadata.keywords.is_none());
4106 assert!(metadata.creator.is_none());
4107 assert!(metadata.producer.is_none());
4108 assert!(metadata.creation_date.is_none());
4109 assert!(metadata.modification_date.is_none());
4110 assert_eq!(metadata.version, "".to_string());
4111 assert!(metadata.page_count.is_none());
4112 }
4113
4114 #[test]
4115 fn test_document_metadata_clone() {
4116 let metadata = DocumentMetadata {
4117 title: Some("Test".to_string()),
4118 version: "1.4".to_string(),
4119 ..Default::default()
4120 };
4121
4122 let cloned = metadata;
4123 assert_eq!(cloned.title, Some("Test".to_string()));
4124 assert_eq!(cloned.version, "1.4".to_string());
4125 }
4126
4127 #[test]
4128 fn test_reader_trailer_validation_error() {
4129 let bad_pdf = b"%PDF-1.4
41311 0 obj
4132<< /Type /Catalog >>
4133endobj
4134xref
41350 2
41360000000000 65535 f
41370000000009 00000 n
4138trailer
4139<< /Size 2 >>
4140startxref
414146
4142%%EOF"
4143 .to_vec();
4144
4145 let cursor = Cursor::new(bad_pdf.clone());
4149 let mut reader = PdfReader::new(cursor).expect("missing /Root recovered by object scan");
4150 let catalog = reader
4151 .catalog()
4152 .expect("catalog resolved from scanned /Root");
4153 assert_eq!(
4154 catalog.get("Type"),
4155 Some(&PdfObject::Name(PdfName("Catalog".to_string()))),
4156 );
4157
4158 let cursor = Cursor::new(bad_pdf);
4160 assert!(PdfReader::new_with_options(cursor, ParseOptions::strict()).is_err());
4161 }
4162
4163 #[test]
4164 fn test_reader_with_options() {
4165 let pdf_data = create_minimal_pdf();
4166 let cursor = Cursor::new(pdf_data);
4167 let mut options = ParseOptions::default();
4168 options.lenient_streams = true;
4169 options.max_recovery_bytes = 2000;
4170 options.collect_warnings = true;
4171
4172 let reader = PdfReader::new_with_options(cursor, options);
4173 assert!(reader.is_ok());
4174 }
4175
4176 #[test]
4177 fn test_lenient_stream_parsing() {
4178 let pdf_data = b"%PDF-1.4
41801 0 obj
4181<< /Type /Catalog /Pages 2 0 R >>
4182endobj
41832 0 obj
4184<< /Type /Pages /Kids [3 0 R] /Count 1 >>
4185endobj
41863 0 obj
4187<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] /Contents 4 0 R >>
4188endobj
41894 0 obj
4190<< /Length 10 >>
4191stream
4192This is a longer stream than 10 bytes
4193endstream
4194endobj
4195xref
41960 5
41970000000000 65535 f
41980000000009 00000 n
41990000000058 00000 n
42000000000116 00000 n
42010000000219 00000 n
4202trailer
4203<< /Size 5 /Root 1 0 R >>
4204startxref
4205299
4206%%EOF"
4207 .to_vec();
4208
4209 let cursor = Cursor::new(pdf_data.clone());
4211 let strict_options = ParseOptions::strict();
4212 let strict_reader = PdfReader::new_with_options(cursor, strict_options);
4213 assert!(strict_reader.is_err());
4215
4216 let cursor = Cursor::new(pdf_data);
4220 let mut options = ParseOptions::default();
4221 options.lenient_streams = true;
4222 options.max_recovery_bytes = 1000;
4223 options.collect_warnings = false;
4224 let mut reader =
4225 PdfReader::new_with_options(cursor, options).expect("mismatched xref reconstructed");
4226 let catalog = reader
4227 .catalog()
4228 .expect("catalog resolves after reconstruction");
4229 assert_eq!(
4230 catalog.get("Type"),
4231 Some(&PdfObject::Name(PdfName("Catalog".to_string()))),
4232 );
4233 }
4234
4235 #[test]
4236 fn test_parse_options_default() {
4237 let options = ParseOptions::default();
4238 assert!(!options.lenient_streams);
4239 assert_eq!(options.max_recovery_bytes, 1000);
4240 assert!(!options.collect_warnings);
4241 }
4242
4243 #[test]
4244 fn test_parse_options_clone() {
4245 let mut options = ParseOptions::default();
4246 options.lenient_streams = true;
4247 options.max_recovery_bytes = 2000;
4248 options.collect_warnings = true;
4249 let cloned = options;
4250 assert!(cloned.lenient_streams);
4251 assert_eq!(cloned.max_recovery_bytes, 2000);
4252 assert!(cloned.collect_warnings);
4253 }
4254
4255 #[allow(dead_code)]
4258 fn create_encrypted_pdf_dict() -> PdfDictionary {
4259 let mut dict = PdfDictionary::new();
4260 dict.insert(
4261 "Filter".to_string(),
4262 PdfObject::Name(PdfName("Standard".to_string())),
4263 );
4264 dict.insert("V".to_string(), PdfObject::Integer(1));
4265 dict.insert("R".to_string(), PdfObject::Integer(2));
4266 dict.insert("O".to_string(), PdfObject::String(PdfString(vec![0u8; 32])));
4267 dict.insert("U".to_string(), PdfObject::String(PdfString(vec![0u8; 32])));
4268 dict.insert("P".to_string(), PdfObject::Integer(-4));
4269 dict
4270 }
4271
4272 fn create_pdf_with_encryption() -> Vec<u8> {
4273 b"%PDF-1.4
42751 0 obj
4276<< /Type /Catalog /Pages 2 0 R >>
4277endobj
42782 0 obj
4279<< /Type /Pages /Kids [3 0 R] /Count 1 >>
4280endobj
42813 0 obj
4282<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] >>
4283endobj
42844 0 obj
4285<< /Filter /Standard /V 1 /R 2 /O (32 bytes of owner password hash data) /U (32 bytes of user password hash data) /P -4 >>
4286endobj
4287xref
42880 5
42890000000000 65535 f
42900000000009 00000 n
42910000000058 00000 n
42920000000116 00000 n
42930000000201 00000 n
4294trailer
4295<< /Size 5 /Root 1 0 R /Encrypt 4 0 R /ID [(file id)] >>
4296startxref
4297295
4298%%EOF"
4299 .to_vec()
4300 }
4301
4302 #[test]
4303 fn test_reader_encryption_detection() {
4304 let unencrypted_pdf = create_minimal_pdf();
4306 let cursor = Cursor::new(unencrypted_pdf);
4307 let reader = PdfReader::new(cursor).unwrap();
4308 assert!(!reader.is_encrypted());
4309 assert!(reader.is_unlocked()); let encrypted_pdf = create_pdf_with_encryption();
4317 let cursor = Cursor::new(encrypted_pdf);
4318 let reader = PdfReader::new(cursor).expect("encrypted PDF opens as locked, not rejected");
4319 assert!(
4320 reader.is_encrypted(),
4321 "must report encrypted, not plaintext"
4322 );
4323 assert!(
4324 !reader.is_unlocked(),
4325 "must stay locked without a valid password"
4326 );
4327 }
4328
4329 #[test]
4330 fn test_reader_encryption_methods_unencrypted() {
4331 let pdf_data = create_minimal_pdf();
4332 let cursor = Cursor::new(pdf_data);
4333 let mut reader = PdfReader::new(cursor).unwrap();
4334
4335 assert!(!reader.is_encrypted());
4337 assert!(reader.is_unlocked());
4338 assert!(reader.encryption_handler().is_none());
4339 assert!(reader.encryption_handler_mut().is_none());
4340
4341 assert!(reader.unlock_with_password("any_password").unwrap());
4343 assert!(reader.try_empty_password().unwrap());
4344 }
4345
4346 #[test]
4347 fn test_reader_encryption_handler_access() {
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.encryption_handler().is_none());
4354 assert!(reader.encryption_handler_mut().is_none());
4355
4356 assert!(!reader.is_encrypted());
4358 assert!(reader.is_unlocked());
4359 }
4360
4361 #[test]
4362 fn test_reader_multiple_password_attempts() {
4363 let pdf_data = create_minimal_pdf();
4364 let cursor = Cursor::new(pdf_data);
4365 let mut reader = PdfReader::new(cursor).unwrap();
4366
4367 let passwords = vec!["test1", "test2", "admin", "", "password"];
4369 for password in passwords {
4370 assert!(reader.unlock_with_password(password).unwrap());
4371 }
4372
4373 for _ in 0..5 {
4375 assert!(reader.try_empty_password().unwrap());
4376 }
4377 }
4378
4379 #[test]
4380 fn test_reader_encryption_state_consistency() {
4381 let pdf_data = create_minimal_pdf();
4382 let cursor = Cursor::new(pdf_data);
4383 let mut reader = PdfReader::new(cursor).unwrap();
4384
4385 assert!(!reader.is_encrypted());
4387 assert!(reader.is_unlocked());
4388 assert!(reader.encryption_handler().is_none());
4389
4390 let _ = reader.unlock_with_password("test");
4392 assert!(!reader.is_encrypted());
4393 assert!(reader.is_unlocked());
4394 assert!(reader.encryption_handler().is_none());
4395
4396 let _ = reader.try_empty_password();
4397 assert!(!reader.is_encrypted());
4398 assert!(reader.is_unlocked());
4399 assert!(reader.encryption_handler().is_none());
4400 }
4401
4402 #[test]
4403 fn test_reader_encryption_error_handling() {
4404 let encrypted_pdf = create_pdf_with_encryption();
4409 let cursor = Cursor::new(encrypted_pdf);
4410
4411 match PdfReader::new(cursor) {
4412 Err(_) => {}
4414 Ok(reader) => {
4417 assert!(
4418 reader.is_encrypted() && !reader.is_unlocked(),
4419 "recovered encrypted PDF must stay encrypted+locked, not open as plaintext"
4420 );
4421 }
4422 }
4423 }
4424
4425 #[test]
4426 fn test_reader_encryption_with_options() {
4427 let pdf_data = create_minimal_pdf();
4428 let cursor = Cursor::new(pdf_data);
4429
4430 let strict_options = ParseOptions::strict();
4432 let strict_reader = PdfReader::new_with_options(cursor, strict_options).unwrap();
4433 assert!(!strict_reader.is_encrypted());
4434 assert!(strict_reader.is_unlocked());
4435
4436 let pdf_data = create_minimal_pdf();
4437 let cursor = Cursor::new(pdf_data);
4438 let lenient_options = ParseOptions::lenient();
4439 let lenient_reader = PdfReader::new_with_options(cursor, lenient_options).unwrap();
4440 assert!(!lenient_reader.is_encrypted());
4441 assert!(lenient_reader.is_unlocked());
4442 }
4443
4444 #[test]
4445 fn test_reader_encryption_integration_edge_cases() {
4446 let pdf_data = create_minimal_pdf();
4447 let cursor = Cursor::new(pdf_data);
4448 let mut reader = PdfReader::new(cursor).unwrap();
4449
4450 assert!(reader.unlock_with_password("").unwrap());
4452 assert!(reader.unlock_with_password(" ").unwrap()); assert!(reader
4454 .unlock_with_password("very_long_password_that_exceeds_normal_length")
4455 .unwrap());
4456 assert!(reader.unlock_with_password("unicode_test_ñáéíóú").unwrap());
4457
4458 assert!(reader.unlock_with_password("pass@#$%^&*()").unwrap());
4460 assert!(reader.unlock_with_password("pass\nwith\nnewlines").unwrap());
4461 assert!(reader.unlock_with_password("pass\twith\ttabs").unwrap());
4462 }
4463
4464 mod rigorous {
4465 use super::*;
4466
4467 #[test]
4472 fn test_reader_invalid_pdf_header() {
4473 let invalid_data = b"This is not a PDF file";
4475 let cursor = Cursor::new(invalid_data.to_vec());
4476 let result = PdfReader::new(cursor);
4477
4478 assert!(result.is_err(), "Should fail on invalid PDF header");
4479 }
4480
4481 #[test]
4482 fn test_reader_truncated_header() {
4483 let truncated = b"%PDF";
4485 let cursor = Cursor::new(truncated.to_vec());
4486 let result = PdfReader::new(cursor);
4487
4488 assert!(result.is_err(), "Should fail on truncated header");
4489 }
4490
4491 #[test]
4492 fn test_reader_empty_file() {
4493 let empty = Vec::new();
4494 let cursor = Cursor::new(empty);
4495 let result = PdfReader::new(cursor);
4496
4497 assert!(result.is_err(), "Should fail on empty file");
4498 }
4499
4500 #[test]
4501 fn test_reader_malformed_version() {
4502 let malformed = b"%PDF-X.Y\n%%\xE2\xE3\xCF\xD3\n";
4504 let cursor = Cursor::new(malformed.to_vec());
4505 let result = PdfReader::new(cursor);
4506
4507 if let Ok(reader) = result {
4509 let _version = reader.version();
4511 }
4512 }
4513
4514 #[test]
4515 fn test_reader_get_nonexistent_object() {
4516 let pdf_data = create_minimal_pdf();
4517 let cursor = Cursor::new(pdf_data);
4518 let mut reader = PdfReader::new(cursor).unwrap();
4519
4520 let result = reader.get_object(999, 0);
4522
4523 assert!(result.is_err(), "Should fail when object doesn't exist");
4524 }
4525
4526 #[test]
4527 fn test_reader_get_object_wrong_generation() {
4528 let pdf_data = create_minimal_pdf();
4529 let cursor = Cursor::new(pdf_data);
4530 let mut reader = PdfReader::new(cursor).unwrap();
4531
4532 let result = reader.get_object(1, 99);
4534
4535 if let Err(e) = result {
4537 let _ = e;
4539 }
4540 }
4541
4542 #[test]
4547 fn test_resolve_direct_object() {
4548 let pdf_data = create_minimal_pdf();
4549 let cursor = Cursor::new(pdf_data);
4550 let mut reader = PdfReader::new(cursor).unwrap();
4551
4552 let direct_obj = PdfObject::Integer(42);
4554
4555 let resolved = reader.resolve(&direct_obj).unwrap();
4556
4557 assert_eq!(resolved, &PdfObject::Integer(42));
4559 }
4560
4561 #[test]
4562 fn test_resolve_reference() {
4563 let pdf_data = create_minimal_pdf();
4564 let cursor = Cursor::new(pdf_data);
4565 let mut reader = PdfReader::new(cursor).unwrap();
4566
4567 let pages_ref = {
4569 let catalog = reader.catalog().unwrap();
4570 if let Some(PdfObject::Reference(obj_num, gen_num)) = catalog.get("Pages") {
4571 PdfObject::Reference(*obj_num, *gen_num)
4572 } else {
4573 panic!("Catalog /Pages must be a Reference");
4574 }
4575 };
4576
4577 let resolved = reader.resolve(&pages_ref).unwrap();
4579
4580 if let PdfObject::Dictionary(dict) = resolved {
4582 assert_eq!(
4583 dict.get("Type"),
4584 Some(&PdfObject::Name(PdfName("Pages".to_string())))
4585 );
4586 } else {
4587 panic!("Expected dictionary, got: {:?}", resolved);
4588 }
4589 }
4590
4591 #[test]
4596 fn test_is_encrypted_on_unencrypted() {
4597 let pdf_data = create_minimal_pdf();
4598 let cursor = Cursor::new(pdf_data);
4599 let reader = PdfReader::new(cursor).unwrap();
4600
4601 assert!(
4602 !reader.is_encrypted(),
4603 "Minimal PDF should not be encrypted"
4604 );
4605 }
4606
4607 #[test]
4608 fn test_is_unlocked_on_unencrypted() {
4609 let pdf_data = create_minimal_pdf();
4610 let cursor = Cursor::new(pdf_data);
4611 let reader = PdfReader::new(cursor).unwrap();
4612
4613 assert!(reader.is_unlocked(), "Unencrypted PDF should be unlocked");
4615 }
4616
4617 #[test]
4618 fn test_try_empty_password_on_unencrypted() {
4619 let pdf_data = create_minimal_pdf();
4620 let cursor = Cursor::new(pdf_data);
4621 let mut reader = PdfReader::new(cursor).unwrap();
4622
4623 let result = reader.try_empty_password();
4625 assert!(result.is_ok());
4626 }
4627
4628 #[test]
4633 fn test_reader_with_strict_options() {
4634 let pdf_data = create_minimal_pdf();
4635 let cursor = Cursor::new(pdf_data);
4636
4637 let options = ParseOptions::strict();
4638 let result = PdfReader::new_with_options(cursor, options);
4639
4640 assert!(result.is_ok(), "Minimal PDF should parse in strict mode");
4641 }
4642
4643 #[test]
4644 fn test_reader_with_lenient_options() {
4645 let pdf_data = create_minimal_pdf();
4646 let cursor = Cursor::new(pdf_data);
4647
4648 let options = ParseOptions::lenient();
4649 let result = PdfReader::new_with_options(cursor, options);
4650
4651 assert!(result.is_ok(), "Minimal PDF should parse in lenient mode");
4652 }
4653
4654 #[test]
4655 fn test_reader_options_accessible() {
4656 let pdf_data = create_minimal_pdf();
4657 let cursor = Cursor::new(pdf_data);
4658
4659 let options = ParseOptions::lenient();
4660 let reader = PdfReader::new_with_options(cursor, options.clone()).unwrap();
4661
4662 let reader_options = reader.options();
4664 assert_eq!(reader_options.strict_mode, options.strict_mode);
4665 }
4666
4667 #[test]
4672 fn test_catalog_has_required_fields() {
4673 let pdf_data = create_minimal_pdf();
4674 let cursor = Cursor::new(pdf_data);
4675 let mut reader = PdfReader::new(cursor).unwrap();
4676
4677 let catalog = reader.catalog().unwrap();
4678
4679 assert_eq!(
4681 catalog.get("Type"),
4682 Some(&PdfObject::Name(PdfName("Catalog".to_string()))),
4683 "Catalog must have /Type /Catalog"
4684 );
4685
4686 assert!(
4688 catalog.contains_key("Pages"),
4689 "Catalog must have /Pages entry"
4690 );
4691 }
4692
4693 #[test]
4694 fn test_info_fields_when_present() {
4695 let pdf_data = create_pdf_with_info();
4696 let cursor = Cursor::new(pdf_data);
4697 let mut reader = PdfReader::new(cursor).unwrap();
4698
4699 let info = reader.info().unwrap();
4700 assert!(info.is_some(), "PDF should have Info dictionary");
4701
4702 let info_dict = info.unwrap();
4703
4704 assert!(info_dict.contains_key("Title"), "Info should have Title");
4706 assert!(info_dict.contains_key("Author"), "Info should have Author");
4707 }
4708
4709 #[test]
4710 fn test_info_none_when_absent() {
4711 let pdf_data = create_minimal_pdf();
4712 let cursor = Cursor::new(pdf_data);
4713 let mut reader = PdfReader::new(cursor).unwrap();
4714
4715 let info = reader.info().unwrap();
4716 assert!(info.is_none(), "Minimal PDF should not have Info");
4717 }
4718
4719 #[test]
4724 fn test_version_exact_values() {
4725 let pdf_data = create_pdf_with_version("1.7");
4726 let cursor = Cursor::new(pdf_data);
4727 let reader = PdfReader::new(cursor).unwrap();
4728
4729 let version = reader.version();
4730 assert_eq!(version.major, 1, "Major version must be exact");
4731 assert_eq!(version.minor, 7, "Minor version must be exact");
4732 }
4733
4734 #[test]
4735 fn test_version_pdf_20() {
4736 let pdf_data = create_pdf_with_version("2.0");
4737 let cursor = Cursor::new(pdf_data);
4738 let reader = PdfReader::new(cursor).unwrap();
4739
4740 let version = reader.version();
4741 assert_eq!(version.major, 2, "PDF 2.0 major version");
4742 assert_eq!(version.minor, 0, "PDF 2.0 minor version");
4743 }
4744
4745 #[test]
4750 fn test_pages_returns_pages_dict() {
4751 let pdf_data = create_minimal_pdf();
4752 let cursor = Cursor::new(pdf_data);
4753 let mut reader = PdfReader::new(cursor).unwrap();
4754
4755 let pages_dict = reader
4756 .pages()
4757 .expect("pages() must return Pages dictionary");
4758
4759 assert_eq!(
4760 pages_dict.get("Type"),
4761 Some(&PdfObject::Name(PdfName("Pages".to_string()))),
4762 "Pages dict must have /Type /Pages"
4763 );
4764 }
4765
4766 #[test]
4767 fn test_page_count_minimal_pdf() {
4768 let pdf_data = create_minimal_pdf();
4769 let cursor = Cursor::new(pdf_data);
4770 let mut reader = PdfReader::new(cursor).unwrap();
4771
4772 let count = reader.page_count().expect("page_count() must succeed");
4773 assert_eq!(count, 0, "Minimal PDF has 0 pages");
4774 }
4775
4776 #[test]
4777 fn test_page_count_with_info_pdf() {
4778 let pdf_data = create_pdf_with_info();
4779 let cursor = Cursor::new(pdf_data);
4780 let mut reader = PdfReader::new(cursor).unwrap();
4781
4782 let count = reader.page_count().expect("page_count() must succeed");
4783 assert_eq!(count, 0, "create_pdf_with_info() has Count 0 in Pages dict");
4784 }
4785
4786 #[test]
4791 fn test_metadata_minimal_pdf() {
4792 let pdf_data = create_minimal_pdf();
4793 let cursor = Cursor::new(pdf_data);
4794 let mut reader = PdfReader::new(cursor).unwrap();
4795
4796 let meta = reader.metadata().expect("metadata() must succeed");
4797
4798 assert!(meta.title.is_none(), "Minimal PDF has no title");
4800 assert!(meta.author.is_none(), "Minimal PDF has no author");
4801 }
4802
4803 #[test]
4804 fn test_metadata_with_info() {
4805 let pdf_data = create_pdf_with_info();
4806 let cursor = Cursor::new(pdf_data);
4807 let mut reader = PdfReader::new(cursor).unwrap();
4808
4809 let meta = reader.metadata().expect("metadata() must succeed");
4810
4811 assert!(meta.title.is_some(), "PDF with Info has title");
4812 assert_eq!(meta.title.unwrap(), "Test PDF", "Title must match");
4813 assert!(meta.author.is_some(), "PDF with Info has author");
4814 assert_eq!(meta.author.unwrap(), "Test Author", "Author must match");
4815 }
4816
4817 #[test]
4822 fn test_resolve_stream_length_direct_integer() {
4823 let pdf_data = create_minimal_pdf();
4824 let cursor = Cursor::new(pdf_data);
4825 let mut reader = PdfReader::new(cursor).unwrap();
4826
4827 let length_obj = PdfObject::Integer(100);
4829
4830 let length = reader
4831 .resolve_stream_length(&length_obj)
4832 .expect("resolve_stream_length must succeed");
4833 assert_eq!(length, Some(100), "Direct integer must be resolved");
4834 }
4835
4836 #[test]
4837 fn test_resolve_stream_length_negative_integer() {
4838 let pdf_data = create_minimal_pdf();
4839 let cursor = Cursor::new(pdf_data);
4840 let mut reader = PdfReader::new(cursor).unwrap();
4841
4842 let length_obj = PdfObject::Integer(-10);
4844
4845 let length = reader
4846 .resolve_stream_length(&length_obj)
4847 .expect("resolve_stream_length must succeed");
4848 assert_eq!(length, None, "Negative integer returns None");
4849 }
4850
4851 #[test]
4852 fn test_resolve_stream_length_non_integer() {
4853 let pdf_data = create_minimal_pdf();
4854 let cursor = Cursor::new(pdf_data);
4855 let mut reader = PdfReader::new(cursor).unwrap();
4856
4857 let name_obj = PdfObject::Name(PdfName("Test".to_string()));
4859
4860 let length = reader
4861 .resolve_stream_length(&name_obj)
4862 .expect("resolve_stream_length must succeed");
4863 assert_eq!(length, None, "Non-integer object returns None");
4864 }
4865
4866 #[test]
4871 fn test_get_all_pages_empty_pdf() {
4872 let pdf_data = create_minimal_pdf();
4873 let cursor = Cursor::new(pdf_data);
4874 let mut reader = PdfReader::new(cursor).unwrap();
4875
4876 let pages = reader
4877 .get_all_pages()
4878 .expect("get_all_pages() must succeed");
4879 assert_eq!(pages.len(), 0, "Minimal PDF has 0 pages");
4880 }
4881
4882 #[test]
4883 fn test_get_all_pages_with_info() {
4884 let pdf_data = create_pdf_with_info();
4885 let cursor = Cursor::new(pdf_data);
4886 let mut reader = PdfReader::new(cursor).unwrap();
4887
4888 let pages = reader
4889 .get_all_pages()
4890 .expect("get_all_pages() must succeed");
4891 assert_eq!(
4892 pages.len(),
4893 0,
4894 "create_pdf_with_info() has 0 pages (Count 0)"
4895 );
4896 }
4897
4898 #[test]
4903 fn test_into_document_consumes_reader() {
4904 let pdf_data = create_minimal_pdf();
4905 let cursor = Cursor::new(pdf_data);
4906 let reader = PdfReader::new(cursor).unwrap();
4907
4908 let document = reader.into_document();
4909
4910 let version = document.version().expect("Document must have version");
4912 assert!(
4913 version.starts_with("1."),
4914 "Document must have PDF 1.x version, got: {}",
4915 version
4916 );
4917
4918 let page_count = document
4920 .page_count()
4921 .expect("Document must allow page_count()");
4922 assert_eq!(
4923 page_count, 0,
4924 "Minimal PDF has 0 pages (Count 0 in test helper)"
4925 );
4926 }
4927
4928 #[test]
4933 fn test_clear_parse_context() {
4934 let pdf_data = create_minimal_pdf();
4935 let cursor = Cursor::new(pdf_data);
4936 let mut reader = PdfReader::new(cursor).unwrap();
4937
4938 reader.clear_parse_context();
4940
4941 let version = reader.version();
4943 assert_eq!(version.major, 1, "Reader must still work after clear");
4944 }
4945
4946 #[test]
4947 fn test_parse_context_mut_accessible() {
4948 let pdf_data = create_minimal_pdf();
4949 let cursor = Cursor::new(pdf_data);
4950 let mut reader = PdfReader::new(cursor).unwrap();
4951
4952 let context = reader.parse_context_mut();
4953
4954 let initial_depth = context.depth;
4956 assert_eq!(initial_depth, 0, "Parse context must start with depth 0");
4957
4958 assert!(
4960 context.max_depth > 0,
4961 "Parse context must have positive max_depth"
4962 );
4963 }
4964
4965 #[test]
4970 fn test_find_byte_pattern_basic() {
4971 let haystack = b"Hello World";
4972 let needle = b"World";
4973 let pos = find_byte_pattern(haystack, needle);
4974 assert_eq!(pos, Some(6), "Must find 'World' at position 6");
4975 }
4976
4977 #[test]
4978 fn test_find_byte_pattern_not_found() {
4979 let haystack = b"Hello World";
4980 let needle = b"Rust";
4981 let pos = find_byte_pattern(haystack, needle);
4982 assert_eq!(pos, None, "Must return None when not found");
4983 }
4984
4985 #[test]
4986 fn test_find_byte_pattern_at_start() {
4987 let haystack = b"Hello World";
4988 let needle = b"Hello";
4989 let pos = find_byte_pattern(haystack, needle);
4990 assert_eq!(pos, Some(0), "Must find at position 0");
4991 }
4992
4993 #[test]
4994 fn test_is_immediate_stream_start_with_stream() {
4995 let data = b"stream\ndata";
4996 assert!(
4997 is_immediate_stream_start(data),
4998 "Must detect 'stream' at start"
4999 );
5000 }
5001
5002 #[test]
5003 fn test_is_immediate_stream_start_with_whitespace() {
5004 let data = b" \n\tstream\ndata";
5005 assert!(
5006 is_immediate_stream_start(data),
5007 "Must detect 'stream' after whitespace"
5008 );
5009 }
5010
5011 #[test]
5012 fn test_is_immediate_stream_start_no_stream() {
5013 let data = b"endobj";
5014 assert!(
5015 !is_immediate_stream_start(data),
5016 "Must return false when 'stream' absent"
5017 );
5018 }
5019 }
5020}