Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/xmldsig/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,6 @@ pub use transforms::{
pub use types::{NodeSet, TransformData, TransformError};
pub use verify::{
DsigError, DsigStatus, FailureReason, KeyResolver, ReferenceProcessingError, ReferenceResult,
ReferencesResult, UriTypeSet, VerifyContext, VerifyResult, VerifyingKey,
ReferenceSet, ReferencesResult, UriTypeSet, VerifyContext, VerifyResult, VerifyingKey,
process_all_references, process_reference, verify_signature_with_pem_key,
};
2 changes: 1 addition & 1 deletion src/xmldsig/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ pub fn parse_signed_info(signed_info_node: Node) -> Result<SignedInfo, ParseErro
/// Parse a single `<ds:Reference>` element.
///
/// Structure: `<Transforms>?` → `<DigestMethod>` → `<DigestValue>`
fn parse_reference(reference_node: Node) -> Result<Reference, ParseError> {
pub(crate) fn parse_reference(reference_node: Node) -> Result<Reference, ParseError> {
let uri = reference_node.attribute("URI").map(String::from);
let id = reference_node.attribute("Id").map(String::from);
let ref_type = reference_node.attribute("Type").map(String::from);
Expand Down
36 changes: 25 additions & 11 deletions src/xmldsig/uri.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
use std::collections::hash_map::Entry;
use std::collections::{HashMap, HashSet};

use roxmltree::{Document, Node};
use roxmltree::{Document, Node, NodeId};

use super::types::{NodeSet, TransformData, TransformError};

Expand Down Expand Up @@ -169,7 +169,7 @@ impl<'a> UriReferenceResolver<'a> {
Ok(TransformData::NodeSet(
NodeSet::entire_document_with_comments(self.doc),
))
} else if let Some(id) = parse_xpointer_id(fragment) {
} else if let Some(id) = parse_xpointer_id_fragment(fragment) {
// xpointer(id('foo')) → same as bare-name #foo
// Reject empty parsed ID (e.g., xpointer(id(''))) — not a valid XML Name
if id.is_empty() {
Expand Down Expand Up @@ -198,6 +198,14 @@ impl<'a> UriReferenceResolver<'a> {
self.id_map.contains_key(id)
}

/// Resolve a same-document ID token to a stable node identity.
///
/// Returns `None` when the ID is absent or ambiguous (duplicate ID collision),
/// matching the resolver behavior used by `dereference()`.
pub(crate) fn node_id_for_id(&self, id: &str) -> Option<NodeId> {
self.id_map.get(id).map(|node| node.id())
}

/// Get the number of registered IDs.
pub fn id_count(&self) -> usize {
self.id_map.len()
Expand All @@ -206,7 +214,7 @@ impl<'a> UriReferenceResolver<'a> {

/// Parse `xpointer(id('value'))` or `xpointer(id("value"))` and return the ID value.
/// Returns `None` if the fragment doesn't match this pattern.
fn parse_xpointer_id(fragment: &str) -> Option<&str> {
pub(crate) fn parse_xpointer_id_fragment(fragment: &str) -> Option<&str> {
let inner = fragment.strip_prefix("xpointer(id(")?.strip_suffix("))")?;

// Strip single or double quotes using safe helpers to avoid panics
Expand Down Expand Up @@ -622,21 +630,27 @@ mod tests {
#[test]
fn parse_xpointer_id_variants() {
// Valid forms
assert_eq!(super::parse_xpointer_id("xpointer(id('foo'))"), Some("foo"));
assert_eq!(
super::parse_xpointer_id(r#"xpointer(id("bar"))"#),
super::parse_xpointer_id_fragment("xpointer(id('foo'))"),
Some("foo")
);
assert_eq!(
super::parse_xpointer_id_fragment(r#"xpointer(id("bar"))"#),
Some("bar")
);

// Invalid forms
assert_eq!(super::parse_xpointer_id("xpointer(/)"), None);
assert_eq!(super::parse_xpointer_id("xpointer(id(foo))"), None); // no quotes
assert_eq!(super::parse_xpointer_id("not-xpointer"), None);
assert_eq!(super::parse_xpointer_id(""), None);
assert_eq!(super::parse_xpointer_id_fragment("xpointer(/)"), None);
assert_eq!(super::parse_xpointer_id_fragment("xpointer(id(foo))"), None); // no quotes
assert_eq!(super::parse_xpointer_id_fragment("not-xpointer"), None);
assert_eq!(super::parse_xpointer_id_fragment(""), None);

// Malformed: single quote char — must not panic (was slicing bug)
assert_eq!(super::parse_xpointer_id("xpointer(id('))"), None);
assert_eq!(super::parse_xpointer_id(r#"xpointer(id("))"#), None);
assert_eq!(super::parse_xpointer_id_fragment("xpointer(id('))"), None);
assert_eq!(
super::parse_xpointer_id_fragment(r#"xpointer(id("))"#),
None
);
}

#[test]
Expand Down
Loading