1use crate::xml_input as xml_sec_xml_input;
4use std::{collections::HashSet, io::Write};
5
6use base64::Engine;
7use xml_sec_xml_input::lexical::Writer;
8
9use crate::c14n::{C14nAlgorithm, C14nMode, canonicalize_bounded_with_xml_base_budget};
10use crate::policy::{PolicyViolation, SigningPolicy};
11use crate::xml::{is_xml_1_0_character, is_xml_ncname};
12
13use super::mutation::{
14 fill_signature_value_with_budget, fill_signed_info_digest_values_with_budget,
15 padded_base64_len_for_xml, projected_signature_value_output_len_at_index_with_budget,
16 zero_base64_placeholder,
17};
18use super::transforms::{
19 ENVELOPED_SIGNATURE_XPATH_EXPR, ENVELOPED_SIGNATURE_XPATH_PREFIX, TransformExecutionBudget,
20 XPathSignatureParseBudget, map_c14n_resource_policy_violation, transform_chain_produces_binary,
21 validate_signing_transform_policy, validate_xpath_namespace_budget_with_resources,
22};
23use super::uri::validate_signing_reference_uri;
24use super::{
25 BASE64_TRANSFORM_URI, DigestAlgorithm, ENVELOPED_SIGNATURE_URI, SignatureAlgorithm,
26 SignatureContext, Transform, XPATH_FILTER2_TRANSFORM_URI, XPATH_TRANSFORM_URI, XPathExpression,
27};
28
29const XMLDSIG_NS: &str = "http://www.w3.org/2000/09/xmldsig#";
30const XML_NS: &str = "http://www.w3.org/XML/1998/namespace";
31const XMLNS_NS: &str = "http://www.w3.org/2000/xmlns/";
32const EXCLUSIVE_C14N_NS: &str = "http://www.w3.org/2001/10/xml-exc-c14n#";
33
34#[derive(Debug, thiserror::Error)]
36pub enum SignatureBuilderError {
37 #[error("invalid XML namespace prefix: {0}")]
39 InvalidNamespacePrefix(String),
40 #[error("XML namespace URI contains a character forbidden by XML 1.0: {0:?}")]
42 InvalidNamespaceUri(String),
43 #[error("XPath namespace binding conflicts with XMLDSig prefix: {0}")]
45 NamespacePrefixConflict(String),
46 #[error("invalid {element} Id: {value}")]
48 InvalidId {
49 element: &'static str,
51 value: String,
53 },
54 #[error("a signature template requires at least one Reference")]
56 MissingReference,
57 #[error("signing policy violation: {0}")]
59 Policy(#[from] PolicyViolation),
60 #[error("invalid XPath expression: {0}")]
62 InvalidXPath(String),
63 #[error("algorithm is not allowed for signing: {0}")]
65 SigningAlgorithmDisabled(&'static str),
66 #[error("signature context is not applicable to algorithm: {0}")]
68 SignatureContextNotApplicable(&'static str),
69 #[error("XML serialization error: {0}")]
71 Serialization(#[from] std::io::Error),
72 #[error("XML writer emitted invalid UTF-8: {0}")]
74 InvalidUtf8(#[from] std::string::FromUtf8Error),
75 #[error("generated XML template is invalid: {0}")]
77 GeneratedXml(#[from] crate::xml::dom::ParseError),
78 #[error("generated SignedInfo canonicalization failed: {0}")]
80 Canonicalization(#[from] crate::c14n::C14nError),
81 #[error("generated XML template is missing SignedInfo")]
83 MissingGeneratedSignedInfo,
84 #[error("generated XML template validation failed: {0}")]
86 GeneratedMutation(#[source] super::mutation::XmlMutationError),
87}
88
89#[derive(Debug, Clone)]
91pub struct ReferenceBuilder {
92 uri: String,
93 id: Option<String>,
94 ref_type: Option<String>,
95 transforms: Vec<Transform>,
96 digest_method: DigestAlgorithm,
97}
98
99impl ReferenceBuilder {
100 #[must_use]
102 pub fn new(digest_method: DigestAlgorithm) -> Self {
103 Self {
104 uri: String::new(),
105 id: None,
106 ref_type: None,
107 transforms: Vec::new(),
108 digest_method,
109 }
110 }
111
112 #[must_use]
117 pub fn uri(mut self, uri: impl Into<String>) -> Self {
118 self.uri = uri.into();
119 self
120 }
121
122 #[must_use]
124 pub fn id(mut self, id: impl Into<String>) -> Self {
125 self.id = Some(id.into());
126 self
127 }
128
129 #[must_use]
131 pub fn ref_type(mut self, ref_type: impl Into<String>) -> Self {
132 self.ref_type = Some(ref_type.into());
133 self
134 }
135
136 #[must_use]
138 pub fn transform(mut self, transform: Transform) -> Self {
139 self.transforms.push(transform);
140 self
141 }
142}
143
144#[derive(Debug, Clone)]
146pub struct SignatureBuilder {
147 c14n_method: C14nAlgorithm,
148 sign_method: SignatureAlgorithm,
149 signature_context: SignatureContext,
150 ns_prefix: Option<String>,
151 signature_id: Option<String>,
152 references: Vec<ReferenceBuilder>,
153 include_key_info: bool,
154}
155
156impl SignatureBuilder {
157 #[must_use]
159 pub fn new(c14n_method: C14nAlgorithm, sign_method: SignatureAlgorithm) -> Self {
160 Self {
161 c14n_method,
162 sign_method,
163 signature_context: SignatureContext::default(),
164 ns_prefix: None,
165 signature_id: None,
166 references: Vec::new(),
167 include_key_info: false,
168 }
169 }
170
171 #[must_use]
173 pub fn ns_prefix(mut self, prefix: impl Into<String>) -> Self {
174 self.ns_prefix = Some(prefix.into());
175 self
176 }
177
178 #[must_use]
180 pub fn signature_id(mut self, id: impl Into<String>) -> Self {
181 self.signature_id = Some(id.into());
182 self
183 }
184
185 #[must_use]
188 pub fn signature_context(mut self, context: SignatureContext) -> Self {
189 self.signature_context = context;
190 self
191 }
192
193 #[must_use]
195 pub fn add_reference(mut self, reference: ReferenceBuilder) -> Self {
196 self.references.push(reference);
197 self
198 }
199
200 #[must_use]
202 pub fn key_info(mut self, include: bool) -> Self {
203 self.include_key_info = include;
204 self
205 }
206
207 pub fn build_template(&self) -> Result<String, SignatureBuilderError> {
209 self.build_template_with_policy(&SigningPolicy::default())
210 }
211
212 pub fn build_template_with_policy(
217 &self,
218 policy: &SigningPolicy,
219 ) -> Result<String, SignatureBuilderError> {
220 let budget = TransformExecutionBudget::from_resources(&policy.resources);
221 let mut xpath_parse_budget = XPathSignatureParseBudget::from_resources(&policy.resources);
222 self.build_template_with_policy_and_signature_output_len(
223 policy,
224 None,
225 &budget,
226 &mut xpath_parse_budget,
227 )
228 }
229
230 pub(super) fn build_template_with_policy_for_signature_output(
231 &self,
232 policy: &SigningPolicy,
233 signature_output_len: usize,
234 budget: &TransformExecutionBudget,
235 xpath_parse_budget: &mut XPathSignatureParseBudget,
236 ) -> Result<String, SignatureBuilderError> {
237 self.build_template_with_policy_and_signature_output_len(
238 policy,
239 Some(signature_output_len),
240 budget,
241 xpath_parse_budget,
242 )
243 }
244
245 fn build_template_with_policy_and_signature_output_len(
246 &self,
247 policy: &SigningPolicy,
248 signature_output_len: Option<usize>,
249 budget: &TransformExecutionBudget,
250 xpath_parse_budget: &mut XPathSignatureParseBudget,
251 ) -> Result<String, SignatureBuilderError> {
252 policy.validate()?;
253 self.validate(policy, xpath_parse_budget)?;
254
255 let prefix = self.ns_prefix.as_deref();
256 let mut writer = Writer::new(Vec::new());
257 let signature_name = qualified_name(prefix, "Signature");
258 let namespace_attr = prefix.map_or_else(|| "xmlns".to_owned(), |p| format!("xmlns:{p}"));
259 let mut attributes = vec![(namespace_attr.as_str(), XMLDSIG_NS)];
260 if let Some(id) = &self.signature_id {
261 attributes.push(("Id", id.as_str()));
262 }
263 writer.start(&signature_name, attributes)?;
264
265 write_start(&mut writer, prefix, "SignedInfo")?;
266 write_algorithm(
267 &mut writer,
268 prefix,
269 "CanonicalizationMethod",
270 self.c14n_method.uri(),
271 )?;
272 if self.signature_context.as_bytes().is_empty() {
273 write_algorithm(
274 &mut writer,
275 prefix,
276 "SignatureMethod",
277 self.sign_method.uri(),
278 )?;
279 } else {
280 let method_name = qualified_name(prefix, "SignatureMethod");
281 writer.start(&method_name, [("Algorithm", self.sign_method.uri())])?;
282 let context_name = self.sign_method.context_element().ok_or(
287 SignatureBuilderError::SignatureContextNotApplicable(self.sign_method.uri()),
288 )?;
289 writer.start(
290 context_name,
291 [("xmlns", super::parse::EXPERIMENTAL_SIGNATURE_NS)],
292 )?;
293 let mut encoded = [0_u8; 340];
294 let length = base64::engine::general_purpose::STANDARD
295 .encode_slice(self.signature_context.as_bytes(), &mut encoded)
296 .map_err(std::io::Error::other)?;
297 writer.text(std::str::from_utf8(&encoded[..length]).map_err(std::io::Error::other)?)?;
298 writer.end(context_name)?;
299 writer.end(&method_name)?;
300 }
301 for reference in &self.references {
302 write_reference(&mut writer, prefix, reference)?;
303 }
304 write_end(&mut writer, prefix, "SignedInfo")?;
305 write_empty(&mut writer, prefix, "SignatureValue")?;
306 if self.include_key_info {
307 write_empty(&mut writer, prefix, "KeyInfo")?;
308 }
309 writer.end(&signature_name)?;
310
311 let template = String::from_utf8(writer.into_inner())?;
312 policy.resources.validate_xml_document_len(template.len())?;
315 let digest_placeholders = self.references.iter().map(|reference| {
316 base64::engine::general_purpose::STANDARD.encode(vec![
317 0_u8;
318 reference
319 .digest_method
320 .output_len()
321 ])
322 });
323 let digest_validation_template = fill_signed_info_digest_values_with_budget(
324 &template,
325 digest_placeholders,
326 Some(policy),
327 Some(budget.xml_parse_work()),
328 )
329 .map_err(map_generated_mutation_error)?;
330 let validation_template = if let Some(signature_output_len) = signature_output_len {
331 let encoded_signature_len = padded_base64_len_for_xml(signature_output_len, policy)
332 .map_err(map_generated_mutation_error)?;
333 let projected_document_len = projected_signature_value_output_len_at_index_with_budget(
334 &digest_validation_template,
335 encoded_signature_len,
336 0,
337 Some(policy),
338 Some(budget.xml_parse_work()),
339 )
340 .map_err(map_generated_mutation_error)?;
341 policy
342 .resources
343 .validate_xml_document_len(projected_document_len)?;
344 let signature_placeholder =
345 zero_base64_placeholder(signature_output_len, encoded_signature_len);
346 fill_signature_value_with_budget(
347 &digest_validation_template,
348 &signature_placeholder,
349 Some(policy),
350 Some(budget.xml_parse_work()),
351 )
352 .map_err(map_generated_mutation_error)?
353 } else {
354 digest_validation_template
355 };
356 policy
357 .resources
358 .validate_xml_document_len(validation_template.len())?;
359 let settings =
360 crate::document::DocumentParseSettings::from_policy(&policy.xml, &policy.resources);
361 let document = super::mutation::parse_with_options_and_budget(
362 &validation_template,
363 settings,
364 Some(budget.xml_parse_work()),
365 )
366 .map_err(|error| match error.into_policy_violation(settings) {
367 Ok(error) => SignatureBuilderError::Policy(error),
368 Err(crate::document::XmlDocumentError::Parse(error)) => {
369 SignatureBuilderError::GeneratedXml(error)
370 }
371 Err(error) => SignatureBuilderError::GeneratedMutation(
372 super::mutation::XmlMutationError::Document(error),
373 ),
374 })?;
375 let signed_info = document
376 .root_element()
377 .children()
378 .find(|node| node.has_tag_name((XMLDSIG_NS, "SignedInfo")))
379 .ok_or(SignatureBuilderError::MissingGeneratedSignedInfo)?;
380 let signed_info_subtree: HashSet<_> =
381 signed_info.descendants().map(|node| node.id()).collect();
382 let mut canonical_signed_info = Vec::new();
383 canonicalize_bounded_with_xml_base_budget(
384 &document,
385 Some(&|node| signed_info_subtree.contains(&node.id())),
386 &self.c14n_method,
387 budget.remaining_c14n_output(),
388 budget.xml_base_resolution(),
389 &mut canonical_signed_info,
390 )
391 .map_err(|error| {
392 map_c14n_resource_policy_violation(
393 &error,
394 crate::policy::resource_name::CANONICALIZED_BYTES,
395 budget.c14n_output_limit(),
396 )
397 .map_or_else(
398 || SignatureBuilderError::Canonicalization(error),
399 SignatureBuilderError::Policy,
400 )
401 })?;
402 budget.charge_c14n_output_policy(canonical_signed_info.len())?;
403 Ok(template)
404 }
405
406 pub(super) fn signature_method(&self) -> SignatureAlgorithm {
407 self.sign_method
408 }
409
410 fn validate(
411 &self,
412 policy: &SigningPolicy,
413 xpath_signature_budget: &mut XPathSignatureParseBudget,
414 ) -> Result<(), SignatureBuilderError> {
415 if !self.signature_context.as_bytes().is_empty()
416 && self.sign_method.context_element().is_none()
417 {
418 return Err(SignatureBuilderError::SignatureContextNotApplicable(
419 self.sign_method.uri(),
420 ));
421 }
422 if let Some(prefix) = &self.ns_prefix
423 && !is_namespace_prefix(prefix)
424 {
425 return Err(SignatureBuilderError::InvalidNamespacePrefix(
426 prefix.clone(),
427 ));
428 }
429 if let Some(id) = &self.signature_id
430 && !is_xml_ncname(id)
431 {
432 return Err(SignatureBuilderError::InvalidId {
433 element: "Signature",
434 value: id.clone(),
435 });
436 }
437 if self.references.is_empty() {
438 return Err(SignatureBuilderError::MissingReference);
439 }
440 if self.references.len() > policy.resources.max_references {
441 return Err(PolicyViolation::ResourceLimit {
442 resource: crate::policy::resource_name::SIGNATURE_REFERENCES,
443 maximum: policy.resources.max_references,
444 actual: self.references.len(),
445 }
446 .into());
447 }
448 for reference in &self.references {
449 validate_signing_reference_uri(&reference.uri, policy)?;
450 let generated_transforms = reference_transforms_for_generation(reference);
451 if generated_transforms.len() > policy.resources.max_transforms_per_reference {
452 return Err(PolicyViolation::ResourceLimit {
453 resource: crate::policy::resource_name::REFERENCE_TRANSFORMS,
454 maximum: policy.resources.max_transforms_per_reference,
455 actual: generated_transforms.len(),
456 }
457 .into());
458 }
459 let initial_binary = !reference.uri.is_empty() && !reference.uri.starts_with('#');
460 validate_signing_transform_policy(
461 initial_binary,
462 &generated_transforms,
463 policy.transforms.allowed_algorithms.as_ref(),
464 )?;
465 for transform in &reference.transforms {
466 match transform {
467 Transform::XpathExcludeAllSignatures => {
468 validate_xpath_source(
469 ENVELOPED_SIGNATURE_XPATH_EXPR,
470 xpath_signature_budget,
471 )?;
472 }
473 Transform::XPath(xpath) => {
474 validate_xpath_source(xpath.expression(), xpath_signature_budget)?;
475 }
476 Transform::XPathFilter2(filters) => {
477 if filters.is_empty() {
478 return Err(SignatureBuilderError::InvalidXPath(
479 "XPath Filter 2.0 requires at least one expression".into(),
480 ));
481 }
482 if filters.len() > policy.resources.max_xpath_filters {
483 return Err(PolicyViolation::ResourceLimit {
484 resource: crate::policy::resource_name::XPATH_FILTERS,
485 maximum: policy.resources.max_xpath_filters,
486 actual: filters.len(),
487 }
488 .into());
489 }
490 for filter in filters {
491 validate_xpath_source(
492 filter.xpath().expression(),
493 xpath_signature_budget,
494 )?;
495 }
496 }
497 _ => {}
498 }
499 }
500 validate_xpath_namespace_budget_with_resources(
501 &reference.transforms,
502 self.ns_prefix.as_deref().map(|prefix| (prefix, XMLDSIG_NS)),
503 &policy.resources,
504 )
505 .map_err(map_transform_validation_error)?;
506 }
507 for (prefix, uri, shares_signature_namespace) in
508 self.references.iter().flat_map(|reference| {
509 reference
510 .transforms
511 .iter()
512 .flat_map(|transform| match transform {
513 Transform::XPath(xpath) => xpath
514 .namespaces()
515 .iter()
516 .map(|(prefix, uri)| (prefix, uri, true))
517 .collect::<Vec<_>>(),
518 Transform::XPathFilter2(filters) => filters
519 .iter()
520 .flat_map(|filter| {
521 filter
522 .xpath()
523 .namespaces()
524 .iter()
525 .map(|(prefix, uri)| (prefix, uri, false))
526 })
527 .collect(),
528 _ => Vec::new(),
529 })
530 })
531 {
532 if uri.is_empty() || uri == XMLNS_NS || !uri.chars().all(is_xml_1_0_character) {
535 return Err(SignatureBuilderError::InvalidNamespaceUri(uri.clone()));
536 }
537 if prefix == "xmlns"
538 || (prefix == "xml") != (uri == XML_NS)
539 || (prefix != "xml" && !is_namespace_prefix(prefix))
540 {
541 return Err(SignatureBuilderError::InvalidNamespacePrefix(
542 prefix.clone(),
543 ));
544 }
545 if shares_signature_namespace
548 && self.ns_prefix.as_ref() == Some(prefix)
549 && uri != XMLDSIG_NS
550 {
551 return Err(SignatureBuilderError::NamespacePrefixConflict(
552 prefix.clone(),
553 ));
554 }
555 }
556 policy.check_signature_algorithm(self.sign_method)?;
557 if policy
558 .transforms
559 .allowed_algorithms
560 .as_ref()
561 .is_some_and(|allowed| !allowed.contains(self.c14n_method.uri()))
562 {
563 return Err(PolicyViolation::Algorithm {
564 operation: "signing transform",
565 algorithm: self.c14n_method.uri().to_owned(),
566 }
567 .into());
568 }
569 for reference in &self.references {
570 if let Some(id) = &reference.id
571 && !is_xml_ncname(id)
572 {
573 return Err(SignatureBuilderError::InvalidId {
574 element: "Reference",
575 value: id.clone(),
576 });
577 }
578 policy.check_digest_algorithm(reference.digest_method)?;
579 }
580 Ok(())
581 }
582}
583
584fn validate_xpath_source(
585 source: &str,
586 budget: &mut XPathSignatureParseBudget,
587) -> Result<(), SignatureBuilderError> {
588 if let Some(character) = source
589 .chars()
590 .find(|character| !is_xml_1_0_character(*character))
591 {
592 return Err(SignatureBuilderError::InvalidXPath(format!(
593 "XPath expression contains a character forbidden by XML 1.0: {character:?}"
594 )));
595 }
596 budget
597 .validate_expression(source)
598 .map_err(map_transform_validation_error)
599}
600
601fn map_transform_validation_error(error: super::TransformError) -> SignatureBuilderError {
602 match error {
603 super::TransformError::Policy(error) => SignatureBuilderError::Policy(error),
604 error => SignatureBuilderError::InvalidXPath(error.to_string()),
605 }
606}
607
608fn map_generated_mutation_error(error: super::mutation::XmlMutationError) -> SignatureBuilderError {
609 match error {
610 super::mutation::XmlMutationError::Policy(violation) => {
611 SignatureBuilderError::Policy(violation)
612 }
613 other => SignatureBuilderError::GeneratedMutation(other),
614 }
615}
616
617fn write_reference<W: Write>(
618 writer: &mut Writer<W>,
619 prefix: Option<&str>,
620 reference: &ReferenceBuilder,
621) -> Result<(), std::io::Error> {
622 let name = qualified_name(prefix, "Reference");
623 let mut attributes = Vec::new();
624 if let Some(id) = &reference.id {
625 attributes.push(("Id", id.as_str()));
626 }
627 if let Some(ref_type) = &reference.ref_type {
628 attributes.push(("Type", ref_type.as_str()));
629 }
630 attributes.push(("URI", reference.uri.as_str()));
631 writer.start(&name, attributes)?;
632
633 let generated_transforms = reference_transforms_for_generation(reference);
634 if !generated_transforms.is_empty() {
635 write_start(writer, prefix, "Transforms")?;
636 for transform in &generated_transforms {
637 write_transform(writer, prefix, transform)?;
638 }
639 write_end(writer, prefix, "Transforms")?;
640 }
641 write_algorithm(
642 writer,
643 prefix,
644 "DigestMethod",
645 reference.digest_method.uri(),
646 )?;
647 write_empty(writer, prefix, "DigestValue")?;
648 writer.end(&name)?;
649 Ok(())
650}
651
652fn reference_transforms_for_generation(reference: &ReferenceBuilder) -> Vec<Transform> {
653 let mut transforms = reference.transforms.clone();
654 let initial_binary = !reference.uri.is_empty() && !reference.uri.starts_with('#');
655 if !transform_chain_produces_binary(initial_binary, &transforms) {
656 transforms.push(Transform::C14n(C14nAlgorithm::new(
657 C14nMode::Inclusive1_1,
658 false,
659 )));
660 }
661 transforms
662}
663
664fn write_transform<W: Write>(
665 writer: &mut Writer<W>,
666 prefix: Option<&str>,
667 transform: &Transform,
668) -> Result<(), std::io::Error> {
669 match transform {
670 Transform::Enveloped => {
671 write_algorithm(writer, prefix, "Transform", ENVELOPED_SIGNATURE_URI)
672 }
673 Transform::XpathExcludeAllSignatures => {
674 let name = qualified_name(prefix, "Transform");
675 writer.start(&name, [("Algorithm", XPATH_TRANSFORM_URI)])?;
676 let xpath_name = qualified_name(prefix, "XPath");
677 let namespace = format!("xmlns:{ENVELOPED_SIGNATURE_XPATH_PREFIX}");
678 writer.start(&xpath_name, [(namespace.as_str(), XMLDSIG_NS)])?;
679 writer.text(ENVELOPED_SIGNATURE_XPATH_EXPR)?;
680 writer.end(&xpath_name)?;
681 writer.end(&name)?;
682 Ok(())
683 }
684 Transform::XPath(xpath) => {
685 let transform_name = qualified_name(prefix, "Transform");
686 writer.start(&transform_name, [("Algorithm", XPATH_TRANSFORM_URI)])?;
687 write_xpath_expression(writer, prefix, "XPath", None, xpath)?;
688 writer.end(&transform_name)?;
689 Ok(())
690 }
691 Transform::XPathFilter2(filters) => {
692 let transform_name = qualified_name(prefix, "Transform");
693 writer.start(
694 &transform_name,
695 [("Algorithm", XPATH_FILTER2_TRANSFORM_URI)],
696 )?;
697 for filter in filters {
698 write_xpath_expression(
699 writer,
700 None,
701 "XPath",
702 Some(filter.operation().as_str()),
703 filter.xpath(),
704 )?;
705 }
706 writer.end(&transform_name)?;
707 Ok(())
708 }
709 Transform::Base64Decode => {
710 write_algorithm(writer, prefix, "Transform", BASE64_TRANSFORM_URI)
711 }
712 Transform::C14n(algorithm) if algorithm.inclusive_prefixes().is_empty() => {
713 write_algorithm(writer, prefix, "Transform", algorithm.uri())
714 }
715 Transform::C14n(algorithm) => {
716 let name = qualified_name(prefix, "Transform");
717 writer.start(&name, [("Algorithm", algorithm.uri())])?;
718
719 if algorithm.mode() == C14nMode::Exclusive1_0 {
720 let mut prefixes: Vec<&str> = algorithm
721 .inclusive_prefixes()
722 .iter()
723 .map(String::as_str)
724 .collect();
725 prefixes.sort_unstable();
726 let prefix_list = prefixes
727 .into_iter()
728 .map(|p| if p.is_empty() { "#default" } else { p })
729 .collect::<Vec<_>>()
730 .join(" ");
731 writer.empty(
732 "ec:InclusiveNamespaces",
733 [
734 ("xmlns:ec", EXCLUSIVE_C14N_NS),
735 ("PrefixList", prefix_list.as_str()),
736 ],
737 )?;
738 }
739 writer.end(&name)?;
740 Ok(())
741 }
742 }
743}
744
745fn write_xpath_expression<W: Write>(
746 writer: &mut Writer<W>,
747 prefix: Option<&str>,
748 local_name: &str,
749 filter: Option<&str>,
750 xpath: &XPathExpression,
751) -> Result<(), std::io::Error> {
752 let name = qualified_name(prefix, local_name);
753 let namespace_attributes = xpath
754 .namespaces()
755 .iter()
756 .filter(|(namespace_prefix, _)| namespace_prefix.as_str() != "xml")
757 .map(|(namespace_prefix, uri)| (format!("xmlns:{namespace_prefix}"), uri))
758 .collect::<Vec<_>>();
759 let mut attributes = Vec::new();
760 if prefix.is_none() && filter.is_some() {
761 attributes.push(("xmlns", XPATH_FILTER2_TRANSFORM_URI));
762 }
763 if let Some(filter) = filter {
764 attributes.push(("Filter", filter));
765 }
766 for (attribute, uri) in &namespace_attributes {
767 attributes.push((attribute.as_str(), uri.as_str()));
768 }
769 writer.start(&name, attributes)?;
770 writer.text(xpath.expression())?;
771 writer.end(&name)?;
772 Ok(())
773}
774
775fn write_algorithm<W: Write>(
776 writer: &mut Writer<W>,
777 prefix: Option<&str>,
778 local_name: &str,
779 algorithm: &str,
780) -> Result<(), std::io::Error> {
781 let name = qualified_name(prefix, local_name);
782 writer.empty(&name, [("Algorithm", algorithm)])?;
783 Ok(())
784}
785
786fn write_start<W: Write>(
787 writer: &mut Writer<W>,
788 prefix: Option<&str>,
789 local_name: &str,
790) -> Result<(), std::io::Error> {
791 writer.start(&qualified_name(prefix, local_name), [])?;
792 Ok(())
793}
794
795fn write_end<W: Write>(
796 writer: &mut Writer<W>,
797 prefix: Option<&str>,
798 local_name: &str,
799) -> Result<(), std::io::Error> {
800 writer.end(&qualified_name(prefix, local_name))?;
801 Ok(())
802}
803
804fn write_empty<W: Write>(
805 writer: &mut Writer<W>,
806 prefix: Option<&str>,
807 local_name: &str,
808) -> Result<(), std::io::Error> {
809 writer.empty(&qualified_name(prefix, local_name), [])?;
810 Ok(())
811}
812
813fn qualified_name(prefix: Option<&str>, local_name: &str) -> String {
814 prefix.map_or_else(
815 || local_name.to_owned(),
816 |prefix| format!("{prefix}:{local_name}"),
817 )
818}
819
820fn is_namespace_prefix(value: &str) -> bool {
821 !matches!(value, "xml" | "xmlns") && is_xml_ncname(value)
823}