1use anyhow::{bail, ensure, Context, Result};
4use quick_xml::events::Event;
5use quick_xml::name::ResolveResult;
6use quick_xml::NsReader;
7
8pub const MAX_DEPTH: usize = 32;
9pub const MAX_NODES: usize = 4096;
10
11pub const EPP: &str = "urn:ietf:params:xml:ns:epp-1.0";
12pub const DOMAIN: &str = "urn:ietf:params:xml:ns:domain-1.0";
13pub const HOST: &str = "urn:ietf:params:xml:ns:host-1.0";
14pub const CONTACT: &str = "urn:ietf:params:xml:ns:contact-1.0";
15
16#[derive(Debug, Default, Clone)]
17pub struct Node {
18 pub ns: String,
19 pub name: String,
20 pub attrs: Vec<(String, String)>,
21 pub text: String,
22 pub children: Vec<Node>,
23}
24
25impl Node {
26 pub fn child(&self, name: &str) -> Option<&Node> {
27 self.children.iter().find(|c| c.name == name)
28 }
29 pub fn all<'a>(&'a self, name: &'a str) -> impl Iterator<Item = &'a Node> + 'a {
30 self.children.iter().filter(move |c| c.name == name)
31 }
32 pub fn attr(&self, name: &str) -> Option<&str> {
33 self.attrs
34 .iter()
35 .find(|(k, _)| k == name)
36 .map(|(_, v)| v.as_str())
37 }
38 pub fn text(&self) -> String {
39 self.text.trim().to_owned()
40 }
41 pub fn text_of(&self, name: &str) -> Option<String> {
42 self.child(name).map(Node::text)
43 }
44 pub fn texts_of(&self, name: &str) -> Vec<String> {
45 self.all(name).map(Node::text).collect()
46 }
47 pub fn first(&self) -> Option<&Node> {
49 self.children.first()
50 }
51}
52
53pub fn parse(bytes: &[u8]) -> Result<Node> {
55 let mut reader = NsReader::from_reader(bytes);
56 reader.config_mut().trim_text(false);
57 let mut buf = Vec::new();
58 let mut stack: Vec<Node> = Vec::new();
59 let mut root: Option<Node> = None;
60 let mut nodes = 0usize;
61 loop {
62 let (ns, event) = reader
63 .read_resolved_event_into(&mut buf)
64 .context("the frame is not well-formed XML")?;
65 let ns = match ns {
66 ResolveResult::Bound(n) => String::from_utf8_lossy(n.as_ref()).into_owned(),
67 ResolveResult::Unbound => String::new(),
68 ResolveResult::Unknown(p) => {
69 bail!("the prefix {} is not declared", String::from_utf8_lossy(&p))
70 }
71 };
72 match event {
73 Event::Start(e) | Event::Empty(e) if root.is_some() => {
74 let _ = e;
75 bail!("content after the root element")
76 }
77 Event::Start(ref e) | Event::Empty(ref e) => {
78 nodes += 1;
79 ensure!(nodes <= MAX_NODES, "more than {MAX_NODES} elements");
80 ensure!(stack.len() < MAX_DEPTH, "nested deeper than {MAX_DEPTH}");
81 let mut node = Node {
82 ns,
83 name: String::from_utf8_lossy(e.local_name().as_ref()).into_owned(),
84 ..Default::default()
85 };
86 for a in e.attributes() {
87 let a = a.context("a malformed attribute")?;
88 let key = a.key.as_ref();
89 if key == b"xmlns" || key.starts_with(b"xmlns:") {
90 continue;
91 }
92 ensure!(node.attrs.len() < 32, "too many attributes");
93 let local = String::from_utf8_lossy(a.key.local_name().as_ref()).into_owned();
94 let value = a
95 .decode_and_unescape_value(reader.decoder())
96 .context("an attribute value is not valid")?
97 .into_owned();
98 node.attrs.push((local, value));
99 }
100 if matches!(event, Event::Empty(_)) {
101 match stack.last_mut() {
102 Some(parent) => parent.children.push(node),
103 None => root = Some(node),
104 }
105 } else {
106 stack.push(node);
107 }
108 }
109 Event::End(_) => {
110 let node = stack.pop().context("an unbalanced end tag")?;
111 match stack.last_mut() {
112 Some(parent) => parent.children.push(node),
113 None => root = Some(node),
114 }
115 }
116 Event::Text(t) => {
117 let text = t.unescape().context("text is not valid")?;
118 match stack.last_mut() {
119 Some(n) => {
120 ensure!(n.text.len() + text.len() <= 65536, "text too long");
121 n.text.push_str(&text);
122 }
123 None => ensure!(text.trim().is_empty(), "text outside the root element"),
124 }
125 }
126 Event::CData(t) => {
127 if let Some(n) = stack.last_mut() {
128 ensure!(n.text.len() + t.len() <= 65536, "text too long");
129 n.text.push_str(&String::from_utf8_lossy(&t));
130 }
131 }
132 Event::DocType(_) => bail!("a DOCTYPE is not allowed"),
133 Event::Eof => break,
134 Event::Decl(_) | Event::Comment(_) | Event::PI(_) => {}
135 }
136 buf.clear();
137 }
138 ensure!(stack.is_empty(), "the document ends inside an element");
139 root.context("the frame holds no element")
140}
141
142pub fn escape(s: &str) -> String {
143 let mut out = String::with_capacity(s.len());
144 for c in s.chars() {
145 match c {
146 '&' => out.push_str("&"),
147 '<' => out.push_str("<"),
148 '>' => out.push_str(">"),
149 '"' => out.push_str("""),
150 '\'' => out.push_str("'"),
151 c => out.push(c),
152 }
153 }
154 out
155}
156
157pub fn el(tag: &str, text: &str) -> String {
159 format!("<{tag}>{}</{tag}>", escape(text))
160}