1use std::borrow::Cow;
4use std::io::{BufRead, Take, Write};
5
6use aster_forge_utils::numbers::usize_to_u64;
7use quick_xml::XmlVersion;
8use quick_xml::escape::unescape;
9use quick_xml::events::attributes::{Attribute, Attributes as QuickAttributes};
10use quick_xml::events::{BytesCData, BytesEnd, BytesPI, BytesStart, BytesText, Event};
11use quick_xml::name::{NamespaceResolver, PrefixDeclaration, ResolveResult};
12use quick_xml::reader::NsReader;
13use quick_xml::writer::Writer;
14
15use crate::syntax::map_quick_xml_error;
16use crate::{Error, ValidatedXml, XmlSafetyError, XmlSafetyPolicy};
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub struct StreamName<'a> {
21 qualified: &'a str,
22 local: &'a str,
23 namespace: Option<&'a str>,
24}
25
26impl<'a> StreamName<'a> {
27 #[must_use]
28 pub fn qualified(self) -> &'a str {
29 self.qualified
30 }
31
32 #[must_use]
33 pub fn local(self) -> &'a str {
34 self.local
35 }
36
37 #[must_use]
38 pub fn namespace(self) -> Option<&'a str> {
39 self.namespace
40 }
41
42 #[must_use]
43 pub fn matches(self, local: &str, namespace: Option<&str>) -> bool {
44 self.local == local && self.namespace == namespace
45 }
46}
47
48pub struct StreamStart<'a> {
50 raw: BytesStart<'a>,
51 namespace: Option<&'a str>,
52 resolver: &'a NamespaceResolver,
53 cached_attribute_values: &'a [CachedAttributeValue],
54}
55
56impl StreamStart<'_> {
57 #[must_use]
60 pub fn name(&self) -> StreamName<'_> {
61 let qualified = self.raw.name().into_inner();
62 let local = self.raw.local_name().into_inner();
63 StreamName {
64 qualified,
65 local,
66 namespace: self.namespace,
67 }
68 }
69
70 #[must_use]
71 pub fn attributes(&self) -> StreamAttributes<'_> {
72 StreamAttributes {
73 inner: self.raw.attributes(),
74 resolver: self.resolver,
75 cached_values: self.cached_attribute_values,
76 cached_index: 0,
77 index: 0,
78 }
79 }
80
81 pub fn attribute(&self, qualified_name: &str) -> Result<Option<Cow<'_, str>>, Error> {
88 for attribute in self.attributes() {
89 let attribute = attribute?;
90 if attribute.name()?.qualified() == qualified_name {
91 return attribute.into_value().map(Some);
92 }
93 }
94 Ok(None)
95 }
96
97 pub fn attribute_ns(
104 &self,
105 local: &str,
106 namespace: Option<&str>,
107 ) -> Result<Option<Cow<'_, str>>, Error> {
108 for attribute in self.attributes() {
109 let attribute = attribute?;
110 if attribute.name()?.matches(local, namespace) {
111 return attribute.into_value().map(Some);
112 }
113 }
114 Ok(None)
115 }
116}
117
118pub struct StreamAttributes<'a> {
120 inner: QuickAttributes<'a>,
121 resolver: &'a NamespaceResolver,
122 cached_values: &'a [CachedAttributeValue],
123 cached_index: usize,
124 index: usize,
125}
126
127impl<'a> Iterator for StreamAttributes<'a> {
128 type Item = Result<StreamAttribute<'a>, Error>;
129
130 fn next(&mut self) -> Option<Self::Item> {
131 let index = self.index;
132 self.index = self.index.saturating_add(1);
133 let cached_value = self
134 .cached_values
135 .get(self.cached_index)
136 .and_then(|cached| {
137 if cached.index == index {
138 self.cached_index += 1;
139 Some(cached.value.as_str())
140 } else {
141 None
142 }
143 });
144 self.inner.next().map(|attribute| {
145 attribute
146 .map(|raw| StreamAttribute {
147 raw,
148 resolver: self.resolver,
149 cached_value,
150 })
151 .map_err(|error| Error::InvalidXml(error.to_string()))
152 })
153 }
154}
155
156pub struct StreamAttribute<'a> {
158 raw: Attribute<'a>,
159 resolver: &'a NamespaceResolver,
160 cached_value: Option<&'a str>,
161}
162
163impl<'a> StreamAttribute<'a> {
164 pub fn name(&self) -> Result<StreamName<'_>, Error> {
170 let qualified = self.raw.key.into_inner();
171 let local = self.raw.key.local_name().into_inner();
172 let namespace = resolve_namespace(
173 self.resolver.resolve_attribute(self.raw.key).0,
174 "attribute namespace",
175 )?;
176 Ok(StreamName {
177 qualified,
178 local,
179 namespace,
180 })
181 }
182
183 pub fn value(&self) -> Result<Cow<'_, str>, Error> {
189 if let Some(value) = self.cached_value {
190 return Ok(Cow::Borrowed(value));
191 }
192 self.raw
193 .normalized_value(XmlVersion::Explicit1_0)
194 .map_err(|error| Error::InvalidXml(error.to_string()))
195 }
196
197 pub fn into_value(self) -> Result<Cow<'a, str>, Error> {
203 if let Some(value) = self.cached_value {
204 return Ok(Cow::Borrowed(value));
205 }
206 self.raw
207 .normalized_value(XmlVersion::Explicit1_0)
208 .map_err(|error| Error::InvalidXml(error.to_string()))
209 }
210}
211
212#[derive(Debug)]
214pub struct StreamEnd<'a> {
215 raw: BytesEnd<'a>,
216 namespace: Option<&'a str>,
217}
218
219impl StreamEnd<'_> {
220 #[must_use]
223 pub fn name(&self) -> StreamName<'_> {
224 let qualified = self.raw.name().into_inner();
225 let local = self.raw.local_name().into_inner();
226 StreamName {
227 qualified,
228 local,
229 namespace: self.namespace,
230 }
231 }
232}
233
234pub struct StreamText<'a> {
236 value: Cow<'a, str>,
237}
238
239impl StreamText<'_> {
240 #[must_use]
241 pub fn value(&self) -> &str {
242 &self.value
243 }
244}
245
246pub struct StreamCData<'a> {
248 value: Cow<'a, str>,
249}
250
251impl StreamCData<'_> {
252 #[must_use]
253 pub fn value(&self) -> &str {
254 &self.value
255 }
256}
257
258pub struct StreamComment<'a> {
260 value: Cow<'a, str>,
261}
262
263impl StreamComment<'_> {
264 #[must_use]
265 pub fn value(&self) -> &str {
266 &self.value
267 }
268}
269
270pub struct StreamProcessingInstruction<'a> {
272 raw: BytesPI<'a>,
273}
274
275impl StreamProcessingInstruction<'_> {
276 #[must_use]
279 pub fn target(&self) -> &str {
280 self.raw.target()
281 }
282
283 #[must_use]
286 pub fn content(&self) -> Option<&str> {
287 let content = self
288 .raw
289 .content()
290 .trim_start_matches(|character: char| character.is_ascii_whitespace());
291 (!content.is_empty()).then_some(content)
292 }
293}
294
295pub enum XmlStreamEvent<'a> {
297 Start(StreamStart<'a>),
298 Empty(StreamStart<'a>),
299 End(StreamEnd<'a>),
300 Text(StreamText<'a>),
301 CData(StreamCData<'a>),
302 Comment(StreamComment<'a>),
303 ProcessingInstruction(StreamProcessingInstruction<'a>),
304 Declaration,
305 DocType,
306 Eof,
307}
308
309#[expect(
310 clippy::struct_excessive_bools,
311 reason = "These independent flags encode XML root, current-event, and terminal reader state."
312)]
313struct StreamState {
314 policy: XmlSafetyPolicy,
315 max_input_bytes_u64: u64,
316 depth: usize,
317 elements: usize,
318 text_bytes: usize,
319 events: usize,
320 root_seen: bool,
321 root_complete: bool,
322 current_start_available: bool,
323 current_start_depth: usize,
324 finished: bool,
325}
326
327pub struct XmlStreamReader<R: BufRead> {
329 reader: NsReader<Take<R>>,
330 buffer: Vec<u8>,
331 cached_attribute_values: Vec<CachedAttributeValue>,
332 state: StreamState,
333}
334
335struct CachedAttributeValue {
336 index: usize,
337 value: String,
338}
339
340impl<R: BufRead> XmlStreamReader<R> {
341 pub fn new(reader: R, policy: XmlSafetyPolicy) -> Result<Self, Error> {
347 policy.validate()?;
348 let read_limit = policy.max_input_bytes.saturating_add(1);
349 let read_limit = usize_to_u64(read_limit, "XML stream byte limit").unwrap_or(u64::MAX);
350 let max_input_bytes_u64 =
351 usize_to_u64(policy.max_input_bytes, "XML stream byte limit").unwrap_or(u64::MAX);
352 let mut reader = NsReader::from_reader(reader.take(read_limit));
353 reader.config_mut().trim_text(false);
354 reader
355 .resolver_mut()
356 .set_max_namespace_bindings(
360 policy
361 .max_depth
362 .saturating_mul(policy.max_attributes_per_element),
363 );
364 Ok(Self {
365 reader,
366 buffer: Vec::new(),
367 cached_attribute_values: Vec::new(),
368 state: StreamState {
369 policy,
370 max_input_bytes_u64,
371 depth: 0,
372 elements: 0,
373 text_bytes: 0,
374 events: 0,
375 root_seen: false,
376 root_complete: false,
377 current_start_available: false,
378 current_start_depth: 0,
379 finished: false,
380 },
381 })
382 }
383
384 #[expect(
391 clippy::too_many_lines,
392 reason = "The event match keeps reader state transitions adjacent to each XML event kind."
393 )]
394 pub fn read_event(&mut self) -> Result<XmlStreamEvent<'_>, Error> {
395 if self.state.finished {
396 return Ok(XmlStreamEvent::Eof);
397 }
398 self.state.current_start_available = false;
399 self.buffer.clear();
400 self.cached_attribute_values.clear();
401 let event = self
402 .reader
403 .read_event_into(&mut self.buffer)
404 .map_err(map_quick_xml_error)?;
405 check_stream_position(&self.reader, &self.state)?;
406 count_event(&mut self.state, &event)?;
407
408 match event {
409 Event::Start(start) => {
410 let namespace = begin_element(
411 &mut self.state,
412 &self.reader,
413 &start,
414 false,
415 &mut self.cached_attribute_values,
416 )?;
417 Ok(XmlStreamEvent::Start(StreamStart {
418 raw: start,
419 namespace,
420 resolver: self.reader.resolver(),
421 cached_attribute_values: &self.cached_attribute_values,
422 }))
423 }
424 Event::Empty(start) => {
425 let namespace = begin_element(
426 &mut self.state,
427 &self.reader,
428 &start,
429 true,
430 &mut self.cached_attribute_values,
431 )?;
432 Ok(XmlStreamEvent::Empty(StreamStart {
433 raw: start,
434 namespace,
435 resolver: self.reader.resolver(),
436 cached_attribute_values: &self.cached_attribute_values,
437 }))
438 }
439 Event::End(end) => {
440 if self.state.depth == 0 {
441 return Err(XmlSafetyError::Malformed.into());
442 }
443 let namespace = resolve_namespace(
444 self.reader.resolver().resolve_element(end.name()).0,
445 "element namespace",
446 )?;
447 self.state.depth -= 1;
448 if self.state.depth == 0 {
449 self.state.root_complete = true;
450 }
451 Ok(XmlStreamEvent::End(StreamEnd {
452 raw: end,
453 namespace,
454 }))
455 }
456 Event::Text(text) => {
457 let value = decode_text(&text)?;
458 count_text(&mut self.state, &value)?;
459 Ok(XmlStreamEvent::Text(StreamText { value }))
460 }
461 Event::CData(cdata) => {
462 let value = Cow::Owned(cdata.as_ref().to_owned());
463 count_text(&mut self.state, &value)?;
464 Ok(XmlStreamEvent::CData(StreamCData { value }))
465 }
466 Event::Comment(comment) => {
467 let value = Cow::Owned(comment.as_ref().to_owned());
468 Ok(XmlStreamEvent::Comment(StreamComment { value }))
469 }
470 Event::PI(pi) => Ok(XmlStreamEvent::ProcessingInstruction(
471 StreamProcessingInstruction { raw: pi },
472 )),
473 Event::GeneralRef(reference) => {
474 let value = decode_reference(&reference)?;
475 count_text(&mut self.state, &value)?;
476 Ok(XmlStreamEvent::Text(StreamText { value }))
477 }
478 Event::Decl(_) => {
479 if self.state.root_seen || self.state.depth != 0 || self.state.root_complete {
480 return Err(XmlSafetyError::Malformed.into());
481 }
482 Ok(XmlStreamEvent::Declaration)
483 }
484 Event::DocType(_) => {
485 if self.state.policy.reject_doctype {
486 return Err(XmlSafetyError::ExternalEntity.into());
487 }
488 if self.state.root_seen || self.state.depth != 0 || self.state.root_complete {
489 return Err(XmlSafetyError::Malformed.into());
490 }
491 Ok(XmlStreamEvent::DocType)
492 }
493 Event::Eof => {
494 if self.state.depth != 0 || !self.state.root_complete {
495 return Err(XmlSafetyError::Malformed.into());
496 }
497 self.state.finished = true;
498 Ok(XmlStreamEvent::Eof)
499 }
500 }
501 }
502
503 pub fn read_text_current(&mut self) -> Result<String, Error> {
510 self.require_current_start()?;
511 self.state.current_start_available = false;
512 let mut output = String::new();
513 loop {
514 match self.read_event()? {
515 XmlStreamEvent::Text(text) => output.push_str(text.value()),
516 XmlStreamEvent::CData(cdata) => output.push_str(cdata.value()),
517 XmlStreamEvent::Comment(_) | XmlStreamEvent::ProcessingInstruction(_) => {}
518 XmlStreamEvent::End(_) => return Ok(output),
519 XmlStreamEvent::Start(_) | XmlStreamEvent::Empty(_) => {
520 return Err(Error::InvalidXml(
521 "text helper encountered a nested element".into(),
522 ));
523 }
524 XmlStreamEvent::Declaration | XmlStreamEvent::DocType | XmlStreamEvent::Eof => {
525 return Err(XmlSafetyError::Malformed.into());
526 }
527 }
528 }
529 }
530
531 pub fn skip_current(&mut self) -> Result<(), Error> {
538 self.require_current_start()?;
539 self.state.current_start_available = false;
540 let mut nested = 1usize;
541 while nested > 0 {
542 match self.read_event()? {
543 XmlStreamEvent::Start(_) => {
544 nested = nested.checked_add(1).ok_or(XmlSafetyError::TooDeep)?;
545 }
546 XmlStreamEvent::End(_) => nested -= 1,
547 XmlStreamEvent::Eof => return Err(XmlSafetyError::Malformed.into()),
548 _ => {}
549 }
550 }
551 Ok(())
552 }
553
554 pub fn capture_current(&mut self, max_bytes: usize) -> Result<ValidatedXml, Error> {
562 self.require_current_start()?;
563 if max_bytes == 0 {
564 return Err(XmlSafetyError::InvalidPolicy.into());
565 }
566 self.state.current_start_available = false;
567 let mut event_reader = quick_xml::Reader::from_reader(self.buffer.as_slice());
568 let Event::Start(mut captured_start) =
569 event_reader.read_event().map_err(map_quick_xml_error)?
570 else {
571 return Err(Error::InvalidData(
572 "stream start buffer is incomplete".into(),
573 ));
574 };
575 for (prefix, namespace) in self.reader.resolver().bindings() {
576 let already_declared = captured_start.attributes().any(|attribute| {
577 let Ok(attribute) = attribute else {
578 return false;
579 };
580 match prefix {
581 PrefixDeclaration::Default => attribute.key.as_ref() == "xmlns",
582 PrefixDeclaration::Named(prefix) => {
583 attribute.key.as_ref().strip_prefix("xmlns:") == Some(prefix)
584 }
585 }
586 });
587 if already_declared {
588 continue;
589 }
590 let namespace = namespace.into_inner();
591 match prefix {
592 PrefixDeclaration::Default => captured_start.push_attribute(("xmlns", namespace)),
593 PrefixDeclaration::Named(prefix) => {
594 let name = format!("xmlns:{prefix}");
595 captured_start.push_attribute((name.as_str(), namespace));
596 }
597 }
598 }
599 let mut writer = Writer::new(LimitedVec::new(Vec::new(), max_bytes));
600 write_capture_event(&mut writer, Event::Start(captured_start))?;
601 let mut nested = 1usize;
602 while nested > 0 {
603 let event = self.read_event()?;
604 match event {
605 XmlStreamEvent::Start(start) => {
606 nested = nested.checked_add(1).ok_or(XmlSafetyError::TooDeep)?;
607 write_capture_event(&mut writer, Event::Start(start.raw.borrow()))?;
608 }
609 XmlStreamEvent::Empty(start) => {
610 write_capture_event(&mut writer, Event::Empty(start.raw.borrow()))?;
611 }
612 XmlStreamEvent::End(end) => {
613 write_capture_event(&mut writer, Event::End(end.raw.borrow()))?;
614 nested -= 1;
615 }
616 XmlStreamEvent::Text(text) => {
617 write_capture_event(&mut writer, Event::Text(BytesText::new(text.value())))?;
618 }
619 XmlStreamEvent::CData(cdata) => {
620 write_capture_event(&mut writer, Event::CData(BytesCData::new(cdata.value())))?;
621 }
622 XmlStreamEvent::Comment(comment) => {
623 write_capture_event(
624 &mut writer,
625 Event::Comment(BytesText::from_escaped(comment.value())),
626 )?;
627 }
628 XmlStreamEvent::ProcessingInstruction(pi) => {
629 write_capture_event(&mut writer, Event::PI(pi.raw.borrow()))?;
630 }
631 XmlStreamEvent::Declaration | XmlStreamEvent::DocType | XmlStreamEvent::Eof => {
632 return Err(XmlSafetyError::Malformed.into());
633 }
634 }
635 }
636 let bytes = writer.into_inner().bytes;
637 let policy = XmlSafetyPolicy {
638 max_input_bytes: max_bytes,
639 ..self.state.policy
640 };
641 ValidatedXml::with_policy(bytes, policy)
642 }
643
644 pub fn into_inner(self) -> R {
645 self.reader.into_inner().into_inner()
646 }
647
648 fn require_current_start(&self) -> Result<(), Error> {
649 if self.state.current_start_available && self.state.current_start_depth == self.state.depth
650 {
651 Ok(())
652 } else {
653 Err(Error::InvalidData(
654 "operation requires the most recently read event to be Start".into(),
655 ))
656 }
657 }
658}
659
660fn check_stream_position<R: BufRead>(
661 reader: &NsReader<Take<R>>,
662 state: &StreamState,
663) -> Result<(), Error> {
664 if reader.buffer_position() > state.max_input_bytes_u64 {
665 Err(XmlSafetyError::InputTooLarge.into())
666 } else {
667 Ok(())
668 }
669}
670
671fn count_event(state: &mut StreamState, event: &Event<'_>) -> Result<(), Error> {
672 if matches!(event, Event::Eof) {
673 return Ok(());
674 }
675 if state.events >= state.policy.max_events {
676 return Err(XmlSafetyError::TooManyEvents.into());
677 }
678 state.events += 1;
679 Ok(())
680}
681
682fn begin_element<'a, R: BufRead>(
683 state: &mut StreamState,
684 reader: &'a NsReader<Take<R>>,
685 start: &BytesStart<'_>,
686 empty: bool,
687 cached_attribute_values: &mut Vec<CachedAttributeValue>,
688) -> Result<Option<&'a str>, Error> {
689 if state.depth == 0 && state.root_complete {
690 return Err(XmlSafetyError::Malformed.into());
691 }
692 if state.depth >= state.policy.max_depth {
693 return Err(XmlSafetyError::TooDeep.into());
694 }
695 let depth = state.depth + 1;
696 if state.elements >= state.policy.max_elements {
697 return Err(XmlSafetyError::TooManyElements.into());
698 }
699 state.elements += 1;
700 let namespace = resolve_namespace(
701 reader.resolver().resolve_element(start.name()).0,
702 "element namespace",
703 )?;
704 for (index, attribute) in start.attributes().enumerate() {
705 let attribute = attribute.map_err(|error| Error::InvalidXml(error.to_string()))?;
706 if index >= state.policy.max_attributes_per_element {
707 return Err(XmlSafetyError::TooManyAttributes.into());
708 }
709 resolve_namespace(
710 reader.resolver().resolve_attribute(attribute.key).0,
711 "attribute namespace",
712 )?;
713 let value = attribute
714 .normalized_value(XmlVersion::Explicit1_0)
715 .map_err(|error| Error::InvalidXml(error.to_string()))?;
716 if let Cow::Owned(value) = value {
717 cached_attribute_values.push(CachedAttributeValue { index, value });
718 }
719 }
720
721 state.root_seen = true;
722 if empty {
723 if state.depth == 0 {
724 state.root_complete = true;
725 }
726 } else {
727 state.depth = depth;
728 state.current_start_depth = depth;
729 state.current_start_available = true;
730 }
731 Ok(namespace)
732}
733
734fn count_text(state: &mut StreamState, value: &str) -> Result<(), Error> {
735 let remaining = state.policy.max_text_bytes - state.text_bytes;
736 if value.len() > remaining {
737 return Err(XmlSafetyError::TextTooLarge.into());
738 }
739 state.text_bytes += value.len();
740 if state.depth == 0 && !value.chars().all(char::is_whitespace) {
741 return Err(XmlSafetyError::Malformed.into());
742 }
743 Ok(())
744}
745
746fn resolve_namespace<'a>(result: ResolveResult<'a>, label: &str) -> Result<Option<&'a str>, Error> {
747 match result {
748 ResolveResult::Unbound => Ok(None),
749 ResolveResult::Bound(namespace) => Ok(Some(namespace.into_inner())),
750 ResolveResult::Unknown(prefix) => Err(Error::InvalidXml(format!(
751 "unknown {label} prefix `{prefix}`"
752 ))),
753 }
754}
755
756fn decode_text<'a>(text: &BytesText<'a>) -> Result<Cow<'a, str>, Error> {
757 unescape(text.as_ref())
758 .map(|value| Cow::Owned(value.into_owned()))
759 .map_err(|error| Error::InvalidXml(error.to_string()))
760}
761
762fn decode_reference<'a>(
763 reference: &quick_xml::events::BytesRef<'a>,
764) -> Result<Cow<'a, str>, Error> {
765 if let Some(character) = reference
766 .resolve_char_ref()
767 .map_err(|error| Error::InvalidXml(error.to_string()))?
768 {
769 return Ok(Cow::Owned(character.to_string()));
770 }
771 Ok(Cow::Borrowed(match reference.as_ref() {
772 "amp" => "&",
773 "lt" => "<",
774 "gt" => ">",
775 "apos" => "'",
776 "quot" => "\"",
777 _ => return Err(XmlSafetyError::ExternalEntity.into()),
778 }))
779}
780
781fn write_capture_event(writer: &mut Writer<LimitedVec>, event: Event<'_>) -> Result<(), Error> {
782 if let Err(error) = writer.write_event(event) {
783 if writer.get_ref().exceeded {
784 return Err(XmlSafetyError::InputTooLarge.into());
785 }
786 return Err(Error::Io(error));
787 }
788 Ok(())
789}
790
791struct LimitedVec {
792 bytes: Vec<u8>,
793 max_bytes: usize,
794 exceeded: bool,
795}
796
797impl LimitedVec {
798 fn new(bytes: Vec<u8>, max_bytes: usize) -> Self {
799 Self {
800 bytes,
801 max_bytes,
802 exceeded: false,
803 }
804 }
805}
806
807impl Write for LimitedVec {
808 fn write(&mut self, buffer: &[u8]) -> std::io::Result<usize> {
809 let Some(new_len) = self.bytes.len().checked_add(buffer.len()) else {
810 self.exceeded = true;
811 return Err(std::io::Error::other("XML capture exceeds byte limit"));
812 };
813 if new_len > self.max_bytes {
814 self.exceeded = true;
815 return Err(std::io::Error::other("XML capture exceeds byte limit"));
816 }
817 self.bytes.extend_from_slice(buffer);
818 Ok(buffer.len())
819 }
820
821 fn flush(&mut self) -> std::io::Result<()> {
822 Ok(())
823 }
824}