Skip to main content

xml_sec/xmldsig/extension/
tree.rs

1//! Accounted original-tree navigation and binary values for extension formats.
2//!
3//! Every read resolves its identity against the original view and charges the
4//! caller's current execution before data-dependent work. Nothing here
5//! authenticates the node it reads.
6
7use crate::document::{DocumentView, NodeIdentity, XmlDocumentError};
8use structured_asn1::memory::{MemoryError, Work};
9
10/// Original tree read failure, independent of the content it would have read.
11#[derive(Debug, thiserror::Error)]
12pub enum TreeError {
13    /// The identity is absent, foreign or stale in this original document view.
14    #[error(transparent)]
15    Document(#[from] XmlDocumentError),
16    /// A sibling query named a node outside the selected parent's children.
17    #[error("XML node is not a direct child of the selected parent")]
18    NotChild,
19    /// Binary content can only be read from an element.
20    #[error("XML binary value owner is not an element")]
21    NotElement,
22    /// The element content is not XML Schema base64Binary.
23    #[error("malformed XML base64 content: {0}")]
24    Base64(&'static str),
25    /// Current shared execution refused the work before it was performed.
26    #[error("XML tree resource failure: {0}")]
27    Resource(MemoryError),
28}
29
30/// One original node, borrowed only for the visitor.
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub enum NodeView<'a> {
33    /// An element's expanded name; prefixes do not participate.
34    Element {
35        /// Namespace name, or None for an unqualified element.
36        namespace: Option<&'a str>,
37        /// Local name after namespace processing.
38        local_name: &'a str,
39    },
40    /// Expanded character data, including CDATA; not original source bytes.
41    Text(&'a str),
42    /// A comment, processing instruction or the document root.
43    Other,
44}
45
46fn charge<W: Work>(work: &W, units: usize) -> Result<(), TreeError> {
47    let units = u64::try_from(units).map_err(|_| TreeError::Resource(MemoryError::WorkExceeded))?;
48    work.charge_work(units).map_err(TreeError::Resource)
49}
50
51/// Inspect one original node. The visitor cannot retain the borrowed names.
52pub fn inspect_node<W: Work, R>(
53    view: DocumentView<'_>,
54    node: NodeIdentity,
55    work: &W,
56    visitor: impl for<'a> FnOnce(NodeView<'a>) -> R,
57) -> Result<R, TreeError> {
58    charge(work, 1)?;
59    let node = view.resolve_node(node)?;
60    Ok(visitor(if node.is_element() {
61        let name = node.tag_name();
62        NodeView::Element {
63            namespace: name.namespace(),
64            local_name: name.name(),
65        }
66    } else if node.is_text() {
67        NodeView::Text(node.text().unwrap_or_default())
68    } else {
69        NodeView::Other
70    }))
71}
72
73/// Return the first direct child, including text and non-element children.
74pub fn first_child<W: Work>(
75    view: DocumentView<'_>,
76    parent: NodeIdentity,
77    work: &W,
78) -> Result<Option<NodeIdentity>, TreeError> {
79    charge(work, 1)?;
80    let parent = view.resolve_node(parent)?;
81    Ok(parent.first_child().map(|child| view.node_identity(child)))
82}
83
84/// Return the next direct sibling within `parent`, in original document order.
85/// A node outside that parent's children is refused rather than followed.
86pub fn next_sibling<W: Work>(
87    view: DocumentView<'_>,
88    parent: NodeIdentity,
89    node: NodeIdentity,
90    work: &W,
91) -> Result<Option<NodeIdentity>, TreeError> {
92    charge(work, 1)?;
93    let parent = view.resolve_node(parent)?;
94    let node = view.resolve_node(node)?;
95    if node.parent() != Some(parent) {
96        return Err(TreeError::NotChild);
97    }
98    Ok(node
99        .following_sibling()
100        .map(|sibling| view.node_identity(sibling)))
101}
102
103/// Whether `node` is `ancestor` itself or one of its descendants.
104pub fn subtree_contains<W: Work>(
105    view: DocumentView<'_>,
106    ancestor: NodeIdentity,
107    node: NodeIdentity,
108    work: &W,
109) -> Result<bool, TreeError> {
110    charge(work, 1)?;
111    let ancestor = view.resolve_node(ancestor)?;
112    let node = view.resolve_node(node)?;
113    Ok(crate::xmldsig::types::subtree_node_id_range(ancestor).contains(&node.id().get()))
114}
115
116/// Decode an element's base64Binary simple content into storage admitted by
117/// `work`, then lend the octets to the visitor. Comments and processing
118/// instructions contribute no text; element children are malformed content.
119/// XML Schema 1.0 Part 2 ยง3.2.16 fixes the alphabet, padding and the four
120/// XML whitespace characters (`whiteSpace` collapse).
121/// <https://www.w3.org/TR/2004/REC-xmlschema-2-20041028/#base64Binary>
122pub fn with_base64<W: Work, R>(
123    view: DocumentView<'_>,
124    owner: NodeIdentity,
125    work: &W,
126    visitor: impl for<'a> FnOnce(&'a [u8]) -> R,
127) -> Result<R, TreeError> {
128    charge(work, 1)?;
129    let node = view.resolve_node(owner)?;
130    if !node.is_element() {
131        return Err(TreeError::NotElement);
132    }
133    // Admit the child visit and both linear passes (lexical check, decode)
134    // before scanning any text.
135    let mut text = 0usize;
136    for child in node.children() {
137        charge(work, 1)?;
138        if child.is_text() {
139            text = text
140                .checked_add(child.text().map_or(0, str::len))
141                .ok_or(TreeError::Resource(MemoryError::WorkExceeded))?;
142        }
143    }
144    charge(
145        work,
146        text.checked_mul(2)
147            .ok_or(TreeError::Resource(MemoryError::WorkExceeded))?,
148    )?;
149    let payload =
150        crate::xmldsig::whitespace::XmlBase64Payload::new(node).map_err(TreeError::Base64)?;
151    let mut decoded = work.vector();
152    decoded
153        .try_reserve_exact(payload.decoded_len)
154        .map_err(TreeError::Resource)?;
155    // The decoder writes into initialized storage; the zero fill is its own
156    // admitted pass within the exact capacity reserved above.
157    const ZEROS: [u8; 512] = [0; 512];
158    let mut remaining = payload.decoded_len;
159    while remaining != 0 {
160        let chunk = remaining.min(ZEROS.len());
161        decoded
162            .try_extend_from_slice(&ZEROS[..chunk])
163            .map_err(TreeError::Resource)?;
164        remaining -= chunk;
165    }
166    payload
167        .decode_into(&mut decoded)
168        .map_err(TreeError::Base64)?;
169    Ok(visitor(&decoded))
170}