1use std::io::{self, Write};
4
5use quick_xml::events::{BytesCData, BytesDecl, BytesEnd, BytesPI, BytesStart, BytesText, Event};
6use quick_xml::writer::Writer;
7
8use crate::{Error, ValidatedXml, XmlSafetyError, is_valid_xml_namespace_name};
9
10const DEFAULT_MAX_OUTPUT_BYTES: usize = 64 * 1024 * 1024;
11const DEFAULT_MAX_DEPTH: usize = 128;
12const DEFAULT_MAX_ATTRIBUTES_PER_ELEMENT: usize = 1_024;
13const XML_NAMESPACE_URI: &str = "http://www.w3.org/XML/1998/namespace";
14const XMLNS_NAMESPACE_URI: &str = "http://www.w3.org/2000/xmlns/";
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub struct XmlWriteOptions {
19 pub max_output_bytes: usize,
20 pub max_depth: usize,
21 pub max_attributes_per_element: usize,
22 pub write_document_declaration: bool,
23}
24
25impl XmlWriteOptions {
26 #[must_use]
27 pub const fn new() -> Self {
28 Self {
29 max_output_bytes: DEFAULT_MAX_OUTPUT_BYTES,
30 max_depth: DEFAULT_MAX_DEPTH,
31 max_attributes_per_element: DEFAULT_MAX_ATTRIBUTES_PER_ELEMENT,
32 write_document_declaration: false,
33 }
34 }
35
36 #[must_use]
37 pub const fn max_output_bytes(mut self, value: usize) -> Self {
38 self.max_output_bytes = value;
39 self
40 }
41
42 #[must_use]
43 pub const fn max_depth(mut self, value: usize) -> Self {
44 self.max_depth = value;
45 self
46 }
47
48 #[must_use]
49 pub const fn max_attributes_per_element(mut self, value: usize) -> Self {
50 self.max_attributes_per_element = value;
51 self
52 }
53
54 #[must_use]
55 pub const fn write_document_declaration(mut self, value: bool) -> Self {
56 self.write_document_declaration = value;
57 self
58 }
59
60 fn validate(self) -> Result<(), Error> {
61 if self.max_output_bytes == 0 || self.max_depth == 0 || self.max_attributes_per_element == 0
62 {
63 Err(XmlSafetyError::InvalidPolicy.into())
64 } else {
65 Ok(())
66 }
67 }
68}
69
70impl Default for XmlWriteOptions {
71 fn default() -> Self {
72 Self::new()
73 }
74}
75
76#[derive(Debug, Clone, Copy, PartialEq, Eq)]
78pub struct XmlWriteAttribute<'a> {
79 pub name: &'a str,
80 pub value: &'a str,
81}
82
83impl<'a> XmlWriteAttribute<'a> {
84 #[must_use]
85 pub const fn new(name: &'a str, value: &'a str) -> Self {
86 Self { name, value }
87 }
88}
89
90impl<'a> From<(&'a str, &'a str)> for XmlWriteAttribute<'a> {
91 fn from((name, value): (&'a str, &'a str)) -> Self {
92 Self { name, value }
93 }
94}
95
96struct NamespaceBinding {
97 prefix: String,
98 uri: Option<String>,
99}
100
101pub struct XmlStreamWriter<W: Write> {
103 writer: Writer<LimitedWriter<W>>,
104 options: XmlWriteOptions,
105 depth: usize,
106 root_seen: bool,
107 root_complete: bool,
108 open_names: Vec<String>,
109 binding_starts: Vec<usize>,
110 bindings: Vec<NamespaceBinding>,
111 instruction: String,
112}
113
114impl<W: Write> XmlStreamWriter<W> {
115 pub fn new(inner: W) -> Result<Self, Error> {
121 Self::with_options(inner, XmlWriteOptions::default())
122 }
123
124 pub fn with_options(inner: W, options: XmlWriteOptions) -> Result<Self, Error> {
131 options.validate()?;
132 let mut output = Self {
133 writer: Writer::new(LimitedWriter::new(inner, options.max_output_bytes)),
134 options,
135 depth: 0,
136 root_seen: false,
137 root_complete: false,
138 open_names: Vec::new(),
139 binding_starts: Vec::new(),
140 bindings: Vec::new(),
141 instruction: String::new(),
142 };
143 if options.write_document_declaration {
144 output.write_event(Event::Decl(BytesDecl::new("1.0", Some("UTF-8"), None)))?;
145 }
146 Ok(output)
147 }
148
149 pub fn start_element<'a, I, A>(&mut self, name: &str, attributes: I) -> Result<(), Error>
156 where
157 I: IntoIterator<Item = A>,
158 A: Into<XmlWriteAttribute<'a>>,
159 {
160 self.write_element(name, attributes, false)
161 }
162
163 pub fn empty_element<'a, I, A>(&mut self, name: &str, attributes: I) -> Result<(), Error>
170 where
171 I: IntoIterator<Item = A>,
172 A: Into<XmlWriteAttribute<'a>>,
173 {
174 self.write_element(name, attributes, true)
175 }
176
177 pub fn start(&mut self, name: &str) -> Result<(), Error> {
183 self.start_element(name, std::iter::empty::<XmlWriteAttribute<'_>>())
184 }
185
186 pub fn empty(&mut self, name: &str) -> Result<(), Error> {
192 self.empty_element(name, std::iter::empty::<XmlWriteAttribute<'_>>())
193 }
194
195 pub fn end_element(&mut self) -> Result<(), Error> {
201 if self.depth == 0 {
202 return Err(Error::InvalidData(
203 "end_element called without an open element".into(),
204 ));
205 }
206 let index = self.depth - 1;
207 let name = self.open_names[index].as_str();
208 let result = self.writer.write_event(Event::End(BytesEnd::new(name)));
209 if let Err(error) = result {
210 return self.map_io_error(error);
211 }
212 self.depth = index;
213 let binding_start = self.binding_starts[index];
214 self.bindings.truncate(binding_start);
215 if self.depth == 0 {
216 self.root_complete = true;
217 }
218 Ok(())
219 }
220
221 pub fn text(&mut self, value: &str) -> Result<(), Error> {
228 validate_xml_text(value)?;
229 if self.depth == 0 && !value.chars().all(char::is_whitespace) {
230 return Err(Error::InvalidData(
231 "text cannot appear outside the root".into(),
232 ));
233 }
234 self.write_event(Event::Text(BytesText::new(value)))
235 }
236
237 pub fn cdata(&mut self, value: &str) -> Result<(), Error> {
244 validate_xml_text(value)?;
245 if self.depth == 0 {
246 return Err(Error::InvalidData(
247 "CDATA cannot appear outside the root".into(),
248 ));
249 }
250 if value.contains("]]>") {
251 return Err(Error::InvalidData("CDATA contains `]]>`".into()));
252 }
253 self.write_event(Event::CData(BytesCData::new(value)))
254 }
255
256 pub fn comment(&mut self, value: &str) -> Result<(), Error> {
263 validate_xml_text(value)?;
264 if value.contains("--") || value.ends_with('-') {
265 return Err(Error::InvalidData(
266 "comment contains an XML-forbidden hyphen sequence".into(),
267 ));
268 }
269 self.write_event(Event::Comment(BytesText::from_escaped(value)))
270 }
271
272 pub fn processing_instruction(
279 &mut self,
280 target: &str,
281 content: Option<&str>,
282 ) -> Result<(), Error> {
283 validate_name(target, false)?;
284 if target.eq_ignore_ascii_case("xml") {
285 return Err(Error::InvalidData(
286 "processing-instruction target cannot be `xml`".into(),
287 ));
288 }
289 self.instruction.clear();
290 self.instruction.push_str(target);
291 if let Some(content) = content {
292 validate_xml_text(content)?;
293 if content.contains("?>") {
294 return Err(Error::InvalidData(
295 "processing instruction contains `?>`".into(),
296 ));
297 }
298 if !content.is_empty() {
299 self.instruction.push(' ');
300 self.instruction.push_str(content);
301 }
302 }
303 let instruction = BytesPI::new(self.instruction.as_str());
304 let result = self.writer.write_event(Event::PI(instruction));
305 match result {
306 Ok(()) => Ok(()),
307 Err(error) => self.map_io_error(error),
308 }
309 }
310
311 pub fn validated_subtree(&mut self, subtree: &ValidatedXml) -> Result<(), Error> {
319 if self.depth == 0 {
320 return Err(Error::InvalidData(
321 "validated subtree requires an open parent element".into(),
322 ));
323 }
324 let caller_has_default_namespace = self.resolve_namespace("").is_some();
325 for element in subtree.document().root().descendants() {
326 if caller_has_default_namespace
327 && element.prefix().is_none()
328 && element.namespace().is_none()
329 {
330 return Err(Error::InvalidData(
331 "unprefixed validated subtree element would inherit the writer default namespace"
332 .into(),
333 ));
334 }
335 if element.attributes().count() > self.options.max_attributes_per_element {
336 return Err(XmlSafetyError::TooManyAttributes.into());
337 }
338 let mut relative_depth = 1usize;
339 let mut parent = element.parent();
340 while let Some(element) = parent {
341 relative_depth = relative_depth
342 .checked_add(1)
343 .ok_or(XmlSafetyError::TooDeep)?;
344 parent = element.parent();
345 }
346 let combined_depth = self
347 .depth
348 .checked_add(relative_depth)
349 .ok_or(XmlSafetyError::TooDeep)?;
350 if combined_depth > self.options.max_depth {
351 return Err(XmlSafetyError::TooDeep.into());
352 }
353 }
354 self.write_raw(subtree.document().root().raw_xml())
355 }
356
357 pub fn written_bytes(&self) -> usize {
358 self.writer.get_ref().written
359 }
360
361 pub fn get_ref(&self) -> &W {
362 &self.writer.get_ref().inner
363 }
364
365 pub fn get_mut(&mut self) -> &mut W {
371 &mut self.writer.get_mut().inner
372 }
373
374 pub fn finish(mut self) -> Result<W, Error> {
381 if self.depth != 0 {
382 return Err(Error::InvalidData(
383 "XML document has unclosed elements".into(),
384 ));
385 }
386 if !self.root_seen || !self.root_complete {
387 return Err(Error::InvalidData(
388 "XML document has no complete root".into(),
389 ));
390 }
391 if let Err(error) = self.writer.get_mut().flush() {
392 if self.writer.get_ref().exceeded {
393 return Err(XmlSafetyError::OutputTooLarge.into());
394 }
395 return Err(Error::Io(error));
396 }
397 Ok(self.writer.into_inner().inner)
398 }
399
400 fn write_element<'a, I, A>(
401 &mut self,
402 name: &str,
403 attributes: I,
404 empty: bool,
405 ) -> Result<(), Error>
406 where
407 I: IntoIterator<Item = A>,
408 A: Into<XmlWriteAttribute<'a>>,
409 {
410 if self.depth == 0 && self.root_complete {
411 return Err(Error::InvalidData(
412 "XML document cannot contain multiple roots".into(),
413 ));
414 }
415 let next_depth = self.depth.checked_add(1).ok_or(XmlSafetyError::TooDeep)?;
416 if next_depth > self.options.max_depth {
417 return Err(XmlSafetyError::TooDeep.into());
418 }
419 validate_name(name, true)?;
420
421 let binding_start = self.bindings.len();
422 let mut start = BytesStart::new(name);
423 let result = (|| {
424 let mut attribute_count = 0usize;
425 for attribute in attributes {
426 let attribute = attribute.into();
427 attribute_count = attribute_count
428 .checked_add(1)
429 .ok_or(XmlSafetyError::TooManyAttributes)?;
430 if attribute_count > self.options.max_attributes_per_element {
431 return Err(XmlSafetyError::TooManyAttributes.into());
432 }
433 validate_name(attribute.name, true)?;
434 validate_xml_text(attribute.value)?;
435 self.record_namespace_binding(attribute)?;
436 start.push_attribute((attribute.name, attribute.value));
437 }
438 for attribute in start.attributes() {
439 let attribute = attribute.map_err(|error| Error::InvalidData(error.to_string()))?;
440 let attribute_name = attribute.key.as_ref();
441 self.validate_attribute_namespace(attribute_name)?;
442 }
443 for (index, left) in start.attributes().enumerate() {
444 let left = left.map_err(|error| Error::InvalidData(error.to_string()))?;
445 let left_name = left.key.as_ref();
446 for right in start.attributes().skip(index + 1) {
447 let right = right.map_err(|error| Error::InvalidData(error.to_string()))?;
448 let right_name = right.key.as_ref();
449 if self.attributes_share_expanded_name(left_name, right_name) {
450 return Err(Error::InvalidData(format!(
451 "attributes `{left_name}` and `{right_name}` have the same expanded name"
452 )));
453 }
454 }
455 }
456 self.validate_element_namespace(name)
457 })();
458 if let Err(error) = result {
459 self.bindings.truncate(binding_start);
460 return Err(error);
461 }
462
463 let event = if empty {
464 Event::Empty(start)
465 } else {
466 Event::Start(start)
467 };
468 if let Err(error) = self.write_event(event) {
469 self.bindings.truncate(binding_start);
470 return Err(error);
471 }
472 self.root_seen = true;
473 if empty {
474 self.bindings.truncate(binding_start);
475 if self.depth == 0 {
476 self.root_complete = true;
477 }
478 } else {
479 if self.open_names.len() == self.depth {
480 self.open_names.push(String::new());
481 self.binding_starts.push(0);
482 }
483 self.open_names[self.depth].clear();
484 self.open_names[self.depth].push_str(name);
485 self.binding_starts[self.depth] = binding_start;
486 self.depth = next_depth;
487 }
488 Ok(())
489 }
490
491 fn record_namespace_binding(&mut self, attribute: XmlWriteAttribute<'_>) -> Result<(), Error> {
492 let Some(prefix) = namespace_declaration_prefix(attribute.name) else {
493 return Ok(());
494 };
495 validate_namespace_binding(prefix, attribute.value)?;
496 self.bindings.push(NamespaceBinding {
497 prefix: prefix.to_owned(),
498 uri: (!attribute.value.is_empty()).then(|| attribute.value.to_owned()),
499 });
500 Ok(())
501 }
502
503 fn validate_element_namespace(&self, qualified_name: &str) -> Result<(), Error> {
504 if let Some((prefix, _)) = qualified_name.split_once(':')
505 && self.resolve_namespace(prefix).is_none()
506 {
507 return Err(Error::InvalidData(format!(
508 "element prefix `{prefix}` has no namespace binding"
509 )));
510 }
511 Ok(())
512 }
513
514 fn validate_attribute_namespace(&self, qualified_name: &str) -> Result<(), Error> {
515 if namespace_declaration_prefix(qualified_name).is_some() {
516 return Ok(());
517 }
518 if let Some((prefix, _)) = qualified_name.split_once(':')
519 && prefix != "xml"
520 && self.resolve_namespace(prefix).is_none()
521 {
522 return Err(Error::InvalidData(format!(
523 "attribute prefix `{prefix}` has no namespace binding"
524 )));
525 }
526 Ok(())
527 }
528
529 fn attributes_share_expanded_name(&self, left: &str, right: &str) -> bool {
530 match (
531 namespace_declaration_prefix(left),
532 namespace_declaration_prefix(right),
533 ) {
534 (Some(left_prefix), Some(right_prefix)) => return left_prefix == right_prefix,
535 (Some(_), None) | (None, Some(_)) => return false,
536 (None, None) => {}
537 }
538 let (left_prefix, left_local) = left.split_once(':').unwrap_or(("", left));
539 let (right_prefix, right_local) = right.split_once(':').unwrap_or(("", right));
540 if left_local != right_local {
541 return false;
542 }
543 let left_namespace = (!left_prefix.is_empty())
544 .then(|| self.resolve_namespace(left_prefix))
545 .flatten();
546 let right_namespace = (!right_prefix.is_empty())
547 .then(|| self.resolve_namespace(right_prefix))
548 .flatten();
549 left_namespace == right_namespace
550 }
551
552 fn resolve_namespace(&self, prefix: &str) -> Option<&str> {
553 if prefix == "xml" {
554 return Some(XML_NAMESPACE_URI);
555 }
556 self.bindings
557 .iter()
558 .rev()
559 .find(|binding| binding.prefix == prefix)
560 .and_then(|binding| binding.uri.as_deref())
561 }
562
563 fn write_event(&mut self, event: Event<'_>) -> Result<(), Error> {
564 match self.writer.write_event(event) {
565 Ok(()) => Ok(()),
566 Err(error) => self.map_io_error(error),
567 }
568 }
569
570 fn write_raw(&mut self, bytes: &[u8]) -> Result<(), Error> {
571 match self.writer.get_mut().write_all(bytes) {
572 Ok(()) => Ok(()),
573 Err(error) => self.map_io_error(error),
574 }
575 }
576
577 fn map_io_error(&self, error: io::Error) -> Result<(), Error> {
578 if self.writer.get_ref().exceeded {
579 Err(XmlSafetyError::OutputTooLarge.into())
580 } else {
581 Err(Error::Io(error))
582 }
583 }
584}
585
586fn namespace_declaration_prefix(name: &str) -> Option<&str> {
587 if name == "xmlns" {
588 Some("")
589 } else {
590 name.strip_prefix("xmlns:")
591 }
592}
593
594fn validate_namespace_binding(prefix: &str, uri: &str) -> Result<(), Error> {
595 if prefix == "xmlns"
596 || uri == XMLNS_NAMESPACE_URI
597 || (prefix == "xml" && uri != XML_NAMESPACE_URI)
598 || (prefix != "xml" && uri == XML_NAMESPACE_URI)
599 || (!prefix.is_empty() && uri.is_empty())
600 || !is_valid_xml_namespace_name(uri)
601 {
602 Err(Error::InvalidData("invalid namespace binding".into()))
603 } else {
604 Ok(())
605 }
606}
607
608fn validate_name(name: &str, qualified: bool) -> Result<(), Error> {
609 if name.is_empty() || (!qualified && name.contains(':')) || name.matches(':').count() > 1 {
610 return Err(Error::InvalidData(format!("invalid XML name `{name}`")));
611 }
612 for part in name.split(':') {
613 let mut characters = part.chars();
614 if !characters.next().is_some_and(is_name_start) || !characters.all(is_name_char) {
615 return Err(Error::InvalidData(format!("invalid XML name `{name}`")));
616 }
617 }
618 Ok(())
619}
620
621fn is_name_start(character: char) -> bool {
622 matches!(
623 character,
624 'A'..='Z'
625 | '_'
626 | 'a'..='z'
627 | '\u{00C0}'..='\u{00D6}'
628 | '\u{00D8}'..='\u{00F6}'
629 | '\u{00F8}'..='\u{02FF}'
630 | '\u{0370}'..='\u{037D}'
631 | '\u{037F}'..='\u{1FFF}'
632 | '\u{200C}'..='\u{200D}'
633 | '\u{2070}'..='\u{218F}'
634 | '\u{2C00}'..='\u{2FEF}'
635 | '\u{3001}'..='\u{D7FF}'
636 | '\u{F900}'..='\u{FDCF}'
637 | '\u{FDF0}'..='\u{FFFD}'
638 | '\u{10000}'..='\u{EFFFF}'
639 )
640}
641
642fn is_name_char(character: char) -> bool {
643 is_name_start(character)
644 || character.is_ascii_digit()
645 || matches!(character, '-' | '.' | '\u{B7}')
646 || ('\u{300}'..='\u{36F}').contains(&character)
647 || ('\u{203F}'..='\u{2040}').contains(&character)
648}
649
650fn validate_xml_text(value: &str) -> Result<(), Error> {
651 if value.chars().all(|character| {
652 matches!(character, '\u{9}' | '\u{A}' | '\u{D}')
653 || ('\u{20}'..='\u{D7FF}').contains(&character)
654 || ('\u{E000}'..='\u{FFFD}').contains(&character)
655 || ('\u{10000}'..='\u{10FFFF}').contains(&character)
656 }) {
657 Ok(())
658 } else {
659 Err(Error::InvalidData(
660 "value contains a character forbidden by XML 1.0".into(),
661 ))
662 }
663}
664
665struct LimitedWriter<W> {
666 inner: W,
667 max_bytes: usize,
668 written: usize,
669 exceeded: bool,
670}
671
672impl<W> LimitedWriter<W> {
673 fn new(inner: W, max_bytes: usize) -> Self {
674 Self {
675 inner,
676 max_bytes,
677 written: 0,
678 exceeded: false,
679 }
680 }
681}
682
683impl<W: Write> Write for LimitedWriter<W> {
684 fn write(&mut self, buffer: &[u8]) -> io::Result<usize> {
685 let Some(new_written) = self.written.checked_add(buffer.len()) else {
686 self.exceeded = true;
687 return Err(io::Error::other("XML output exceeds byte limit"));
688 };
689 if new_written > self.max_bytes {
690 self.exceeded = true;
691 return Err(io::Error::other("XML output exceeds byte limit"));
692 }
693 self.inner.write_all(buffer)?;
694 self.written = new_written;
695 Ok(buffer.len())
696 }
697
698 fn flush(&mut self) -> io::Result<()> {
699 self.inner.flush()
700 }
701}