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