use super::references::References;
use super::seal::Seal;
use super::stamp_annot::StampAnnot;
#[derive(Debug, Clone)]
pub struct Provider {
pub name: String,
pub version: Option<String>,
pub company: Option<String>,
}
impl Provider {
#[must_use]
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
version: None,
company: None,
}
}
#[must_use]
pub fn version(mut self, version: impl Into<String>) -> Self {
self.version = Some(version.into());
self
}
#[must_use]
pub fn company(mut self, company: impl Into<String>) -> Self {
self.company = Some(company.into());
self
}
#[must_use]
pub fn to_xml_string(&self) -> String {
let mut xml = format!(r#"<ofd:Provider Name="{}""#, self.name);
if let Some(ref v) = self.version {
use std::fmt::Write;
let _ = write!(xml, r#" Version="{v}""#);
}
if let Some(ref c) = self.company {
use std::fmt::Write;
let _ = write!(xml, r#" Company="{c}""#);
}
xml.push_str(" />");
xml
}
}
#[derive(Debug, Clone)]
pub struct SignedInfo {
pub provider: Provider,
pub signature_method: Option<String>,
pub signature_datetime: Option<String>,
pub references: References,
pub stamp_annots: Vec<StampAnnot>,
pub seal: Option<Seal>,
}
impl SignedInfo {
#[must_use]
pub fn new(provider: Provider, references: References) -> Self {
Self {
provider,
signature_method: None,
signature_datetime: None,
references,
stamp_annots: Vec::new(),
seal: None,
}
}
#[must_use]
pub fn signature_method(mut self, method: impl Into<String>) -> Self {
self.signature_method = Some(method.into());
self
}
#[must_use]
pub fn signature_datetime(mut self, datetime: impl Into<String>) -> Self {
self.signature_datetime = Some(datetime.into());
self
}
#[must_use]
pub fn add_stamp_annot(mut self, stamp_annot: StampAnnot) -> Self {
self.stamp_annots.push(stamp_annot);
self
}
#[must_use]
pub fn seal(mut self, seal: Seal) -> Self {
self.seal = Some(seal);
self
}
#[must_use]
pub fn to_xml_string(&self) -> String {
use std::fmt::Write;
let mut xml = String::from("<ofd:SignedInfo>");
xml.push('\n');
xml.push_str(&self.provider.to_xml_string());
if let Some(ref method) = self.signature_method {
xml.push('\n');
let _ = write!(xml, "<ofd:SignatureMethod>{method}</ofd:SignatureMethod>");
}
if let Some(ref dt) = self.signature_datetime {
xml.push('\n');
let _ = write!(xml, "<ofd:SignatureDateTime>{dt}</ofd:SignatureDateTime>");
}
xml.push('\n');
xml.push_str(&self.references.to_xml_string());
for sa in &self.stamp_annots {
xml.push('\n');
xml.push_str(&sa.to_xml_string());
}
if let Some(ref seal) = self.seal {
xml.push('\n');
xml.push_str(&seal.to_xml_string());
}
xml.push_str("\n</ofd:SignedInfo>");
xml
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::basic_type::{ST_Box, ST_RefID};
#[test]
fn test_signed_info_new() {
let si = SignedInfo::new(
Provider::new("TestProvider"),
References::new().add_reference(crate::signatures::reference::Reference::new(
"/Doc_0/Document.xml",
"hash",
)),
);
assert_eq!(si.provider.name, "TestProvider");
assert!(si.signature_method.is_none());
assert!(si.seal.is_none());
assert!(si.stamp_annots.is_empty());
}
#[test]
fn test_signed_info_builder() {
let si = SignedInfo::new(
Provider::new("MyProvider").version("1.0").company("MyCo"),
References::new().check_method("SM3"),
)
.signature_method("1.2.156.10197.1.501")
.signature_datetime("2025-01-01T00:00:00")
.add_stamp_annot(StampAnnot::new(
"s1",
ST_RefID::new(1),
ST_Box::new(0.0, 0.0, 100.0, 100.0),
))
.seal(Seal::new("/Doc_0/Signs/Sign_0/Seal.esl"));
assert_eq!(si.signature_method.as_deref(), Some("1.2.156.10197.1.501"));
assert_eq!(
si.signature_datetime.as_deref(),
Some("2025-01-01T00:00:00")
);
assert_eq!(si.stamp_annots.len(), 1);
assert!(si.seal.is_some());
}
#[test]
fn test_signed_info_xml() {
let si = SignedInfo::new(
Provider::new("Prov"),
References::new().add_reference(crate::signatures::reference::Reference::new(
"/Doc_0/Document.xml",
"abc",
)),
)
.signature_method("SM2");
let xml = si.to_xml_string();
assert!(xml.contains("<ofd:SignedInfo>"));
assert!(xml.contains("Name=\"Prov\""));
assert!(xml.contains("<ofd:SignatureMethod>SM2</ofd:SignatureMethod>"));
assert!(xml.contains("</ofd:SignedInfo>"));
}
#[test]
fn test_provider_builder() {
let p = Provider::new("Test").version("2.0").company("ACME");
assert_eq!(p.name, "Test");
assert_eq!(p.version.as_deref(), Some("2.0"));
assert_eq!(p.company.as_deref(), Some("ACME"));
let xml = p.to_xml_string();
assert!(xml.contains("Name=\"Test\""));
assert!(xml.contains("Version=\"2.0\""));
assert!(xml.contains("Company=\"ACME\""));
}
}