Skip to main content

rpki/xml/
encode.rs

1use std::{fmt, io};
2use std::io::Write as _;
3use std::fmt::Write as _;
4use crate::util::base64;
5use super::decode::Name;
6
7
8//------------ Writer --------------------------------------------------------
9
10/// Wraps a writer for producing XML.
11#[derive(Debug)]
12pub struct Writer<W> {
13    /// The wrapped writer.
14    wrapped: W,
15
16    /// A place to store an error for delayed error handling.
17    ///
18    /// This is necessary so we can use `Drop` for elements which doesn’t
19    /// allow us to return an error.
20    error: Option<io::Error>,
21
22    /// The string to add for each level of indentation.
23    indent: &'static str,
24
25    /// The current indentation level.
26    ///
27    /// This is the number of times we should repeat `self.indent` at the
28    /// beginning of a line.
29    indent_level: usize,
30}
31
32impl<W: io::Write> Writer<W> {
33    /// Create a new XML writer by wrapping an IO writer.
34    ///
35    /// The writer will use an indent string of two spaces.
36    pub fn new(wrapped: W) -> Self {
37        Writer {
38            wrapped,
39            error: None,
40            indent: "  ",
41            indent_level: 0,
42        }
43    }
44
45    /// Change the indent string.
46    ///
47    /// After calling this method, each line will be started with the provided
48    /// string repeated for each indent level.
49    pub fn set_indent(&mut self, s: &'static str) {
50        self.indent = s
51    }
52
53    /// Start an XML element.
54    ///
55    /// This will write the beginning of the tag to the writer and therefore
56    /// may error. Upon success, it returns an [`Element`] which can be used
57    /// to add attributes and content. The element is finished when this
58    /// element is dropped – which is necessary to regain access to `self` as
59    /// well.
60    pub fn element<'s>(
61        &'s mut self, tag: Name<'static, 'static>,
62    ) -> Result<Element<'s, W>, io::Error> {
63        Element::start(self, tag)
64    }
65
66    /// Concludes writing and returns the writer.
67    pub fn into_wrapped(mut self) -> Result<W, io::Error> {
68        if let Some(err) = self.error.take() {
69            Err(err)
70        }
71        else {
72            Ok(self.wrapped)
73        }
74    }
75
76    /// Concludes writing and drops the writer.
77    pub fn done(mut self) -> Result<(), io::Error> {
78        if let Some(err) = self.error.take() {
79            Err(err)
80        }
81        else {
82            Ok(())
83        }
84    }
85}
86
87/// # Internal Interface
88///
89impl<W: io::Write> Writer<W> {
90    /// Stores an error for delayed error handling.
91    fn store_error(&mut self, error: io::Error) {
92        self.error = Some(error)
93    }
94
95    /// Increases indent by one level.
96    fn indent(&mut self) {
97        self.indent_level = self.indent_level.saturating_add(1);
98    }
99
100    /// Decreases indent by one level.
101    fn dedent(&mut self) {
102        self.indent_level = self.indent_level.saturating_sub(1);
103    }
104
105    /// Writes the indent.
106    ///
107    /// This does not add a line break.
108    fn write_indent(&mut self) -> Result<(), io::Error> {
109        if self.indent_level == 0 || self.indent.is_empty() {
110            return Ok(())
111        }
112
113        for _ in 0..self.indent_level {
114            self.write_all(self.indent.as_bytes())?
115        }
116        Ok(())
117    }
118}
119
120impl<W: io::Write> io::Write for Writer<W> {
121    fn write(&mut self, buf: &[u8]) -> Result<usize, io::Error> {
122        if let Some(err) = self.error.take() {
123            return Err(err)
124        }
125        self.wrapped.write(buf)
126    }
127
128    fn flush(&mut self) -> Result<(), io::Error> {
129        if let Some(err) = self.error.take() {
130            return Err(err)
131        }
132        self.wrapped.flush()
133    }
134}
135
136
137//------------ Element -------------------------------------------------------
138
139/// An XML element in the process of being written.
140#[derive(Debug)]
141pub struct Element<'a, W: io::Write> {
142    /// The writer to write to.
143    writer: &'a mut Writer<W>,
144
145    /// The tag.
146    tag: Name<'static, 'static>,
147
148    /// Is the element still empty?
149    ///
150    /// We have to keep this because of the different way empty elements are
151    /// closed.
152    empty: bool,
153}
154
155impl<'a, W: io::Write> Element<'a, W> {
156    /// Start a new element using the given writer and tag.
157    ///
158    /// Writes the start as far as that’s possible and then returns the
159    /// element.
160    fn start(
161        writer: &'a mut Writer<W>, tag: Name<'static, 'static>,
162    ) -> Result<Self, io::Error> {
163        writer.write_all(b"<")?;
164        if let Some(ns) = tag.namespace() {
165            writer.write_all(ns)?;
166            writer.write_all(b":")?;
167        }
168        writer.write_all(tag.local())?;
169        Ok(Element { writer, tag, empty: true })
170    }
171
172    /// Write an attribute.
173    pub fn attr(
174        mut self, name: &str, value: &(impl Text + ?Sized),
175    ) -> Result<Self, io::Error> {
176        self.writer.write_all(b" ")?;
177        self.writer.write_all(name.as_bytes())?;
178        self.writer.write_all(b"=\"")?;
179        value.write_escaped(TextEscape::Attr, &mut self.writer)?;
180        self.writer.write_all(b"\"")?;
181        Ok(self)
182    }
183
184    /// Write an optional attribute.
185    pub fn attr_opt(
186        self, name: &str, value: Option<&(impl Text + ?Sized)>,
187    ) -> Result<Self, io::Error> {
188        match value {
189            None => Ok(self),
190            Some(value) => self.attr(name, value)
191        }
192    }
193
194    /// Write the content of the element.
195    ///
196    /// The actual content is written by the closure passed in.
197    pub fn content(
198        mut self, op: impl FnOnce(&mut Content<W>) -> Result<(), io::Error>
199    ) -> Result<Self, io::Error> {
200        self.empty = false;
201        self.writer.write_all(b">")?;
202        self.writer.indent();
203        op(&mut Content { writer: self.writer})?;
204        self.writer.dedent();
205        Ok(self)
206    }
207
208    /// Writes the end of the element.
209    fn end(&mut self) -> Result<(), io::Error> {
210        if self.empty {
211            self.writer.write_all(b"/>")?;
212        }
213        else {
214            self.writer.write_all(b"\n")?;
215            self.writer.write_indent()?;
216            self.writer.write_all(b"</")?;
217            if let Some(ns) = self.tag.namespace() {
218                self.writer.write_all(ns)?;
219                self.writer.write_all(b":")?;
220            }
221            self.writer.write_all(self.tag.local())?;
222            self.writer.write_all(b">")?;
223        }
224        Ok(())
225    }
226}
227
228impl<W: io::Write> Drop for Element<'_, W> {
229    fn drop(&mut self) {
230        if let Err(err) = self.end() {
231            self.writer.store_error(err)
232        }
233    }
234}
235
236
237//------------ Content -------------------------------------------------------
238
239/// The content of an element.
240///
241/// This is passed to the closure for [`Element::content`] to use for actually
242/// producing content.
243#[derive(Debug)]
244pub struct Content<'a, W> {
245    /// The wrapped writer.
246    writer: &'a mut Writer<W>,
247}
248
249impl<W: io::Write> Content<'_, W> {
250    /// Add an element with the given tag.
251    ///
252    /// This will write the beginning of the tag to the writer and therefore
253    /// may error. Upon success, it returns an [`Element`] which can be used
254    /// to add attributes and content. The element is finished when this
255    /// element is dropped – which is necessary to regain access to `self` as
256    /// well.
257    pub fn element<'s>(
258        &'s mut self, tag: Name<'static, 'static>
259    ) -> Result<Element<'s, W>, io::Error> {
260        self.writer.write_all(b"\n")?;
261        self.writer.write_indent()?;
262        Element::start(self.writer, tag)
263    }
264
265    /// Add an optional element with the given tag if the given option
266    /// is some.
267    pub fn element_opt<'s, T>(
268        &'s mut self,
269        option: Option<&T>,
270        tag: Name<'static, 'static>,
271        op: impl FnOnce(&T, Element<'s, W>) -> Result<(), io::Error>
272    ) -> Result<(), io::Error> {
273        if let Some(opt) = option {
274            let element = self.element(tag)?;
275            op(opt, element)
276        } else {
277            Ok(())
278        }
279    }
280
281    /// Write some PCDATA text.
282    ///
283    /// The text will be correctly escaped while it is being written.
284    pub fn pcdata(
285        &mut self, text: &(impl Text + ?Sized)
286    ) -> Result<(), io::Error> {
287        self.writer.write_all(b"\n")?;
288        self.writer.write_indent()?;
289        text.write_escaped(TextEscape::Pcdata, &mut self.writer)
290    }
291
292    /// Write raw text.
293    ///
294    /// The text will not be escaped at all. This may lead to invalid XML.
295    pub fn raw(
296        &mut self, text: &(impl Text + ?Sized)
297    ) -> Result<(), io::Error> {
298        self.writer.write_all(b"\n")?;
299        self.writer.write_indent()?;
300        text.write_raw(&mut self.writer)
301    }
302
303    /// Write data encoded in BASE64.
304    pub fn base64(
305        &mut self, data: &(impl Text + ?Sized)
306    ) -> Result<(), io::Error> {
307        self.writer.write_all(b"\n")?;
308        self.writer.write_indent()?;
309        data.write_base64(&mut self.writer)
310    }
311}
312
313
314//------------ Text ----------------------------------------------------------
315
316/// Text to be written in XML.
317///
318/// This is a helper trait to allow passing different things to the various
319/// text writing methods and still retain reasonable performance.
320pub trait Text {
321    /// Write text escaped for the given mode to `target`.
322    fn write_escaped(
323        &self, mode: TextEscape, target: &mut impl io::Write
324    ) -> Result<(), io::Error>;
325
326    /// Write text as is to `target`.
327    fn write_raw(
328        &self, target: &mut impl io::Write
329    ) -> Result<(), io::Error>;
330
331    /// Write text encoded in BASE64 to `target`.
332    fn write_base64(
333        &self, target: &mut impl io::Write
334    ) -> Result<(), io::Error> {
335        self.write_raw(&mut base64::Xml.encode_writer(target))
336    }
337}
338
339impl Text for [u8] {
340    fn write_escaped(
341        &self, mode: TextEscape, target: &mut impl io::Write
342    ) -> Result<(), io::Error> {
343        mode.write_escaped(self, target)
344    }
345
346    fn write_raw(
347        &self, target: &mut impl io::Write
348    ) -> Result<(), io::Error> {
349        target.write_all(self)
350    }
351}
352
353impl Text for str {
354    fn write_escaped(
355        &self, mode: TextEscape, target: &mut impl io::Write
356    ) -> Result<(), io::Error> {
357        mode.write_escaped(self.as_bytes(), target)
358    }
359
360    fn write_raw(
361        &self, target: &mut impl io::Write
362    ) -> Result<(), io::Error> {
363        target.write_all(self.as_bytes())
364    }
365}
366
367impl<T: fmt::Display> Text for T {
368    fn write_escaped(
369        &self, mode: TextEscape, target: &mut impl io::Write
370    ) -> Result<(), io::Error> {
371        let mut adaptor = DisplayText::new(target, mode);
372        match write!(adaptor, "{self}") {
373            Ok(()) => Ok(()),
374            Err(_) => match adaptor.into_result() {
375                Ok(()) => Err(io::Error::other("formatter error")),
376                Err(err) => Err(err)
377            }
378        }
379    }
380
381    fn write_raw(
382        &self, target: &mut impl io::Write
383    ) -> Result<(), io::Error> {
384        write!(target, "{self}")
385    }
386}
387
388
389//------------ DisplayText ---------------------------------------------------
390
391/// A helper struct to transparently escape text via `fmt::Write`.
392struct DisplayText<'a, W> {
393    inner: &'a mut W,
394    escape: TextEscape,
395    error: Result<(), io::Error>,
396}
397
398impl<'a, W: io::Write> DisplayText<'a, W> {
399    /// Creates a new instance atop the given writer for the given mode.
400    fn new(inner: &'a mut W, escape: TextEscape) -> Self {
401        DisplayText {
402            inner, escape,
403            error: Ok(()),
404        }
405    }
406
407    /// Unwraps the struct into the final result.
408    ///
409    /// Because `fmt::Write` doesn’t handle IO errors, we have to keep any
410    /// around and you need to use this function to get the error in the end.
411    fn into_result(self) -> Result<(), io::Error> {
412        self.error
413    }
414}
415
416impl<W: io::Write> fmt::Write for DisplayText<'_, W> {
417    fn write_str(&mut self, s: &str) -> fmt::Result {
418        match self.escape.write_escaped(s.as_bytes(), self.inner) {
419            Ok(()) => Ok(()),
420            Err(err) => {
421                self.error = Err(err);
422                Err(fmt::Error)
423            }
424        }
425    }
426}
427
428
429//------------ TextEscape ----------------------------------------------------
430
431/// The escape mode for writing text.
432#[derive(Clone, Copy, Debug)]
433pub enum TextEscape {
434    /// The text appears as an attribute value.
435    Attr,
436
437    /// The text appears as PCDATA.
438    Pcdata,
439}
440
441impl TextEscape {
442    /// Return the text for replacing the given character if necessary.
443    fn replace_char(self, ch: u8) -> Option<&'static str> {
444        match self {
445            TextEscape::Attr => {
446                match ch {
447                    b'<' => Some("&lt;"),
448                    b'>' => Some("&gt;"),
449                    b'"' => Some("&quot;"),
450                    b'\'' => Some("&apos;"),
451                    b'&' => Some("&amp;"),
452                    _ => None
453                }
454            }
455            TextEscape::Pcdata => {
456                match ch {
457                    b'<' => Some("&lt;"),
458                    b'&' => Some("&amp;"),
459                    _ => None
460                }
461            }
462        }
463    }
464
465    /// Write an octet sequence escaping all necessary characters.
466    fn write_escaped(
467        self, mut s: &[u8], target: &mut impl io::Write
468    ) -> Result<(), io::Error> {
469        while !s.is_empty() {
470            let mut iter = s.iter().enumerate().map(|(idx, ch)| {
471                (idx, self.replace_char(*ch))
472            });
473            let end = loop {
474                match iter.next() {
475                    Some((idx, Some(repl))) => {
476                        // Write up to index, write replacement string,
477                        // break with index.
478                        target.write_all(&s[0..idx])?;
479                        target.write_all(repl.as_bytes())?;
480                        break idx;
481                    }
482                    Some((_, None)) => { }
483                    None => {
484                        return target.write_all(s);
485                    }
486                }
487            };
488            s = &s[end + 1..];
489        }
490        Ok(())
491    }
492}
493
494//============ Tests =========================================================
495
496#[cfg(test)]
497mod test {
498    use super::*;
499
500    const ROOT_TAG: Name = Name::unqualified(b"root");
501    const OUTER_TAG: Name = Name::unqualified(b"outer");
502    const INNER_TAG: Name = Name::unqualified(b"inner");
503
504    #[test]
505    fn xml_doc_should_not_be_wrapped_with_whitespace() -> io::Result<()> {
506        let mut buf = Vec::<u8>::new();
507        let mut writer = Writer::new(&mut buf);
508
509        writer.element(ROOT_TAG)?;
510        writer.done()?;
511
512        assert_eq!("<root/>", std::str::from_utf8(&buf).unwrap());
513        Ok(())
514    }
515
516    #[test]
517    fn test_indent() -> io::Result<()> {
518        let mut buf = Vec::<u8>::new();
519        let mut writer = Writer::new(&mut buf);
520
521        writer
522            .element(ROOT_TAG)?
523            .content(|content| {
524                content.element(OUTER_TAG)?.content(|content| {
525                    content.element(INNER_TAG)?;
526                    Ok(())
527                })?;
528                Ok(())
529            })?;
530        writer.done()?;
531
532        assert_eq!(
533            "<root>\
534            \n  <outer>\
535            \n    <inner/>\
536            \n  </outer>\
537            \n</root>",
538            std::str::from_utf8(&buf).unwrap()
539        );
540        Ok(())
541    }
542}