1use rpdfium_core::{Name, PdfSource};
15use rpdfium_parser::{Object, ObjectStore};
16
17use crate::error::{DocError, DocResult};
18
19const MAX_SIGNATURES: usize = 1_000;
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31pub enum DocMdpPermission {
32 NoChanges,
34 FormFillingAndSigning,
36 AnnotationFormFillingAndSigning,
38}
39
40impl DocMdpPermission {
41 pub fn from_p_value(p: i64) -> Option<Self> {
45 match p {
46 1 => Some(Self::NoChanges),
47 2 => Some(Self::FormFillingAndSigning),
48 3 => Some(Self::AnnotationFormFillingAndSigning),
49 _ => None,
50 }
51 }
52
53 pub fn as_u32(self) -> u32 {
55 match self {
56 Self::NoChanges => 1,
57 Self::FormFillingAndSigning => 2,
58 Self::AnnotationFormFillingAndSigning => 3,
59 }
60 }
61}
62
63#[derive(Debug, Clone)]
73pub struct SignatureObject {
74 contents: Option<Vec<u8>>,
75 byte_range: Vec<(u64, u64)>,
76 sub_filter: Option<String>,
77 reason: Option<String>,
78 time: Option<String>,
79 doc_mdp_permission: Option<DocMdpPermission>,
80}
81
82impl SignatureObject {
83 pub fn contents(&self) -> Option<&[u8]> {
89 self.contents.as_deref()
90 }
91
92 #[inline]
96 pub fn signature_obj_get_contents(&self) -> Option<&[u8]> {
97 self.contents()
98 }
99
100 #[deprecated(
104 note = "use `signature_obj_get_contents()` — matches upstream `FPDFSignatureObj_GetContents`"
105 )]
106 #[inline]
107 pub fn get_contents(&self) -> Option<&[u8]> {
108 self.contents()
109 }
110
111 pub fn byte_range(&self) -> &[(u64, u64)] {
119 &self.byte_range
120 }
121
122 #[inline]
126 pub fn signature_obj_get_byte_range(&self) -> &[(u64, u64)] {
127 self.byte_range()
128 }
129
130 #[deprecated(
134 note = "use `signature_obj_get_byte_range()` — matches upstream `FPDFSignatureObj_GetByteRange`"
135 )]
136 #[inline]
137 pub fn get_byte_range(&self) -> &[(u64, u64)] {
138 self.byte_range()
139 }
140
141 pub fn sub_filter(&self) -> Option<&str> {
147 self.sub_filter.as_deref()
148 }
149
150 #[inline]
154 pub fn signature_obj_get_sub_filter(&self) -> Option<&str> {
155 self.sub_filter()
156 }
157
158 #[deprecated(
162 note = "use `signature_obj_get_sub_filter()` — matches upstream `FPDFSignatureObj_GetSubFilter`"
163 )]
164 #[inline]
165 pub fn get_sub_filter(&self) -> Option<&str> {
166 self.sub_filter()
167 }
168
169 pub fn reason(&self) -> Option<&str> {
175 self.reason.as_deref()
176 }
177
178 #[inline]
182 pub fn signature_obj_get_reason(&self) -> Option<&str> {
183 self.reason()
184 }
185
186 #[deprecated(
190 note = "use `signature_obj_get_reason()` — matches upstream `FPDFSignatureObj_GetReason`"
191 )]
192 #[inline]
193 pub fn get_reason(&self) -> Option<&str> {
194 self.reason()
195 }
196
197 pub fn time(&self) -> Option<&str> {
203 self.time.as_deref()
204 }
205
206 #[inline]
210 pub fn signature_obj_get_time(&self) -> Option<&str> {
211 self.time()
212 }
213
214 #[deprecated(
218 note = "use `signature_obj_get_time()` — matches upstream `FPDFSignatureObj_GetTime`"
219 )]
220 #[inline]
221 pub fn get_time(&self) -> Option<&str> {
222 self.time()
223 }
224
225 #[deprecated(
227 note = "use `signature_obj_get_time()` — matches upstream `FPDFSignatureObj_GetTime`"
228 )]
229 #[inline]
230 pub fn signing_time(&self) -> Option<&str> {
231 self.time()
232 }
233
234 pub fn doc_mdp_permission(&self) -> Option<DocMdpPermission> {
240 self.doc_mdp_permission
241 }
242
243 #[inline]
247 pub fn signature_obj_get_doc_mdp_permission(&self) -> Option<DocMdpPermission> {
248 self.doc_mdp_permission()
249 }
250
251 #[deprecated(
255 note = "use `signature_obj_get_doc_mdp_permission()` — matches upstream `FPDFSignatureObj_GetDocMDPPermission`"
256 )]
257 #[inline]
258 pub fn get_doc_mdp_permission(&self) -> Option<DocMdpPermission> {
259 self.doc_mdp_permission()
260 }
261}
262
263pub fn collect_signatures<S: PdfSource>(
275 catalog: &Object,
276 store: &ObjectStore<S>,
277) -> DocResult<Vec<SignatureObject>> {
278 let catalog_dict = store
280 .deep_resolve(catalog)
281 .map_err(|e| DocError::Parser(e.to_string()))?;
282 let catalog_dict = catalog_dict.as_dict().ok_or(DocError::UnexpectedType)?;
283
284 let acroform_obj = match catalog_dict.get(&Name::acro_form()) {
286 Some(obj) => obj,
287 None => return Ok(Vec::new()),
288 };
289 let acroform = store
290 .deep_resolve(acroform_obj)
291 .map_err(|e| DocError::Parser(e.to_string()))?;
292 let acroform_dict = match acroform.as_dict() {
293 Some(d) => d,
294 None => return Ok(Vec::new()),
295 };
296
297 let fields_obj = match acroform_dict.get(&Name::fields()) {
299 Some(obj) => obj,
300 None => return Ok(Vec::new()),
301 };
302 let fields_resolved = store
303 .deep_resolve(fields_obj)
304 .map_err(|e| DocError::Parser(e.to_string()))?;
305 let fields_arr = match fields_resolved.as_array() {
306 Some(arr) => arr,
307 None => return Ok(Vec::new()),
308 };
309
310 let mut signatures = Vec::new();
311
312 for field_ref in fields_arr {
313 if signatures.len() >= MAX_SIGNATURES {
314 break;
315 }
316
317 let field_obj = store
319 .deep_resolve(field_ref)
320 .map_err(|e| DocError::Parser(e.to_string()))?;
321 let field_dict = match field_obj.as_dict() {
322 Some(d) => d,
323 None => continue,
324 };
325
326 let ft = match field_dict.get(&Name::ft()) {
328 Some(obj) => obj,
329 None => continue,
330 };
331 let ft_resolved = store
332 .deep_resolve(ft)
333 .map_err(|e| DocError::Parser(e.to_string()))?;
334 let ft_name = match ft_resolved.as_name() {
335 Some(n) => n,
336 None => continue,
337 };
338 if *ft_name != Name::sig() {
339 continue;
340 }
341
342 let v_obj = match field_dict.get(&Name::v()) {
344 Some(obj) => obj,
345 None => continue,
346 };
347 let sig_dict_obj = store
348 .deep_resolve(v_obj)
349 .map_err(|e| DocError::Parser(e.to_string()))?;
350 let sig_dict = match sig_dict_obj.as_dict() {
351 Some(d) => d,
352 None => continue,
353 };
354
355 let sig = parse_sig_dict(sig_dict, store)?;
356 signatures.push(sig);
357 }
358
359 Ok(signatures)
360}
361
362fn parse_sig_dict<S: PdfSource>(
364 sig_dict: &std::collections::HashMap<Name, Object>,
365 store: &ObjectStore<S>,
366) -> DocResult<SignatureObject> {
367 let contents = sig_dict
369 .get(&Name::contents())
370 .and_then(|obj| store.deep_resolve(obj).ok())
371 .and_then(|obj| obj.as_string().map(|s| s.as_bytes().to_vec()));
372
373 let byte_range = sig_dict
375 .get(&Name::byte_range())
376 .and_then(|obj| store.deep_resolve(obj).ok())
377 .and_then(|obj| {
378 obj.as_array().map(|arr| {
379 arr.iter()
380 .filter_map(|item| {
381 store
382 .deep_resolve(item)
383 .ok()
384 .and_then(|o| o.as_i64())
385 .map(|v| v.max(0) as u64)
386 })
387 .collect::<Vec<u64>>()
388 })
389 })
390 .map(|flat| {
391 flat.chunks_exact(2)
392 .map(|pair| (pair[0], pair[1]))
393 .collect::<Vec<(u64, u64)>>()
394 })
395 .unwrap_or_default();
396
397 let sub_filter = sig_dict
399 .get(&Name::sub_filter())
400 .and_then(|obj| store.deep_resolve(obj).ok())
401 .and_then(|obj| obj.as_name().map(|n| n.as_str().to_string()));
402
403 let reason = sig_dict
405 .get(&Name::reason())
406 .and_then(|obj| store.deep_resolve(obj).ok())
407 .and_then(|obj| obj.as_string().map(|s| s.to_string_lossy()));
408
409 let time = sig_dict
411 .get(&Name::m())
412 .and_then(|obj| store.deep_resolve(obj).ok())
413 .and_then(|obj| obj.as_string().map(|s| s.to_string_lossy()));
414
415 let doc_mdp_permission = parse_doc_mdp(sig_dict, store);
417
418 Ok(SignatureObject {
419 contents,
420 byte_range,
421 sub_filter,
422 reason,
423 time,
424 doc_mdp_permission,
425 })
426}
427
428fn parse_doc_mdp<S: PdfSource>(
430 sig_dict: &std::collections::HashMap<Name, Object>,
431 store: &ObjectStore<S>,
432) -> Option<DocMdpPermission> {
433 let ref_obj = sig_dict.get(&Name::reference())?;
434 let ref_resolved = store.deep_resolve(ref_obj).ok()?;
435 let ref_arr = ref_resolved.as_array()?;
436
437 for entry_ref in ref_arr {
438 let entry_obj = store.deep_resolve(entry_ref).ok()?;
439 let entry_dict = entry_obj.as_dict()?;
440
441 let tm_obj = entry_dict.get(&Name::transform_method())?;
443 let tm_resolved = store.deep_resolve(tm_obj).ok()?;
444 let tm_name = tm_resolved.as_name()?;
445 if *tm_name != Name::doc_mdp() {
446 continue;
447 }
448
449 let tp_obj = entry_dict.get(&Name::transform_params())?;
453 let tp_resolved = store.deep_resolve(tp_obj).ok()?;
454 let tp_dict = tp_resolved.as_dict()?;
455
456 let p_val = tp_dict
457 .get(&Name::p())
458 .and_then(|obj| store.deep_resolve(obj).ok())
459 .and_then(|obj| obj.as_i64())
460 .unwrap_or(2); return DocMdpPermission::from_p_value(p_val);
463 }
464
465 None
466}
467
468#[cfg(test)]
473mod tests {
474 use std::collections::HashMap;
475 use std::sync::Arc;
476
477 use rpdfium_core::{Name, PdfString};
478 use rpdfium_parser::object::Object;
479 use rpdfium_parser::store::ObjectStore;
480
481 use super::*;
482
483 fn build_pdf_with_sig(field_obj_num: u32) -> Vec<u8> {
486 let _ = field_obj_num; let mut pdf = Vec::new();
504 pdf.extend_from_slice(b"%PDF-1.7\n");
505
506 let obj1_offset = pdf.len();
507 pdf.extend_from_slice(
508 b"1 0 obj\n<< /Type /Catalog /Pages 2 0 R /AcroForm 4 0 R >>\nendobj\n",
509 );
510
511 let obj2_offset = pdf.len();
512 pdf.extend_from_slice(b"2 0 obj\n<< /Type /Pages /Kids [] /Count 0 >>\nendobj\n");
513
514 let obj3_offset = pdf.len();
515 pdf.extend_from_slice(b"3 0 obj\n<< /FT /Sig /V 5 0 R /T (Signature1) >>\nendobj\n");
516
517 let obj4_offset = pdf.len();
518 pdf.extend_from_slice(b"4 0 obj\n<< /Fields [3 0 R] >>\nendobj\n");
519
520 let obj5_offset = pdf.len();
521 pdf.extend_from_slice(
523 b"5 0 obj\n\
524 << /Type /Sig\n\
525 /Contents <DEADBEEF>\n\
526 /ByteRange [0 10 20 30]\n\
527 /SubFilter /adbe.pkcs7.detached\n\
528 /Reason (test reason)\n\
529 /M (D:20240101000000)\n\
530 /Reference [6 0 R]\n\
531 >>\nendobj\n",
532 );
533
534 let obj6_offset = pdf.len();
535 pdf.extend_from_slice(
536 b"6 0 obj\n<< /TransformMethod /DocMDP /TransformParams 7 0 R >>\nendobj\n",
537 );
538
539 let obj7_offset = pdf.len();
540 pdf.extend_from_slice(b"7 0 obj\n<< /P 2 >>\nendobj\n");
541
542 let xref_offset = pdf.len();
543 pdf.extend_from_slice(b"xref\n");
544 pdf.extend_from_slice(b"0 8\n");
545 pdf.extend_from_slice(b"0000000000 65535 f \n");
546 pdf.extend_from_slice(format!("{:010} 00000 n \n", obj1_offset).as_bytes());
547 pdf.extend_from_slice(format!("{:010} 00000 n \n", obj2_offset).as_bytes());
548 pdf.extend_from_slice(format!("{:010} 00000 n \n", obj3_offset).as_bytes());
549 pdf.extend_from_slice(format!("{:010} 00000 n \n", obj4_offset).as_bytes());
550 pdf.extend_from_slice(format!("{:010} 00000 n \n", obj5_offset).as_bytes());
551 pdf.extend_from_slice(format!("{:010} 00000 n \n", obj6_offset).as_bytes());
552 pdf.extend_from_slice(format!("{:010} 00000 n \n", obj7_offset).as_bytes());
553 pdf.extend_from_slice(b"trailer\n<< /Size 8 /Root 1 0 R >>\n");
554 pdf.extend_from_slice(format!("startxref\n{}\n%%%%EOF\n", xref_offset).as_bytes());
555 pdf
556 }
557
558 fn build_minimal_pdf(extra: &[u8]) -> Vec<u8> {
559 let mut pdf = Vec::new();
560 pdf.extend_from_slice(b"%PDF-1.7\n");
561 let obj1_offset = pdf.len();
562 pdf.extend_from_slice(b"1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n");
563 let obj2_offset = pdf.len();
564 pdf.extend_from_slice(b"2 0 obj\n<< /Type /Pages /Kids [] /Count 0 >>\nendobj\n");
565 pdf.extend_from_slice(extra);
566 let xref_offset = pdf.len();
567 pdf.extend_from_slice(b"xref\n0 3\n");
568 pdf.extend_from_slice(b"0000000000 65535 f \n");
569 pdf.extend_from_slice(format!("{:010} 00000 n \n", obj1_offset).as_bytes());
570 pdf.extend_from_slice(format!("{:010} 00000 n \n", obj2_offset).as_bytes());
571 pdf.extend_from_slice(b"trailer\n<< /Size 3 /Root 1 0 R >>\n");
572 pdf.extend_from_slice(format!("startxref\n{}\n%%%%EOF\n", xref_offset).as_bytes());
573 pdf
574 }
575
576 fn open_arc(data: Vec<u8>) -> (ObjectStore<Arc<[u8]>>, rpdfium_core::error::ObjectId) {
577 let arc: Arc<[u8]> = Arc::from(data);
578 let store = ObjectStore::open(arc, rpdfium_core::ParsingMode::Lenient).unwrap();
579 let cat_id = store.trailer().root;
580 (store, cat_id)
581 }
582
583 fn make_empty_store() -> ObjectStore<Vec<u8>> {
590 let pdf = build_minimal_pdf(&[]);
591 ObjectStore::open(pdf, rpdfium_core::ParsingMode::Lenient).unwrap()
592 }
593
594 #[test]
595 fn test_no_acroform_returns_empty() {
596 let pdf = build_minimal_pdf(&[]);
598 let (store, cat_id) = open_arc(pdf);
599 let cat = store.resolve(cat_id).unwrap();
600 let sigs = collect_signatures(cat, &store).unwrap();
601 assert!(sigs.is_empty());
602 }
603
604 #[test]
605 fn test_collects_one_signature() {
606 let pdf = build_pdf_with_sig(3);
607 let (store, cat_id) = open_arc(pdf);
608 let cat = store.resolve(cat_id).unwrap();
609 let sigs = collect_signatures(cat, &store).unwrap();
610 assert_eq!(sigs.len(), 1);
611 let sig = &sigs[0];
612 assert_eq!(sig.contents(), Some(&[0xDE, 0xAD, 0xBE, 0xEF][..]));
614 assert_eq!(sig.byte_range(), &[(0, 10), (20, 30)]);
616 assert_eq!(sig.sub_filter(), Some("adbe.pkcs7.detached"));
618 assert_eq!(sig.reason(), Some("test reason"));
620 assert_eq!(sig.time(), Some("D:20240101000000"));
622 assert_eq!(
624 sig.doc_mdp_permission(),
625 Some(DocMdpPermission::FormFillingAndSigning)
626 );
627 }
628
629 #[test]
630 fn test_empty_byte_range() {
631 let mut sig_dict: HashMap<Name, Object> = HashMap::new();
633 sig_dict.insert(
634 Name::contents(),
635 Object::String(PdfString::from_bytes(vec![0xAA])),
636 );
637 let store = make_empty_store();
638 let sig = parse_sig_dict(&sig_dict, &store).unwrap();
639 assert_eq!(sig.byte_range(), &[]);
640 }
641
642 #[test]
643 fn test_odd_byte_range_truncates() {
644 let mut sig_dict: HashMap<Name, Object> = HashMap::new();
646 sig_dict.insert(
647 Name::byte_range(),
648 Object::Array(vec![
649 Object::Integer(0),
650 Object::Integer(10),
651 Object::Integer(20), ]),
653 );
654 let store = make_empty_store();
655 let sig = parse_sig_dict(&sig_dict, &store).unwrap();
656 assert_eq!(sig.byte_range(), &[(0, 10)]);
658 }
659
660 #[test]
661 fn test_doc_mdp_none() {
662 let sig_dict: HashMap<Name, Object> = HashMap::new();
664 let store = make_empty_store();
665 let sig = parse_sig_dict(&sig_dict, &store).unwrap();
666 assert_eq!(sig.doc_mdp_permission(), None);
667 }
668
669 #[test]
670 fn test_doc_mdp_level_2() {
671 assert_eq!(
672 DocMdpPermission::from_p_value(2),
673 Some(DocMdpPermission::FormFillingAndSigning)
674 );
675 }
676
677 #[test]
678 fn test_doc_mdp_level_3() {
679 assert_eq!(
680 DocMdpPermission::from_p_value(3),
681 Some(DocMdpPermission::AnnotationFormFillingAndSigning)
682 );
683 }
684
685 #[test]
686 fn test_invalid_p_value() {
687 assert_eq!(DocMdpPermission::from_p_value(99), None);
688 assert_eq!(DocMdpPermission::from_p_value(0), None);
689 assert_eq!(DocMdpPermission::from_p_value(-1), None);
690 }
691
692 #[test]
693 fn test_non_sig_fields_skipped() {
694 let mut pdf = Vec::new();
697 pdf.extend_from_slice(b"%PDF-1.7\n");
698
699 let obj1_offset = pdf.len();
700 pdf.extend_from_slice(
701 b"1 0 obj\n<< /Type /Catalog /Pages 2 0 R /AcroForm 5 0 R >>\nendobj\n",
702 );
703 let obj2_offset = pdf.len();
704 pdf.extend_from_slice(b"2 0 obj\n<< /Type /Pages /Kids [] /Count 0 >>\nendobj\n");
705 let obj3_offset = pdf.len();
707 pdf.extend_from_slice(b"3 0 obj\n<< /FT /Tx /T (TextField) /V (hello) >>\nendobj\n");
708 let obj4_offset = pdf.len();
710 pdf.extend_from_slice(b"4 0 obj\n<< /FT /Sig /V 6 0 R /T (Signature1) >>\nendobj\n");
711 let obj5_offset = pdf.len();
713 pdf.extend_from_slice(b"5 0 obj\n<< /Fields [3 0 R 4 0 R] >>\nendobj\n");
714 let obj6_offset = pdf.len();
716 pdf.extend_from_slice(
717 b"6 0 obj\n<< /Type /Sig /Contents <AABB> /SubFilter /test >>\nendobj\n",
718 );
719
720 let xref_offset = pdf.len();
721 pdf.extend_from_slice(b"xref\n0 7\n");
722 pdf.extend_from_slice(b"0000000000 65535 f \n");
723 for offset in &[
724 obj1_offset,
725 obj2_offset,
726 obj3_offset,
727 obj4_offset,
728 obj5_offset,
729 obj6_offset,
730 ] {
731 pdf.extend_from_slice(format!("{:010} 00000 n \n", offset).as_bytes());
732 }
733 pdf.extend_from_slice(b"trailer\n<< /Size 7 /Root 1 0 R >>\n");
734 pdf.extend_from_slice(format!("startxref\n{}\n%%%%EOF\n", xref_offset).as_bytes());
735
736 let (store, cat_id) = open_arc(pdf);
737 let cat = store.resolve(cat_id).unwrap();
738 let sigs = collect_signatures(cat, &store).unwrap();
739 assert_eq!(sigs.len(), 1, "only the Sig field should be collected");
740 assert_eq!(sigs[0].sub_filter(), Some("test"));
741 }
742
743 #[test]
744 fn test_doc_mdp_as_u32() {
745 assert_eq!(DocMdpPermission::NoChanges.as_u32(), 1);
746 assert_eq!(DocMdpPermission::FormFillingAndSigning.as_u32(), 2);
747 assert_eq!(
748 DocMdpPermission::AnnotationFormFillingAndSigning.as_u32(),
749 3
750 );
751 }
752
753 #[test]
758 fn test_doc_mdp_missing_p_defaults_to_level_2() {
759 let mut sig_dict: HashMap<Name, Object> = HashMap::new();
762 let transform_params = Object::Dictionary(HashMap::new()); let mut ref_entry: HashMap<Name, Object> = HashMap::new();
765 ref_entry.insert(Name::transform_method(), Object::Name(Name::doc_mdp()));
766 ref_entry.insert(Name::transform_params(), transform_params);
767 sig_dict.insert(
768 Name::reference(),
769 Object::Array(vec![Object::Dictionary(ref_entry)]),
770 );
771 let store = make_empty_store();
772 let sig = parse_sig_dict(&sig_dict, &store).unwrap();
773 assert_eq!(
774 sig.doc_mdp_permission(),
775 Some(DocMdpPermission::FormFillingAndSigning),
776 "/P absent in /TransformParams should default to level 2"
777 );
778 }
779
780 #[test]
785 fn test_contents_non_string_returns_none() {
786 let mut sig_dict: HashMap<Name, Object> = HashMap::new();
788 sig_dict.insert(Name::contents(), Object::Integer(42));
789 let store = make_empty_store();
790 let sig = parse_sig_dict(&sig_dict, &store).unwrap();
791 assert_eq!(
792 sig.contents(),
793 None,
794 "/Contents as integer should yield None"
795 );
796 }
797
798 #[test]
799 fn test_byte_range_non_array_returns_empty() {
800 let mut sig_dict: HashMap<Name, Object> = HashMap::new();
802 sig_dict.insert(Name::byte_range(), Object::Dictionary(HashMap::new()));
803 let store = make_empty_store();
804 let sig = parse_sig_dict(&sig_dict, &store).unwrap();
805 assert!(
806 sig.byte_range().is_empty(),
807 "/ByteRange as dict should yield empty slice"
808 );
809 }
810
811 #[test]
812 fn test_reference_non_array_is_ignored() {
813 let mut ref_entry: HashMap<Name, Object> = HashMap::new();
815 ref_entry.insert(Name::transform_method(), Object::Name(Name::doc_mdp()));
816 let mut tp: HashMap<Name, Object> = HashMap::new();
817 tp.insert(Name::p(), Object::Integer(1));
818 ref_entry.insert(Name::transform_params(), Object::Dictionary(tp));
819
820 let mut sig_dict: HashMap<Name, Object> = HashMap::new();
821 sig_dict.insert(Name::reference(), Object::Dictionary(ref_entry));
823 let store = make_empty_store();
824 let sig = parse_sig_dict(&sig_dict, &store).unwrap();
825 assert_eq!(
826 sig.doc_mdp_permission(),
827 None,
828 "/Reference as bare dict (not array) should yield None"
829 );
830 }
831}