| // Copyright 2026 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #![forbid(unsafe_code)] |
| |
| use crate::cxx::ffi::Node; |
| use cxx::{CxxString, UniquePtr}; |
| use std::fmt::Write; |
| use std::pin::Pin; |
| use xml::attribute::OwnedAttribute; |
| use xml::namespace::Namespace; |
| use xml::reader::{EventReader, XmlEvent}; |
| |
| const MAX_DEPTH: usize = 200; |
| |
| fn populate_node( |
| node: &mut UniquePtr<Node>, |
| attributes: Vec<OwnedAttribute>, |
| namespace: &Namespace, |
| parent_namespace: Option<&Namespace>, |
| ) { |
| for attr in attributes { |
| let prefix = attr.name.prefix.as_deref().unwrap_or(""); |
| crate::cxx::ffi::set_attribute( |
| node.as_mut().unwrap(), |
| &attr.name.local_name, |
| prefix, |
| &attr.value, |
| ); |
| } |
| for (prefix, uri) in namespace { |
| // Per https://www.w3.org/TR/xml-names/#ns-decl, "xml" is reserved and may be declared, but |
| // must be bound to ttp://www.w3.org/XML/1998/namespace. xml-rs already checks |
| // that, so no need to validate that here. |
| // "xmlns" is also reserved but must not be declared; this is also enforced by |
| // xml-rs. |
| if prefix == "xml" || prefix == "xmlns" { |
| continue; |
| } |
| // For compatibility with the original C++ parser, which omits this on the root |
| // element if not otherwise specified. |
| if prefix.is_empty() && uri.is_empty() && parent_namespace.is_none() { |
| continue; |
| } |
| // `namespace` includes declarations from the parent (and transitively, ancestor |
| // nodes); avoid duplicating inherited declarations into all descendant |
| // nodes. |
| if parent_namespace.and_then(|ns| ns.get(prefix)) == Some(uri) { |
| continue; |
| } |
| crate::cxx::ffi::set_namespace(node.as_mut().unwrap(), prefix, uri); |
| } |
| } |
| |
| pub fn decode_xml_bytes(xml: &[u8], err: Pin<&mut CxxString>) -> UniquePtr<Node> { |
| let Ok(xml) = str::from_utf8(xml) else { |
| return UniquePtr::null(); |
| }; |
| decode_xml_str(xml, err) |
| } |
| |
| pub fn decode_xml_str(xml: &str, mut err: Pin<&mut CxxString>) -> UniquePtr<Node> { |
| let parser = EventReader::from_str(xml); |
| let mut stack: Vec<(UniquePtr<Node>, Namespace)> = Vec::new(); |
| let mut root: UniquePtr<Node> = UniquePtr::null(); |
| |
| for e in parser { |
| match e { |
| Ok(XmlEvent::StartElement { name, attributes, namespace }) => { |
| if !root.is_null() || stack.len() >= MAX_DEPTH { |
| return UniquePtr::null(); |
| } |
| |
| let prefix = name.prefix.as_deref().unwrap_or(""); |
| let mut node = crate::cxx::ffi::create_element(&name.local_name, prefix); |
| |
| let parent_namespace = stack.last().map(|(_, ns)| ns); |
| populate_node(&mut node, attributes, &namespace, parent_namespace); |
| |
| stack.push((node, namespace)); |
| } |
| Ok(XmlEvent::EndElement { .. }) => { |
| let (child, _) = match stack.pop() { |
| Some(x) => x, |
| None => return UniquePtr::null(), |
| }; |
| if let Some((parent, _)) = stack.last_mut() { |
| crate::cxx::ffi::add_child(parent.as_mut().unwrap(), child); |
| } else { |
| root = child; |
| } |
| } |
| Ok(XmlEvent::Characters(data)) => { |
| if let Some((parent, _)) = stack.last_mut() { |
| let text_node = crate::cxx::ffi::create_text_node(&data); |
| crate::cxx::ffi::add_child(parent.as_mut().unwrap(), text_node); |
| } else if !data.trim().is_empty() { |
| return UniquePtr::null(); |
| } |
| } |
| Ok(XmlEvent::CData(data)) => { |
| if let Some((parent, _)) = stack.last_mut() { |
| let cdata_node = crate::cxx::ffi::create_cdata_node(&data); |
| crate::cxx::ffi::add_child(parent.as_mut().unwrap(), cdata_node); |
| } else { |
| return UniquePtr::null(); |
| } |
| } |
| Ok(XmlEvent::Whitespace(_)) |
| | Ok(XmlEvent::Comment(_)) |
| | Ok(XmlEvent::ProcessingInstruction { .. }) |
| | Ok(XmlEvent::StartDocument { .. }) |
| | Ok(XmlEvent::EndDocument) |
| | Ok(XmlEvent::Doctype { .. }) => {} |
| Err(e) => { |
| write!(&mut err, "{}", e).unwrap(); |
| return UniquePtr::null(); |
| } |
| } |
| } |
| |
| if !stack.is_empty() { |
| return UniquePtr::null(); |
| } |
| |
| root |
| } |