use std::collections::HashSet;
use crate::xml::dom::Node;
use crate::xml::dom::storage::Storage as DomStorage;
use super::C14nError;
use super::NodeVisibility;
use super::ns_common::collect_ns_declarations;
use super::prefix::{attribute_prefix, element_prefix};
use super::scratch::{List, NamespaceMap, NamespaceRendering, Scratch};
use super::serialize::NsRenderer;
pub(crate) struct ExclusiveNsRenderer<'a> {
inclusive_prefixes: Prefixes<'a>,
}
enum Prefixes<'a> {
Stored(&'a HashSet<String>),
#[cfg(feature = "xmldsig")]
Lexical(&'a str),
}
impl Prefixes<'_> {
fn contains(&self, prefix: &str) -> bool {
match self {
Self::Stored(prefixes) => prefixes.contains(prefix),
#[cfg(feature = "xmldsig")]
Self::Lexical(list) => {
for token in super::prefix::inclusive_tokens(list) {
if token == prefix || (prefix.is_empty() && token == "#default") {
return true;
}
}
false
}
}
}
fn work(&self, prefix: &str) -> usize {
let probes = match self {
Self::Stored(set) => set.capacity(),
#[cfg(feature = "xmldsig")]
Self::Lexical(list) => list.len(),
};
probes
.saturating_add(2)
.saturating_mul(prefix.len().saturating_add(1))
.saturating_mul(4)
}
}
impl<'a> ExclusiveNsRenderer<'a> {
pub(crate) fn new(inclusive_prefixes: &'a HashSet<String>) -> Self {
Self {
inclusive_prefixes: Prefixes::Stored(inclusive_prefixes),
}
}
#[cfg(feature = "xmldsig")]
pub(crate) fn from_prefix_list(list: &'a str) -> Self {
Self {
inclusive_prefixes: Prefixes::Lexical(list),
}
}
}
impl<S: Scratch, D: DomStorage> NsRenderer<S, D> for ExclusiveNsRenderer<'_> {
fn render_namespaces<'n>(
&self,
node: Node<'n, '_, D>,
parent_rendered: &S::Namespaces<'n>,
visibility: Option<&dyn NodeVisibility<D>>,
scratch: &S,
) -> Result<NamespaceRendering<'n, S>, C14nError> {
let utilized = visibly_utilized_prefixes(node, visibility, scratch)?;
for ns in node.namespaces() {
let prefix = ns.name().unwrap_or("");
scratch.charge(self.inclusive_prefixes.work(prefix))?;
}
let (mut declarations, mut rendered) = collect_ns_declarations(
node,
parent_rendered,
visibility,
|prefix| self.inclusive_prefixes.contains(prefix),
|prefix, _| {
Ok(utilized.get(prefix, false)?.is_some()
|| self.inclusive_prefixes.contains(prefix))
},
scratch,
)?;
if let (Some(visibility), Some(parent)) = (visibility, node.parent())
&& parent.is_element()
{
for (ordinal, namespace) in node.namespaces().enumerate() {
let prefix = namespace.name().unwrap_or("");
let uri = namespace.uri();
scratch.charge(prefix.len().saturating_add(uri.len()).saturating_add(1))?;
let selected_here = visibility.contains_namespace_at(node, ordinal, namespace)?;
let declaration_suppressed =
parent_rendered.get(prefix, false)?.flatten() == Some(uri);
debug_assert!(
!(selected_here
&& declaration_suppressed
&& declarations.iter().any(|(declared, _)| *declared == prefix))
);
let nearest_utilizer_omitted_namespace =
parent_rendered.get(prefix, true)? == Some(None);
let uses_exclusive_rendering = utilized.get(prefix, false)?.is_some()
&& !self.inclusive_prefixes.contains(prefix);
if uses_exclusive_rendering
&& selected_here
&& declaration_suppressed
&& nearest_utilizer_omitted_namespace
{
declarations.push((prefix, uri))?;
declarations.sort(|left, right| {
scratch.charge(left.0.len().min(right.0.len()))?;
Ok(left.0.cmp(right.0))
})?;
}
if uses_exclusive_rendering {
rendered.insert(prefix, true, selected_here.then_some(uri))?;
}
}
}
Ok((declarations, rendered))
}
fn renders_selected_namespace_of_omitted_element(&self, prefix: &str) -> bool {
self.inclusive_prefixes.contains(prefix)
}
}
fn visibly_utilized_prefixes<'a, S: Scratch, D: DomStorage>(
node: Node<'a, '_, D>,
visibility: Option<&dyn NodeVisibility<D>>,
scratch: &S,
) -> Result<S::Namespaces<'a>, C14nError> {
let mut utilized = scratch.namespaces();
let el_prefix = element_prefix(node);
if !el_prefix.is_empty() {
utilized.insert(el_prefix, false, None)?;
} else {
utilized.insert("", false, None)?;
}
for (ordinal, attr) in node.attributes().enumerate() {
scratch.charge(attr.name().len().saturating_add(1))?;
if let Some(set) = visibility
&& !set.contains_attribute_at(node, ordinal, attr)?
{
continue;
}
let attr_prefix = attribute_prefix(node, &attr);
if !attr_prefix.is_empty() {
utilized.insert(attr_prefix, false, None)?;
}
}
Ok(utilized)
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
#[cfg(feature = "xmldsig")]
#[test]
fn stored_and_lexical_prefix_lists_use_the_same_xml_separators() {
let list = "p\u{a0}q p\t#default";
let algorithm = crate::c14n::C14nAlgorithm::new(crate::c14n::C14nMode::Exclusive1_0, false)
.with_prefix_list(list);
let stored = ExclusiveNsRenderer::new(algorithm.inclusive_prefixes());
let lexical = ExclusiveNsRenderer::from_prefix_list(list);
for (prefix, expected) in [("p", true), ("q", false), ("p\u{a0}q", true), ("", true)] {
assert_eq!(stored.inclusive_prefixes.contains(prefix), expected);
assert_eq!(lexical.inclusive_prefixes.contains(prefix), expected);
}
}
use super::super::serialize::{
C14nConfig, serialize_canonical, serialize_canonical_visible_with_position,
};
use super::*;
use crate::xml::dom::Document;
use std::collections::HashSet;
struct NamespaceGapVisibility;
impl NodeVisibility for NamespaceGapVisibility {
fn contains_node(&self, _node: Node<'_, '_>) -> Result<bool, C14nError> {
Ok(true)
}
fn contains_attribute(
&self,
_owner: Node<'_, '_>,
_namespace: Option<&str>,
_local_name: &str,
) -> Result<bool, C14nError> {
Ok(true)
}
fn contains_namespace(
&self,
owner: Node<'_, '_>,
prefix: &str,
_uri: &str,
) -> Result<bool, C14nError> {
Ok(prefix != "p" || owner.tag_name().name() != "gap")
}
}
fn exc_c14n(xml: &str, prefix_list: &HashSet<String>) -> String {
let doc = Document::parse(xml).expect("parse");
let renderer = ExclusiveNsRenderer::new(prefix_list);
let mut out = Vec::new();
serialize_canonical(
&doc,
None,
false,
&renderer,
C14nConfig {
inherit_xml_attrs: false,
fixup_xml_base: false,
},
&mut out,
)
.expect("c14n");
String::from_utf8(out).expect("utf8")
}
#[test]
fn only_utilized_ns_rendered() {
let xml = r#"<root xmlns:a="http://a.com" xmlns:b="http://b.com"><a:child/></root>"#;
let result = exc_c14n(xml, &HashSet::new());
assert!(!result.contains("xmlns:b"));
assert!(result.contains(r#"<a:child xmlns:a="http://a.com">"#));
}
#[test]
fn forced_prefix_via_prefix_list() {
let xml = r#"<root xmlns:a="http://a.com" xmlns:b="http://b.com"><child/></root>"#;
let mut forced = HashSet::new();
forced.insert("b".to_string());
let result = exc_c14n(xml, &forced);
assert!(result.contains(r#"xmlns:b="http://b.com""#));
}
#[test]
fn sibling_elements_redeclare() {
let xml = r#"<root xmlns:a="http://a.com"><a:one/><a:two/></root>"#;
let result = exc_c14n(xml, &HashSet::new());
assert!(result.contains(r#"<a:one xmlns:a="http://a.com">"#));
assert!(result.contains(r#"<a:two xmlns:a="http://a.com">"#));
}
#[test]
fn default_ns_utilized() {
let xml = r#"<root xmlns="http://example.com"><child/></root>"#;
let result = exc_c14n(xml, &HashSet::new());
assert!(result.contains(r#"<root xmlns="http://example.com">"#));
assert_eq!(
result,
r#"<root xmlns="http://example.com"><child></child></root>"#
);
}
#[test]
fn unprefixed_element_undeclares_default_ns() {
let xml = r#"<root xmlns="http://example.com"><child xmlns=""/></root>"#;
let result = exc_c14n(xml, &HashSet::new());
assert!(
result.contains(r#"<child xmlns="">"#),
"xmlns=\"\" must be emitted for undeclaration. Got: {result}"
);
}
#[test]
fn redeclares_prefix_after_one_namespace_node_discontinuity() {
let xml = r#"<p:root xmlns:p="urn:p"><p:gap><p:leaf/></p:gap></p:root>"#;
let doc = Document::parse(xml).expect("parse");
let prefix_list = HashSet::new();
let renderer = ExclusiveNsRenderer::new(&prefix_list);
let mut out = Vec::new();
serialize_canonical_visible_with_position(
&doc,
Some(&NamespaceGapVisibility),
false,
&renderer,
C14nConfig {
inherit_xml_attrs: false,
fixup_xml_base: false,
},
None,
&mut out,
)
.expect("c14n");
assert_eq!(
String::from_utf8(out).expect("utf8"),
r#"<p:root xmlns:p="urn:p"><p:gap><p:leaf xmlns:p="urn:p"></p:leaf></p:gap></p:root>"#
);
}
}