Skip to main content

xml_sec/xmldsig/
extension.rs

1//! Borrowed, accounted XML reads for signature-extension consumers.
2//!
3//! Reading a scalar does not authenticate its owning node, validate a signature
4//! or authorize a profile. Consumers retain those independent obligations.
5
6use crate::document::{DocumentView, NodeIdentity, XmlDocumentError};
7use structured_asn1::memory::{MemoryError, Work};
8
9mod scalar;
10pub use scalar::*;
11mod tree;
12pub use tree::*;
13
14/// Schema boolean attribute without an invented default for an absent value.
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub enum BooleanAttribute {
17    /// No attribute with the selected expanded name exists.
18    Absent,
19    /// An explicitly supplied XML Schema boolean value.
20    Value(bool),
21    /// The attribute exists but is outside the schema lexical space.
22    Malformed,
23}
24
25/// Processing failure, independent of an absent or malformed scalar value.
26#[derive(Debug, thiserror::Error)]
27pub enum ScalarError {
28    /// The identity is absent, foreign or stale in this original document view.
29    #[error(transparent)]
30    Document(#[from] XmlDocumentError),
31    /// Attributes can only be selected from an element.
32    #[error("XML scalar owner is not an element")]
33    NotElement,
34    /// Current shared execution refused the work before it was performed.
35    #[error("XML scalar resource failure: {0}")]
36    Resource(MemoryError),
37}
38
39pub(super) fn charge_bytes<W: Work>(work: &W, bytes: usize) -> Result<(), ScalarError> {
40    let bytes =
41        u64::try_from(bytes).map_err(|_| ScalarError::Resource(MemoryError::WorkExceeded))?;
42    work.charge_work(bytes).map_err(ScalarError::Resource)
43}
44
45/// Read an original attribute by expanded name with no allocation or reparsing.
46///
47/// `view` preserves document/generation identity. `work` is the caller's current
48/// execution, not a new account; lookup and lexical scans are admitted before
49/// execution. This mechanism does not prove the owner is authenticated and does
50/// not supply profile-specific defaults or permission.
51pub fn boolean_attribute<W: Work>(
52    view: DocumentView<'_>,
53    owner: NodeIdentity,
54    namespace: Option<&str>,
55    local_name: &str,
56    work: &W,
57) -> Result<BooleanAttribute, ScalarError> {
58    work.charge_work(1).map_err(ScalarError::Resource)?;
59    let node = view.resolve_node(owner)?;
60    if !node.is_element() {
61        return Err(ScalarError::NotElement);
62    }
63    for attribute in node.attributes() {
64        work.charge_work(1).map_err(ScalarError::Resource)?;
65        charge_bytes(work, attribute.name().len())?;
66        if attribute.name() != local_name {
67            continue;
68        }
69        if let Some(uri) = attribute.namespace() {
70            charge_bytes(work, uri.len())?;
71        }
72        if attribute.namespace() != namespace {
73            continue;
74        }
75        let value = attribute.value();
76        charge_bytes(work, value.len())?;
77        work.charge_work(5).map_err(ScalarError::Resource)?;
78        // XSD 1.0 Second Edition ยงยง3.2.2.1, 4.3.6: boolean admits only
79        // true/false/1/0, with fixed whiteSpace=collapse. None contains interior
80        // whitespace, so trimming XML whitespace suffices without a new String.
81        // https://www.w3.org/TR/2004/REC-xmlschema-2-20041028/#boolean
82        // https://www.w3.org/TR/2004/REC-xmlschema-2-20041028/#rf-whiteSpace
83        return Ok(match value.trim_matches([' ', '\t', '\r', '\n']) {
84            "true" | "1" => BooleanAttribute::Value(true),
85            "false" | "0" => BooleanAttribute::Value(false),
86            _ => BooleanAttribute::Malformed,
87        });
88    }
89    Ok(BooleanAttribute::Absent)
90}