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
21const MANUAL_DICT_WINDOW: usize = 256 * 1024;
26
27fn is_immediate_stream_start(data: &[u8]) -> bool {
29 let mut i = 0;
30
31 while i < data.len() && matches!(data[i], b' ' | b'\t' | b'\n' | b'\r') {
33 i += 1;
34 }
35
36 data[i..].starts_with(b"stream")
38}
39
40pub struct PdfReader<R: Read + Seek> {
42 reader: BufReader<R>,
43 header: PdfHeader,
44 xref: XRefTable,
45 trailer: PdfTrailer,
46 object_cache: HashMap<(u32, u16), PdfObject>,
48 object_stream_cache: HashMap<u32, ObjectStream>,
50 page_tree: Option<super::page_tree::PageTree>,
52 parse_context: StackSafeContext,
54 options: super::ParseOptions,
56 encryption_handler: Option<EncryptionHandler>,
58 objects_being_reconstructed: std::sync::Mutex<std::collections::HashSet<u32>>,
60 max_reconstruction_depth: u32,
62}
63
64impl<R: Read + Seek> PdfReader<R> {
65 pub fn options(&self) -> &super::ParseOptions {
67 &self.options
68 }
69
70 pub fn is_encrypted(&self) -> bool {
72 self.encryption_handler.is_some()
73 }
74
75 pub fn trailer(&self) -> &PdfTrailer {
83 &self.trailer
84 }
85
86 pub fn is_unlocked(&self) -> bool {
88 match &self.encryption_handler {
89 Some(handler) => handler.is_unlocked(),
90 None => true, }
92 }
93
94 pub fn encryption_handler_mut(&mut self) -> Option<&mut EncryptionHandler> {
96 self.encryption_handler.as_mut()
97 }
98
99 pub fn encryption_handler(&self) -> Option<&EncryptionHandler> {
101 self.encryption_handler.as_ref()
102 }
103
104 pub fn unlock_with_password(&mut self, password: &str) -> ParseResult<bool> {
106 match &mut self.encryption_handler {
107 Some(handler) => {
108 if handler.unlock_with_user_password(password).unwrap_or(false) {
110 Ok(true)
111 } else {
112 Ok(handler
114 .unlock_with_owner_password(password)
115 .unwrap_or(false))
116 }
117 }
118 None => Ok(true), }
120 }
121
122 pub fn try_empty_password(&mut self) -> ParseResult<bool> {
124 match &mut self.encryption_handler {
125 Some(handler) => Ok(handler.try_empty_password().unwrap_or(false)),
126 None => Ok(true), }
128 }
129
130 pub fn unlock(&mut self, password: &str) -> ParseResult<()> {
161 if !self.is_encrypted() {
163 return Ok(());
164 }
165
166 if self.is_unlocked() {
168 return Ok(());
169 }
170
171 let success = self.unlock_with_password(password)?;
173
174 if success {
175 Ok(())
176 } else {
177 Err(ParseError::WrongPassword)
178 }
179 }
180
181 fn ensure_unlocked(&self) -> ParseResult<()> {
183 if self.is_encrypted() && !self.is_unlocked() {
184 return Err(ParseError::PdfLocked);
185 }
186 Ok(())
187 }
188
189 fn decrypt_object_if_needed(
195 &self,
196 obj: PdfObject,
197 obj_num: u32,
198 gen_num: u16,
199 ) -> ParseResult<PdfObject> {
200 let handler = match &self.encryption_handler {
202 Some(h) if h.is_unlocked() => h,
203 _ => return Ok(obj), };
205
206 let obj_id = ObjectId::new(obj_num, gen_num);
207
208 match obj {
209 PdfObject::String(ref s) => {
210 let decrypted_bytes = handler.decrypt_string(s.as_bytes(), &obj_id)?;
212 Ok(PdfObject::String(PdfString::new(decrypted_bytes)))
213 }
214 PdfObject::Stream(ref stream) => {
215 let should_decrypt = stream
217 .dict
218 .get("StmF")
219 .and_then(|o| o.as_name())
220 .map(|n| n.0.as_str() != "Identity")
221 .unwrap_or(true); if should_decrypt {
224 let decrypted_data = handler.decrypt_stream(&stream.data, &obj_id)?;
225
226 let mut new_stream = stream.clone();
228 new_stream.data = decrypted_data;
229 Ok(PdfObject::Stream(new_stream))
230 } else {
231 Ok(obj) }
233 }
234 PdfObject::Dictionary(ref dict) => {
235 let mut new_dict = PdfDictionary::new();
237 for (key, value) in dict.0.iter() {
238 let decrypted_value =
239 self.decrypt_object_if_needed(value.clone(), obj_num, gen_num)?;
240 new_dict.insert(key.0.clone(), decrypted_value);
241 }
242 Ok(PdfObject::Dictionary(new_dict))
243 }
244 PdfObject::Array(ref arr) => {
245 let mut new_arr = Vec::new();
247 for elem in arr.0.iter() {
248 let decrypted_elem =
249 self.decrypt_object_if_needed(elem.clone(), obj_num, gen_num)?;
250 new_arr.push(decrypted_elem);
251 }
252 Ok(PdfObject::Array(PdfArray(new_arr)))
253 }
254 _ => Ok(obj),
256 }
257 }
258}
259
260impl PdfReader<File> {
261 pub fn open<P: AsRef<Path>>(path: P) -> ParseResult<Self> {
263 #[cfg(feature = "verbose-debug")]
264 {
265 use std::io::Write;
266 if let Ok(mut f) = std::fs::File::create("/tmp/pdf_open_debug.log") {
267 writeln!(f, "Opening file: {:?}", path.as_ref()).ok();
268 }
269 }
270 let file = File::open(path)?;
271 let options = super::ParseOptions::lenient();
273 Self::new_with_options(file, options)
274 }
275
276 pub fn open_strict<P: AsRef<Path>>(path: P) -> ParseResult<Self> {
278 let file = File::open(path)?;
279 let options = super::ParseOptions::strict();
280 Self::new_with_options(file, options)
281 }
282
283 pub fn open_with_options<P: AsRef<Path>>(
285 path: P,
286 options: super::ParseOptions,
287 ) -> ParseResult<Self> {
288 let file = File::open(path)?;
289 Self::new_with_options(file, options)
290 }
291
292 pub fn open_document<P: AsRef<Path>>(
294 path: P,
295 ) -> ParseResult<super::document::PdfDocument<File>> {
296 let reader = Self::open(path)?;
297 Ok(reader.into_document())
298 }
299}
300
301impl<R: Read + Seek> PdfReader<R> {
302 pub fn new(reader: R) -> ParseResult<Self> {
309 let mut options = super::ParseOptions::default();
312 options.lenient_streams = true;
313 Self::new_with_options(reader, options)
314 }
315
316 pub fn new_with_options(reader: R, options: super::ParseOptions) -> ParseResult<Self> {
318 let mut buf_reader = BufReader::new(reader);
319
320 let start_pos = buf_reader.stream_position()?;
322 buf_reader.seek(SeekFrom::End(0))?;
323 let file_size = buf_reader.stream_position()?;
324 buf_reader.seek(SeekFrom::Start(start_pos))?;
325
326 if file_size == 0 {
327 return Err(ParseError::EmptyFile);
328 }
329
330 let header = PdfHeader::parse(&mut buf_reader)?;
332 #[cfg(feature = "verbose-debug")]
333 tracing::debug!("Header parsed: version {}", header.version);
334
335 let xref = XRefTable::parse_with_options(&mut buf_reader, &options)?;
337 #[cfg(feature = "verbose-debug")]
338 tracing::debug!("XRef table parsed with {} entries", xref.len());
339
340 let trailer_dict = xref.trailer().ok_or(ParseError::InvalidTrailer)?.clone();
342
343 let xref_offset = xref.xref_offset();
344 let trailer = PdfTrailer::from_dict(trailer_dict, xref_offset)?;
345
346 trailer.validate()?;
348
349 let encryption_handler = if EncryptionHandler::detect_encryption(trailer.dict()) {
351 if let Ok(Some((encrypt_obj_num, encrypt_gen_num))) = trailer.encrypt() {
352 let mut temp_reader = Self {
354 reader: buf_reader,
355 header: header.clone(),
356 xref: xref.clone(),
357 trailer: trailer.clone(),
358 object_cache: HashMap::new(),
359 object_stream_cache: HashMap::new(),
360 page_tree: None,
361 parse_context: StackSafeContext::new(),
362 options: options.clone(),
363 encryption_handler: None,
364 objects_being_reconstructed: std::sync::Mutex::new(
365 std::collections::HashSet::new(),
366 ),
367 max_reconstruction_depth: 100,
368 };
369
370 let encrypt_obj = temp_reader.get_object(encrypt_obj_num, encrypt_gen_num)?;
372 if let Some(encrypt_dict) = encrypt_obj.as_dict() {
373 let file_id = trailer.id().and_then(|id_obj| {
375 if let PdfObject::Array(ref id_array) = id_obj {
376 if let Some(PdfObject::String(ref id_bytes)) = id_array.get(0) {
377 Some(id_bytes.as_bytes().to_vec())
378 } else {
379 None
380 }
381 } else {
382 None
383 }
384 });
385
386 match EncryptionHandler::new(encrypt_dict, file_id) {
387 Ok(mut handler) => {
388 let _ = handler.try_empty_password();
390 buf_reader = temp_reader.reader;
392 Some(handler)
393 }
394 Err(_) => {
395 let _ = temp_reader.reader;
397 return Err(ParseError::EncryptionNotSupported);
398 }
399 }
400 } else {
401 let _ = temp_reader.reader;
402 return Err(ParseError::EncryptionNotSupported);
403 }
404 } else {
405 return Err(ParseError::EncryptionNotSupported);
406 }
407 } else {
408 None
409 };
410
411 Ok(Self {
412 reader: buf_reader,
413 header,
414 xref,
415 trailer,
416 object_cache: HashMap::new(),
417 object_stream_cache: HashMap::new(),
418 page_tree: None,
419 parse_context: StackSafeContext::new(),
420 options,
421 encryption_handler,
422 objects_being_reconstructed: std::sync::Mutex::new(std::collections::HashSet::new()),
423 max_reconstruction_depth: 100,
424 })
425 }
426
427 pub fn version(&self) -> &super::header::PdfVersion {
429 &self.header.version
430 }
431
432 pub fn catalog(&mut self) -> ParseResult<&PdfDictionary> {
434 let (obj_num, gen_num) = match self.trailer.root() {
436 Ok(root) => {
437 if let Ok(obj) = self.get_object(root.0, root.1) {
440 if let Some(dict) = obj.as_dict() {
441 if let Some(type_obj) = dict.get("Type") {
443 if let Some(type_name) = type_obj.as_name() {
444 if type_name.0 != "Catalog" {
445 tracing::warn!("Trailer /Root points to /Type/{} (not Catalog), scanning for real catalog", type_name.0);
446 if let Ok(catalog_ref) = self.find_catalog_object() {
448 catalog_ref
449 } else {
450 root }
452 } else {
453 root }
455 } else {
456 root }
458 } else {
459 root }
461 } else {
462 root }
464 } else {
465 root }
467 }
468 Err(_) => {
469 #[cfg(debug_assertions)]
471 tracing::warn!("Trailer missing Root entry, attempting recovery");
472
473 if let Some(root) = self.trailer.find_root_fallback() {
475 root
476 } else {
477 if let Ok(catalog_ref) = self.find_catalog_object() {
479 catalog_ref
480 } else {
481 return Err(ParseError::MissingKey("Root".to_string()));
482 }
483 }
484 }
485 };
486
487 let key = (obj_num, gen_num);
489 let needs_reconstruction = {
490 match self.get_object(obj_num, gen_num) {
491 Ok(catalog) => {
492 if catalog.as_dict().is_some() {
494 false
496 } else {
497 true
499 }
500 }
501 Err(_) => {
502 true
504 }
505 }
506 };
507
508 if !needs_reconstruction {
509 let catalog = self.get_object(obj_num, gen_num)?;
511 return catalog.as_dict().ok_or_else(|| ParseError::SyntaxError {
512 position: 0,
513 message: format!("Catalog object {} {} is not a dictionary", obj_num, gen_num),
514 });
515 }
516
517 match self.extract_object_manually(obj_num) {
520 Ok(dict) => {
521 let obj = PdfObject::Dictionary(dict);
523 self.object_cache.insert(key, obj);
524
525 use crate::parser::xref::XRefEntry;
527 let xref_entry = XRefEntry {
528 offset: 0, generation: gen_num,
530 in_use: true,
531 };
532 self.xref.add_entry(obj_num, xref_entry);
533
534 if let Some(PdfObject::Dictionary(ref dict)) = self.object_cache.get(&key) {
536 return Ok(dict);
537 }
538 }
539 Err(_e) => {}
540 }
541
542 Err(ParseError::SyntaxError {
544 position: 0,
545 message: format!(
546 "Catalog object {} could not be parsed or reconstructed as a dictionary",
547 obj_num
548 ),
549 })
550 }
551
552 pub fn info(&mut self) -> ParseResult<Option<&PdfDictionary>> {
554 match self.trailer.info() {
555 Some((obj_num, gen_num)) => {
556 let info = self.get_object(obj_num, gen_num)?;
557 Ok(info.as_dict())
558 }
559 None => Ok(None),
560 }
561 }
562
563 pub fn get_object(&mut self, obj_num: u32, gen_num: u16) -> ParseResult<&PdfObject> {
565 self.ensure_unlocked()?;
567
568 let key = (obj_num, gen_num);
569
570 if self.object_cache.contains_key(&key) {
572 return Ok(&self.object_cache[&key]);
573 }
574
575 {
577 let being_loaded =
578 self.objects_being_reconstructed
579 .lock()
580 .map_err(|_| ParseError::SyntaxError {
581 position: 0,
582 message: "Mutex poisoned during circular reference check".to_string(),
583 })?;
584 if being_loaded.contains(&obj_num) {
585 drop(being_loaded);
586 if self.options.collect_warnings {}
587 self.object_cache.insert(key, PdfObject::Null);
588 return Ok(&self.object_cache[&key]);
589 }
590 }
591
592 {
594 let being_loaded =
595 self.objects_being_reconstructed
596 .lock()
597 .map_err(|_| ParseError::SyntaxError {
598 position: 0,
599 message: "Mutex poisoned during depth limit check".to_string(),
600 })?;
601 let depth = being_loaded.len() as u32;
602 if depth >= self.max_reconstruction_depth {
603 drop(being_loaded);
604 if self.options.collect_warnings {}
605 return Err(ParseError::SyntaxError {
606 position: 0,
607 message: format!(
608 "Maximum object loading depth ({}) exceeded",
609 self.max_reconstruction_depth
610 ),
611 });
612 }
613 }
614
615 self.objects_being_reconstructed
617 .lock()
618 .map_err(|_| ParseError::SyntaxError {
619 position: 0,
620 message: "Mutex poisoned while marking object as being loaded".to_string(),
621 })?
622 .insert(obj_num);
623
624 match self.load_object_from_disk(obj_num, gen_num) {
626 Ok(_) => {
627 self.objects_being_reconstructed
629 .lock()
630 .map_err(|_| ParseError::SyntaxError {
631 position: 0,
632 message: "Mutex poisoned while unmarking object after successful load"
633 .to_string(),
634 })?
635 .remove(&obj_num);
636 Ok(&self.object_cache[&key])
638 }
639 Err(e) => {
640 if let Ok(mut guard) = self.objects_being_reconstructed.lock() {
643 guard.remove(&obj_num);
644 }
645 Err(e)
646 }
647 }
648 }
649
650 fn load_object_from_disk(&mut self, obj_num: u32, gen_num: u16) -> ParseResult<&PdfObject> {
652 let key = (obj_num, gen_num);
653
654 if self.object_cache.contains_key(&key) {
656 return Ok(&self.object_cache[&key]);
657 }
658
659 if let Some(ext_entry) = self.xref.get_extended_entry(obj_num) {
661 if let Some((stream_obj_num, index_in_stream)) = ext_entry.compressed_info {
662 return self.get_compressed_object(
664 obj_num,
665 gen_num,
666 stream_obj_num,
667 index_in_stream,
668 );
669 }
670 } else {
671 }
672
673 let (current_offset, _generation) = {
675 let entry = self.xref.get_entry(obj_num);
676
677 match entry {
678 Some(entry) => {
679 if !entry.in_use {
680 self.object_cache.insert(key, PdfObject::Null);
682 return Ok(&self.object_cache[&key]);
683 }
684
685 if entry.generation != gen_num {
686 if self.options.lenient_syntax {
687 if self.options.collect_warnings {
689 tracing::warn!("Object {} generation mismatch - expected {}, found {}, using available",
690 obj_num, gen_num, entry.generation);
691 }
692 } else {
693 return Err(ParseError::InvalidReference(obj_num, gen_num));
694 }
695 }
696
697 (entry.offset, entry.generation)
698 }
699 None => {
700 if self.is_reconstructible_object(obj_num) {
702 return self.attempt_manual_object_reconstruction(obj_num, gen_num, 0);
703 } else {
704 if self.options.lenient_syntax {
705 if self.options.collect_warnings {
707 tracing::warn!(
708 "Object {} {} R not found in XRef, returning null object",
709 obj_num,
710 gen_num
711 );
712 }
713 self.object_cache.insert(key, PdfObject::Null);
714 return Ok(&self.object_cache[&key]);
715 } else {
716 return Err(ParseError::InvalidReference(obj_num, gen_num));
717 }
718 }
719 }
720 }
721 };
722
723 self.reader.seek(std::io::SeekFrom::Start(current_offset))?;
727
728 let mut lexer =
730 super::lexer::Lexer::new_with_options(&mut self.reader, self.options.clone());
731
732 {
734 let token = lexer.next_token()?;
736 let read_obj_num = match token {
737 super::lexer::Token::Integer(n) => n as u32,
738 _ => {
739 if self.options.lenient_syntax {
741 if self.options.collect_warnings {
743 tracing::debug!(
744 "Warning: Using expected object number {obj_num} instead of parsed token: {:?}",
745 token
746 );
747 }
748 obj_num
749 } else {
750 return Err(ParseError::SyntaxError {
751 position: current_offset as usize,
752 message: "Expected object number".to_string(),
753 });
754 }
755 }
756 };
757
758 if read_obj_num != obj_num && !self.options.lenient_syntax {
759 return Err(ParseError::SyntaxError {
760 position: current_offset as usize,
761 message: format!(
762 "Object number mismatch: expected {obj_num}, found {read_obj_num}"
763 ),
764 });
765 }
766
767 let token = lexer.next_token()?;
769 let _read_gen_num = match token {
770 super::lexer::Token::Integer(n) => n as u16,
771 _ => {
772 if self.options.lenient_syntax {
774 if self.options.collect_warnings {
775 tracing::warn!(
776 "Using generation 0 instead of parsed token for object {obj_num}"
777 );
778 }
779 0
780 } else {
781 return Err(ParseError::SyntaxError {
782 position: current_offset as usize,
783 message: "Expected generation number".to_string(),
784 });
785 }
786 }
787 };
788
789 let token = lexer.next_token()?;
791 match token {
792 super::lexer::Token::Obj => {}
793 _ => {
794 if self.options.lenient_syntax {
795 if self.options.collect_warnings {
797 tracing::warn!("Expected 'obj' keyword for object {obj_num} {gen_num}, continuing anyway");
798 }
799 } else {
800 return Err(ParseError::SyntaxError {
801 position: current_offset as usize,
802 message: "Expected 'obj' keyword".to_string(),
803 });
804 }
805 }
806 }
807 }
808
809 self.parse_context.enter()?;
811
812 let obj = match PdfObject::parse_with_options(&mut lexer, &self.options) {
813 Ok(obj) => {
814 self.parse_context.exit();
815 if obj_num == 102 && self.options.collect_warnings {}
817 obj
818 }
819 Err(e) => {
820 self.parse_context.exit();
821
822 if self.is_reconstructible_object(obj_num)
824 && self.can_attempt_manual_reconstruction(&e)
825 {
826 match self.attempt_manual_object_reconstruction(
827 obj_num,
828 gen_num,
829 current_offset,
830 ) {
831 Ok(reconstructed_obj) => {
832 return Ok(reconstructed_obj);
833 }
834 Err(_reconstruction_error) => {}
835 }
836 }
837
838 return Err(e);
839 }
840 };
841
842 let token = lexer.next_token()?;
844 match token {
845 super::lexer::Token::EndObj => {}
846 _ => {
847 if self.options.lenient_syntax {
848 if self.options.collect_warnings {
850 tracing::warn!("Expected 'endobj' keyword after object {obj_num} {gen_num}, continuing anyway");
851 }
852 } else {
853 return Err(ParseError::SyntaxError {
854 position: current_offset as usize,
855 message: "Expected 'endobj' keyword".to_string(),
856 });
857 }
858 }
859 };
860
861 let decrypted_obj = self.decrypt_object_if_needed(obj, obj_num, gen_num)?;
863
864 self.object_cache.insert(key, decrypted_obj);
866
867 Ok(&self.object_cache[&key])
868 }
869
870 pub fn resolve<'a>(&'a mut self, obj: &'a PdfObject) -> ParseResult<&'a PdfObject> {
872 match obj {
873 PdfObject::Reference(obj_num, gen_num) => self.get_object(*obj_num, *gen_num),
874 _ => Ok(obj),
875 }
876 }
877
878 pub fn resolve_stream_length(&mut self, obj: &PdfObject) -> ParseResult<Option<usize>> {
881 match obj {
882 PdfObject::Integer(len) => {
883 if *len >= 0 {
884 Ok(Some(*len as usize))
885 } else {
886 Ok(None)
888 }
889 }
890 PdfObject::Reference(obj_num, gen_num) => {
891 let resolved = self.get_object(*obj_num, *gen_num)?;
892 match resolved {
893 PdfObject::Integer(len) => {
894 if *len >= 0 {
895 Ok(Some(*len as usize))
896 } else {
897 Ok(None)
898 }
899 }
900 _ => {
901 Ok(None)
903 }
904 }
905 }
906 _ => {
907 Ok(None)
909 }
910 }
911 }
912
913 fn get_compressed_object(
915 &mut self,
916 obj_num: u32,
917 gen_num: u16,
918 stream_obj_num: u32,
919 _index_in_stream: u32,
920 ) -> ParseResult<&PdfObject> {
921 let key = (obj_num, gen_num);
922
923 if !self.object_stream_cache.contains_key(&stream_obj_num) {
925 let stream_obj = self.get_object(stream_obj_num, 0)?;
927
928 if let Some(stream) = stream_obj.as_stream() {
929 let obj_stream = ObjectStream::parse(stream.clone(), &self.options)?;
931 self.object_stream_cache.insert(stream_obj_num, obj_stream);
932 } else {
933 return Err(ParseError::SyntaxError {
934 position: 0,
935 message: format!("Object {stream_obj_num} is not a stream"),
936 });
937 }
938 }
939
940 let obj_stream = &self.object_stream_cache[&stream_obj_num];
942 let obj = obj_stream
943 .get_object(obj_num)
944 .ok_or_else(|| ParseError::SyntaxError {
945 position: 0,
946 message: format!("Object {obj_num} not found in object stream {stream_obj_num}"),
947 })?;
948
949 let decrypted_obj = self.decrypt_object_if_needed(obj.clone(), obj_num, gen_num)?;
951
952 self.object_cache.insert(key, decrypted_obj);
954 Ok(&self.object_cache[&key])
955 }
956
957 pub fn pages(&mut self) -> ParseResult<&PdfDictionary> {
959 let (pages_obj_num, pages_gen_num) = {
961 let catalog = self.catalog()?;
962
963 if let Some(pages_ref) = catalog.get("Pages") {
965 match pages_ref {
966 PdfObject::Reference(obj_num, gen_num) => (*obj_num, *gen_num),
967 _ => {
968 return Err(ParseError::SyntaxError {
969 position: 0,
970 message: "Pages must be a reference".to_string(),
971 })
972 }
973 }
974 } else {
975 #[cfg(debug_assertions)]
977 tracing::warn!("Catalog missing Pages entry, attempting recovery");
978
979 if let Ok(page_refs) = self.find_page_objects() {
981 if !page_refs.is_empty() {
982 return self.create_synthetic_pages_dict(&page_refs);
984 }
985 }
986
987 if self.options.lenient_syntax {
989 if self.options.collect_warnings {
990 tracing::warn!("Missing Pages in catalog, searching for page tree");
991 }
992 let mut found_pages = None;
994 for i in 1..self.xref.len() as u32 {
995 if let Ok(obj) = self.get_object(i, 0) {
996 if let Some(dict) = obj.as_dict() {
997 if let Some(obj_type) = dict.get("Type").and_then(|t| t.as_name()) {
998 if obj_type.0 == "Pages" {
999 found_pages = Some((i, 0));
1000 break;
1001 }
1002 }
1003 }
1004 }
1005 }
1006 if let Some((obj_num, gen_num)) = found_pages {
1007 (obj_num, gen_num)
1008 } else {
1009 return Err(ParseError::MissingKey("Pages".to_string()));
1010 }
1011 } else {
1012 return Err(ParseError::MissingKey("Pages".to_string()));
1013 }
1014 }
1015 };
1016
1017 let needs_double_resolve = {
1020 let pages_obj = self.get_object(pages_obj_num, pages_gen_num)?;
1021 pages_obj.as_reference()
1022 };
1023
1024 let (final_obj_num, final_gen_num) =
1026 if let Some((ref_obj_num, ref_gen_num)) = needs_double_resolve {
1027 (ref_obj_num, ref_gen_num)
1028 } else {
1029 (pages_obj_num, pages_gen_num)
1030 };
1031
1032 let actual_pages_num = {
1034 let is_valid_dict = {
1036 let pages_obj = self.get_object(final_obj_num, final_gen_num)?;
1037 pages_obj.as_dict().is_some()
1038 };
1039
1040 if is_valid_dict {
1041 final_obj_num
1043 } else {
1044 #[cfg(debug_assertions)]
1046 tracing::warn!("Pages reference invalid, searching for valid Pages object");
1047
1048 if self.options.lenient_syntax {
1049 let xref_len = self.xref.len() as u32;
1051 let mut found_pages_num = None;
1052
1053 for i in 1..xref_len {
1054 let is_pages = {
1056 if let Ok(obj) = self.get_object(i, 0) {
1057 if let Some(dict) = obj.as_dict() {
1058 if let Some(obj_type) =
1059 dict.get("Type").and_then(|t| t.as_name())
1060 {
1061 obj_type.0 == "Pages"
1062 } else {
1063 false
1064 }
1065 } else {
1066 false
1067 }
1068 } else {
1069 false
1070 }
1071 };
1072
1073 if is_pages {
1074 found_pages_num = Some(i);
1075 break;
1076 }
1077 }
1078
1079 if let Some(obj_num) = found_pages_num {
1080 #[cfg(debug_assertions)]
1081 tracing::debug!("Found valid Pages object at {} 0 R", obj_num);
1082 obj_num
1083 } else {
1084 return Err(ParseError::SyntaxError {
1086 position: 0,
1087 message: "Pages is not a dictionary and no valid Pages object found"
1088 .to_string(),
1089 });
1090 }
1091 } else {
1092 return Err(ParseError::SyntaxError {
1094 position: 0,
1095 message: "Pages is not a dictionary".to_string(),
1096 });
1097 }
1098 }
1099 };
1100
1101 let pages_obj = self.get_object(actual_pages_num, 0)?;
1103 pages_obj.as_dict().ok_or_else(|| ParseError::SyntaxError {
1104 position: 0,
1105 message: "Pages object is not a dictionary".to_string(),
1106 })
1107 }
1108
1109 pub fn page_count(&mut self) -> ParseResult<u32> {
1111 const MAX_PAGE_COUNT: u32 = 100_000;
1114
1115 match self.pages() {
1117 Ok(pages) => {
1118 if let Some(count_obj) = pages.get("Count") {
1120 if let Some(count) = count_obj.as_integer() {
1121 let count = count as u32;
1122 if count <= MAX_PAGE_COUNT {
1123 return Ok(count);
1124 }
1125 tracing::warn!(
1126 "PDF /Count {} exceeds limit {}, falling back to Kids array length",
1127 count,
1128 MAX_PAGE_COUNT
1129 );
1130 }
1132 }
1133
1134 if let Some(kids_obj) = pages.get("Kids") {
1136 if let Some(kids_array) = kids_obj.as_array() {
1137 return Ok(kids_array.0.len() as u32);
1138 }
1139 }
1140
1141 Ok(0)
1142 }
1143 Err(_) => {
1144 tracing::debug!("Standard page extraction failed, trying direct extraction");
1146 self.page_count_fallback()
1147 }
1148 }
1149 }
1150
1151 fn page_count_fallback(&mut self) -> ParseResult<u32> {
1153 if let Some(count) = self.extract_page_count_from_linearization() {
1155 tracing::debug!("Found page count {} from linearization", count);
1156 return Ok(count);
1157 }
1158
1159 if let Some(count) = self.count_page_objects_directly() {
1161 tracing::debug!("Found {} pages by counting page objects", count);
1162 return Ok(count);
1163 }
1164
1165 Ok(0)
1166 }
1167
1168 fn extract_page_count_from_linearization(&mut self) -> Option<u32> {
1170 match self.get_object(100, 0) {
1172 Ok(obj) => {
1173 tracing::debug!("Found object 100: {:?}", obj);
1174 if let Some(dict) = obj.as_dict() {
1175 tracing::debug!("Object 100 is a dictionary with {} keys", dict.0.len());
1176 if let Some(n_obj) = dict.get("N") {
1178 tracing::debug!("Found /N field: {:?}", n_obj);
1179 if let Some(count) = n_obj.as_integer() {
1180 tracing::debug!("Extracted page count from linearization: {}", count);
1181 return Some(count as u32);
1182 }
1183 } else {
1184 tracing::debug!("No /N field found in object 100");
1185 for (key, value) in &dict.0 {
1186 tracing::debug!(" {:?}: {:?}", key, value);
1187 }
1188 }
1189 } else {
1190 tracing::debug!("Object 100 is not a dictionary: {:?}", obj);
1191 }
1192 }
1193 Err(e) => {
1194 tracing::debug!("Failed to get object 100: {:?}", e);
1195 tracing::debug!("Attempting direct content extraction...");
1196 return self.extract_n_value_from_raw_object_100();
1198 }
1199 }
1200
1201 None
1202 }
1203
1204 fn extract_n_value_from_raw_object_100(&mut self) -> Option<u32> {
1205 if let Some(entry) = self.xref.get_entry(100) {
1207 if self.reader.seek(SeekFrom::Start(entry.offset)).is_err() {
1209 return None;
1210 }
1211
1212 let mut buffer = vec![0u8; 1024];
1214 if let Ok(bytes_read) = self.reader.read(&mut buffer) {
1215 if bytes_read == 0 {
1216 return None;
1217 }
1218
1219 let content = String::from_utf8_lossy(&buffer[..bytes_read]);
1221 tracing::debug!("Raw content around object 100:\n{}", content);
1222
1223 if let Some(n_pos) = content.find("/N ") {
1225 let after_n = &content[n_pos + 3..];
1226 tracing::debug!(
1227 "Content after /N: {}",
1228 &after_n[..std::cmp::min(50, after_n.len())]
1229 );
1230
1231 let mut num_str = String::new();
1233 for ch in after_n.chars() {
1234 if ch.is_ascii_digit() {
1235 num_str.push(ch);
1236 } else if !num_str.is_empty() {
1237 break;
1239 }
1240 }
1242
1243 if !num_str.is_empty() {
1244 if let Ok(page_count) = num_str.parse::<u32>() {
1245 tracing::debug!(
1246 "Extracted page count from raw content: {}",
1247 page_count
1248 );
1249 return Some(page_count);
1250 }
1251 }
1252 }
1253 }
1254 }
1255 None
1256 }
1257
1258 #[allow(dead_code)]
1259 fn find_object_pattern(&mut self, obj_num: u32, gen_num: u16) -> Option<u64> {
1260 let pattern = format!("{} {} obj", obj_num, gen_num);
1261
1262 let original_pos = self.reader.stream_position().unwrap_or(0);
1264
1265 if self.reader.seek(SeekFrom::Start(0)).is_err() {
1267 return None;
1268 }
1269
1270 let mut buffer = vec![0u8; 8192];
1272 let mut file_content = Vec::new();
1273
1274 loop {
1275 match self.reader.read(&mut buffer) {
1276 Ok(0) => break, Ok(bytes_read) => {
1278 file_content.extend_from_slice(&buffer[..bytes_read]);
1279 }
1280 Err(_) => return None,
1281 }
1282 }
1283
1284 let content = String::from_utf8_lossy(&file_content);
1286 if let Some(pattern_pos) = content.find(&pattern) {
1287 let after_pattern = pattern_pos + pattern.len();
1289 let search_area = &content[after_pattern..];
1290
1291 if let Some(dict_start_offset) = search_area.find("<<") {
1292 let dict_start_pos = after_pattern + dict_start_offset;
1293
1294 self.reader.seek(SeekFrom::Start(original_pos)).ok();
1296 return Some(dict_start_pos as u64);
1297 } else {
1298 }
1299 }
1300
1301 self.reader.seek(SeekFrom::Start(original_pos)).ok();
1303 None
1304 }
1305
1306 fn can_attempt_manual_reconstruction(&self, error: &ParseError) -> bool {
1308 match error {
1309 ParseError::SyntaxError { .. } => true,
1311 ParseError::UnexpectedToken { .. } => true,
1312 _ => false,
1314 }
1315 }
1316
1317 fn is_reconstructible_object(&self, obj_num: u32) -> bool {
1319 if obj_num == 102 || obj_num == 113 || obj_num == 114 {
1321 return true;
1322 }
1323
1324 let page_objects = [
1327 1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 30, 34, 37, 39, 42, 44, 46, 49, 52,
1328 54, 56, 58, 60, 62, 64, 67, 69, 71, 73, 75, 77, 79, 81, 83, 85, 87, 89, 91, 93, 104,
1329 ];
1330
1331 let content_objects = [
1334 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 29, 31, 32, 33, 35, 36, 38, 40, 41,
1335 43, 45, 47, 48, 50, 51, 53, 55, 57, 59, 61, 63, 65, 66, 68, 70, 72, 74, 76, 78, 80, 82,
1336 84, 86, 88, 90, 92, 94, 95, 96, 97, 98, 99, 100, 101, 105, 106, 107, 108, 109, 110,
1337 111,
1338 ];
1339
1340 page_objects.contains(&obj_num) || content_objects.contains(&obj_num)
1341 }
1342
1343 fn is_page_object(&self, obj_num: u32) -> bool {
1345 let page_objects = [
1346 1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 30, 34, 37, 39, 42, 44, 46, 49, 52,
1347 54, 56, 58, 60, 62, 64, 67, 69, 71, 73, 75, 77, 79, 81, 83, 85, 87, 89, 91, 93, 104,
1348 ];
1349 page_objects.contains(&obj_num)
1350 }
1351
1352 fn parse_page_dictionary_content(
1354 &self,
1355 dict_content: &str,
1356 result_dict: &mut std::collections::HashMap<
1357 crate::parser::objects::PdfName,
1358 crate::parser::objects::PdfObject,
1359 >,
1360 _obj_num: u32,
1361 ) -> ParseResult<()> {
1362 use crate::parser::objects::{PdfArray, PdfName, PdfObject};
1363 use std::collections::HashMap;
1364
1365 if let Some(mediabox_start) = dict_content.find("/MediaBox") {
1367 let mediabox_area = &dict_content[mediabox_start..];
1368 if let Some(start_bracket) = mediabox_area.find("[") {
1369 if let Some(end_bracket) = mediabox_area.find("]") {
1370 let mediabox_content = &mediabox_area[start_bracket + 1..end_bracket];
1371 let values: Vec<f32> = mediabox_content
1372 .split_whitespace()
1373 .filter_map(|s| s.parse().ok())
1374 .collect();
1375
1376 if values.len() == 4 {
1377 let mediabox = PdfArray(vec![
1378 PdfObject::Integer(values[0] as i64),
1379 PdfObject::Integer(values[1] as i64),
1380 PdfObject::Integer(values[2] as i64),
1381 PdfObject::Integer(values[3] as i64),
1382 ]);
1383 result_dict
1384 .insert(PdfName("MediaBox".to_string()), PdfObject::Array(mediabox));
1385 }
1386 }
1387 }
1388 }
1389
1390 if let Some(contents_match) = dict_content.find("/Contents") {
1392 let contents_area = &dict_content[contents_match..];
1393 let parts: Vec<&str> = contents_area.split_whitespace().collect();
1395 if parts.len() >= 3 {
1396 if let (Ok(obj_ref), Ok(gen_ref)) =
1397 (parts[1].parse::<u32>(), parts[2].parse::<u16>())
1398 {
1399 if parts.len() > 3 && parts[3] == "R" {
1400 result_dict.insert(
1401 PdfName("Contents".to_string()),
1402 PdfObject::Reference(obj_ref, gen_ref),
1403 );
1404 }
1405 }
1406 }
1407 }
1408
1409 if dict_content.contains("/Parent") {
1411 result_dict.insert(
1412 PdfName("Parent".to_string()),
1413 PdfObject::Reference(113, 0), );
1415 }
1416
1417 if dict_content.contains("/Resources") {
1419 if let Ok(parsed_resources) = self.parse_resources_from_content(&dict_content) {
1420 result_dict.insert(PdfName("Resources".to_string()), parsed_resources);
1421 } else {
1422 let resources = HashMap::new();
1424 result_dict.insert(
1425 PdfName("Resources".to_string()),
1426 PdfObject::Dictionary(crate::parser::objects::PdfDictionary(resources)),
1427 );
1428 }
1429 }
1430
1431 Ok(())
1432 }
1433
1434 fn attempt_manual_object_reconstruction(
1436 &mut self,
1437 obj_num: u32,
1438 gen_num: u16,
1439 _current_offset: u64,
1440 ) -> ParseResult<&PdfObject> {
1441 let is_circular = self
1443 .objects_being_reconstructed
1444 .lock()
1445 .map_err(|_| ParseError::SyntaxError {
1446 position: 0,
1447 message: "Mutex poisoned during circular reference check".to_string(),
1448 })?
1449 .contains(&obj_num);
1450
1451 if is_circular {
1452 tracing::debug!(
1453 "Warning: Circular reconstruction detected for object {} {} - attempting manual extraction",
1454 obj_num, gen_num
1455 );
1456
1457 match self.extract_object_or_stream_manually(obj_num) {
1461 Ok(obj) => {
1462 tracing::debug!(
1463 " Successfully extracted object {} {} manually despite circular reference",
1464 obj_num, gen_num
1465 );
1466 self.object_cache.insert((obj_num, gen_num), obj);
1467 return Ok(&self.object_cache[&(obj_num, gen_num)]);
1468 }
1469 Err(e) => {
1470 tracing::debug!(
1471 " Manual extraction failed: {} - breaking cycle with null object",
1472 e
1473 );
1474 self.object_cache
1476 .insert((obj_num, gen_num), PdfObject::Null);
1477 return Ok(&self.object_cache[&(obj_num, gen_num)]);
1478 }
1479 }
1480 }
1481
1482 let current_depth = self
1484 .objects_being_reconstructed
1485 .lock()
1486 .map_err(|_| ParseError::SyntaxError {
1487 position: 0,
1488 message: "Mutex poisoned during depth check".to_string(),
1489 })?
1490 .len() as u32;
1491 if current_depth >= self.max_reconstruction_depth {
1492 return Err(ParseError::SyntaxError {
1493 position: 0,
1494 message: format!(
1495 "Maximum reconstruction depth ({}) exceeded for object {} {}",
1496 self.max_reconstruction_depth, obj_num, gen_num
1497 ),
1498 });
1499 }
1500
1501 self.objects_being_reconstructed
1503 .lock()
1504 .map_err(|_| ParseError::SyntaxError {
1505 position: 0,
1506 message: "Mutex poisoned while marking object as being reconstructed".to_string(),
1507 })?
1508 .insert(obj_num);
1509
1510 let reconstructed_obj = match self.smart_object_reconstruction(obj_num, gen_num) {
1512 Ok(obj) => obj,
1513 Err(_) => {
1514 match self.extract_object_or_stream_manually(obj_num) {
1516 Ok(obj) => obj,
1517 Err(e) => {
1518 if self.options.lenient_syntax {
1520 PdfObject::Null
1521 } else {
1522 if let Ok(mut guard) = self.objects_being_reconstructed.lock() {
1524 guard.remove(&obj_num);
1525 }
1526 return Err(e);
1527 }
1528 }
1529 }
1530 }
1531 };
1532
1533 self.objects_being_reconstructed
1535 .lock()
1536 .map_err(|_| ParseError::SyntaxError {
1537 position: 0,
1538 message: "Mutex poisoned while unmarking reconstructed object".to_string(),
1539 })?
1540 .remove(&obj_num);
1541
1542 self.object_cache
1543 .insert((obj_num, gen_num), reconstructed_obj);
1544
1545 use crate::parser::xref::XRefEntry;
1547 let xref_entry = XRefEntry {
1548 offset: 0, generation: gen_num,
1550 in_use: true,
1551 };
1552 self.xref.add_entry(obj_num, xref_entry);
1553
1554 self.object_cache
1555 .get(&(obj_num, gen_num))
1556 .ok_or_else(|| ParseError::SyntaxError {
1557 position: 0,
1558 message: format!(
1559 "Object {} {} not in cache after reconstruction",
1560 obj_num, gen_num
1561 ),
1562 })
1563 }
1564
1565 fn smart_object_reconstruction(
1567 &mut self,
1568 obj_num: u32,
1569 gen_num: u16,
1570 ) -> ParseResult<PdfObject> {
1571 if let Ok(inferred_obj) = self.infer_object_from_context(obj_num) {
1575 return Ok(inferred_obj);
1576 }
1577
1578 if let Ok(scanned_obj) = self.scan_for_object_patterns(obj_num) {
1580 return Ok(scanned_obj);
1581 }
1582
1583 if let Ok(synthetic_obj) = self.create_synthetic_object(obj_num) {
1585 return Ok(synthetic_obj);
1586 }
1587
1588 Err(ParseError::SyntaxError {
1589 position: 0,
1590 message: format!("Could not reconstruct object {} {}", obj_num, gen_num),
1591 })
1592 }
1593
1594 fn infer_object_from_context(&mut self, obj_num: u32) -> ParseResult<PdfObject> {
1596 for (_key, obj) in self.object_cache.iter() {
1600 if let PdfObject::Dictionary(dict) = obj {
1601 for (key, value) in dict.0.iter() {
1602 if let PdfObject::Reference(ref_num, _) = value {
1603 if *ref_num == obj_num {
1604 match key.as_str() {
1606 "Font" | "F1" | "F2" | "F3" => {
1607 return Ok(self.create_font_object(obj_num));
1608 }
1609 "XObject" | "Image" | "Im1" => {
1610 return Ok(self.create_xobject(obj_num));
1611 }
1612 "Contents" => {
1613 return Ok(self.create_content_stream(obj_num));
1614 }
1615 "Resources" => {
1616 return Ok(self.create_resources_dict(obj_num));
1617 }
1618 _ => continue,
1619 }
1620 }
1621 }
1622 }
1623 }
1624 }
1625
1626 Err(ParseError::SyntaxError {
1627 position: 0,
1628 message: "Cannot infer object type from context".to_string(),
1629 })
1630 }
1631
1632 fn scan_for_object_patterns(&mut self, obj_num: u32) -> ParseResult<PdfObject> {
1634 self.extract_object_or_stream_manually(obj_num)
1637 }
1638
1639 fn create_synthetic_object(&mut self, obj_num: u32) -> ParseResult<PdfObject> {
1641 use super::objects::{PdfDictionary, PdfName, PdfObject};
1642
1643 match obj_num {
1645 1..=10 => {
1646 let mut dict = PdfDictionary::new();
1648 dict.insert(
1649 "Type".to_string(),
1650 PdfObject::Name(PdfName("Null".to_string())),
1651 );
1652 Ok(PdfObject::Dictionary(dict))
1653 }
1654 _ => {
1655 Ok(PdfObject::Null)
1657 }
1658 }
1659 }
1660
1661 fn create_font_object(&self, _obj_num: u32) -> PdfObject {
1662 use super::objects::{PdfDictionary, PdfName, PdfObject};
1663 let mut font_dict = PdfDictionary::new();
1664 font_dict.insert(
1665 "Type".to_string(),
1666 PdfObject::Name(PdfName("Font".to_string())),
1667 );
1668 font_dict.insert(
1669 "Subtype".to_string(),
1670 PdfObject::Name(PdfName("Type1".to_string())),
1671 );
1672 font_dict.insert(
1673 "BaseFont".to_string(),
1674 PdfObject::Name(PdfName("Helvetica".to_string())),
1675 );
1676 PdfObject::Dictionary(font_dict)
1677 }
1678
1679 fn create_xobject(&self, _obj_num: u32) -> PdfObject {
1680 use super::objects::{PdfDictionary, PdfName, PdfObject};
1681 let mut xobj_dict = PdfDictionary::new();
1682 xobj_dict.insert(
1683 "Type".to_string(),
1684 PdfObject::Name(PdfName("XObject".to_string())),
1685 );
1686 xobj_dict.insert(
1687 "Subtype".to_string(),
1688 PdfObject::Name(PdfName("Form".to_string())),
1689 );
1690 PdfObject::Dictionary(xobj_dict)
1691 }
1692
1693 fn create_content_stream(&self, _obj_num: u32) -> PdfObject {
1694 use super::objects::{PdfDictionary, PdfObject, PdfStream};
1695 let mut stream_dict = PdfDictionary::new();
1696 stream_dict.insert("Length".to_string(), PdfObject::Integer(0));
1697
1698 let stream = PdfStream {
1699 dict: stream_dict,
1700 data: Vec::new(),
1701 };
1702 PdfObject::Stream(stream)
1703 }
1704
1705 fn create_resources_dict(&self, _obj_num: u32) -> PdfObject {
1706 use super::objects::{PdfArray, PdfDictionary, PdfObject};
1707 let mut res_dict = PdfDictionary::new();
1708 res_dict.insert("ProcSet".to_string(), PdfObject::Array(PdfArray::new()));
1709 PdfObject::Dictionary(res_dict)
1710 }
1711
1712 fn extract_object_manually(
1713 &mut self,
1714 obj_num: u32,
1715 ) -> ParseResult<crate::parser::objects::PdfDictionary> {
1716 use crate::parser::objects::{PdfArray, PdfDictionary, PdfName, PdfObject};
1717 use std::collections::HashMap;
1718
1719 let original_pos = self.reader.stream_position().unwrap_or(0);
1721
1722 let window = match read_object_window(&mut self.reader, obj_num, MANUAL_DICT_WINDOW) {
1726 Ok(Some((_, w))) => w,
1727 Ok(None) => {
1728 self.reader.seek(SeekFrom::Start(original_pos)).ok();
1729 return Err(ParseError::SyntaxError {
1730 position: 0,
1731 message: format!("Object {obj_num} not found in manual extraction"),
1732 });
1733 }
1734 Err(_) => {
1735 self.reader.seek(SeekFrom::Start(original_pos)).ok();
1736 return Err(ParseError::SyntaxError {
1737 position: 0,
1738 message: "Failed to read file for manual extraction".to_string(),
1739 });
1740 }
1741 };
1742
1743 let content = String::from_utf8_lossy(&window);
1744
1745 let pattern = format!("{} 0 obj", obj_num);
1747 if let Some(start) = content.find(&pattern) {
1748 let search_area = &content[start..];
1749 if let Some(dict_start) = search_area.find("<<") {
1750 let mut bracket_count = 1;
1752 let mut pos = dict_start + 2;
1753 let bytes = search_area.as_bytes();
1754 let mut dict_end = None;
1755
1756 while pos < bytes.len() - 1 && bracket_count > 0 {
1757 if bytes[pos] == b'<' && bytes[pos + 1] == b'<' {
1758 bracket_count += 1;
1759 pos += 2;
1760 } else if bytes[pos] == b'>' && bytes[pos + 1] == b'>' {
1761 bracket_count -= 1;
1762 if bracket_count == 0 {
1763 dict_end = Some(pos);
1764 break;
1765 }
1766 pos += 2;
1767 } else {
1768 pos += 1;
1769 }
1770 }
1771
1772 if let Some(dict_end) = dict_end {
1773 let dict_content = &search_area[dict_start + 2..dict_end];
1774
1775 let mut result_dict = HashMap::new();
1777
1778 if dict_content.contains("/Type/Catalog")
1781 || dict_content.contains("/Type /Catalog")
1782 {
1783 result_dict.insert(
1784 PdfName("Type".to_string()),
1785 PdfObject::Name(PdfName("Catalog".to_string())),
1786 );
1787
1788 if let Some(pages_start) = dict_content.find("/Pages") {
1792 let after_pages = &dict_content[pages_start + 6..]; let trimmed = after_pages.trim_start();
1795 let parts: Vec<&str> = trimmed.split_whitespace().collect();
1797 if parts.len() >= 3 {
1798 if let (Ok(obj), Ok(gen)) =
1802 (parts[0].parse::<u32>(), parts[1].parse::<u16>())
1803 {
1804 if parts[2] == "R" || parts[2].starts_with('R') {
1805 result_dict.insert(
1806 PdfName("Pages".to_string()),
1807 PdfObject::Reference(obj, gen),
1808 );
1809 }
1810 }
1811 }
1812 }
1813
1814 if let Some(ver_start) = dict_content.find("/Version") {
1817 let after_ver = &dict_content[ver_start + 8..];
1818 if let Some(ver_end) = after_ver.find(|c: char| c == '/' || c == '>') {
1819 let version_str = after_ver[..ver_end].trim();
1820 result_dict.insert(
1821 PdfName("Version".to_string()),
1822 PdfObject::Name(PdfName(
1823 version_str.trim_start_matches('/').to_string(),
1824 )),
1825 );
1826 }
1827 }
1828
1829 if let Some(meta_start) = dict_content.find("/Metadata") {
1831 let after_meta = &dict_content[meta_start + 9..];
1832 let parts: Vec<&str> = after_meta.split_whitespace().collect();
1833 if parts.len() >= 3 {
1834 if let (Ok(obj), Ok(gen)) =
1835 (parts[0].parse::<u32>(), parts[1].parse::<u16>())
1836 {
1837 if parts[2] == "R" {
1838 result_dict.insert(
1839 PdfName("Metadata".to_string()),
1840 PdfObject::Reference(obj, gen),
1841 );
1842 }
1843 }
1844 }
1845 }
1846
1847 if let Some(acro_start) = dict_content.find("/AcroForm") {
1849 let after_acro = &dict_content[acro_start + 9..];
1850 if after_acro.trim_start().starts_with("<<") {
1852 } else {
1854 let parts: Vec<&str> = after_acro.split_whitespace().collect();
1855 if parts.len() >= 3 {
1856 if let (Ok(obj), Ok(gen)) =
1857 (parts[0].parse::<u32>(), parts[1].parse::<u16>())
1858 {
1859 if parts[2] == "R" {
1860 result_dict.insert(
1861 PdfName("AcroForm".to_string()),
1862 PdfObject::Reference(obj, gen),
1863 );
1864 }
1865 }
1866 }
1867 }
1868 }
1869 } else if obj_num == 102 {
1870 if dict_content.contains("/Type /Catalog") {
1872 result_dict.insert(
1874 PdfName("Type".to_string()),
1875 PdfObject::Name(PdfName("Catalog".to_string())),
1876 );
1877
1878 if dict_content.contains("/Dests 139 0 R") {
1880 result_dict.insert(
1881 PdfName("Dests".to_string()),
1882 PdfObject::Reference(139, 0),
1883 );
1884 }
1885
1886 if dict_content.contains("/Pages 113 0 R") {
1888 result_dict.insert(
1889 PdfName("Pages".to_string()),
1890 PdfObject::Reference(113, 0),
1891 );
1892 }
1893 } else {
1894 self.reader.seek(SeekFrom::Start(original_pos)).ok();
1897 return Err(ParseError::SyntaxError {
1898 position: 0,
1899 message:
1900 "Object 102 is not a corrupted catalog, cannot reconstruct"
1901 .to_string(),
1902 });
1903 }
1904 } else if obj_num == 113 {
1905 result_dict.insert(
1908 PdfName("Type".to_string()),
1909 PdfObject::Name(PdfName("Pages".to_string())),
1910 );
1911
1912 let page_refs = match self.find_page_objects() {
1914 Ok(refs) => refs,
1915 Err(_e) => {
1916 vec![]
1917 }
1918 };
1919
1920 let page_count = if page_refs.is_empty() {
1922 44
1923 } else {
1924 page_refs.len() as i64
1925 };
1926 result_dict
1927 .insert(PdfName("Count".to_string()), PdfObject::Integer(page_count));
1928
1929 let kids_array: Vec<PdfObject> = page_refs
1931 .into_iter()
1932 .map(|(obj_num, gen_num)| PdfObject::Reference(obj_num, gen_num))
1933 .collect();
1934
1935 result_dict.insert(
1936 PdfName("Kids".to_string()),
1937 PdfObject::Array(PdfArray(kids_array)),
1938 );
1939 } else if obj_num == 114 {
1940 result_dict.insert(
1943 PdfName("Type".to_string()),
1944 PdfObject::Name(PdfName("Pages".to_string())),
1945 );
1946
1947 let page_refs = match self.find_page_objects() {
1949 Ok(refs) => refs,
1950 Err(_e) => {
1951 vec![]
1952 }
1953 };
1954
1955 let page_count = if page_refs.is_empty() {
1957 44
1958 } else {
1959 page_refs.len() as i64
1960 };
1961 result_dict
1962 .insert(PdfName("Count".to_string()), PdfObject::Integer(page_count));
1963
1964 let kids_array: Vec<PdfObject> = page_refs
1966 .into_iter()
1967 .map(|(obj_num, gen_num)| PdfObject::Reference(obj_num, gen_num))
1968 .collect();
1969
1970 result_dict.insert(
1971 PdfName("Kids".to_string()),
1972 PdfObject::Array(PdfArray(kids_array)),
1973 );
1974 } else if self.is_page_object(obj_num) {
1975 result_dict.insert(
1978 PdfName("Type".to_string()),
1979 PdfObject::Name(PdfName("Page".to_string())),
1980 );
1981
1982 self.parse_page_dictionary_content(
1984 &dict_content,
1985 &mut result_dict,
1986 obj_num,
1987 )?;
1988 }
1989
1990 self.reader.seek(SeekFrom::Start(original_pos)).ok();
1992
1993 return Ok(PdfDictionary(result_dict));
1994 }
1995 }
1996 }
1997
1998 self.reader.seek(SeekFrom::Start(original_pos)).ok();
2000
2001 if obj_num == 113 {
2003 let mut result_dict = HashMap::new();
2004 result_dict.insert(
2005 PdfName("Type".to_string()),
2006 PdfObject::Name(PdfName("Pages".to_string())),
2007 );
2008
2009 let page_refs = match self.find_page_objects() {
2011 Ok(refs) => refs,
2012 Err(_e) => {
2013 vec![]
2014 }
2015 };
2016
2017 let page_count = if page_refs.is_empty() {
2019 44
2020 } else {
2021 page_refs.len() as i64
2022 };
2023 result_dict.insert(PdfName("Count".to_string()), PdfObject::Integer(page_count));
2024
2025 let kids_array: Vec<PdfObject> = page_refs
2027 .into_iter()
2028 .map(|(obj_num, gen_num)| PdfObject::Reference(obj_num, gen_num))
2029 .collect();
2030
2031 result_dict.insert(
2032 PdfName("Kids".to_string()),
2033 PdfObject::Array(PdfArray(kids_array)),
2034 );
2035
2036 return Ok(PdfDictionary(result_dict));
2037 } else if obj_num == 114 {
2038 let mut result_dict = HashMap::new();
2039 result_dict.insert(
2040 PdfName("Type".to_string()),
2041 PdfObject::Name(PdfName("Pages".to_string())),
2042 );
2043
2044 let page_refs = match self.find_page_objects() {
2046 Ok(refs) => refs,
2047 Err(_e) => {
2048 vec![]
2049 }
2050 };
2051
2052 let page_count = if page_refs.is_empty() {
2054 44
2055 } else {
2056 page_refs.len() as i64
2057 };
2058 result_dict.insert(PdfName("Count".to_string()), PdfObject::Integer(page_count));
2059
2060 let kids_array: Vec<PdfObject> = page_refs
2062 .into_iter()
2063 .map(|(obj_num, gen_num)| PdfObject::Reference(obj_num, gen_num))
2064 .collect();
2065
2066 result_dict.insert(
2067 PdfName("Kids".to_string()),
2068 PdfObject::Array(PdfArray(kids_array)),
2069 );
2070
2071 return Ok(PdfDictionary(result_dict));
2072 }
2073
2074 Err(ParseError::SyntaxError {
2075 position: 0,
2076 message: "Could not find catalog dictionary in manual extraction".to_string(),
2077 })
2078 }
2079
2080 fn extract_object_or_stream_manually(&mut self, obj_num: u32) -> ParseResult<PdfObject> {
2082 use crate::parser::objects::PdfObject;
2083
2084 let original_pos = self.reader.stream_position().unwrap_or(0);
2086
2087 let (obj_offset, window) =
2092 match read_object_window(&mut self.reader, obj_num, MANUAL_DICT_WINDOW) {
2093 Ok(Some(v)) => v,
2094 Ok(None) => {
2095 self.reader.seek(SeekFrom::Start(original_pos)).ok();
2096 return Err(ParseError::SyntaxError {
2097 position: 0,
2098 message: format!("Could not manually extract object {obj_num}"),
2099 });
2100 }
2101 Err(_) => {
2102 self.reader.seek(SeekFrom::Start(original_pos)).ok();
2103 return Err(ParseError::SyntaxError {
2104 position: 0,
2105 message: "Failed to read file for manual extraction".to_string(),
2106 });
2107 }
2108 };
2109
2110 if let Some(dict_start) = find_byte_pattern(&window, b"<<") {
2113 let mut bracket_count = 1;
2115 let mut pos = dict_start + 2;
2116 let mut dict_end = None;
2117
2118 while pos < window.len().saturating_sub(1) && bracket_count > 0 {
2119 if window[pos] == b'<' && window[pos + 1] == b'<' {
2120 bracket_count += 1;
2121 pos += 2;
2122 } else if window[pos] == b'>' && window[pos + 1] == b'>' {
2123 bracket_count -= 1;
2124 if bracket_count == 0 {
2125 dict_end = Some(pos);
2126 break;
2127 }
2128 pos += 2;
2129 } else {
2130 pos += 1;
2131 }
2132 }
2133
2134 if let Some(dict_end_pos) = dict_end {
2135 let dict_content = String::from_utf8_lossy(&window[dict_start + 2..dict_end_pos]);
2136 let dict_bytes = &window[dict_start..dict_end_pos + 2];
2138
2139 let after_dict = &window[dict_end_pos + 2..];
2141 if is_immediate_stream_start(after_dict) {
2142 let after_dict_abs = obj_offset + (dict_end_pos + 2) as u64;
2144 return self.reconstruct_stream_object_bounded(
2145 obj_num,
2146 dict_bytes,
2147 &dict_content,
2148 after_dict_abs,
2149 after_dict,
2150 );
2151 } else {
2152 self.reader.seek(SeekFrom::Start(original_pos)).ok();
2154 return self
2155 .extract_object_manually(obj_num)
2156 .map(PdfObject::Dictionary);
2157 }
2158 }
2159 }
2160
2161 self.reader.seek(SeekFrom::Start(original_pos)).ok();
2163
2164 Err(ParseError::SyntaxError {
2165 position: 0,
2166 message: format!("Could not manually extract object {obj_num}"),
2167 })
2168 }
2169
2170 fn reconstruct_stream_object_bounded(
2178 &mut self,
2179 obj_num: u32,
2180 dict_bytes: &[u8],
2181 dict_content: &str,
2182 after_dict_abs: u64,
2183 after_dict: &[u8],
2184 ) -> ParseResult<PdfObject> {
2185 use crate::parser::objects::{PdfDictionary, PdfName, PdfObject, PdfStream};
2186 use std::collections::HashMap;
2187
2188 let opts = self.options.clone();
2197 let mut dict: HashMap<PdfName, PdfObject> = {
2198 let mut lexer = super::lexer::Lexer::new_with_options(
2199 std::io::Cursor::new(dict_bytes),
2200 opts.clone(),
2201 );
2202 match PdfObject::parse_with_options(&mut lexer, &opts) {
2203 Ok(PdfObject::Dictionary(d)) => d.0,
2204 Ok(PdfObject::Stream(s)) => s.dict.0,
2207 _ => {
2210 let mut d = HashMap::new();
2211 if dict_content.contains("/Filter /FlateDecode") {
2212 d.insert(
2213 PdfName("Filter".to_string()),
2214 PdfObject::Name(PdfName("FlateDecode".to_string())),
2215 );
2216 }
2217 d
2218 }
2219 }
2220 };
2221
2222 let length = self.parse_stream_length(dict_content)?;
2224
2225 let Some(stream_kw) = find_byte_pattern(after_dict, b"stream") else {
2227 return Err(ParseError::SyntaxError {
2228 position: 0,
2229 message: format!("Could not reconstruct stream for object {obj_num}"),
2230 });
2231 };
2232 let after_kw = stream_kw + 6; let data_rel = match after_dict.get(after_kw) {
2234 Some(b'\r') if after_dict.get(after_kw + 1) == Some(&b'\n') => after_kw + 2,
2235 Some(b'\r') | Some(b'\n') => after_kw + 1,
2236 _ => after_kw,
2237 };
2238 let data_abs = after_dict_abs + data_rel as u64;
2239
2240 let data = match length {
2242 Some(len) => {
2243 let body = read_window_at(&mut self.reader, data_abs, len)?;
2250 dict.insert(
2251 PdfName("Length".to_string()),
2252 PdfObject::Integer(len as i64),
2253 );
2254 body
2255 }
2256 None => {
2257 let body = self.read_stream_until_endstream(data_abs)?;
2260 dict.insert(
2265 PdfName("Length".to_string()),
2266 PdfObject::Integer(body.len() as i64),
2267 );
2268 body
2269 }
2270 };
2271
2272 Ok(PdfObject::Stream(PdfStream {
2273 dict: PdfDictionary(dict),
2274 data,
2275 }))
2276 }
2277
2278 fn parse_stream_length(&mut self, dict_content: &str) -> ParseResult<Option<usize>> {
2282 let Some(idx) = dict_content.find("/Length") else {
2283 return Ok(None);
2284 };
2285 let rest = dict_content[idx + "/Length".len()..].trim_start();
2286 let tokens: Vec<&str> = rest.split_whitespace().collect();
2287
2288 if tokens.len() >= 3 && tokens[2] == "R" {
2290 if let Ok(len_obj) = tokens[0].parse::<u32>() {
2291 return self.resolve_length_object(len_obj);
2292 }
2293 }
2294
2295 if let Some(tok) = tokens.first() {
2297 if let Ok(n) = tok.parse::<i64>() {
2298 if n >= 0 {
2299 return Ok(Some(n as usize));
2300 }
2301 }
2302 }
2303 Ok(None)
2304 }
2305
2306 fn resolve_length_object(&mut self, len_obj: u32) -> ParseResult<Option<usize>> {
2311 let Some((_, window)) = read_object_window(&mut self.reader, len_obj, 4096)? else {
2312 return Ok(None);
2313 };
2314 let text = String::from_utf8_lossy(&window);
2315 if let Some(obj_pos) = text.find("obj") {
2317 let after = text[obj_pos + 3..].trim_start();
2318 let digits: String = after.chars().take_while(|c| c.is_ascii_digit()).collect();
2319 if let Ok(n) = digits.parse::<usize>() {
2320 return Ok(Some(n));
2321 }
2322 }
2323 Ok(None)
2324 }
2325
2326 fn read_stream_until_endstream(&mut self, data_abs: u64) -> ParseResult<Vec<u8>> {
2330 const WIN: usize = 64 * 1024;
2331 const MAX_STREAM: usize = 64 * 1024 * 1024; let mut acc: Vec<u8> = Vec::new();
2334 let mut offset = data_abs;
2335 let mut searched = 0usize;
2336 loop {
2337 let chunk = read_window_at(&mut self.reader, offset, WIN)?;
2338 if chunk.is_empty() {
2339 break; }
2341 acc.extend_from_slice(&chunk);
2342
2343 let from = searched.saturating_sub(b"endstream".len() - 1);
2346 if let Some(rel) = find_byte_pattern(&acc[from..], b"endstream") {
2347 acc.truncate(from + rel);
2348 break;
2349 }
2350 searched = acc.len();
2351 offset += chunk.len() as u64;
2352
2353 if chunk.len() < WIN {
2354 break; }
2356 if acc.len() > MAX_STREAM {
2357 tracing::warn!(
2361 "stream body exceeds {} MiB runaway guard without endstream; truncating",
2362 MAX_STREAM / (1024 * 1024)
2363 );
2364 break;
2365 }
2366 }
2367
2368 if acc.last() == Some(&b'\n') {
2370 acc.pop();
2371 if acc.last() == Some(&b'\r') {
2372 acc.pop();
2373 }
2374 } else if acc.last() == Some(&b'\r') {
2375 acc.pop();
2376 }
2377 Ok(acc)
2378 }
2379
2380 fn parse_resources_from_content(&self, dict_content: &str) -> ParseResult<PdfObject> {
2382 use crate::parser::objects::{PdfDictionary, PdfName, PdfObject};
2383 use std::collections::HashMap;
2384
2385 if let Some(resources_start) = dict_content.find("/Resources") {
2387 if let Some(bracket_start) = dict_content[resources_start..].find("<<") {
2389 let abs_bracket_start = resources_start + bracket_start + 2;
2390
2391 let mut bracket_count = 1;
2393 let mut end_pos = abs_bracket_start;
2394 let chars: Vec<char> = dict_content.chars().collect();
2395
2396 while end_pos < chars.len() && bracket_count > 0 {
2397 if end_pos + 1 < chars.len() {
2398 if chars[end_pos] == '<' && chars[end_pos + 1] == '<' {
2399 bracket_count += 1;
2400 end_pos += 2;
2401 continue;
2402 } else if chars[end_pos] == '>' && chars[end_pos + 1] == '>' {
2403 bracket_count -= 1;
2404 end_pos += 2;
2405 continue;
2406 }
2407 }
2408 end_pos += 1;
2409 }
2410
2411 if bracket_count == 0 {
2412 let resources_content = &dict_content[abs_bracket_start..end_pos - 2];
2413
2414 let mut resources_dict = HashMap::new();
2416
2417 if let Some(font_start) = resources_content.find("/Font") {
2419 if let Some(font_bracket) = resources_content[font_start..].find("<<") {
2420 let abs_font_start = font_start + font_bracket + 2;
2421
2422 let mut font_dict = HashMap::new();
2424
2425 let font_section = &resources_content[abs_font_start..];
2427 let mut pos = 0;
2428 while let Some(f_pos) = font_section[pos..].find("/F") {
2429 let abs_f_pos = pos + f_pos;
2430 if let Some(space_pos) = font_section[abs_f_pos..].find(" ") {
2431 let font_name = &font_section[abs_f_pos..abs_f_pos + space_pos];
2432
2433 let after_name = &font_section[abs_f_pos + space_pos..];
2435 if let Some(r_pos) = after_name.find(" R") {
2436 let ref_part = after_name[..r_pos].trim();
2437 if let Some(parts) = ref_part
2438 .split_whitespace()
2439 .collect::<Vec<&str>>()
2440 .get(0..2)
2441 {
2442 if let (Ok(obj_num), Ok(gen_num)) =
2443 (parts[0].parse::<u32>(), parts[1].parse::<u16>())
2444 {
2445 font_dict.insert(
2446 PdfName(font_name[1..].to_string()), PdfObject::Reference(obj_num, gen_num),
2448 );
2449 }
2450 }
2451 }
2452 }
2453 pos = abs_f_pos + 1;
2454 }
2455
2456 if !font_dict.is_empty() {
2457 resources_dict.insert(
2458 PdfName("Font".to_string()),
2459 PdfObject::Dictionary(PdfDictionary(font_dict)),
2460 );
2461 }
2462 }
2463 }
2464
2465 return Ok(PdfObject::Dictionary(PdfDictionary(resources_dict)));
2466 }
2467 }
2468 }
2469
2470 Err(ParseError::SyntaxError {
2471 position: 0,
2472 message: "Could not parse Resources".to_string(),
2473 })
2474 }
2475
2476 #[allow(dead_code)]
2477 fn extract_catalog_directly(
2478 &mut self,
2479 obj_num: u32,
2480 gen_num: u16,
2481 ) -> ParseResult<&PdfDictionary> {
2482 if let Some(entry) = self.xref.get_entry(obj_num) {
2484 if self.reader.seek(SeekFrom::Start(entry.offset)).is_err() {
2486 return Err(ParseError::SyntaxError {
2487 position: 0,
2488 message: "Failed to seek to catalog object".to_string(),
2489 });
2490 }
2491
2492 let mut buffer = vec![0u8; 2048];
2494 if let Ok(bytes_read) = self.reader.read(&mut buffer) {
2495 let content = String::from_utf8_lossy(&buffer[..bytes_read]);
2496 tracing::debug!("Raw catalog content:\n{}", content);
2497
2498 if let Some(dict_start) = content.find("<<") {
2500 if let Some(dict_end) = content[dict_start..].find(">>") {
2501 let dict_content = &content[dict_start..dict_start + dict_end + 2];
2502 tracing::debug!("Found dictionary content: {}", dict_content);
2503
2504 if let Ok(dict) = self.parse_dictionary_from_string(dict_content) {
2506 let key = (obj_num, gen_num);
2508 self.object_cache.insert(key, PdfObject::Dictionary(dict));
2509
2510 if let Some(PdfObject::Dictionary(ref dict)) =
2512 self.object_cache.get(&key)
2513 {
2514 return Ok(dict);
2515 }
2516 }
2517 }
2518 }
2519 }
2520 }
2521
2522 Err(ParseError::SyntaxError {
2523 position: 0,
2524 message: "Failed to extract catalog directly".to_string(),
2525 })
2526 }
2527
2528 #[allow(dead_code)]
2529 fn parse_dictionary_from_string(&self, dict_str: &str) -> ParseResult<PdfDictionary> {
2530 use crate::parser::lexer::{Lexer, Token};
2531
2532 let mut cursor = std::io::Cursor::new(dict_str.as_bytes());
2534 let mut lexer = Lexer::new_with_options(&mut cursor, self.options.clone());
2535
2536 match lexer.next_token()? {
2538 Token::DictStart => {
2539 let mut dict = std::collections::HashMap::new();
2540
2541 loop {
2542 let token = lexer.next_token()?;
2543 match token {
2544 Token::DictEnd => break,
2545 Token::Name(key) => {
2546 let value = PdfObject::parse_with_options(&mut lexer, &self.options)?;
2548 dict.insert(crate::parser::objects::PdfName(key), value);
2549 }
2550 _ => {
2551 return Err(ParseError::SyntaxError {
2552 position: 0,
2553 message: "Invalid dictionary format".to_string(),
2554 });
2555 }
2556 }
2557 }
2558
2559 Ok(PdfDictionary(dict))
2560 }
2561 _ => Err(ParseError::SyntaxError {
2562 position: 0,
2563 message: "Expected dictionary start".to_string(),
2564 }),
2565 }
2566 }
2567
2568 fn count_page_objects_directly(&mut self) -> Option<u32> {
2570 let mut page_count = 0;
2571
2572 for obj_num in 1..self.xref.len() as u32 {
2574 if let Ok(obj) = self.get_object(obj_num, 0) {
2575 if let Some(dict) = obj.as_dict() {
2576 if let Some(obj_type) = dict.get("Type").and_then(|t| t.as_name()) {
2577 if obj_type.0 == "Page" {
2578 page_count += 1;
2579 }
2580 }
2581 }
2582 }
2583 }
2584
2585 if page_count > 0 {
2586 Some(page_count)
2587 } else {
2588 None
2589 }
2590 }
2591
2592 pub fn metadata(&mut self) -> ParseResult<DocumentMetadata> {
2594 let mut metadata = DocumentMetadata::default();
2595
2596 if let Some(info_dict) = self.info()? {
2597 if let Some(title) = info_dict.get("Title").and_then(|o| o.as_string()) {
2598 metadata.title = title.as_str().ok().map(|s| s.to_string());
2599 }
2600 if let Some(author) = info_dict.get("Author").and_then(|o| o.as_string()) {
2601 metadata.author = author.as_str().ok().map(|s| s.to_string());
2602 }
2603 if let Some(subject) = info_dict.get("Subject").and_then(|o| o.as_string()) {
2604 metadata.subject = subject.as_str().ok().map(|s| s.to_string());
2605 }
2606 if let Some(keywords) = info_dict.get("Keywords").and_then(|o| o.as_string()) {
2607 metadata.keywords = keywords.as_str().ok().map(|s| s.to_string());
2608 }
2609 if let Some(creator) = info_dict.get("Creator").and_then(|o| o.as_string()) {
2610 metadata.creator = creator.as_str().ok().map(|s| s.to_string());
2611 }
2612 if let Some(producer) = info_dict.get("Producer").and_then(|o| o.as_string()) {
2613 metadata.producer = producer.as_str().ok().map(|s| s.to_string());
2614 }
2615 }
2616
2617 metadata.version = self.version().to_string();
2618 metadata.page_count = self.page_count().ok();
2619
2620 Ok(metadata)
2621 }
2622
2623 fn ensure_page_tree(&mut self) -> ParseResult<()> {
2625 if self.page_tree.is_none() {
2626 let page_count = self.page_count()?;
2627 self.page_tree = Some(super::page_tree::PageTree::new(page_count));
2628 }
2629 Ok(())
2630 }
2631
2632 pub fn get_page(&mut self, _index: u32) -> ParseResult<&super::page_tree::ParsedPage> {
2638 self.ensure_page_tree()?;
2639
2640 Err(ParseError::SyntaxError {
2644 position: 0,
2645 message: "get_page not implemented due to borrow checker constraints. Use PdfDocument instead.".to_string(),
2646 })
2647 }
2648
2649 pub fn get_all_pages(&mut self) -> ParseResult<Vec<super::page_tree::ParsedPage>> {
2651 let page_count = self.page_count()?;
2652 let mut pages = Vec::with_capacity(page_count as usize);
2653
2654 for i in 0..page_count {
2655 let page = self.get_page(i)?.clone();
2656 pages.push(page);
2657 }
2658
2659 Ok(pages)
2660 }
2661
2662 pub fn into_document(self) -> super::document::PdfDocument<R> {
2664 super::document::PdfDocument::new(self)
2665 }
2666
2667 pub fn clear_parse_context(&mut self) {
2669 self.parse_context = StackSafeContext::new();
2670 }
2671
2672 pub fn parse_context_mut(&mut self) -> &mut StackSafeContext {
2674 &mut self.parse_context
2675 }
2676
2677 fn find_page_objects(&mut self) -> ParseResult<Vec<(u32, u16)>> {
2684 let original_pos = self.reader.stream_position().unwrap_or(0);
2685 let result = scan_page_object_refs(&mut self.reader);
2686 self.reader.seek(SeekFrom::Start(original_pos)).ok();
2687 Ok(result.unwrap_or_default())
2688 }
2689
2690 fn find_catalog_object(&mut self) -> ParseResult<(u32, u16)> {
2692 let obj_numbers: Vec<u32> = self.xref.entries().keys().copied().collect();
2697
2698 for obj_num in obj_numbers {
2700 if let Ok(obj) = self.get_object(obj_num, 0) {
2702 if let Some(dict) = obj.as_dict() {
2703 if let Some(type_obj) = dict.get("Type") {
2705 if let Some(type_name) = type_obj.as_name() {
2706 if type_name.0 == "Catalog" {
2707 return Ok((obj_num, 0));
2708 }
2709 if type_name.0 == "Sig"
2711 || type_name.0 == "Pages"
2712 || type_name.0 == "Page"
2713 {
2714 continue;
2715 }
2716 }
2717 }
2718 }
2719 }
2720 }
2721
2722 for obj_num in [1, 2, 3, 4, 5] {
2724 if let Ok(obj) = self.get_object(obj_num, 0) {
2725 if let Some(dict) = obj.as_dict() {
2726 if dict.contains_key("Pages") {
2728 return Ok((obj_num, 0));
2729 }
2730 }
2731 }
2732 }
2733
2734 Err(ParseError::MissingKey(
2735 "Could not find Catalog object".to_string(),
2736 ))
2737 }
2738
2739 fn create_synthetic_pages_dict(
2741 &mut self,
2742 page_refs: &[(u32, u16)],
2743 ) -> ParseResult<&PdfDictionary> {
2744 use super::objects::{PdfArray, PdfName};
2745
2746 let mut valid_page_refs = Vec::new();
2748 for (obj_num, gen_num) in page_refs {
2749 if let Ok(page_obj) = self.get_object(*obj_num, *gen_num) {
2750 if let Some(page_dict) = page_obj.as_dict() {
2751 if let Some(obj_type) = page_dict.get("Type").and_then(|t| t.as_name()) {
2753 if obj_type.0 == "Page" {
2754 valid_page_refs.push((*obj_num, *gen_num));
2755 continue;
2756 }
2757 }
2758
2759 if page_dict.contains_key("MediaBox") || page_dict.contains_key("Contents") {
2761 valid_page_refs.push((*obj_num, *gen_num));
2762 }
2763 }
2764 }
2765 }
2766
2767 if valid_page_refs.is_empty() {
2768 return Err(ParseError::SyntaxError {
2769 position: 0,
2770 message: "No valid page objects found for synthetic Pages tree".to_string(),
2771 });
2772 }
2773
2774 if valid_page_refs.len() > 10 {
2776 return self.create_hierarchical_pages_tree(&valid_page_refs);
2777 }
2778
2779 let mut kids = PdfArray::new();
2781 for (obj_num, gen_num) in &valid_page_refs {
2782 kids.push(PdfObject::Reference(*obj_num, *gen_num));
2783 }
2784
2785 let mut pages_dict = PdfDictionary::new();
2787 pages_dict.insert(
2788 "Type".to_string(),
2789 PdfObject::Name(PdfName("Pages".to_string())),
2790 );
2791 pages_dict.insert("Kids".to_string(), PdfObject::Array(kids));
2792 pages_dict.insert(
2793 "Count".to_string(),
2794 PdfObject::Integer(valid_page_refs.len() as i64),
2795 );
2796
2797 let mut media_box = None;
2799 for (obj_num, gen_num) in valid_page_refs.iter().take(3) {
2800 if let Ok(page_obj) = self.get_object(*obj_num, *gen_num) {
2801 if let Some(page_dict) = page_obj.as_dict() {
2802 if let Some(mb) = page_dict.get("MediaBox") {
2803 media_box = Some(mb.clone());
2804 }
2805 }
2806 }
2807 }
2808
2809 if let Some(mb) = media_box {
2811 pages_dict.insert("MediaBox".to_string(), mb);
2812 } else {
2813 let mut mb_array = PdfArray::new();
2814 mb_array.push(PdfObject::Integer(0));
2815 mb_array.push(PdfObject::Integer(0));
2816 mb_array.push(PdfObject::Integer(612));
2817 mb_array.push(PdfObject::Integer(792));
2818 pages_dict.insert("MediaBox".to_string(), PdfObject::Array(mb_array));
2819 }
2820
2821 let synthetic_key = (u32::MAX - 1, 0);
2823 self.object_cache
2824 .insert(synthetic_key, PdfObject::Dictionary(pages_dict));
2825
2826 if let PdfObject::Dictionary(dict) = &self.object_cache[&synthetic_key] {
2828 Ok(dict)
2829 } else {
2830 unreachable!("Just inserted dictionary")
2831 }
2832 }
2833
2834 fn create_hierarchical_pages_tree(
2836 &mut self,
2837 page_refs: &[(u32, u16)],
2838 ) -> ParseResult<&PdfDictionary> {
2839 use super::objects::{PdfArray, PdfName};
2840
2841 const PAGES_PER_NODE: usize = 10; let chunks: Vec<&[(u32, u16)]> = page_refs.chunks(PAGES_PER_NODE).collect();
2845 let mut intermediate_nodes = Vec::new();
2846
2847 for (chunk_idx, chunk) in chunks.iter().enumerate() {
2849 let mut kids = PdfArray::new();
2850 for (obj_num, gen_num) in chunk.iter() {
2851 kids.push(PdfObject::Reference(*obj_num, *gen_num));
2852 }
2853
2854 let mut intermediate_dict = PdfDictionary::new();
2855 intermediate_dict.insert(
2856 "Type".to_string(),
2857 PdfObject::Name(PdfName("Pages".to_string())),
2858 );
2859 intermediate_dict.insert("Kids".to_string(), PdfObject::Array(kids));
2860 intermediate_dict.insert("Count".to_string(), PdfObject::Integer(chunk.len() as i64));
2861
2862 let intermediate_key = (u32::MAX - 2 - chunk_idx as u32, 0);
2864 self.object_cache
2865 .insert(intermediate_key, PdfObject::Dictionary(intermediate_dict));
2866
2867 intermediate_nodes.push(intermediate_key);
2868 }
2869
2870 let mut root_kids = PdfArray::new();
2872 for (obj_num, gen_num) in &intermediate_nodes {
2873 root_kids.push(PdfObject::Reference(*obj_num, *gen_num));
2874 }
2875
2876 let mut root_pages_dict = PdfDictionary::new();
2877 root_pages_dict.insert(
2878 "Type".to_string(),
2879 PdfObject::Name(PdfName("Pages".to_string())),
2880 );
2881 root_pages_dict.insert("Kids".to_string(), PdfObject::Array(root_kids));
2882 root_pages_dict.insert(
2883 "Count".to_string(),
2884 PdfObject::Integer(page_refs.len() as i64),
2885 );
2886
2887 if let Some((obj_num, gen_num)) = page_refs.first() {
2889 if let Ok(page_obj) = self.get_object(*obj_num, *gen_num) {
2890 if let Some(page_dict) = page_obj.as_dict() {
2891 if let Some(mb) = page_dict.get("MediaBox") {
2892 root_pages_dict.insert("MediaBox".to_string(), mb.clone());
2893 }
2894 }
2895 }
2896 }
2897
2898 let root_key = (u32::MAX - 1, 0);
2900 self.object_cache
2901 .insert(root_key, PdfObject::Dictionary(root_pages_dict));
2902
2903 if let PdfObject::Dictionary(dict) = &self.object_cache[&root_key] {
2905 Ok(dict)
2906 } else {
2907 unreachable!("Just inserted dictionary")
2908 }
2909 }
2910
2911 pub fn signatures(&mut self) -> ParseResult<Vec<crate::signatures::SignatureField>> {
2941 crate::signatures::detect_signature_fields(self).map_err(|e| ParseError::SyntaxError {
2942 position: 0,
2943 message: format!("Failed to detect signatures: {}", e),
2944 })
2945 }
2946
2947 pub fn verify_signatures(
2982 &mut self,
2983 ) -> ParseResult<Vec<crate::signatures::FullSignatureValidationResult>> {
2984 self.verify_signatures_with_trust_store(crate::signatures::TrustStore::default())
2985 }
2986
2987 pub fn verify_signatures_with_trust_store(
3019 &mut self,
3020 trust_store: crate::signatures::TrustStore,
3021 ) -> ParseResult<Vec<crate::signatures::FullSignatureValidationResult>> {
3022 use crate::signatures::{
3023 has_incremental_update, parse_pkcs7_signature, validate_certificate, verify_signature,
3024 FullSignatureValidationResult,
3025 };
3026
3027 let original_pos = self.reader.stream_position().unwrap_or(0);
3029 self.reader.seek(SeekFrom::Start(0))?;
3030
3031 let mut pdf_bytes = Vec::new();
3032 self.reader.read_to_end(&mut pdf_bytes)?;
3033
3034 self.reader.seek(SeekFrom::Start(original_pos)).ok();
3036
3037 let signature_fields = self.signatures()?;
3039
3040 let mut results = Vec::new();
3041
3042 for field in signature_fields {
3043 let mut result = FullSignatureValidationResult {
3044 field: field.clone(),
3045 signer_name: None,
3046 signing_time: None,
3047 hash_valid: false,
3048 signature_valid: false,
3049 certificate_result: None,
3050 has_modifications_after_signing: false,
3051 errors: Vec::new(),
3052 warnings: Vec::new(),
3053 };
3054
3055 result.has_modifications_after_signing =
3057 has_incremental_update(&pdf_bytes, &field.byte_range);
3058
3059 let parsed_sig = match parse_pkcs7_signature(&field.contents) {
3061 Ok(sig) => sig,
3062 Err(e) => {
3063 result
3064 .errors
3065 .push(format!("Failed to parse signature: {}", e));
3066 results.push(result);
3067 continue;
3068 }
3069 };
3070
3071 result.signing_time = parsed_sig.signing_time.clone();
3073 result.signer_name = parsed_sig.signer_common_name().ok();
3074
3075 match verify_signature(&pdf_bytes, &parsed_sig, &field.byte_range) {
3077 Ok(verification) => {
3078 result.hash_valid = verification.hash_valid;
3079 result.signature_valid = verification.signature_valid;
3080 if let Some(details) = verification.details {
3081 result.warnings.push(details);
3082 }
3083 }
3084 Err(e) => {
3085 result
3086 .errors
3087 .push(format!("Signature verification failed: {}", e));
3088 }
3089 }
3090
3091 match validate_certificate(&parsed_sig.signer_certificate_der, &trust_store) {
3093 Ok(cert_result) => {
3094 result.certificate_result = Some(cert_result);
3095 }
3096 Err(e) => {
3097 result
3098 .warnings
3099 .push(format!("Certificate validation failed: {}", e));
3100 }
3101 }
3102
3103 results.push(result);
3104 }
3105
3106 Ok(results)
3107 }
3108}
3109
3110#[derive(Debug, Default, Clone)]
3112pub struct DocumentMetadata {
3113 pub title: Option<String>,
3114 pub author: Option<String>,
3115 pub subject: Option<String>,
3116 pub keywords: Option<String>,
3117 pub creator: Option<String>,
3118 pub producer: Option<String>,
3119 pub creation_date: Option<String>,
3120 pub modification_date: Option<String>,
3121 pub version: String,
3122 pub page_count: Option<u32>,
3123}
3124
3125pub struct EOLIter<'s> {
3126 remainder: &'s str,
3127}
3128impl<'s> Iterator for EOLIter<'s> {
3129 type Item = &'s str;
3130
3131 fn next(&mut self) -> Option<Self::Item> {
3132 if self.remainder.is_empty() {
3133 return None;
3134 }
3135
3136 if let Some((i, sep)) = ["\r\n", "\n", "\r"]
3137 .iter()
3138 .filter_map(|&sep| self.remainder.find(sep).map(|i| (i, sep)))
3139 .min_by_key(|(i, _)| *i)
3140 {
3141 let (line, rest) = self.remainder.split_at(i);
3142 self.remainder = &rest[sep.len()..];
3143 Some(line)
3144 } else {
3145 let line = self.remainder;
3146 self.remainder = "";
3147 Some(line)
3148 }
3149 }
3150}
3151pub trait PDFLines: AsRef<str> {
3152 fn pdf_lines(&self) -> EOLIter<'_> {
3153 EOLIter {
3154 remainder: self.as_ref(),
3155 }
3156 }
3157}
3158impl PDFLines for &str {}
3159impl<'a> PDFLines for std::borrow::Cow<'a, str> {}
3160impl PDFLines for String {}
3161
3162#[cfg(test)]
3163mod tests {
3164
3165 use super::*;
3166 use crate::parser::objects::{PdfName, PdfString};
3167 use crate::parser::test_helpers::*;
3168 use crate::parser::ParseOptions;
3169 use std::io::Cursor;
3170
3171 #[test]
3172 fn test_reader_construction() {
3173 let pdf_data = create_minimal_pdf();
3174 let cursor = Cursor::new(pdf_data);
3175 let result = PdfReader::new(cursor);
3176 assert!(result.is_ok());
3177 }
3178
3179 struct CountingReader<R> {
3186 inner: R,
3187 total_read: std::sync::Arc<std::sync::atomic::AtomicUsize>,
3188 max_read: std::sync::Arc<std::sync::atomic::AtomicUsize>,
3189 }
3190 impl<R: Read> Read for CountingReader<R> {
3191 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
3192 self.max_read
3193 .fetch_max(buf.len(), std::sync::atomic::Ordering::SeqCst);
3194 let n = self.inner.read(buf)?;
3195 self.total_read
3196 .fetch_add(n, std::sync::atomic::Ordering::SeqCst);
3197 Ok(n)
3198 }
3199 }
3200 impl<R: Seek> Seek for CountingReader<R> {
3201 fn seek(&mut self, p: SeekFrom) -> std::io::Result<u64> {
3202 self.inner.seek(p)
3203 }
3204 }
3205
3206 #[test]
3207 fn test_extract_stream_manually_bounded_honors_length() {
3208 use crate::parser::objects::PdfObject;
3209 use std::sync::atomic::{AtomicUsize, Ordering};
3210 use std::sync::Arc;
3211
3212 const L: usize = 300 * 1024; const FILLER: usize = 4 * 1024 * 1024; let payload: Vec<u8> = (0..L).map(|i| (i % 251) as u8).collect();
3224 let filler: Vec<u8> = (0..FILLER).map(|i| ((i + 7) % 251) as u8).collect();
3225
3226 let header = b"%PDF-1.4\n";
3227 let obj1 = b"1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n";
3228 let obj2 = b"2 0 obj\n<< /Type /Pages /Kids [] /Count 0 >>\nendobj\n";
3229
3230 let mut data = Vec::new();
3231 data.extend_from_slice(header);
3232 let obj1_start = data.len();
3233 data.extend_from_slice(obj1);
3234 let obj2_start = data.len();
3235 data.extend_from_slice(obj2);
3236
3237 data.extend_from_slice(
3240 b"4 0 obj\n<< /Type /Metadata /Subtype /XML /Length 307200 >>\nstream\n",
3241 );
3242 data.extend_from_slice(&payload);
3243 data.extend_from_slice(b"\nendstream\nendobj\n");
3244
3245 data.extend_from_slice(b"5 0 obj\n<< /Length 4194304 >>\nstream\n");
3247 data.extend_from_slice(&filler);
3248 data.extend_from_slice(b"\nendstream\nendobj\n");
3249
3250 let xref_start = data.len();
3252 let xref = format!(
3253 "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"
3254 );
3255 data.extend_from_slice(xref.as_bytes());
3256 let file_len = data.len();
3257
3258 let counter = Arc::new(AtomicUsize::new(0));
3259 let reader = CountingReader {
3260 inner: Cursor::new(data),
3261 total_read: counter.clone(),
3262 max_read: Arc::new(AtomicUsize::new(0)),
3263 };
3264 let mut pdf = PdfReader::new_with_options(reader, ParseOptions::tolerant())
3265 .expect("minimal PDF must parse");
3266
3267 counter.store(0, Ordering::SeqCst);
3269 let obj = pdf
3270 .extract_object_or_stream_manually(4)
3271 .expect("stream object 4 must be extracted");
3272
3273 let stream = match &obj {
3275 PdfObject::Stream(s) => s,
3276 other => panic!("expected a stream, got {other:?}"),
3277 };
3278 assert_eq!(
3279 stream.data.len(),
3280 L,
3281 "stream body must equal /Length (no truncation)"
3282 );
3283 assert_eq!(stream.data, payload, "stream body content must be intact");
3284
3285 let total_read = counter.load(Ordering::SeqCst);
3289 assert!(
3290 total_read <= L + 2 * MANUAL_DICT_WINDOW,
3291 "manual stream extraction read {total_read} bytes total (file={file_len}); not bounded by object size"
3292 );
3293 }
3294
3295 #[cfg(test)]
3299 fn build_manual_stream_pdf(obj4_dict_body: &str, payload: &[u8]) -> Vec<u8> {
3300 let header = b"%PDF-1.4\n";
3301 let obj1 = b"1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n";
3302 let obj2 = b"2 0 obj\n<< /Type /Pages /Kids [] /Count 0 >>\nendobj\n";
3303
3304 let mut data = Vec::new();
3305 data.extend_from_slice(header);
3306 let obj1_start = data.len();
3307 data.extend_from_slice(obj1);
3308 let obj2_start = data.len();
3309 data.extend_from_slice(obj2);
3310
3311 data.extend_from_slice(format!("4 0 obj\n<<{obj4_dict_body}>>\nstream\n").as_bytes());
3313 data.extend_from_slice(payload);
3314 data.extend_from_slice(b"\nendstream\nendobj\n");
3315
3316 let xref_start = data.len();
3318 let xref = format!(
3319 "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"
3320 );
3321 data.extend_from_slice(xref.as_bytes());
3322 data
3323 }
3324
3325 #[test]
3326 fn test_reconstruct_preserves_non_flate_filter() {
3327 use crate::parser::objects::PdfObject;
3328
3329 let payload = b"\xff\xd8\xff\xe0not-a-real-jpeg\xff\xd9";
3335 let len = payload.len();
3336 let dict_body =
3337 format!(" /Type /XObject /Subtype /Image /Filter /DCTDecode /Length {len} ");
3338 let data = build_manual_stream_pdf(&dict_body, payload);
3339
3340 let mut pdf = PdfReader::new_with_options(Cursor::new(data), ParseOptions::tolerant())
3341 .expect("minimal PDF must parse");
3342 let obj = pdf
3343 .extract_object_or_stream_manually(4)
3344 .expect("stream object 4 must be extracted");
3345 let stream = match &obj {
3346 PdfObject::Stream(s) => s,
3347 other => panic!("expected a stream, got {other:?}"),
3348 };
3349
3350 assert_eq!(
3351 stream
3352 .dict
3353 .get("Filter")
3354 .and_then(|o| o.as_name())
3355 .map(|n| n.0.as_str()),
3356 Some("DCTDecode"),
3357 "non-Flate /Filter must be preserved in reconstructed stream dict"
3358 );
3359 assert_eq!(
3360 stream
3361 .dict
3362 .get("Subtype")
3363 .and_then(|o| o.as_name())
3364 .map(|n| n.0.as_str()),
3365 Some("Image"),
3366 "/Subtype must be preserved, not dropped"
3367 );
3368 assert_eq!(stream.data, payload, "stream body must be intact");
3369 }
3370
3371 #[test]
3372 fn test_reconstruct_preserves_filter_array_and_decodeparms() {
3373 use crate::parser::objects::PdfObject;
3374
3375 let payload = b"compressed-bytes-placeholder";
3380 let len = payload.len();
3381 let dict_body = format!(
3382 " /Filter[/LZWDecode /FlateDecode] /DecodeParms[null<< /Predictor 12 /Columns 4 >>] /Length {len} "
3383 );
3384 let data = build_manual_stream_pdf(&dict_body, payload);
3385
3386 let mut pdf = PdfReader::new_with_options(Cursor::new(data), ParseOptions::tolerant())
3387 .expect("minimal PDF must parse");
3388 let obj = pdf
3389 .extract_object_or_stream_manually(4)
3390 .expect("stream object 4 must be extracted");
3391 let stream = match &obj {
3392 PdfObject::Stream(s) => s,
3393 other => panic!("expected a stream, got {other:?}"),
3394 };
3395
3396 let filter = stream
3397 .dict
3398 .get("Filter")
3399 .and_then(|o| o.as_array())
3400 .expect("/Filter array must be preserved");
3401 let names: Vec<&str> = filter
3402 .0
3403 .iter()
3404 .filter_map(|o| o.as_name())
3405 .map(|n| n.0.as_str())
3406 .collect();
3407 assert_eq!(
3408 names,
3409 vec!["LZWDecode", "FlateDecode"],
3410 "filter array entries must be preserved in order"
3411 );
3412 assert!(
3413 stream.dict.get("DecodeParms").is_some(),
3414 "/DecodeParms must be preserved alongside the filter chain"
3415 );
3416 assert_eq!(stream.data, payload, "stream body must be intact");
3417 }
3418
3419 #[test]
3420 fn test_reconstruct_malformed_dict_falls_back_without_panic() {
3421 use crate::parser::objects::PdfObject;
3422
3423 let payload = b"0123456789";
3429 let len = payload.len();
3430 let dict_body = format!(" /Length {len} /BadKey ");
3432 let data = build_manual_stream_pdf(&dict_body, payload);
3433
3434 let mut pdf = PdfReader::new_with_options(Cursor::new(data), ParseOptions::tolerant())
3435 .expect("minimal PDF must parse");
3436 let obj = pdf
3437 .extract_object_or_stream_manually(4)
3438 .expect("malformed-dict stream must still be reconstructed via fallback");
3439 let stream = match &obj {
3440 PdfObject::Stream(s) => s,
3441 other => panic!("expected a stream, got {other:?}"),
3442 };
3443
3444 assert_eq!(
3445 stream.data, payload,
3446 "fallback path must still read the bounded body intact"
3447 );
3448 assert!(
3450 stream.dict.get("Filter").is_none(),
3451 "fallback dict must not invent a /Filter"
3452 );
3453 }
3454
3455 #[test]
3456 fn test_find_page_objects_bounded() {
3457 use std::sync::atomic::{AtomicUsize, Ordering};
3458 use std::sync::Arc;
3459
3460 const FILLER: usize = 2 * 1024 * 1024;
3466 let filler: Vec<u8> = (0..FILLER).map(|i| (i % 251) as u8).collect();
3467
3468 let header = b"%PDF-1.4\n";
3469 let obj1 = b"1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n";
3470 let obj2 = b"2 0 obj\n<< /Type /Pages /Kids [4 0 R 5 0 R 6 0 R] /Count 3 >>\nendobj\n";
3471
3472 let mut data = Vec::new();
3473 data.extend_from_slice(header);
3474 let obj1_start = data.len();
3475 data.extend_from_slice(obj1);
3476 let obj2_start = data.len();
3477 data.extend_from_slice(obj2);
3478
3479 for n in [4u32, 5, 6] {
3481 data.extend_from_slice(
3482 format!(
3483 "{n} 0 obj\n<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] >>\nendobj\n"
3484 )
3485 .as_bytes(),
3486 );
3487 }
3488
3489 data.extend_from_slice(b"7 0 obj\n<< /Length 2097152 >>\nstream\n");
3491 data.extend_from_slice(&filler);
3492 data.extend_from_slice(b"\nendstream\nendobj\n");
3493
3494 let xref_start = data.len();
3495 let xref = format!(
3496 "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"
3497 );
3498 data.extend_from_slice(xref.as_bytes());
3499 let file_len = data.len();
3500
3501 let max_read = Arc::new(AtomicUsize::new(0));
3502 let reader = CountingReader {
3503 inner: Cursor::new(data),
3504 total_read: Arc::new(AtomicUsize::new(0)),
3505 max_read: max_read.clone(),
3506 };
3507 let mut pdf = PdfReader::new_with_options(reader, ParseOptions::tolerant())
3508 .expect("minimal PDF must parse");
3509
3510 max_read.store(0, Ordering::SeqCst);
3512 let pages = pdf.find_page_objects().expect("page scan must succeed");
3513
3514 assert_eq!(
3516 pages,
3517 vec![(4, 0), (5, 0), (6, 0)],
3518 "must find exactly the three /Type /Page objects"
3519 );
3520
3521 let peak = max_read.load(Ordering::SeqCst);
3523 assert!(
3524 peak <= 128 * 1024,
3525 "page scan requested {peak} bytes in one read (file={file_len}); not bounded"
3526 );
3527 }
3528
3529 #[test]
3530 fn test_signature_verification_reads_full_file_intentionally() {
3531 use std::sync::atomic::{AtomicUsize, Ordering};
3532 use std::sync::Arc;
3533
3534 let data = create_minimal_pdf();
3539 let file_len = data.len();
3540
3541 let total = Arc::new(AtomicUsize::new(0));
3542 let reader = CountingReader {
3543 inner: Cursor::new(data),
3544 total_read: total.clone(),
3545 max_read: Arc::new(AtomicUsize::new(0)),
3546 };
3547 let mut pdf = PdfReader::new_with_options(reader, ParseOptions::tolerant())
3548 .expect("minimal PDF must parse");
3549
3550 total.store(0, Ordering::SeqCst);
3551 let _ = pdf.verify_signatures();
3553 let read = total.load(Ordering::SeqCst);
3554 assert!(
3555 read >= file_len,
3556 "signature verification must read the whole file (read {read}, file {file_len})"
3557 );
3558 }
3559
3560 #[test]
3561 fn test_reader_version() {
3562 let pdf_data = create_minimal_pdf();
3563 let cursor = Cursor::new(pdf_data);
3564 let reader = PdfReader::new(cursor).unwrap();
3565 assert_eq!(reader.version().major, 1);
3566 assert_eq!(reader.version().minor, 4);
3567 }
3568
3569 #[test]
3570 fn test_reader_different_versions() {
3571 let versions = vec![
3572 "1.0", "1.1", "1.2", "1.3", "1.4", "1.5", "1.6", "1.7", "2.0",
3573 ];
3574
3575 for version in versions {
3576 let pdf_data = create_pdf_with_version(version);
3577 let cursor = Cursor::new(pdf_data);
3578 let reader = PdfReader::new(cursor).unwrap();
3579
3580 let parts: Vec<&str> = version.split('.').collect();
3581 assert_eq!(reader.version().major, parts[0].parse::<u8>().unwrap());
3582 assert_eq!(reader.version().minor, parts[1].parse::<u8>().unwrap());
3583 }
3584 }
3585
3586 #[test]
3587 fn test_reader_catalog() {
3588 let pdf_data = create_minimal_pdf();
3589 let cursor = Cursor::new(pdf_data);
3590 let mut reader = PdfReader::new(cursor).unwrap();
3591
3592 let catalog = reader.catalog();
3593 assert!(catalog.is_ok());
3594
3595 let catalog_dict = catalog.unwrap();
3596 assert_eq!(
3597 catalog_dict.get("Type"),
3598 Some(&PdfObject::Name(PdfName("Catalog".to_string())))
3599 );
3600 }
3601
3602 #[test]
3603 fn test_reader_info_none() {
3604 let pdf_data = create_minimal_pdf();
3605 let cursor = Cursor::new(pdf_data);
3606 let mut reader = PdfReader::new(cursor).unwrap();
3607
3608 let info = reader.info().unwrap();
3609 assert!(info.is_none());
3610 }
3611
3612 #[test]
3613 fn test_reader_info_present() {
3614 let pdf_data = create_pdf_with_info();
3615 let cursor = Cursor::new(pdf_data);
3616 let mut reader = PdfReader::new(cursor).unwrap();
3617
3618 let info = reader.info().unwrap();
3619 assert!(info.is_some());
3620
3621 let info_dict = info.unwrap();
3622 assert_eq!(
3623 info_dict.get("Title"),
3624 Some(&PdfObject::String(PdfString(
3625 "Test PDF".to_string().into_bytes()
3626 )))
3627 );
3628 assert_eq!(
3629 info_dict.get("Author"),
3630 Some(&PdfObject::String(PdfString(
3631 "Test Author".to_string().into_bytes()
3632 )))
3633 );
3634 }
3635
3636 #[test]
3637 fn test_reader_get_object() {
3638 let pdf_data = create_minimal_pdf();
3639 let cursor = Cursor::new(pdf_data);
3640 let mut reader = PdfReader::new(cursor).unwrap();
3641
3642 let obj = reader.get_object(1, 0);
3644 assert!(obj.is_ok());
3645
3646 let catalog = obj.unwrap();
3647 assert!(catalog.as_dict().is_some());
3648 }
3649
3650 #[test]
3651 fn test_reader_get_invalid_object() {
3652 let pdf_data = create_minimal_pdf();
3653 let cursor = Cursor::new(pdf_data);
3654 let mut reader = PdfReader::new(cursor).unwrap();
3655
3656 let obj = reader.get_object(999, 0);
3658 assert!(obj.is_err());
3659 }
3660
3661 #[test]
3662 fn test_reader_get_free_object() {
3663 let pdf_data = create_minimal_pdf();
3664 let cursor = Cursor::new(pdf_data);
3665 let mut reader = PdfReader::new(cursor).unwrap();
3666
3667 let obj = reader.get_object(0, 65535);
3669 assert!(obj.is_ok());
3670 assert_eq!(obj.unwrap(), &PdfObject::Null);
3671 }
3672
3673 #[test]
3674 fn test_reader_resolve_reference() {
3675 let pdf_data = create_minimal_pdf();
3676 let cursor = Cursor::new(pdf_data);
3677 let mut reader = PdfReader::new(cursor).unwrap();
3678
3679 let ref_obj = PdfObject::Reference(1, 0);
3681 let resolved = reader.resolve(&ref_obj);
3682
3683 assert!(resolved.is_ok());
3684 assert!(resolved.unwrap().as_dict().is_some());
3685 }
3686
3687 #[test]
3688 fn test_reader_resolve_non_reference() {
3689 let pdf_data = create_minimal_pdf();
3690 let cursor = Cursor::new(pdf_data);
3691 let mut reader = PdfReader::new(cursor).unwrap();
3692
3693 let int_obj = PdfObject::Integer(42);
3695 let resolved = reader.resolve(&int_obj).unwrap();
3696
3697 assert_eq!(resolved, &PdfObject::Integer(42));
3698 }
3699
3700 #[test]
3701 fn test_reader_cache_behavior() {
3702 let pdf_data = create_minimal_pdf();
3703 let cursor = Cursor::new(pdf_data);
3704 let mut reader = PdfReader::new(cursor).unwrap();
3705
3706 let obj1 = reader.get_object(1, 0).unwrap();
3708 assert!(obj1.as_dict().is_some());
3709
3710 let obj2 = reader.get_object(1, 0).unwrap();
3712 assert!(obj2.as_dict().is_some());
3713 }
3714
3715 #[test]
3716 fn test_reader_wrong_generation() {
3717 let pdf_data = create_minimal_pdf();
3718 let cursor = Cursor::new(pdf_data);
3719 let mut reader = PdfReader::new(cursor).unwrap();
3720
3721 let obj = reader.get_object(1, 99);
3723 assert!(obj.is_err());
3724 }
3725
3726 #[test]
3727 fn test_reader_invalid_pdf() {
3728 let invalid_data = b"This is not a PDF file";
3729 let cursor = Cursor::new(invalid_data.to_vec());
3730 let result = PdfReader::new(cursor);
3731
3732 assert!(result.is_err());
3733 }
3734
3735 #[test]
3736 fn test_reader_corrupt_xref() {
3737 let corrupt_pdf = b"%PDF-1.4
37381 0 obj
3739<< /Type /Catalog >>
3740endobj
3741xref
3742corrupted xref table
3743trailer
3744<< /Size 2 /Root 1 0 R >>
3745startxref
374624
3747%%EOF"
3748 .to_vec();
3749
3750 let cursor = Cursor::new(corrupt_pdf);
3751 let result = PdfReader::new(cursor);
3752 assert!(result.is_err());
3755 }
3756
3757 #[test]
3758 fn test_reader_missing_trailer() {
3759 let pdf_no_trailer = b"%PDF-1.4
37601 0 obj
3761<< /Type /Catalog >>
3762endobj
3763xref
37640 2
37650000000000 65535 f
37660000000009 00000 n
3767startxref
376824
3769%%EOF"
3770 .to_vec();
3771
3772 let cursor = Cursor::new(pdf_no_trailer);
3773 let result = PdfReader::new(cursor);
3774 assert!(result.is_err());
3777 }
3778
3779 #[test]
3780 fn test_reader_empty_pdf() {
3781 let cursor = Cursor::new(Vec::new());
3782 let result = PdfReader::new(cursor);
3783 assert!(result.is_err());
3784 }
3785
3786 #[test]
3787 fn test_reader_page_count() {
3788 let pdf_data = create_minimal_pdf();
3789 let cursor = Cursor::new(pdf_data);
3790 let mut reader = PdfReader::new(cursor).unwrap();
3791
3792 let count = reader.page_count();
3793 assert!(count.is_ok());
3794 assert_eq!(count.unwrap(), 0); }
3796
3797 #[test]
3798 fn test_reader_into_document() {
3799 let pdf_data = create_minimal_pdf();
3800 let cursor = Cursor::new(pdf_data);
3801 let reader = PdfReader::new(cursor).unwrap();
3802
3803 let document = reader.into_document();
3804 let page_count = document.page_count();
3806 assert!(page_count.is_ok());
3807 }
3808
3809 #[test]
3810 fn test_reader_pages_dict() {
3811 let pdf_data = create_minimal_pdf();
3812 let cursor = Cursor::new(pdf_data);
3813 let mut reader = PdfReader::new(cursor).unwrap();
3814
3815 let pages = reader.pages();
3816 assert!(pages.is_ok());
3817 let pages_dict = pages.unwrap();
3818 assert_eq!(
3819 pages_dict.get("Type"),
3820 Some(&PdfObject::Name(PdfName("Pages".to_string())))
3821 );
3822 }
3823
3824 #[test]
3825 fn test_reader_pdf_with_binary_data() {
3826 let pdf_data = create_pdf_with_binary_marker();
3827
3828 let cursor = Cursor::new(pdf_data);
3829 let result = PdfReader::new(cursor);
3830 assert!(result.is_ok());
3831 }
3832
3833 #[test]
3834 fn test_reader_metadata() {
3835 let pdf_data = create_pdf_with_info();
3836 let cursor = Cursor::new(pdf_data);
3837 let mut reader = PdfReader::new(cursor).unwrap();
3838
3839 let metadata = reader.metadata().unwrap();
3840 assert_eq!(metadata.title, Some("Test PDF".to_string()));
3841 assert_eq!(metadata.author, Some("Test Author".to_string()));
3842 assert_eq!(metadata.subject, Some("Testing".to_string()));
3843 assert_eq!(metadata.version, "1.4".to_string());
3844 }
3845
3846 #[test]
3847 fn test_reader_metadata_empty() {
3848 let pdf_data = create_minimal_pdf();
3849 let cursor = Cursor::new(pdf_data);
3850 let mut reader = PdfReader::new(cursor).unwrap();
3851
3852 let metadata = reader.metadata().unwrap();
3853 assert!(metadata.title.is_none());
3854 assert!(metadata.author.is_none());
3855 assert_eq!(metadata.version, "1.4".to_string());
3856 assert_eq!(metadata.page_count, Some(0));
3857 }
3858
3859 #[test]
3860 fn test_reader_object_number_mismatch() {
3861 let pdf_data = create_minimal_pdf();
3865 let cursor = Cursor::new(pdf_data);
3866 let mut reader = PdfReader::new(cursor).unwrap();
3867
3868 let result = reader.get_object(1, 99);
3871 assert!(result.is_err());
3872
3873 let result2 = reader.get_object(999, 0);
3875 assert!(result2.is_err());
3876 }
3877
3878 #[test]
3879 fn test_document_metadata_struct() {
3880 let metadata = DocumentMetadata {
3881 title: Some("Title".to_string()),
3882 author: Some("Author".to_string()),
3883 subject: Some("Subject".to_string()),
3884 keywords: Some("Keywords".to_string()),
3885 creator: Some("Creator".to_string()),
3886 producer: Some("Producer".to_string()),
3887 creation_date: Some("D:20240101".to_string()),
3888 modification_date: Some("D:20240102".to_string()),
3889 version: "1.5".to_string(),
3890 page_count: Some(10),
3891 };
3892
3893 assert_eq!(metadata.title, Some("Title".to_string()));
3894 assert_eq!(metadata.page_count, Some(10));
3895 }
3896
3897 #[test]
3898 fn test_document_metadata_default() {
3899 let metadata = DocumentMetadata::default();
3900 assert!(metadata.title.is_none());
3901 assert!(metadata.author.is_none());
3902 assert!(metadata.subject.is_none());
3903 assert!(metadata.keywords.is_none());
3904 assert!(metadata.creator.is_none());
3905 assert!(metadata.producer.is_none());
3906 assert!(metadata.creation_date.is_none());
3907 assert!(metadata.modification_date.is_none());
3908 assert_eq!(metadata.version, "".to_string());
3909 assert!(metadata.page_count.is_none());
3910 }
3911
3912 #[test]
3913 fn test_document_metadata_clone() {
3914 let metadata = DocumentMetadata {
3915 title: Some("Test".to_string()),
3916 version: "1.4".to_string(),
3917 ..Default::default()
3918 };
3919
3920 let cloned = metadata;
3921 assert_eq!(cloned.title, Some("Test".to_string()));
3922 assert_eq!(cloned.version, "1.4".to_string());
3923 }
3924
3925 #[test]
3926 fn test_reader_trailer_validation_error() {
3927 let bad_pdf = b"%PDF-1.4
39291 0 obj
3930<< /Type /Catalog >>
3931endobj
3932xref
39330 2
39340000000000 65535 f
39350000000009 00000 n
3936trailer
3937<< /Size 2 >>
3938startxref
393946
3940%%EOF"
3941 .to_vec();
3942
3943 let cursor = Cursor::new(bad_pdf);
3944 let result = PdfReader::new(cursor);
3945 assert!(result.is_err());
3948 }
3949
3950 #[test]
3951 fn test_reader_with_options() {
3952 let pdf_data = create_minimal_pdf();
3953 let cursor = Cursor::new(pdf_data);
3954 let mut options = ParseOptions::default();
3955 options.lenient_streams = true;
3956 options.max_recovery_bytes = 2000;
3957 options.collect_warnings = true;
3958
3959 let reader = PdfReader::new_with_options(cursor, options);
3960 assert!(reader.is_ok());
3961 }
3962
3963 #[test]
3964 fn test_lenient_stream_parsing() {
3965 let pdf_data = b"%PDF-1.4
39671 0 obj
3968<< /Type /Catalog /Pages 2 0 R >>
3969endobj
39702 0 obj
3971<< /Type /Pages /Kids [3 0 R] /Count 1 >>
3972endobj
39733 0 obj
3974<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] /Contents 4 0 R >>
3975endobj
39764 0 obj
3977<< /Length 10 >>
3978stream
3979This is a longer stream than 10 bytes
3980endstream
3981endobj
3982xref
39830 5
39840000000000 65535 f
39850000000009 00000 n
39860000000058 00000 n
39870000000116 00000 n
39880000000219 00000 n
3989trailer
3990<< /Size 5 /Root 1 0 R >>
3991startxref
3992299
3993%%EOF"
3994 .to_vec();
3995
3996 let cursor = Cursor::new(pdf_data.clone());
3998 let strict_options = ParseOptions::strict();
3999 let strict_reader = PdfReader::new_with_options(cursor, strict_options);
4000 assert!(strict_reader.is_err());
4002
4003 let cursor = Cursor::new(pdf_data);
4005 let mut options = ParseOptions::default();
4006 options.lenient_streams = true;
4007 options.max_recovery_bytes = 1000;
4008 options.collect_warnings = false;
4009 let lenient_reader = PdfReader::new_with_options(cursor, options);
4010 assert!(lenient_reader.is_err());
4011 }
4012
4013 #[test]
4014 fn test_parse_options_default() {
4015 let options = ParseOptions::default();
4016 assert!(!options.lenient_streams);
4017 assert_eq!(options.max_recovery_bytes, 1000);
4018 assert!(!options.collect_warnings);
4019 }
4020
4021 #[test]
4022 fn test_parse_options_clone() {
4023 let mut options = ParseOptions::default();
4024 options.lenient_streams = true;
4025 options.max_recovery_bytes = 2000;
4026 options.collect_warnings = true;
4027 let cloned = options;
4028 assert!(cloned.lenient_streams);
4029 assert_eq!(cloned.max_recovery_bytes, 2000);
4030 assert!(cloned.collect_warnings);
4031 }
4032
4033 #[allow(dead_code)]
4036 fn create_encrypted_pdf_dict() -> PdfDictionary {
4037 let mut dict = PdfDictionary::new();
4038 dict.insert(
4039 "Filter".to_string(),
4040 PdfObject::Name(PdfName("Standard".to_string())),
4041 );
4042 dict.insert("V".to_string(), PdfObject::Integer(1));
4043 dict.insert("R".to_string(), PdfObject::Integer(2));
4044 dict.insert("O".to_string(), PdfObject::String(PdfString(vec![0u8; 32])));
4045 dict.insert("U".to_string(), PdfObject::String(PdfString(vec![0u8; 32])));
4046 dict.insert("P".to_string(), PdfObject::Integer(-4));
4047 dict
4048 }
4049
4050 fn create_pdf_with_encryption() -> Vec<u8> {
4051 b"%PDF-1.4
40531 0 obj
4054<< /Type /Catalog /Pages 2 0 R >>
4055endobj
40562 0 obj
4057<< /Type /Pages /Kids [3 0 R] /Count 1 >>
4058endobj
40593 0 obj
4060<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] >>
4061endobj
40624 0 obj
4063<< /Filter /Standard /V 1 /R 2 /O (32 bytes of owner password hash data) /U (32 bytes of user password hash data) /P -4 >>
4064endobj
4065xref
40660 5
40670000000000 65535 f
40680000000009 00000 n
40690000000058 00000 n
40700000000116 00000 n
40710000000201 00000 n
4072trailer
4073<< /Size 5 /Root 1 0 R /Encrypt 4 0 R /ID [(file id)] >>
4074startxref
4075295
4076%%EOF"
4077 .to_vec()
4078 }
4079
4080 #[test]
4081 fn test_reader_encryption_detection() {
4082 let unencrypted_pdf = create_minimal_pdf();
4084 let cursor = Cursor::new(unencrypted_pdf);
4085 let reader = PdfReader::new(cursor).unwrap();
4086 assert!(!reader.is_encrypted());
4087 assert!(reader.is_unlocked()); let encrypted_pdf = create_pdf_with_encryption();
4091 let cursor = Cursor::new(encrypted_pdf);
4092 let result = PdfReader::new(cursor);
4093 assert!(result.is_err());
4095 }
4096
4097 #[test]
4098 fn test_reader_encryption_methods_unencrypted() {
4099 let pdf_data = create_minimal_pdf();
4100 let cursor = Cursor::new(pdf_data);
4101 let mut reader = PdfReader::new(cursor).unwrap();
4102
4103 assert!(!reader.is_encrypted());
4105 assert!(reader.is_unlocked());
4106 assert!(reader.encryption_handler().is_none());
4107 assert!(reader.encryption_handler_mut().is_none());
4108
4109 assert!(reader.unlock_with_password("any_password").unwrap());
4111 assert!(reader.try_empty_password().unwrap());
4112 }
4113
4114 #[test]
4115 fn test_reader_encryption_handler_access() {
4116 let pdf_data = create_minimal_pdf();
4117 let cursor = Cursor::new(pdf_data);
4118 let mut reader = PdfReader::new(cursor).unwrap();
4119
4120 assert!(reader.encryption_handler().is_none());
4122 assert!(reader.encryption_handler_mut().is_none());
4123
4124 assert!(!reader.is_encrypted());
4126 assert!(reader.is_unlocked());
4127 }
4128
4129 #[test]
4130 fn test_reader_multiple_password_attempts() {
4131 let pdf_data = create_minimal_pdf();
4132 let cursor = Cursor::new(pdf_data);
4133 let mut reader = PdfReader::new(cursor).unwrap();
4134
4135 let passwords = vec!["test1", "test2", "admin", "", "password"];
4137 for password in passwords {
4138 assert!(reader.unlock_with_password(password).unwrap());
4139 }
4140
4141 for _ in 0..5 {
4143 assert!(reader.try_empty_password().unwrap());
4144 }
4145 }
4146
4147 #[test]
4148 fn test_reader_encryption_state_consistency() {
4149 let pdf_data = create_minimal_pdf();
4150 let cursor = Cursor::new(pdf_data);
4151 let mut reader = PdfReader::new(cursor).unwrap();
4152
4153 assert!(!reader.is_encrypted());
4155 assert!(reader.is_unlocked());
4156 assert!(reader.encryption_handler().is_none());
4157
4158 let _ = reader.unlock_with_password("test");
4160 assert!(!reader.is_encrypted());
4161 assert!(reader.is_unlocked());
4162 assert!(reader.encryption_handler().is_none());
4163
4164 let _ = reader.try_empty_password();
4165 assert!(!reader.is_encrypted());
4166 assert!(reader.is_unlocked());
4167 assert!(reader.encryption_handler().is_none());
4168 }
4169
4170 #[test]
4171 fn test_reader_encryption_error_handling() {
4172 let encrypted_pdf = create_pdf_with_encryption();
4174 let cursor = Cursor::new(encrypted_pdf);
4175
4176 let result = PdfReader::new(cursor);
4178 match result {
4179 Err(ParseError::EncryptionNotSupported) => {
4180 }
4182 Err(_) => {
4183 }
4185 Ok(_) => {
4186 panic!("Should not successfully create reader for encrypted PDF without password");
4187 }
4188 }
4189 }
4190
4191 #[test]
4192 fn test_reader_encryption_with_options() {
4193 let pdf_data = create_minimal_pdf();
4194 let cursor = Cursor::new(pdf_data);
4195
4196 let strict_options = ParseOptions::strict();
4198 let strict_reader = PdfReader::new_with_options(cursor, strict_options).unwrap();
4199 assert!(!strict_reader.is_encrypted());
4200 assert!(strict_reader.is_unlocked());
4201
4202 let pdf_data = create_minimal_pdf();
4203 let cursor = Cursor::new(pdf_data);
4204 let lenient_options = ParseOptions::lenient();
4205 let lenient_reader = PdfReader::new_with_options(cursor, lenient_options).unwrap();
4206 assert!(!lenient_reader.is_encrypted());
4207 assert!(lenient_reader.is_unlocked());
4208 }
4209
4210 #[test]
4211 fn test_reader_encryption_integration_edge_cases() {
4212 let pdf_data = create_minimal_pdf();
4213 let cursor = Cursor::new(pdf_data);
4214 let mut reader = PdfReader::new(cursor).unwrap();
4215
4216 assert!(reader.unlock_with_password("").unwrap());
4218 assert!(reader.unlock_with_password(" ").unwrap()); assert!(reader
4220 .unlock_with_password("very_long_password_that_exceeds_normal_length")
4221 .unwrap());
4222 assert!(reader.unlock_with_password("unicode_test_ñáéíóú").unwrap());
4223
4224 assert!(reader.unlock_with_password("pass@#$%^&*()").unwrap());
4226 assert!(reader.unlock_with_password("pass\nwith\nnewlines").unwrap());
4227 assert!(reader.unlock_with_password("pass\twith\ttabs").unwrap());
4228 }
4229
4230 mod rigorous {
4231 use super::*;
4232
4233 #[test]
4238 fn test_reader_invalid_pdf_header() {
4239 let invalid_data = b"This is not a PDF file";
4241 let cursor = Cursor::new(invalid_data.to_vec());
4242 let result = PdfReader::new(cursor);
4243
4244 assert!(result.is_err(), "Should fail on invalid PDF header");
4245 }
4246
4247 #[test]
4248 fn test_reader_truncated_header() {
4249 let truncated = b"%PDF";
4251 let cursor = Cursor::new(truncated.to_vec());
4252 let result = PdfReader::new(cursor);
4253
4254 assert!(result.is_err(), "Should fail on truncated header");
4255 }
4256
4257 #[test]
4258 fn test_reader_empty_file() {
4259 let empty = Vec::new();
4260 let cursor = Cursor::new(empty);
4261 let result = PdfReader::new(cursor);
4262
4263 assert!(result.is_err(), "Should fail on empty file");
4264 }
4265
4266 #[test]
4267 fn test_reader_malformed_version() {
4268 let malformed = b"%PDF-X.Y\n%%\xE2\xE3\xCF\xD3\n";
4270 let cursor = Cursor::new(malformed.to_vec());
4271 let result = PdfReader::new(cursor);
4272
4273 if let Ok(reader) = result {
4275 let _version = reader.version();
4277 }
4278 }
4279
4280 #[test]
4281 fn test_reader_get_nonexistent_object() {
4282 let pdf_data = create_minimal_pdf();
4283 let cursor = Cursor::new(pdf_data);
4284 let mut reader = PdfReader::new(cursor).unwrap();
4285
4286 let result = reader.get_object(999, 0);
4288
4289 assert!(result.is_err(), "Should fail when object doesn't exist");
4290 }
4291
4292 #[test]
4293 fn test_reader_get_object_wrong_generation() {
4294 let pdf_data = create_minimal_pdf();
4295 let cursor = Cursor::new(pdf_data);
4296 let mut reader = PdfReader::new(cursor).unwrap();
4297
4298 let result = reader.get_object(1, 99);
4300
4301 if let Err(e) = result {
4303 let _ = e;
4305 }
4306 }
4307
4308 #[test]
4313 fn test_resolve_direct_object() {
4314 let pdf_data = create_minimal_pdf();
4315 let cursor = Cursor::new(pdf_data);
4316 let mut reader = PdfReader::new(cursor).unwrap();
4317
4318 let direct_obj = PdfObject::Integer(42);
4320
4321 let resolved = reader.resolve(&direct_obj).unwrap();
4322
4323 assert_eq!(resolved, &PdfObject::Integer(42));
4325 }
4326
4327 #[test]
4328 fn test_resolve_reference() {
4329 let pdf_data = create_minimal_pdf();
4330 let cursor = Cursor::new(pdf_data);
4331 let mut reader = PdfReader::new(cursor).unwrap();
4332
4333 let pages_ref = {
4335 let catalog = reader.catalog().unwrap();
4336 if let Some(PdfObject::Reference(obj_num, gen_num)) = catalog.get("Pages") {
4337 PdfObject::Reference(*obj_num, *gen_num)
4338 } else {
4339 panic!("Catalog /Pages must be a Reference");
4340 }
4341 };
4342
4343 let resolved = reader.resolve(&pages_ref).unwrap();
4345
4346 if let PdfObject::Dictionary(dict) = resolved {
4348 assert_eq!(
4349 dict.get("Type"),
4350 Some(&PdfObject::Name(PdfName("Pages".to_string())))
4351 );
4352 } else {
4353 panic!("Expected dictionary, got: {:?}", resolved);
4354 }
4355 }
4356
4357 #[test]
4362 fn test_is_encrypted_on_unencrypted() {
4363 let pdf_data = create_minimal_pdf();
4364 let cursor = Cursor::new(pdf_data);
4365 let reader = PdfReader::new(cursor).unwrap();
4366
4367 assert!(
4368 !reader.is_encrypted(),
4369 "Minimal PDF should not be encrypted"
4370 );
4371 }
4372
4373 #[test]
4374 fn test_is_unlocked_on_unencrypted() {
4375 let pdf_data = create_minimal_pdf();
4376 let cursor = Cursor::new(pdf_data);
4377 let reader = PdfReader::new(cursor).unwrap();
4378
4379 assert!(reader.is_unlocked(), "Unencrypted PDF should be unlocked");
4381 }
4382
4383 #[test]
4384 fn test_try_empty_password_on_unencrypted() {
4385 let pdf_data = create_minimal_pdf();
4386 let cursor = Cursor::new(pdf_data);
4387 let mut reader = PdfReader::new(cursor).unwrap();
4388
4389 let result = reader.try_empty_password();
4391 assert!(result.is_ok());
4392 }
4393
4394 #[test]
4399 fn test_reader_with_strict_options() {
4400 let pdf_data = create_minimal_pdf();
4401 let cursor = Cursor::new(pdf_data);
4402
4403 let options = ParseOptions::strict();
4404 let result = PdfReader::new_with_options(cursor, options);
4405
4406 assert!(result.is_ok(), "Minimal PDF should parse in strict mode");
4407 }
4408
4409 #[test]
4410 fn test_reader_with_lenient_options() {
4411 let pdf_data = create_minimal_pdf();
4412 let cursor = Cursor::new(pdf_data);
4413
4414 let options = ParseOptions::lenient();
4415 let result = PdfReader::new_with_options(cursor, options);
4416
4417 assert!(result.is_ok(), "Minimal PDF should parse in lenient mode");
4418 }
4419
4420 #[test]
4421 fn test_reader_options_accessible() {
4422 let pdf_data = create_minimal_pdf();
4423 let cursor = Cursor::new(pdf_data);
4424
4425 let options = ParseOptions::lenient();
4426 let reader = PdfReader::new_with_options(cursor, options.clone()).unwrap();
4427
4428 let reader_options = reader.options();
4430 assert_eq!(reader_options.strict_mode, options.strict_mode);
4431 }
4432
4433 #[test]
4438 fn test_catalog_has_required_fields() {
4439 let pdf_data = create_minimal_pdf();
4440 let cursor = Cursor::new(pdf_data);
4441 let mut reader = PdfReader::new(cursor).unwrap();
4442
4443 let catalog = reader.catalog().unwrap();
4444
4445 assert_eq!(
4447 catalog.get("Type"),
4448 Some(&PdfObject::Name(PdfName("Catalog".to_string()))),
4449 "Catalog must have /Type /Catalog"
4450 );
4451
4452 assert!(
4454 catalog.contains_key("Pages"),
4455 "Catalog must have /Pages entry"
4456 );
4457 }
4458
4459 #[test]
4460 fn test_info_fields_when_present() {
4461 let pdf_data = create_pdf_with_info();
4462 let cursor = Cursor::new(pdf_data);
4463 let mut reader = PdfReader::new(cursor).unwrap();
4464
4465 let info = reader.info().unwrap();
4466 assert!(info.is_some(), "PDF should have Info dictionary");
4467
4468 let info_dict = info.unwrap();
4469
4470 assert!(info_dict.contains_key("Title"), "Info should have Title");
4472 assert!(info_dict.contains_key("Author"), "Info should have Author");
4473 }
4474
4475 #[test]
4476 fn test_info_none_when_absent() {
4477 let pdf_data = create_minimal_pdf();
4478 let cursor = Cursor::new(pdf_data);
4479 let mut reader = PdfReader::new(cursor).unwrap();
4480
4481 let info = reader.info().unwrap();
4482 assert!(info.is_none(), "Minimal PDF should not have Info");
4483 }
4484
4485 #[test]
4490 fn test_version_exact_values() {
4491 let pdf_data = create_pdf_with_version("1.7");
4492 let cursor = Cursor::new(pdf_data);
4493 let reader = PdfReader::new(cursor).unwrap();
4494
4495 let version = reader.version();
4496 assert_eq!(version.major, 1, "Major version must be exact");
4497 assert_eq!(version.minor, 7, "Minor version must be exact");
4498 }
4499
4500 #[test]
4501 fn test_version_pdf_20() {
4502 let pdf_data = create_pdf_with_version("2.0");
4503 let cursor = Cursor::new(pdf_data);
4504 let reader = PdfReader::new(cursor).unwrap();
4505
4506 let version = reader.version();
4507 assert_eq!(version.major, 2, "PDF 2.0 major version");
4508 assert_eq!(version.minor, 0, "PDF 2.0 minor version");
4509 }
4510
4511 #[test]
4516 fn test_pages_returns_pages_dict() {
4517 let pdf_data = create_minimal_pdf();
4518 let cursor = Cursor::new(pdf_data);
4519 let mut reader = PdfReader::new(cursor).unwrap();
4520
4521 let pages_dict = reader
4522 .pages()
4523 .expect("pages() must return Pages dictionary");
4524
4525 assert_eq!(
4526 pages_dict.get("Type"),
4527 Some(&PdfObject::Name(PdfName("Pages".to_string()))),
4528 "Pages dict must have /Type /Pages"
4529 );
4530 }
4531
4532 #[test]
4533 fn test_page_count_minimal_pdf() {
4534 let pdf_data = create_minimal_pdf();
4535 let cursor = Cursor::new(pdf_data);
4536 let mut reader = PdfReader::new(cursor).unwrap();
4537
4538 let count = reader.page_count().expect("page_count() must succeed");
4539 assert_eq!(count, 0, "Minimal PDF has 0 pages");
4540 }
4541
4542 #[test]
4543 fn test_page_count_with_info_pdf() {
4544 let pdf_data = create_pdf_with_info();
4545 let cursor = Cursor::new(pdf_data);
4546 let mut reader = PdfReader::new(cursor).unwrap();
4547
4548 let count = reader.page_count().expect("page_count() must succeed");
4549 assert_eq!(count, 0, "create_pdf_with_info() has Count 0 in Pages dict");
4550 }
4551
4552 #[test]
4557 fn test_metadata_minimal_pdf() {
4558 let pdf_data = create_minimal_pdf();
4559 let cursor = Cursor::new(pdf_data);
4560 let mut reader = PdfReader::new(cursor).unwrap();
4561
4562 let meta = reader.metadata().expect("metadata() must succeed");
4563
4564 assert!(meta.title.is_none(), "Minimal PDF has no title");
4566 assert!(meta.author.is_none(), "Minimal PDF has no author");
4567 }
4568
4569 #[test]
4570 fn test_metadata_with_info() {
4571 let pdf_data = create_pdf_with_info();
4572 let cursor = Cursor::new(pdf_data);
4573 let mut reader = PdfReader::new(cursor).unwrap();
4574
4575 let meta = reader.metadata().expect("metadata() must succeed");
4576
4577 assert!(meta.title.is_some(), "PDF with Info has title");
4578 assert_eq!(meta.title.unwrap(), "Test PDF", "Title must match");
4579 assert!(meta.author.is_some(), "PDF with Info has author");
4580 assert_eq!(meta.author.unwrap(), "Test Author", "Author must match");
4581 }
4582
4583 #[test]
4588 fn test_resolve_stream_length_direct_integer() {
4589 let pdf_data = create_minimal_pdf();
4590 let cursor = Cursor::new(pdf_data);
4591 let mut reader = PdfReader::new(cursor).unwrap();
4592
4593 let length_obj = PdfObject::Integer(100);
4595
4596 let length = reader
4597 .resolve_stream_length(&length_obj)
4598 .expect("resolve_stream_length must succeed");
4599 assert_eq!(length, Some(100), "Direct integer must be resolved");
4600 }
4601
4602 #[test]
4603 fn test_resolve_stream_length_negative_integer() {
4604 let pdf_data = create_minimal_pdf();
4605 let cursor = Cursor::new(pdf_data);
4606 let mut reader = PdfReader::new(cursor).unwrap();
4607
4608 let length_obj = PdfObject::Integer(-10);
4610
4611 let length = reader
4612 .resolve_stream_length(&length_obj)
4613 .expect("resolve_stream_length must succeed");
4614 assert_eq!(length, None, "Negative integer returns None");
4615 }
4616
4617 #[test]
4618 fn test_resolve_stream_length_non_integer() {
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 name_obj = PdfObject::Name(PdfName("Test".to_string()));
4625
4626 let length = reader
4627 .resolve_stream_length(&name_obj)
4628 .expect("resolve_stream_length must succeed");
4629 assert_eq!(length, None, "Non-integer object returns None");
4630 }
4631
4632 #[test]
4637 fn test_get_all_pages_empty_pdf() {
4638 let pdf_data = create_minimal_pdf();
4639 let cursor = Cursor::new(pdf_data);
4640 let mut reader = PdfReader::new(cursor).unwrap();
4641
4642 let pages = reader
4643 .get_all_pages()
4644 .expect("get_all_pages() must succeed");
4645 assert_eq!(pages.len(), 0, "Minimal PDF has 0 pages");
4646 }
4647
4648 #[test]
4649 fn test_get_all_pages_with_info() {
4650 let pdf_data = create_pdf_with_info();
4651 let cursor = Cursor::new(pdf_data);
4652 let mut reader = PdfReader::new(cursor).unwrap();
4653
4654 let pages = reader
4655 .get_all_pages()
4656 .expect("get_all_pages() must succeed");
4657 assert_eq!(
4658 pages.len(),
4659 0,
4660 "create_pdf_with_info() has 0 pages (Count 0)"
4661 );
4662 }
4663
4664 #[test]
4669 fn test_into_document_consumes_reader() {
4670 let pdf_data = create_minimal_pdf();
4671 let cursor = Cursor::new(pdf_data);
4672 let reader = PdfReader::new(cursor).unwrap();
4673
4674 let document = reader.into_document();
4675
4676 let version = document.version().expect("Document must have version");
4678 assert!(
4679 version.starts_with("1."),
4680 "Document must have PDF 1.x version, got: {}",
4681 version
4682 );
4683
4684 let page_count = document
4686 .page_count()
4687 .expect("Document must allow page_count()");
4688 assert_eq!(
4689 page_count, 0,
4690 "Minimal PDF has 0 pages (Count 0 in test helper)"
4691 );
4692 }
4693
4694 #[test]
4699 fn test_clear_parse_context() {
4700 let pdf_data = create_minimal_pdf();
4701 let cursor = Cursor::new(pdf_data);
4702 let mut reader = PdfReader::new(cursor).unwrap();
4703
4704 reader.clear_parse_context();
4706
4707 let version = reader.version();
4709 assert_eq!(version.major, 1, "Reader must still work after clear");
4710 }
4711
4712 #[test]
4713 fn test_parse_context_mut_accessible() {
4714 let pdf_data = create_minimal_pdf();
4715 let cursor = Cursor::new(pdf_data);
4716 let mut reader = PdfReader::new(cursor).unwrap();
4717
4718 let context = reader.parse_context_mut();
4719
4720 let initial_depth = context.depth;
4722 assert_eq!(initial_depth, 0, "Parse context must start with depth 0");
4723
4724 assert!(
4726 context.max_depth > 0,
4727 "Parse context must have positive max_depth"
4728 );
4729 }
4730
4731 #[test]
4736 fn test_find_byte_pattern_basic() {
4737 let haystack = b"Hello World";
4738 let needle = b"World";
4739 let pos = find_byte_pattern(haystack, needle);
4740 assert_eq!(pos, Some(6), "Must find 'World' at position 6");
4741 }
4742
4743 #[test]
4744 fn test_find_byte_pattern_not_found() {
4745 let haystack = b"Hello World";
4746 let needle = b"Rust";
4747 let pos = find_byte_pattern(haystack, needle);
4748 assert_eq!(pos, None, "Must return None when not found");
4749 }
4750
4751 #[test]
4752 fn test_find_byte_pattern_at_start() {
4753 let haystack = b"Hello World";
4754 let needle = b"Hello";
4755 let pos = find_byte_pattern(haystack, needle);
4756 assert_eq!(pos, Some(0), "Must find at position 0");
4757 }
4758
4759 #[test]
4760 fn test_is_immediate_stream_start_with_stream() {
4761 let data = b"stream\ndata";
4762 assert!(
4763 is_immediate_stream_start(data),
4764 "Must detect 'stream' at start"
4765 );
4766 }
4767
4768 #[test]
4769 fn test_is_immediate_stream_start_with_whitespace() {
4770 let data = b" \n\tstream\ndata";
4771 assert!(
4772 is_immediate_stream_start(data),
4773 "Must detect 'stream' after whitespace"
4774 );
4775 }
4776
4777 #[test]
4778 fn test_is_immediate_stream_start_no_stream() {
4779 let data = b"endobj";
4780 assert!(
4781 !is_immediate_stream_start(data),
4782 "Must return false when 'stream' absent"
4783 );
4784 }
4785 }
4786}