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