Skip to main content

easyofd_core/signatures/
signature.rs

1//! 数字签名注册信息(Signature)。
2//!
3//! 对应 Java: org.ofdrw.core.signatures.Signature
4//!
5//! GB/T 33190 第 18.1 节 图 85 表 66。
6
7use std::fmt::Write;
8
9/// 签名类型枚举。
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum SigType {
12    /// 签章(默认值)。
13    Seal,
14    /// 签名。
15    Sign,
16}
17
18impl SigType {
19    /// 获取枚举的字符串表示。
20    #[must_use]
21    pub fn as_str(&self) -> &'static str {
22        match self {
23            Self::Seal => "Seal",
24            Self::Sign => "Sign",
25        }
26    }
27
28    /// 从字符串解析。
29    #[allow(clippy::should_implement_trait)]
30    pub fn from_str(s: &str) -> Result<Self, String> {
31        match s {
32            "Seal" => Ok(Self::Seal),
33            "Sign" => Ok(Self::Sign),
34            _ => Err(format!("未知的签名类型: {s}")),
35        }
36    }
37}
38
39impl std::fmt::Display for SigType {
40    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
41        f.write_str(self.as_str())
42    }
43}
44
45/// 数字签名或安全签章在列表中的注册信息。
46///
47/// 每个签名或签章对应一个节点。
48/// 推荐使用 `sNNN` 的编码方式,NNN 从 1 开始。
49#[derive(Debug, Clone)]
50pub struct Signature {
51    /// 签名或签章的标识(必选)。
52    pub id: String,
53    /// 签名节点类型(可选),默认 Seal。
54    pub sig_type: Option<SigType>,
55    /// 基于的签名 ID(可选,OFD 2.0)。
56    /// 验证时应同时验证"基"签名。
57    pub relative: Option<String>,
58    /// 指向包内的签名描述文件路径(必选)。
59    pub base_loc: String,
60}
61
62impl Signature {
63    /// 创建新的签名注册信息。
64    #[must_use]
65    pub fn new(id: impl Into<String>, base_loc: impl Into<String>) -> Self {
66        Self {
67            id: id.into(),
68            sig_type: None,
69            relative: None,
70            base_loc: base_loc.into(),
71        }
72    }
73
74    /// 设置签名类型。
75    #[must_use]
76    pub fn sig_type(mut self, sig_type: SigType) -> Self {
77        self.sig_type = Some(sig_type);
78        self
79    }
80
81    /// 设置基于的签名 ID(OFD 2.0)。
82    #[must_use]
83    pub fn relative(mut self, id: impl Into<String>) -> Self {
84        self.relative = Some(id.into());
85        self
86    }
87
88    /// 序列化为 XML 字符串。
89    #[must_use]
90    pub fn to_xml_string(&self) -> String {
91        let mut xml = format!(r#"<ofd:Signature ID="{}""#, self.id);
92        if let Some(ref sig_type) = self.sig_type {
93            let _ = write!(xml, r#" Type="{}""#, sig_type.as_str());
94        }
95        if let Some(ref rel) = self.relative {
96            let _ = write!(xml, r#" Relative="{rel}""#);
97        }
98        let _ = write!(xml, r#" BaseLoc="{}""#, self.base_loc);
99        xml.push_str(" />");
100        xml
101    }
102}
103
104#[cfg(test)]
105mod tests {
106    use super::*;
107
108    #[test]
109    fn test_signature_new() {
110        let sig = Signature::new("s1", "/Doc_0/Signs/Sign_0/Signature.xml");
111        assert_eq!(sig.id, "s1");
112        assert_eq!(sig.base_loc, "/Doc_0/Signs/Sign_0/Signature.xml");
113        assert!(sig.sig_type.is_none());
114        assert!(sig.relative.is_none());
115    }
116
117    #[test]
118    fn test_signature_builder() {
119        let sig = Signature::new("s2", "/Doc_0/Signs/Sign_1/Signature.xml")
120            .sig_type(SigType::Sign)
121            .relative("s1");
122        assert_eq!(sig.sig_type, Some(SigType::Sign));
123        assert_eq!(sig.relative.as_deref(), Some("s1"));
124    }
125
126    #[test]
127    fn test_signature_xml_full() {
128        let sig = Signature::new("s1", "/Doc_0/Signs/Sign_0/Signature.xml").sig_type(SigType::Seal);
129        let xml = sig.to_xml_string();
130        assert!(xml.contains(r#"ID="s1""#));
131        assert!(xml.contains(r#"Type="Seal""#));
132        assert!(xml.contains(r#"BaseLoc="/Doc_0/Signs/Sign_0/Signature.xml""#));
133        assert!(!xml.contains("Relative"));
134    }
135
136    #[test]
137    fn test_sig_type_display() {
138        assert_eq!(SigType::Seal.to_string(), "Seal");
139        assert_eq!(SigType::Sign.to_string(), "Sign");
140        assert_eq!(SigType::from_str("Seal").unwrap(), SigType::Seal);
141        assert!(SigType::from_str("Unknown").is_err());
142    }
143}