aster_forge_xml/
stream.rs

1//! Bounded, namespace-aware streaming XML reader.
2
3use 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/// A namespace-resolved XML name borrowed from one streaming event.
19#[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
48/// A start or empty element event.
49pub 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    /// Resolves the start element's qualified, local, and namespace names.
58    ///
59    #[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    /// Returns a matching attribute by qualified name.
82    ///
83    /// # Errors
84    ///
85    /// Returns an error when an attribute is malformed or its name, namespace, or value cannot be
86    /// decoded.
87    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    /// Returns a matching attribute by local name and namespace.
98    ///
99    /// # Errors
100    ///
101    /// Returns an error when an attribute is malformed or its name, namespace, or value cannot be
102    /// decoded.
103    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
118/// Iterator over attributes of a streaming start event.
119pub 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
156/// A namespace-resolved attribute borrowed from a streaming start event.
157pub struct StreamAttribute<'a> {
158    raw: Attribute<'a>,
159    resolver: &'a NamespaceResolver,
160    cached_value: Option<&'a str>,
161}
162
163impl<'a> StreamAttribute<'a> {
164    /// Resolves the attribute's qualified, local, and namespace names.
165    ///
166    /// # Errors
167    ///
168    /// Returns an error when its namespace cannot be resolved.
169    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    /// Decodes and normalizes the attribute value without consuming the event.
184    ///
185    /// # Errors
186    ///
187    /// Returns an error when the attribute value has invalid entity syntax.
188    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    /// Decodes and normalizes the attribute value while consuming the event.
198    ///
199    /// # Errors
200    ///
201    /// Returns an error when the attribute value has invalid entity syntax.
202    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/// An end element event.
213#[derive(Debug)]
214pub struct StreamEnd<'a> {
215    raw: BytesEnd<'a>,
216    namespace: Option<&'a str>,
217}
218
219impl StreamEnd<'_> {
220    /// Resolves the end element's qualified, local, and namespace names.
221    ///
222    #[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
234/// Decoded and unescaped character data.
235pub 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
246/// Decoded CDATA content.
247pub 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
258/// Decoded XML comment content.
259pub 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
270/// A processing instruction.
271pub struct StreamProcessingInstruction<'a> {
272    raw: BytesPI<'a>,
273}
274
275impl StreamProcessingInstruction<'_> {
276    /// Returns the processing-instruction target.
277    ///
278    #[must_use]
279    pub fn target(&self) -> &str {
280        self.raw.target()
281    }
282
283    /// Returns trimmed processing-instruction content when present.
284    ///
285    #[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
295/// One bounded streaming XML event.
296pub 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
327/// A streaming XML reader that enforces [`XmlSafetyPolicy`] without retaining a full document.
328pub 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    /// Creates a bounded streaming reader.
342    ///
343    /// # Errors
344    ///
345    /// Returns an error when `policy` contains a zero limit.
346    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            // quick-xml 0.42 bounds namespace bindings currently in scope, while Forge's
357            // attribute limit applies to one element. Keep the limits independent and derive
358            // the in-scope bound from the maximum active depth and per-element declarations.
359            .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    /// Reads and validates the next XML event.
385    ///
386    /// # Errors
387    ///
388    /// Returns an error for underlying I/O failures, malformed or invalidly encoded XML, forbidden
389    /// document constructs, or any configured safety-limit violation.
390    #[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    /// Reads direct text and CDATA until the end of the current start element.
504    ///
505    /// # Errors
506    ///
507    /// Returns an error when no current start element is available, a nested element is found, or
508    /// event reading or XML validation fails.
509    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    /// Skips the current start element and all descendants with constant retained memory.
532    ///
533    /// # Errors
534    ///
535    /// Returns an error when no current start element is available, nesting overflows, the document
536    /// ends early, or event reading or XML validation fails.
537    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    /// Materializes only the current subtree as a validated owned XML value.
555    ///
556    /// # Errors
557    ///
558    /// Returns an error when no current start element is available, `max_bytes` is zero, namespace
559    /// synthesis or event decoding fails, the subtree exceeds its output bound, or the captured XML
560    /// fails validation.
561    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}