1use super::wire::MAX_MESSAGE_BYTES;
4use anyhow::{bail, ensure, Context, Result};
5use quick_xml::{events::Event, Reader};
6use serde::{Deserialize, Serialize};
7use std::collections::{BTreeMap, BTreeSet};
8
9pub const MAX_DEPTH: usize = 32;
10pub const MAX_NODES: usize = 8192;
11pub const MAX_TEXT_BYTES: usize = 65536;
12pub const MAX_NAME_BYTES: usize = 256;
13pub const MAX_URI_BYTES: usize = 4096;
14pub const MAX_ATTRIBUTE_BYTES: usize = 4096;
15pub const MAX_ATTRIBUTES: usize = 32;
16pub const MAX_NAMESPACES: usize = 16;
17pub const MAX_RETAINED_BYTES: usize = 8 * 1024 * 1024;
18const XML: &str = "http://www.w3.org/XML/1998/namespace";
19const XMLNS: &str = "http://www.w3.org/2000/xmlns/";
20
21#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
22#[serde(deny_unknown_fields)]
23pub struct Document {
24 #[serde(default)]
25 pub context: Vec<Binding>,
26 pub nodes: Vec<Node>,
27}
28#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
29#[serde(deny_unknown_fields)]
30pub struct Binding {
31 pub prefix: String,
32 pub uri: String,
33}
34#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
35#[serde(deny_unknown_fields)]
36pub struct Attribute {
37 pub name: String,
38 pub namespace: String,
39 pub value: String,
40}
41#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
42#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
43pub enum Node {
44 Start {
45 name: String,
46 namespace: String,
47 #[serde(default)]
48 bindings: Vec<Binding>,
49 #[serde(default)]
50 attributes: Vec<Attribute>,
51 },
52 End {
53 name: String,
54 namespace: String,
55 },
56 Text {
57 text: String,
58 },
59}
60
61fn xml_chars(text: &str) -> bool {
62 text.chars().all(
63 |c| matches!(c as u32, 9 | 10 | 13 | 0x20..=0xd7ff | 0xe000..=0xfffd | 0x10000..=0x10ffff),
64 )
65}
66fn name_start(c: char) -> bool {
67 matches!(c as u32, 0x41..=0x5a | 0x5f | 0x61..=0x7a | 0xc0..=0xd6 | 0xd8..=0xf6 | 0xf8..=0x2ff | 0x370..=0x37d | 0x37f..=0x1fff | 0x200c..=0x200d | 0x2070..=0x218f | 0x2c00..=0x2fef | 0x3001..=0xd7ff | 0xf900..=0xfdcf | 0xfdf0..=0xfffd | 0x10000..=0xeffff)
68}
69fn ncname(name: &str) -> bool {
70 let mut chars = name.chars();
71 chars.next().is_some_and(name_start)
72 && chars.all(|c| name_start(c) || matches!(c as u32, 0x2d | 0x2e | 0x30..=0x39 | 0xb7 | 0x300..=0x36f | 0x203f..=0x2040))
73}
74pub fn qualified(name: &str) -> Result<(&str, &str)> {
75 ensure!(
76 !name.is_empty() && name.len() <= MAX_NAME_BYTES,
77 "NETCONF XML name bound"
78 );
79 let (prefix, local) = match name.split_once(':') {
80 Some((prefix, local)) => {
81 ensure!(!prefix.is_empty(), "NETCONF empty XML name prefix");
82 (prefix, local)
83 }
84 None => ("", name),
85 };
86 ensure!(
87 (prefix.is_empty() || ncname(prefix)) && ncname(local),
88 "NETCONF XML qualified name"
89 );
90 ensure!(
91 prefix != "xmlns" && name != "xmlns",
92 "NETCONF reserved namespace name"
93 );
94 Ok((prefix, local))
95}
96fn namespace(name: &str, element: bool, active: &BTreeMap<String, String>) -> Result<String> {
97 let (prefix, _) = qualified(name)?;
98 let value = match prefix {
99 "xml" => XML,
100 "" if !element => "",
101 "" => active.get("").map(String::as_str).unwrap_or(""),
102 _ => active
103 .get(prefix)
104 .map(String::as_str)
105 .context("NETCONF undeclared XML namespace prefix")?,
106 };
107 ensure!(
108 value.len() <= MAX_URI_BYTES && xml_chars(value),
109 "NETCONF namespace URI bound"
110 );
111 Ok(value.to_owned())
112}
113fn bindings(
114 values: &[Binding],
115 inherited: &BTreeMap<String, String>,
116) -> Result<BTreeMap<String, String>> {
117 ensure!(
118 values.len() <= MAX_NAMESPACES,
119 "NETCONF namespace binding count"
120 );
121 let mut active = inherited.clone();
122 let mut seen = BTreeSet::new();
123 for b in values {
124 ensure!(
125 b.prefix.len() <= MAX_NAME_BYTES && (b.prefix.is_empty() || ncname(&b.prefix)),
126 "NETCONF namespace prefix"
127 );
128 ensure!(
129 seen.insert(&b.prefix),
130 "NETCONF duplicate namespace binding"
131 );
132 ensure!(
133 b.uri.len() <= MAX_URI_BYTES && xml_chars(&b.uri),
134 "NETCONF namespace URI bound"
135 );
136 ensure!(
137 b.prefix != "xmlns" && b.uri != XMLNS,
138 "NETCONF reserved namespace binding"
139 );
140 ensure!(
141 (b.prefix == "xml") == (b.uri == XML),
142 "NETCONF reserved xml prefix binding"
143 );
144 ensure!(
145 b.prefix.is_empty() || !b.uri.is_empty(),
146 "NETCONF empty prefixed namespace"
147 );
148 active.insert(b.prefix.clone(), b.uri.clone());
149 }
150 ensure!(
151 active.len() <= MAX_NAMESPACES,
152 "NETCONF active namespace count"
153 );
154 Ok(active)
155}
156fn retained(node: &Node) -> usize {
157 match node {
158 Node::Start {
159 name,
160 namespace,
161 bindings,
162 attributes,
163 } => {
164 256 + name.len()
165 + namespace.len()
166 + bindings
167 .iter()
168 .map(|b| 64 + b.prefix.len() + b.uri.len())
169 .sum::<usize>()
170 + attributes
171 .iter()
172 .map(|a| 128 + a.name.len() + a.namespace.len() + a.value.len())
173 .sum::<usize>()
174 }
175 Node::End { name, namespace } => 128 + name.len() + namespace.len(),
176 Node::Text { text } => 64 + text.len(),
177 }
178}
179fn append(doc: &mut Document, node: Node, bytes: &mut usize) -> Result<()> {
180 *bytes = bytes.saturating_add(retained(&node));
181 ensure!(
182 *bytes <= MAX_RETAINED_BYTES,
183 "NETCONF XML retained-content bound"
184 );
185 if let Node::Text { text } = &node {
186 ensure!(
187 text.len() <= MAX_TEXT_BYTES && xml_chars(text),
188 "NETCONF XML text bound/characters"
189 );
190 if let Some(Node::Text { text: previous }) = doc.nodes.last_mut() {
191 ensure!(
192 text.len() <= MAX_TEXT_BYTES.saturating_sub(previous.len()),
193 "NETCONF contiguous XML text bound"
194 );
195 previous.push_str(text);
196 return Ok(());
197 }
198 }
199 ensure!(doc.nodes.len() < MAX_NODES, "NETCONF XML node count");
200 doc.nodes.push(node);
201 Ok(())
202}
203
204pub fn parse(message: &[u8]) -> Result<Document> {
205 ensure!(
206 !message.is_empty() && message.len() <= MAX_MESSAGE_BYTES,
207 "NETCONF XML message byte bound"
208 );
209 let text = std::str::from_utf8(message)?;
210 let mut reader = Reader::from_str(text);
211 reader.config_mut().expand_empty_elements = true;
212 reader.config_mut().check_comments = true;
213 let mut doc = Document::default();
214 let mut stack: Vec<(String, String, BTreeMap<String, String>)> = Vec::new();
215 let mut bytes = 0usize;
216 let mut roots = 0usize;
217 let mut seen = false;
218 loop {
219 match reader.read_event()? {
220 Event::Start(element) => {
221 ensure!(stack.len() < MAX_DEPTH, "NETCONF XML depth bound");
222 if stack.is_empty() {
223 roots += 1;
224 ensure!(roots == 1, "NETCONF multiple XML roots");
225 }
226 let name = std::str::from_utf8(element.name().as_ref())?.to_owned();
227 qualified(&name)?;
228 let mut local_bindings = Vec::new();
229 let mut raw_attrs = Vec::new();
230 for attribute in element.attributes() {
231 let attribute = attribute?;
232 ensure!(
233 attribute.value.len() <= MAX_ATTRIBUTE_BYTES,
234 "NETCONF raw XML attribute bound"
235 );
236 let key = std::str::from_utf8(attribute.key.as_ref())?;
237 let raw = std::str::from_utf8(&attribute.value)?
239 .replace("\r\n", " ")
240 .replace(['\r', '\n', '\t'], " ");
241 let value = quick_xml::escape::unescape(&raw)?.into_owned();
242 ensure!(
243 value.len() <= MAX_ATTRIBUTE_BYTES && xml_chars(&value),
244 "NETCONF XML attribute bound/characters"
245 );
246 if key == "xmlns" || key.starts_with("xmlns:") {
247 ensure!(
248 local_bindings.len() < MAX_NAMESPACES,
249 "NETCONF namespace binding count"
250 );
251 ensure!(
252 key == "xmlns" || !key.strip_prefix("xmlns:").unwrap_or("").is_empty(),
253 "NETCONF empty declared namespace prefix"
254 );
255 local_bindings.push(Binding {
256 prefix: key.strip_prefix("xmlns:").unwrap_or("").into(),
257 uri: value,
258 });
259 } else {
260 ensure!(
261 raw_attrs.len() < MAX_ATTRIBUTES,
262 "NETCONF XML attribute count"
263 );
264 qualified(key)?;
265 raw_attrs.push((key.to_owned(), value));
266 }
267 }
268 let active = bindings(
269 &local_bindings,
270 stack.last().map(|v| &v.2).unwrap_or(&BTreeMap::new()),
271 )?;
272 let ns = namespace(&name, true, &active)?;
273 let mut attrs = Vec::new();
274 let mut expanded = BTreeSet::new();
275 for (name, value) in raw_attrs {
276 let attribute_ns = namespace(&name, false, &active)?;
277 ensure!(
278 expanded.insert((attribute_ns.clone(), qualified(&name)?.1.to_owned())),
279 "NETCONF duplicate expanded attribute name"
280 );
281 attrs.push(Attribute {
282 name,
283 namespace: attribute_ns,
284 value,
285 });
286 }
287 append(
288 &mut doc,
289 Node::Start {
290 name: name.clone(),
291 namespace: ns.clone(),
292 bindings: local_bindings,
293 attributes: attrs,
294 },
295 &mut bytes,
296 )?;
297 stack.push((name, ns, active));
298 seen = true;
299 }
300 Event::End(element) => {
301 let name = std::str::from_utf8(element.name().as_ref())?.to_owned();
302 let ns = namespace(
303 &name,
304 true,
305 &stack.last().context("NETCONF unmatched XML end")?.2,
306 )?;
307 let (start, start_ns, _) = stack.pop().context("NETCONF unmatched XML end")?;
308 ensure!(
309 name == start && ns == start_ns,
310 "NETCONF mismatched XML end"
311 );
312 append(
313 &mut doc,
314 Node::End {
315 name,
316 namespace: ns,
317 },
318 &mut bytes,
319 )?;
320 }
321 Event::Text(value) => {
322 ensure!(value.len() <= MAX_TEXT_BYTES, "NETCONF raw XML text bound");
323 let raw = std::str::from_utf8(value.as_ref())?
324 .replace("\r\n", "\n")
325 .replace('\r', "\n");
326 let value = quick_xml::escape::unescape(&raw)?.into_owned();
327 if stack.is_empty() {
328 ensure!(value.trim().is_empty(), "NETCONF text outside XML root");
329 } else {
330 append(&mut doc, Node::Text { text: value }, &mut bytes)?;
331 }
332 seen = true;
333 }
334 Event::CData(value) => {
335 ensure!(
336 !stack.is_empty() && value.len() <= MAX_TEXT_BYTES,
337 "NETCONF XML CDATA bound/position"
338 );
339 let value = std::str::from_utf8(value.as_ref())?
340 .replace("\r\n", "\n")
341 .replace('\r', "\n");
342 append(&mut doc, Node::Text { text: value }, &mut bytes)?;
343 }
344 Event::Decl(decl) => {
345 ensure!(
346 !seen && doc.nodes.is_empty(),
347 "NETCONF XML declaration position"
348 );
349 ensure!(
350 decl.version()?.as_ref() == b"1.0",
351 "NETCONF selected XML1.0 version"
352 );
353 if let Some(encoding) = decl.encoding() {
354 ensure!(
355 encoding?.as_ref().eq_ignore_ascii_case(b"utf-8"),
356 "NETCONF selected UTF8 encoding"
357 );
358 }
359 seen = true;
360 }
361 Event::DocType(_) => bail!("NETCONF XML DTD is refused"),
362 Event::Comment(_) => seen = true,
363 Event::PI(value) => {
364 ensure!(
365 !value.target().eq_ignore_ascii_case(b"xml"),
366 "NETCONF reserved XML processing instruction"
367 );
368 seen = true;
369 }
370 Event::Empty(_) => unreachable!("empty XML elements expand into Start/End"),
371 Event::Eof => break,
372 }
373 }
374 ensure!(
375 roots == 1 && stack.is_empty(),
376 "NETCONF incomplete XML document"
377 );
378 Ok(doc)
379}
380
381fn escape(output: &mut String, text: &str, attribute: bool) -> Result<()> {
382 ensure!(xml_chars(text), "NETCONF outgoing XML characters");
383 for c in text.chars() {
384 let encoded = match c {
385 '&' => Some("&"),
386 '<' => Some("<"),
387 '>' => Some(">"),
388 '"' if attribute => Some("""),
389 '\r' => Some(" "),
390 '\n' if attribute => Some(" "),
391 '\t' if attribute => Some("	"),
392 _ => None,
393 };
394 if let Some(encoded) = encoded {
395 output.push_str(encoded);
396 } else {
397 output.push(c);
398 }
399 ensure!(
400 output.len() <= MAX_MESSAGE_BYTES,
401 "NETCONF outgoing XML byte bound"
402 );
403 }
404 Ok(())
405}
406fn binding_text(output: &mut String, prefix: &str, uri: &str) -> Result<()> {
407 output.push_str(" xmlns");
408 if !prefix.is_empty() {
409 output.push(':');
410 output.push_str(prefix);
411 }
412 output.push_str("=\"");
413 escape(output, uri, true)?;
414 output.push('"');
415 Ok(())
416}
417
418pub fn embedded(doc: &Document, local: &str, uri: &str) -> Result<String> {
421 ensure!(
422 ncname(local) && uri.len() <= MAX_URI_BYTES,
423 "NETCONF XML wrapper name/URI"
424 );
425 ensure!(
426 doc.nodes.len() <= MAX_NODES,
427 "NETCONF outgoing XML node count"
428 );
429 let context = bindings(&doc.context, &BTreeMap::new())?;
430 let mut prefixes: BTreeSet<&str> = doc.context.iter().map(|b| b.prefix.as_str()).collect();
431 for node in &doc.nodes {
432 if let Node::Start { bindings, .. } = node {
433 prefixes.extend(bindings.iter().map(|b| b.prefix.as_str()));
434 }
435 }
436 let prefix = (0..=prefixes.len())
437 .map(|n| format!("netget{n}"))
438 .find(|p| !prefixes.contains(p.as_str()))
439 .context("NETCONF XML wrapper prefix")?;
440 let mut output = format!("<{prefix}:{local}");
441 binding_text(&mut output, &prefix, uri)?;
442 for b in &doc.context {
443 binding_text(&mut output, &b.prefix, &b.uri)?;
444 }
445 output.push('>');
446 let mut stack: Vec<(String, String, BTreeMap<String, String>)> = Vec::new();
447 let mut bytes = 0usize;
448 for node in &doc.nodes {
449 bytes = bytes.saturating_add(retained(node));
450 ensure!(
451 bytes <= MAX_RETAINED_BYTES,
452 "NETCONF outgoing XML retained-content bound"
453 );
454 match node {
455 Node::Start {
456 name,
457 namespace,
458 bindings: local_bindings,
459 attributes,
460 } => {
461 ensure!(
462 stack.len() < MAX_DEPTH && attributes.len() <= MAX_ATTRIBUTES,
463 "NETCONF outgoing XML depth/attribute bound"
464 );
465 let (p, _) = qualified(name)?;
466 ensure!(
467 namespace.len() <= MAX_URI_BYTES
468 && xml_chars(namespace)
469 && (p.is_empty() || !namespace.is_empty()),
470 "NETCONF outgoing element namespace"
471 );
472 let inherited = stack.last().map(|v| &v.2).unwrap_or(&context);
473 let mut active = bindings(local_bindings, inherited)?;
474 let mut declarations: BTreeMap<String, String> = local_bindings
475 .iter()
476 .map(|b| (b.prefix.clone(), b.uri.clone()))
477 .collect();
478 let mut declare = |p: &str, ns: &str| -> Result<()> {
479 ensure!(
480 p != "xmlns" && ns != XMLNS && ((p == "xml") == (ns == XML)),
481 "NETCONF outgoing reserved namespace"
482 );
483 if let Some(explicit) = declarations.get(p) {
484 ensure!(explicit == ns, "NETCONF conflicting namespace declaration");
485 }
486 if active.get(p).map(String::as_str).unwrap_or("") != ns {
487 active.insert(p.to_owned(), ns.to_owned());
488 declarations.insert(p.to_owned(), ns.to_owned());
489 }
490 Ok(())
491 };
492 declare(p, namespace)?;
493 let mut expanded = BTreeSet::new();
494 for a in attributes {
495 let (p, local) = qualified(&a.name)?;
496 ensure!(
497 a.namespace.len() <= MAX_URI_BYTES
498 && a.value.len() <= MAX_ATTRIBUTE_BYTES
499 && xml_chars(&a.value),
500 "NETCONF outgoing attribute bound"
501 );
502 ensure!(
503 !p.is_empty() || a.namespace.is_empty(),
504 "NETCONF unprefixed attribute namespace"
505 );
506 if !p.is_empty() {
507 ensure!(
508 !a.namespace.is_empty(),
509 "NETCONF prefixed attribute namespace"
510 );
511 declare(p, &a.namespace)?;
512 }
513 ensure!(
514 expanded.insert((&a.namespace, local)),
515 "NETCONF duplicate outgoing expanded attribute"
516 );
517 }
518 ensure!(
519 active.len() <= MAX_NAMESPACES,
520 "NETCONF outgoing active namespace count"
521 );
522 output.push('<');
523 output.push_str(name);
524 for (p, ns) in declarations {
525 binding_text(&mut output, &p, &ns)?;
526 }
527 for a in attributes {
528 output.push(' ');
529 output.push_str(&a.name);
530 output.push_str("=\"");
531 escape(&mut output, &a.value, true)?;
532 output.push('"');
533 }
534 output.push('>');
535 stack.push((name.clone(), namespace.clone(), active));
536 }
537 Node::End { name, namespace } => {
538 let (start, ns, _) = stack.pop().context("NETCONF outgoing unmatched XML end")?;
539 ensure!(
540 *name == start && *namespace == ns,
541 "NETCONF outgoing mismatched XML end"
542 );
543 output.push_str("</");
544 output.push_str(name);
545 output.push('>');
546 }
547 Node::Text { text } => {
548 ensure!(
549 text.len() <= MAX_TEXT_BYTES,
550 "NETCONF outgoing XML text bound"
551 );
552 escape(&mut output, text, false)?;
553 }
554 }
555 ensure!(
556 output.len() <= MAX_MESSAGE_BYTES,
557 "NETCONF outgoing XML byte bound"
558 );
559 }
560 ensure!(stack.is_empty(), "NETCONF outgoing incomplete XML fragment");
561 output.push_str(&format!("</{prefix}:{local}>"));
562 ensure!(
563 output.len() <= MAX_MESSAGE_BYTES,
564 "NETCONF outgoing XML byte bound"
565 );
566 parse(output.as_bytes())?;
568 Ok(output)
569}
570
571pub fn element(doc: &Document, index: usize) -> Result<(&str, &str, &[Attribute])> {
572 match doc.nodes.get(index) {
573 Some(Node::Start {
574 name,
575 namespace,
576 attributes,
577 ..
578 }) => Ok((qualified(name)?.1, namespace, attributes)),
579 _ => bail!("NETCONF expected XML element"),
580 }
581}
582pub fn is_element(doc: &Document, index: usize, local: &str, uri: &str) -> bool {
583 element(doc, index).is_ok_and(|(name, namespace, _)| name == local && namespace == uri)
584}
585pub fn end_index(doc: &Document, start: usize) -> Result<usize> {
586 element(doc, start)?;
587 let mut depth = 0usize;
588 for (index, node) in doc.nodes.iter().enumerate().skip(start) {
589 match node {
590 Node::Start { .. } => {
591 ensure!(depth < MAX_DEPTH, "NETCONF XML subtree depth");
592 depth += 1;
593 }
594 Node::End { .. } => {
595 depth = depth.checked_sub(1).context("NETCONF XML subtree end")?;
596 if depth == 0 {
597 return Ok(index);
598 }
599 }
600 Node::Text { .. } => {}
601 }
602 }
603 bail!("NETCONF incomplete XML subtree")
604}
605pub fn children(doc: &Document, start: usize) -> Result<Vec<usize>> {
608 let end = end_index(doc, start)?;
609 let mut index = start + 1;
610 let mut children = Vec::new();
611 while index < end {
612 match &doc.nodes[index] {
613 Node::Start { .. } => {
614 children.push(index);
615 index = end_index(doc, index)? + 1;
616 }
617 Node::Text { text } => {
618 ensure!(
619 text.trim().is_empty(),
620 "NETCONF protocol envelope mixed text"
621 );
622 index += 1;
623 }
624 Node::End { .. } => bail!("NETCONF unexpected XML child end"),
625 }
626 }
627 Ok(children)
628}
629pub fn text(doc: &Document, start: usize) -> Result<String> {
630 let end = end_index(doc, start)?;
631 let mut output = String::new();
632 for node in &doc.nodes[start + 1..end] {
633 if let Node::Text { text } = node {
634 ensure!(
635 text.len() <= MAX_TEXT_BYTES.saturating_sub(output.len()),
636 "NETCONF XML scalar text bound"
637 );
638 output.push_str(text);
639 } else {
640 bail!("NETCONF scalar XML field has child elements")
641 }
642 }
643 Ok(output)
644}
645pub fn inner(doc: &Document, start: usize) -> Result<Document> {
648 let end = end_index(doc, start)?;
649 let mut scopes = vec![bindings(&doc.context, &BTreeMap::new())?];
650 for (index, node) in doc.nodes.iter().enumerate().take(start + 1) {
651 match node {
652 Node::Start {
653 bindings: local, ..
654 } => {
655 ensure!(
656 scopes.len() <= MAX_DEPTH,
657 "NETCONF XML namespace scope depth"
658 );
659 scopes.push(bindings(local, scopes.last().expect("context scope"))?);
660 }
661 Node::End { .. } => {
662 ensure!(scopes.len() > 1, "NETCONF XML namespace scope underflow");
663 scopes.pop();
664 }
665 Node::Text { .. } => {}
666 }
667 if index == start {
668 break;
669 }
670 }
671 Ok(Document {
672 context: scopes
673 .last()
674 .expect("context scope")
675 .iter()
676 .map(|(prefix, uri)| Binding {
677 prefix: prefix.clone(),
678 uri: uri.clone(),
679 })
680 .collect(),
681 nodes: doc.nodes[start + 1..end].to_vec(),
682 })
683}
684
685const FRAGMENT_ROOT: &str = "netget-fragment";
686const FRAGMENT_URI: &str = "urn:netget:netconf:fragment";
687
688pub fn parse_fragment(text: &str) -> Result<Document> {
692 parse_fragment_in(text, None)
693}
694
695pub fn parse_fragment_in(text: &str, default_namespace: Option<&str>) -> Result<Document> {
699 ensure!(
700 text.len() <= MAX_MESSAGE_BYTES - 128,
701 "NETCONF XML fragment byte bound"
702 );
703 ensure!(
704 !text.trim_start().starts_with("<?xml"),
705 "NETCONF XML fragment must not carry a declaration"
706 );
707 let wrapper = default_namespace.unwrap_or(FRAGMENT_URI);
708 let wrapped = format!("<{FRAGMENT_ROOT} xmlns=\"{wrapper}\">{text}</{FRAGMENT_ROOT}>");
709 let doc = parse(wrapped.as_bytes())?;
710 let mut inner = inner(&doc, 0)?;
711 inner
714 .context
715 .retain(|b| !(b.prefix.is_empty() && b.uri == wrapper));
716 for node in &mut inner.nodes {
717 if default_namespace.is_some() {
718 break;
719 }
720 if let Node::Start { namespace, .. } | Node::End { namespace, .. } = node {
721 if namespace == FRAGMENT_URI {
722 namespace.clear();
723 }
724 }
725 }
726 Ok(inner)
727}
728
729pub fn render_fragment(doc: &Document) -> Result<String> {
732 let mut output = String::new();
733 let mut depth = 0usize;
734 for node in &doc.nodes {
735 match node {
736 Node::Start {
737 name,
738 namespace,
739 bindings: local,
740 attributes,
741 } => {
742 ensure!(depth < MAX_DEPTH, "NETCONF outgoing XML depth bound");
743 let (element_prefix, _) = qualified(name)?;
744 output.push('<');
745 output.push_str(name);
746 let mut declared: BTreeMap<&str, &str> = BTreeMap::new();
747 if depth == 0 {
748 for b in &doc.context {
749 declared.insert(&b.prefix, &b.uri);
750 }
751 }
752 for b in local {
753 declared.insert(&b.prefix, &b.uri);
754 }
755 if depth == 0
758 && element_prefix.is_empty()
759 && declared.get("") != Some(&namespace.as_str())
760 {
761 declared.insert("", namespace);
762 }
763 for (prefix, uri) in declared {
764 if prefix == "xml" {
765 continue;
766 }
767 binding_text(&mut output, prefix, uri)?;
768 }
769 for a in attributes {
770 qualified(&a.name)?;
771 output.push(' ');
772 output.push_str(&a.name);
773 output.push_str("=\"");
774 escape(&mut output, &a.value, true)?;
775 output.push('"');
776 }
777 output.push('>');
778 depth += 1;
779 }
780 Node::End { name, .. } => {
781 depth = depth
782 .checked_sub(1)
783 .context("NETCONF outgoing unmatched XML end")?;
784 output.push_str("</");
785 output.push_str(name);
786 output.push('>');
787 }
788 Node::Text { text } => escape(&mut output, text, false)?,
789 }
790 ensure!(
791 output.len() <= MAX_MESSAGE_BYTES,
792 "NETCONF outgoing XML byte bound"
793 );
794 }
795 ensure!(depth == 0, "NETCONF outgoing incomplete XML fragment");
796 Ok(output)
797}