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