Skip to main content

easyofd_core/signatures/
references.rs

1//! 签名的范围(References)。
2//!
3//! 对应 Java: org.ofdrw.core.signatures.range.References
4//!
5//! GB/T 33190 第 18.2.2 节 图 87 表 68。
6
7use super::check_method::CheckMethod;
8use super::reference::Reference;
9
10/// 签名的范围。
11///
12/// 包含受本次签名保护的文件摘要记录列表。
13/// 一个受保护的包内文件对应一个 [`Reference`] 节点。
14#[derive(Debug, Clone)]
15pub struct References {
16    /// 摘要方法(可选)。
17    /// 视应用场景的不同使用不同的摘要方法。
18    pub check_method: Option<String>,
19    /// 针对各文件的摘要节点列表。
20    pub references: Vec<Reference>,
21}
22
23impl References {
24    /// 创建空的签名范围。
25    #[must_use]
26    pub fn new() -> Self {
27        Self {
28            check_method: None,
29            references: Vec::new(),
30        }
31    }
32
33    /// 设置摘要方法(字符串形式,保持向后兼容)。
34    #[must_use]
35    pub fn check_method(mut self, method: impl Into<String>) -> Self {
36        self.check_method = Some(method.into());
37        self
38    }
39
40    /// 设置摘要方法(类型安全的枚举形式)。
41    ///
42    /// 使用 [`CheckMethod`] 枚举避免拼写错误,产出的字符串与 Java 完全一致。
43    ///
44    /// # 示例
45    ///
46    /// ```
47    /// use easyofd_core::signatures::{CheckMethod, References};
48    ///
49    /// let refs = References::new().set_check_method(CheckMethod::Sm3);
50    /// assert_eq!(refs.check_method_str(), Some("SM3"));
51    /// ```
52    #[must_use]
53    pub fn set_check_method(mut self, method: CheckMethod) -> Self {
54        self.check_method = Some(method.as_str().to_owned());
55        self
56    }
57
58    /// 获取摘要方法的字符串值。
59    #[must_use]
60    pub fn check_method_str(&self) -> Option<&str> {
61        self.check_method.as_deref()
62    }
63
64    /// 获取摘要方法枚举值(若已设置且可解析)。
65    ///
66    /// # 示例
67    ///
68    /// ```
69    /// use easyofd_core::signatures::{CheckMethod, References};
70    ///
71    /// let refs = References::new().check_method("SM3");
72    /// assert_eq!(refs.check_method_enum(), Some(CheckMethod::Sm3));
73    /// ```
74    #[must_use]
75    pub fn check_method_enum(&self) -> Option<CheckMethod> {
76        self.check_method
77            .as_deref()
78            .and_then(|s| CheckMethod::try_from_str(s).ok())
79    }
80
81    /// 添加一个文件摘要节点。
82    #[must_use]
83    pub fn add_reference(mut self, reference: Reference) -> Self {
84        self.references.push(reference);
85        self
86    }
87
88    /// 检查是否包含指定路径的文件。
89    pub fn has_file(&self, abs_loc: &str) -> bool {
90        self.references.iter().any(|r| r.file_ref == abs_loc)
91    }
92
93    /// 序列化为 XML 字符串。
94    #[must_use]
95    pub fn to_xml_string(&self) -> String {
96        let mut xml = String::from("<ofd:References");
97        if let Some(ref method) = self.check_method {
98            use std::fmt::Write;
99            let _ = write!(xml, r#" CheckMethod="{method}""#);
100        }
101        xml.push('>');
102        for r in &self.references {
103            xml.push('\n');
104            xml.push_str(&r.to_xml_string());
105        }
106        xml.push_str("\n</ofd:References>");
107        xml
108    }
109}
110
111impl Default for References {
112    fn default() -> Self {
113        Self::new()
114    }
115}
116
117#[cfg(test)]
118mod tests {
119    use super::*;
120
121    #[test]
122    fn test_references_new() {
123        let refs = References::new();
124        assert!(refs.check_method.is_none());
125        assert!(refs.references.is_empty());
126    }
127
128    #[test]
129    fn test_references_builder() {
130        let refs = References::new()
131            .check_method("SM3")
132            .add_reference(Reference::new("/Doc_0/Document.xml", "abc"))
133            .add_reference(Reference::new("/Doc_0/Pages/Page_0/Content.xml", "def"));
134        assert_eq!(refs.check_method.as_deref(), Some("SM3"));
135        assert_eq!(refs.references.len(), 2);
136    }
137
138    #[test]
139    fn test_references_has_file() {
140        let refs = References::new().add_reference(Reference::new("/Doc_0/Document.xml", "abc"));
141        assert!(refs.has_file("/Doc_0/Document.xml"));
142        assert!(!refs.has_file("/Doc_0/Other.xml"));
143    }
144
145    #[test]
146    fn test_references_xml() {
147        let refs = References::new()
148            .check_method("SHA256")
149            .add_reference(Reference::new("/Doc_0/Document.xml", "hash1"));
150        let xml = refs.to_xml_string();
151        assert!(xml.contains("CheckMethod=\"SHA256\""));
152        assert!(xml.contains("FileRef=\"/Doc_0/Document.xml\""));
153        assert!(xml.contains("</ofd:References>"));
154    }
155
156    #[test]
157    fn test_references_default() {
158        let refs = References::default();
159        assert!(refs.references.is_empty());
160    }
161
162    #[test]
163    fn test_set_check_method_enum() {
164        let refs = References::new().set_check_method(CheckMethod::Sm3);
165        assert_eq!(refs.check_method.as_deref(), Some("SM3"));
166        assert_eq!(refs.check_method_str(), Some("SM3"));
167    }
168
169    #[test]
170    fn test_check_method_enum_roundtrip() {
171        for method in [
172            CheckMethod::Md5,
173            CheckMethod::Sha1,
174            CheckMethod::Sha256,
175            CheckMethod::Sm3,
176        ] {
177            let refs = References::new().set_check_method(method);
178            assert_eq!(refs.check_method_enum(), Some(method));
179        }
180    }
181
182    #[test]
183    fn test_check_method_enum_none() {
184        let refs = References::new();
185        assert!(refs.check_method_enum().is_none());
186    }
187
188    #[test]
189    fn test_check_method_enum_unknown_string() {
190        let refs = References::new().check_method("UNKNOWN");
191        assert!(refs.check_method_enum().is_none());
192    }
193
194    #[test]
195    fn test_set_check_method_xml_unchanged() {
196        // 确保枚举 API 产出的 XML 与字符串 API 完全一致
197        let refs_str = References::new().check_method("SM3");
198        let refs_enum = References::new().set_check_method(CheckMethod::Sm3);
199        assert_eq!(refs_str.to_xml_string(), refs_enum.to_xml_string());
200    }
201}