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