use crate::xml_input as xml_sec_xml_input;
use std::{collections::HashSet, io::Write};
use base64::Engine;
use xml_sec_xml_input::lexical::Writer;
use crate::c14n::{C14nAlgorithm, C14nMode, canonicalize_bounded_with_xml_base_budget};
use crate::policy::{PolicyViolation, SigningPolicy};
use crate::xml::{is_xml_1_0_character, is_xml_ncname};
use super::mutation::{
fill_signature_value_with_budget, fill_signed_info_digest_values_with_budget,
padded_base64_len_for_xml, projected_signature_value_output_len_at_index_with_budget,
zero_base64_placeholder,
};
use super::transforms::{
ENVELOPED_SIGNATURE_XPATH_EXPR, ENVELOPED_SIGNATURE_XPATH_PREFIX, TransformExecutionBudget,
XPathSignatureParseBudget, map_c14n_resource_policy_violation, transform_chain_produces_binary,
validate_signing_transform_policy, validate_xpath_namespace_budget_with_resources,
};
use super::uri::validate_signing_reference_uri;
use super::{
BASE64_TRANSFORM_URI, DigestAlgorithm, ENVELOPED_SIGNATURE_URI, SignatureAlgorithm,
SignatureContext, Transform, XPATH_FILTER2_TRANSFORM_URI, XPATH_TRANSFORM_URI, XPathExpression,
};
const XMLDSIG_NS: &str = "http://www.w3.org/2000/09/xmldsig#";
const XML_NS: &str = "http://www.w3.org/XML/1998/namespace";
const XMLNS_NS: &str = "http://www.w3.org/2000/xmlns/";
const EXCLUSIVE_C14N_NS: &str = "http://www.w3.org/2001/10/xml-exc-c14n#";
#[derive(Debug, thiserror::Error)]
pub enum SignatureBuilderError {
#[error("invalid XML namespace prefix: {0}")]
InvalidNamespacePrefix(String),
#[error("XML namespace URI contains a character forbidden by XML 1.0: {0:?}")]
InvalidNamespaceUri(String),
#[error("XPath namespace binding conflicts with XMLDSig prefix: {0}")]
NamespacePrefixConflict(String),
#[error("invalid {element} Id: {value}")]
InvalidId {
element: &'static str,
value: String,
},
#[error("a signature template requires at least one Reference")]
MissingReference,
#[error("signing policy violation: {0}")]
Policy(#[from] PolicyViolation),
#[error("invalid XPath expression: {0}")]
InvalidXPath(String),
#[error("invalid transform: {0}")]
InvalidTransform(String),
#[error("algorithm is not allowed for signing: {0}")]
SigningAlgorithmDisabled(&'static str),
#[error("signature context is not applicable to algorithm: {0}")]
SignatureContextNotApplicable(&'static str),
#[error("RSA-PSS salt length exceeds xs:int")]
InvalidRsaPssParameters,
#[error("XML serialization error: {0}")]
Serialization(#[from] std::io::Error),
#[error("XML writer emitted invalid UTF-8: {0}")]
InvalidUtf8(#[from] std::string::FromUtf8Error),
#[error("generated XML template is invalid: {0}")]
GeneratedXml(#[from] crate::xml::dom::ParseError),
#[error("generated SignedInfo canonicalization failed: {0}")]
Canonicalization(#[from] crate::c14n::C14nError),
#[error("generated XML template is missing SignedInfo")]
MissingGeneratedSignedInfo,
#[error("generated XML template validation failed: {0}")]
GeneratedMutation(#[source] super::mutation::XmlMutationError),
}
#[derive(Debug, Clone)]
pub struct ReferenceBuilder {
uri: String,
id: Option<String>,
ref_type: Option<String>,
transforms: Vec<Transform>,
digest_method: DigestAlgorithm,
}
impl ReferenceBuilder {
#[must_use]
pub fn new(digest_method: DigestAlgorithm) -> Self {
Self {
uri: String::new(),
id: None,
ref_type: None,
transforms: Vec::new(),
digest_method,
}
}
#[must_use]
pub fn uri(mut self, uri: impl Into<String>) -> Self {
self.uri = uri.into();
self
}
#[must_use]
pub fn id(mut self, id: impl Into<String>) -> Self {
self.id = Some(id.into());
self
}
#[must_use]
pub fn ref_type(mut self, ref_type: impl Into<String>) -> Self {
self.ref_type = Some(ref_type.into());
self
}
#[must_use]
pub fn transform(mut self, transform: Transform) -> Self {
self.transforms.push(transform);
self
}
}
#[derive(Debug, Clone)]
pub struct SignatureBuilder {
c14n_method: C14nAlgorithm,
sign_method: SignatureAlgorithm,
signature_context: SignatureContext,
ns_prefix: Option<String>,
signature_id: Option<String>,
references: Vec<ReferenceBuilder>,
include_key_info: bool,
}
impl SignatureBuilder {
#[must_use]
pub fn new(c14n_method: C14nAlgorithm, sign_method: SignatureAlgorithm) -> Self {
Self {
c14n_method,
sign_method,
signature_context: SignatureContext::default(),
ns_prefix: None,
signature_id: None,
references: Vec::new(),
include_key_info: false,
}
}
#[must_use]
pub fn ns_prefix(mut self, prefix: impl Into<String>) -> Self {
self.ns_prefix = Some(prefix.into());
self
}
#[must_use]
pub fn signature_id(mut self, id: impl Into<String>) -> Self {
self.signature_id = Some(id.into());
self
}
#[must_use]
pub fn signature_context(mut self, context: SignatureContext) -> Self {
self.signature_context = context;
self
}
#[must_use]
pub fn add_reference(mut self, reference: ReferenceBuilder) -> Self {
self.references.push(reference);
self
}
#[must_use]
pub fn key_info(mut self, include: bool) -> Self {
self.include_key_info = include;
self
}
pub fn build_template(&self) -> Result<String, SignatureBuilderError> {
self.build_template_with_policy(&SigningPolicy::default())
}
pub fn build_template_with_policy(
&self,
policy: &SigningPolicy,
) -> Result<String, SignatureBuilderError> {
let budget = TransformExecutionBudget::from_resources(&policy.resources);
let mut xpath_parse_budget = XPathSignatureParseBudget::from_resources(&policy.resources);
self.build_template_with_policy_and_signature_output_len(
policy,
None,
None,
&budget,
&mut xpath_parse_budget,
)
}
pub(super) fn build_template_with_policy_for_signature_output(
&self,
policy: &SigningPolicy,
signature_output_len: usize,
encapsulation: Option<crate::provider::KeyEncapsulationAlgorithm>,
budget: &TransformExecutionBudget,
xpath_parse_budget: &mut XPathSignatureParseBudget,
) -> Result<String, SignatureBuilderError> {
self.build_template_with_policy_and_signature_output_len(
policy,
Some(signature_output_len),
encapsulation,
budget,
xpath_parse_budget,
)
}
fn build_template_with_policy_and_signature_output_len(
&self,
policy: &SigningPolicy,
signature_output_len: Option<usize>,
encapsulation: Option<crate::provider::KeyEncapsulationAlgorithm>,
budget: &TransformExecutionBudget,
xpath_parse_budget: &mut XPathSignatureParseBudget,
) -> Result<String, SignatureBuilderError> {
policy.validate()?;
self.validate(policy, xpath_parse_budget)?;
let prefix = self.ns_prefix.as_deref();
let mut writer = Writer::new(Vec::new());
let signature_name = qualified_name(prefix, "Signature");
let namespace_attr = prefix.map_or_else(|| "xmlns".to_owned(), |p| format!("xmlns:{p}"));
let mut attributes = vec![(namespace_attr.as_str(), XMLDSIG_NS)];
if let Some(id) = &self.signature_id {
attributes.push(("Id", id.as_str()));
}
writer.start(&signature_name, attributes)?;
write_start(&mut writer, prefix, "SignedInfo")?;
write_algorithm(
&mut writer,
prefix,
"CanonicalizationMethod",
self.c14n_method.uri(),
)?;
if let SignatureAlgorithm::RsaPss(parameters) = self.sign_method {
let method_name = qualified_name(prefix, "SignatureMethod");
writer.start(&method_name, [("Algorithm", self.sign_method.uri())])?;
writer.start("RSAPSSParams", [("xmlns", super::parse::RSA_PSS_NS)])?;
writer.empty(
"DigestMethod",
[
("xmlns", XMLDSIG_NS),
("Algorithm", parameters.digest.uri()),
],
)?;
writer.start(
"MaskGenerationFunction",
[("Algorithm", "http://www.w3.org/2007/05/xmldsig-more#MGF1")],
)?;
writer.empty(
"DigestMethod",
[
("xmlns", XMLDSIG_NS),
("Algorithm", parameters.mgf_digest.uri()),
],
)?;
writer.end("MaskGenerationFunction")?;
writer.start("SaltLength", std::iter::empty::<(&str, &str)>())?;
writer.text(¶meters.salt_len.to_string())?;
writer.end("SaltLength")?;
writer.end("RSAPSSParams")?;
writer.end(&method_name)?;
} else if self.signature_context.as_bytes().is_empty() {
write_algorithm(
&mut writer,
prefix,
"SignatureMethod",
self.sign_method.uri(),
)?;
} else {
let method_name = qualified_name(prefix, "SignatureMethod");
writer.start(&method_name, [("Algorithm", self.sign_method.uri())])?;
let context_name = self.sign_method.context_element().ok_or(
SignatureBuilderError::SignatureContextNotApplicable(self.sign_method.uri()),
)?;
writer.start(
context_name,
[("xmlns", super::parse::EXPERIMENTAL_SIGNATURE_NS)],
)?;
let mut encoded = [0_u8; 340];
let length = base64::engine::general_purpose::STANDARD
.encode_slice(self.signature_context.as_bytes(), &mut encoded)
.map_err(std::io::Error::other)?;
writer.text(std::str::from_utf8(&encoded[..length]).map_err(std::io::Error::other)?)?;
writer.end(context_name)?;
writer.end(&method_name)?;
}
for reference in &self.references {
write_reference(&mut writer, prefix, reference)?;
}
write_end(&mut writer, prefix, "SignedInfo")?;
write_empty(&mut writer, prefix, "SignatureValue")?;
if let Some(algorithm) = encapsulation {
policy.key_establishment.check_encapsulation(algorithm)?;
write_start(&mut writer, prefix, "KeyInfo")?;
writer.start(
"EncapsulationMechanism",
[
("xmlns", crate::key_establishment::ENCAPSULATION_NS),
("Algorithm", algorithm.uri()),
],
)?;
writer.empty("KeyInfo", [("xmlns", XMLDSIG_NS)])?;
writer.start(
"CipherData",
[("xmlns", "http://www.w3.org/2001/04/xmlenc#")],
)?;
writer.empty("CipherValue", std::iter::empty::<(&str, &str)>())?;
writer.end("CipherData")?;
writer.end("EncapsulationMechanism")?;
write_end(&mut writer, prefix, "KeyInfo")?;
} else if self.include_key_info {
write_empty(&mut writer, prefix, "KeyInfo")?;
}
writer.end(&signature_name)?;
let template = String::from_utf8(writer.into_inner())?;
policy.resources.validate_xml_document_len(template.len())?;
let digest_placeholders = self.references.iter().map(|reference| {
base64::engine::general_purpose::STANDARD.encode(vec![
0_u8;
reference
.digest_method
.output_len()
])
});
let digest_validation_template = fill_signed_info_digest_values_with_budget(
&template,
digest_placeholders,
Some(policy),
Some(budget.xml_parse_work()),
)
.map_err(map_generated_mutation_error)?;
let validation_template = if let Some(signature_output_len) = signature_output_len {
let encoded_signature_len = padded_base64_len_for_xml(signature_output_len, policy)
.map_err(map_generated_mutation_error)?;
let projected_document_len = projected_signature_value_output_len_at_index_with_budget(
&digest_validation_template,
encoded_signature_len,
0,
Some(policy),
Some(budget.xml_parse_work()),
)
.map_err(map_generated_mutation_error)?;
policy
.resources
.validate_xml_document_len(projected_document_len)?;
let signature_placeholder =
zero_base64_placeholder(signature_output_len, encoded_signature_len);
fill_signature_value_with_budget(
&digest_validation_template,
&signature_placeholder,
Some(policy),
Some(budget.xml_parse_work()),
)
.map_err(map_generated_mutation_error)?
} else {
digest_validation_template
};
policy
.resources
.validate_xml_document_len(validation_template.len())?;
let settings =
crate::document::DocumentParseSettings::from_policy(&policy.xml, &policy.resources);
let document = super::mutation::parse_with_options_and_budget(
&validation_template,
settings,
Some(budget.xml_parse_work()),
)
.map_err(|error| match error.into_policy_violation(settings) {
Ok(error) => SignatureBuilderError::Policy(error),
Err(crate::document::XmlDocumentError::Parse(error)) => {
SignatureBuilderError::GeneratedXml(error)
}
Err(error) => SignatureBuilderError::GeneratedMutation(
super::mutation::XmlMutationError::Document(error),
),
})?;
let signed_info = document
.root_element()
.children()
.find(|node| node.has_tag_name((XMLDSIG_NS, "SignedInfo")))
.ok_or(SignatureBuilderError::MissingGeneratedSignedInfo)?;
let signed_info_subtree: HashSet<_> =
signed_info.descendants().map(|node| node.id()).collect();
let mut canonical_signed_info = Vec::new();
canonicalize_bounded_with_xml_base_budget(
&document,
Some(&|node| signed_info_subtree.contains(&node.id())),
&self.c14n_method,
budget.remaining_c14n_output(),
budget.xml_base_resolution(),
&mut canonical_signed_info,
)
.map_err(|error| {
map_c14n_resource_policy_violation(
&error,
crate::policy::resource_name::CANONICALIZED_BYTES,
budget.c14n_output_limit(),
)
.map_or_else(
|| SignatureBuilderError::Canonicalization(error),
SignatureBuilderError::Policy,
)
})?;
budget.charge_c14n_output_policy(canonical_signed_info.len())?;
Ok(template)
}
pub(super) fn signature_method(&self) -> SignatureAlgorithm {
self.sign_method
}
fn validate(
&self,
policy: &SigningPolicy,
xpath_signature_budget: &mut XPathSignatureParseBudget,
) -> Result<(), SignatureBuilderError> {
if let Some(parameters) = self.sign_method.rsa_pss_parameters()
&& parameters.salt_len > i32::MAX as usize
{
return Err(SignatureBuilderError::InvalidRsaPssParameters);
}
if !self.signature_context.as_bytes().is_empty()
&& self.sign_method.context_element().is_none()
{
return Err(SignatureBuilderError::SignatureContextNotApplicable(
self.sign_method.uri(),
));
}
if let Some(prefix) = &self.ns_prefix
&& !is_namespace_prefix(prefix)
{
return Err(SignatureBuilderError::InvalidNamespacePrefix(
prefix.clone(),
));
}
if let Some(id) = &self.signature_id
&& !is_xml_ncname(id)
{
return Err(SignatureBuilderError::InvalidId {
element: "Signature",
value: id.clone(),
});
}
if self.references.is_empty() {
return Err(SignatureBuilderError::MissingReference);
}
if self.references.len() > policy.resources.max_references {
return Err(PolicyViolation::ResourceLimit {
resource: crate::policy::resource_name::SIGNATURE_REFERENCES,
maximum: policy.resources.max_references,
actual: self.references.len(),
}
.into());
}
for reference in &self.references {
validate_signing_reference_uri(&reference.uri, policy)?;
for transform in &reference.transforms {
if let Transform::Relationship(selectors) = transform {
xpath_signature_budget
.validate_relationship_selectors(selectors)
.map_err(|error| match error {
super::TransformError::Policy(violation) => {
SignatureBuilderError::Policy(violation)
}
other => SignatureBuilderError::InvalidTransform(other.to_string()),
})?;
}
}
let generated_transforms = reference_transforms_for_generation(reference);
super::transforms::validate_relationship_chain(
&generated_transforms,
policy.transforms.opc_relationship_edition,
)
.map_err(|error| SignatureBuilderError::InvalidTransform(error.to_string()))?;
if generated_transforms.len() > policy.resources.max_transforms_per_reference {
return Err(PolicyViolation::ResourceLimit {
resource: crate::policy::resource_name::REFERENCE_TRANSFORMS,
maximum: policy.resources.max_transforms_per_reference,
actual: generated_transforms.len(),
}
.into());
}
let initial_binary = !reference.uri.is_empty() && !reference.uri.starts_with('#');
validate_signing_transform_policy(
initial_binary,
&generated_transforms,
policy.transforms.allowed_algorithms.as_ref(),
)?;
for transform in &reference.transforms {
match transform {
Transform::XpathExcludeAllSignatures => {
validate_xpath_source(
ENVELOPED_SIGNATURE_XPATH_EXPR,
xpath_signature_budget,
)?;
}
Transform::XPath(xpath) => {
validate_xpath_source(xpath.expression(), xpath_signature_budget)?;
}
Transform::XPathFilter2(filters) => {
if filters.is_empty() {
return Err(SignatureBuilderError::InvalidXPath(
"XPath Filter 2.0 requires at least one expression".into(),
));
}
if filters.len() > policy.resources.max_xpath_filters {
return Err(PolicyViolation::ResourceLimit {
resource: crate::policy::resource_name::XPATH_FILTERS,
maximum: policy.resources.max_xpath_filters,
actual: filters.len(),
}
.into());
}
for filter in filters {
validate_xpath_source(
filter.xpath().expression(),
xpath_signature_budget,
)?;
}
}
_ => {}
}
}
validate_xpath_namespace_budget_with_resources(
&reference.transforms,
self.ns_prefix.as_deref().map(|prefix| (prefix, XMLDSIG_NS)),
&policy.resources,
)
.map_err(map_transform_validation_error)?;
}
for (prefix, uri, shares_signature_namespace) in
self.references.iter().flat_map(|reference| {
reference
.transforms
.iter()
.flat_map(|transform| match transform {
Transform::XPath(xpath) => xpath
.namespaces()
.iter()
.map(|(prefix, uri)| (prefix, uri, true))
.collect::<Vec<_>>(),
Transform::XPathFilter2(filters) => filters
.iter()
.flat_map(|filter| {
filter
.xpath()
.namespaces()
.iter()
.map(|(prefix, uri)| (prefix, uri, false))
})
.collect(),
_ => Vec::new(),
})
})
{
if uri.is_empty() || uri == XMLNS_NS || !uri.chars().all(is_xml_1_0_character) {
return Err(SignatureBuilderError::InvalidNamespaceUri(uri.clone()));
}
if prefix == "xmlns"
|| (prefix == "xml") != (uri == XML_NS)
|| (prefix != "xml" && !is_namespace_prefix(prefix))
{
return Err(SignatureBuilderError::InvalidNamespacePrefix(
prefix.clone(),
));
}
if shares_signature_namespace
&& self.ns_prefix.as_ref() == Some(prefix)
&& uri != XMLDSIG_NS
{
return Err(SignatureBuilderError::NamespacePrefixConflict(
prefix.clone(),
));
}
}
policy.check_signature_algorithm(self.sign_method)?;
if policy
.transforms
.allowed_algorithms
.as_ref()
.is_some_and(|allowed| !allowed.contains(self.c14n_method.uri()))
{
return Err(PolicyViolation::Algorithm {
operation: "signing transform",
algorithm: self.c14n_method.uri().to_owned(),
}
.into());
}
for reference in &self.references {
if let Some(id) = &reference.id
&& !is_xml_ncname(id)
{
return Err(SignatureBuilderError::InvalidId {
element: "Reference",
value: id.clone(),
});
}
policy.check_digest_algorithm(reference.digest_method)?;
}
Ok(())
}
}
fn validate_xpath_source(
source: &str,
budget: &mut XPathSignatureParseBudget,
) -> Result<(), SignatureBuilderError> {
if let Some(character) = source
.chars()
.find(|character| !is_xml_1_0_character(*character))
{
return Err(SignatureBuilderError::InvalidXPath(format!(
"XPath expression contains a character forbidden by XML 1.0: {character:?}"
)));
}
budget
.validate_expression(source)
.map_err(map_transform_validation_error)
}
fn map_transform_validation_error(error: super::TransformError) -> SignatureBuilderError {
match error {
super::TransformError::Policy(error) => SignatureBuilderError::Policy(error),
error => SignatureBuilderError::InvalidXPath(error.to_string()),
}
}
fn map_generated_mutation_error(error: super::mutation::XmlMutationError) -> SignatureBuilderError {
match error {
super::mutation::XmlMutationError::Policy(violation) => {
SignatureBuilderError::Policy(violation)
}
other => SignatureBuilderError::GeneratedMutation(other),
}
}
fn write_reference<W: Write>(
writer: &mut Writer<W>,
prefix: Option<&str>,
reference: &ReferenceBuilder,
) -> Result<(), std::io::Error> {
let name = qualified_name(prefix, "Reference");
let mut attributes = Vec::new();
if let Some(id) = &reference.id {
attributes.push(("Id", id.as_str()));
}
if let Some(ref_type) = &reference.ref_type {
attributes.push(("Type", ref_type.as_str()));
}
attributes.push(("URI", reference.uri.as_str()));
writer.start(&name, attributes)?;
let generated_transforms = reference_transforms_for_generation(reference);
if !generated_transforms.is_empty() {
write_start(writer, prefix, "Transforms")?;
for transform in &generated_transforms {
write_transform(writer, prefix, transform)?;
}
write_end(writer, prefix, "Transforms")?;
}
write_algorithm(
writer,
prefix,
"DigestMethod",
reference.digest_method.uri(),
)?;
write_empty(writer, prefix, "DigestValue")?;
writer.end(&name)?;
Ok(())
}
fn reference_transforms_for_generation(reference: &ReferenceBuilder) -> Vec<Transform> {
let mut transforms = reference.transforms.clone();
let initial_binary = !reference.uri.is_empty() && !reference.uri.starts_with('#');
if !transform_chain_produces_binary(initial_binary, &transforms) {
transforms.push(Transform::C14n(C14nAlgorithm::new(
C14nMode::Inclusive1_1,
false,
)));
}
transforms
}
fn write_transform<W: Write>(
writer: &mut Writer<W>,
prefix: Option<&str>,
transform: &Transform,
) -> Result<(), std::io::Error> {
match transform {
Transform::Relationship(selectors) => {
let name = qualified_name(prefix, "Transform");
writer.start(&name, [("Algorithm", super::RELATIONSHIP_TRANSFORM_URI)])?;
for selector in selectors {
let (element, attribute, value) = selector.parameter();
writer.empty(
element,
[
(
"xmlns",
"http://schemas.openxmlformats.org/package/2006/digital-signature",
),
(attribute, value),
],
)?;
}
writer.end(&name)
}
Transform::Enveloped => {
write_algorithm(writer, prefix, "Transform", ENVELOPED_SIGNATURE_URI)
}
Transform::XpathExcludeAllSignatures => {
let name = qualified_name(prefix, "Transform");
writer.start(&name, [("Algorithm", XPATH_TRANSFORM_URI)])?;
let xpath_name = qualified_name(prefix, "XPath");
let namespace = format!("xmlns:{ENVELOPED_SIGNATURE_XPATH_PREFIX}");
writer.start(&xpath_name, [(namespace.as_str(), XMLDSIG_NS)])?;
writer.text(ENVELOPED_SIGNATURE_XPATH_EXPR)?;
writer.end(&xpath_name)?;
writer.end(&name)?;
Ok(())
}
Transform::XPath(xpath) => {
let transform_name = qualified_name(prefix, "Transform");
writer.start(&transform_name, [("Algorithm", XPATH_TRANSFORM_URI)])?;
write_xpath_expression(writer, prefix, "XPath", None, xpath)?;
writer.end(&transform_name)?;
Ok(())
}
Transform::XPathFilter2(filters) => {
let transform_name = qualified_name(prefix, "Transform");
writer.start(
&transform_name,
[("Algorithm", XPATH_FILTER2_TRANSFORM_URI)],
)?;
for filter in filters {
write_xpath_expression(
writer,
None,
"XPath",
Some(filter.operation().as_str()),
filter.xpath(),
)?;
}
writer.end(&transform_name)?;
Ok(())
}
Transform::Base64Decode => {
write_algorithm(writer, prefix, "Transform", BASE64_TRANSFORM_URI)
}
Transform::C14n(algorithm) if algorithm.inclusive_prefixes().is_empty() => {
write_algorithm(writer, prefix, "Transform", algorithm.uri())
}
Transform::C14n(algorithm) => {
let name = qualified_name(prefix, "Transform");
writer.start(&name, [("Algorithm", algorithm.uri())])?;
if algorithm.mode() == C14nMode::Exclusive1_0 {
let mut prefixes: Vec<&str> = algorithm
.inclusive_prefixes()
.iter()
.map(String::as_str)
.collect();
prefixes.sort_unstable();
let prefix_list = prefixes
.into_iter()
.map(|p| if p.is_empty() { "#default" } else { p })
.collect::<Vec<_>>()
.join(" ");
writer.empty(
"ec:InclusiveNamespaces",
[
("xmlns:ec", EXCLUSIVE_C14N_NS),
("PrefixList", prefix_list.as_str()),
],
)?;
}
writer.end(&name)?;
Ok(())
}
}
}
fn write_xpath_expression<W: Write>(
writer: &mut Writer<W>,
prefix: Option<&str>,
local_name: &str,
filter: Option<&str>,
xpath: &XPathExpression,
) -> Result<(), std::io::Error> {
let name = qualified_name(prefix, local_name);
let namespace_attributes = xpath
.namespaces()
.iter()
.filter(|(namespace_prefix, _)| namespace_prefix.as_str() != "xml")
.map(|(namespace_prefix, uri)| (format!("xmlns:{namespace_prefix}"), uri))
.collect::<Vec<_>>();
let mut attributes = Vec::new();
if prefix.is_none() && filter.is_some() {
attributes.push(("xmlns", XPATH_FILTER2_TRANSFORM_URI));
}
if let Some(filter) = filter {
attributes.push(("Filter", filter));
}
for (attribute, uri) in &namespace_attributes {
attributes.push((attribute.as_str(), uri.as_str()));
}
writer.start(&name, attributes)?;
writer.text(xpath.expression())?;
writer.end(&name)?;
Ok(())
}
fn write_algorithm<W: Write>(
writer: &mut Writer<W>,
prefix: Option<&str>,
local_name: &str,
algorithm: &str,
) -> Result<(), std::io::Error> {
let name = qualified_name(prefix, local_name);
writer.empty(&name, [("Algorithm", algorithm)])?;
Ok(())
}
fn write_start<W: Write>(
writer: &mut Writer<W>,
prefix: Option<&str>,
local_name: &str,
) -> Result<(), std::io::Error> {
writer.start(&qualified_name(prefix, local_name), [])?;
Ok(())
}
fn write_end<W: Write>(
writer: &mut Writer<W>,
prefix: Option<&str>,
local_name: &str,
) -> Result<(), std::io::Error> {
writer.end(&qualified_name(prefix, local_name))?;
Ok(())
}
fn write_empty<W: Write>(
writer: &mut Writer<W>,
prefix: Option<&str>,
local_name: &str,
) -> Result<(), std::io::Error> {
writer.empty(&qualified_name(prefix, local_name), [])?;
Ok(())
}
fn qualified_name(prefix: Option<&str>, local_name: &str) -> String {
prefix.map_or_else(
|| local_name.to_owned(),
|prefix| format!("{prefix}:{local_name}"),
)
}
fn is_namespace_prefix(value: &str) -> bool {
!matches!(value, "xml" | "xmlns") && is_xml_ncname(value)
}