apple_xar/
table_of_contents.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5//! XAR XML table of contents data structure.
6
7use {
8    crate::{format::XarChecksum, Error, XarResult},
9    base64::{engine::general_purpose::STANDARD as STANDARD_ENGINE, Engine},
10    digest::DynDigest,
11    serde::Deserialize,
12    std::{
13        fmt::{Display, Formatter},
14        io::{Read, Write},
15        ops::{Deref, DerefMut},
16    },
17    x509_certificate::{CapturedX509Certificate, X509CertificateError},
18    xml::{
19        common::XmlVersion,
20        writer::{EmitterConfig, EventWriter, XmlEvent},
21    },
22};
23
24/// An XML table of contents in a XAR file.
25#[derive(Clone, Debug, Deserialize)]
26#[serde(deny_unknown_fields)]
27pub struct TableOfContents {
28    toc: XarToC,
29}
30
31impl Deref for TableOfContents {
32    type Target = XarToC;
33
34    fn deref(&self) -> &Self::Target {
35        &self.toc
36    }
37}
38
39impl DerefMut for TableOfContents {
40    fn deref_mut(&mut self) -> &mut Self::Target {
41        &mut self.toc
42    }
43}
44
45impl TableOfContents {
46    /// Parse XML table of contents from a reader.
47    pub fn from_reader(reader: impl Read) -> XarResult<Self> {
48        Ok(serde_xml_rs::from_reader(reader)?)
49    }
50
51    /// Resolve the complete list of files.
52    ///
53    /// Files are sorted by their numerical ID, which should hopefully also
54    /// be the order that file data occurs in the heap. Each elements consists of
55    /// the full filename and the <file> record.
56    pub fn files(&self) -> XarResult<Vec<(String, File)>> {
57        let mut files = self
58            .toc
59            .files
60            .iter()
61            .map(|f| f.files(None))
62            .collect::<XarResult<Vec<_>>>()?
63            .into_iter()
64            .flat_map(|x| x.into_iter())
65            .collect::<Vec<_>>();
66
67        files.sort_by(|a, b| a.1.id.cmp(&b.1.id));
68
69        Ok(files)
70    }
71
72    pub fn to_xml(&self) -> XarResult<Vec<u8>> {
73        let mut emitter = EmitterConfig::new().create_writer(std::io::BufWriter::new(vec![]));
74        self.write_xml(&mut emitter)?;
75
76        emitter
77            .into_inner()
78            .into_inner()
79            .map_err(|e| Error::Io(std::io::Error::new(std::io::ErrorKind::Other, e)))
80    }
81
82    pub fn write_xml<W: Write>(&self, writer: &mut EventWriter<W>) -> XarResult<()> {
83        writer.write(XmlEvent::StartDocument {
84            version: XmlVersion::Version10,
85            encoding: Some("UTF-8"),
86            standalone: None,
87        })?;
88
89        writer.write(XmlEvent::start_element("xar"))?;
90        writer.write(XmlEvent::start_element("toc"))?;
91
92        writer.write(XmlEvent::start_element("creation-time"))?;
93        writer.write(XmlEvent::characters(&self.creation_time))?;
94        writer.write(XmlEvent::end_element())?;
95
96        self.checksum.write_xml(writer)?;
97
98        if let Some(sig) = &self.signature {
99            sig.write_xml(writer, "signature")?;
100        }
101        if let Some(sig) = &self.x_signature {
102            sig.write_xml(writer, "x-signature")?;
103        }
104
105        for file in &self.files {
106            file.write_xml(writer)?;
107        }
108
109        writer.write(XmlEvent::end_element().name("toc"))?;
110        writer.write(XmlEvent::end_element().name("xar"))?;
111
112        Ok(())
113    }
114}
115
116/// The main data structure inside a table of contents.
117#[derive(Clone, Debug, Deserialize)]
118#[serde(deny_unknown_fields, rename_all = "kebab-case")]
119pub struct XarToC {
120    pub creation_time: String,
121    pub checksum: Checksum,
122    #[serde(rename = "file")]
123    pub files: Vec<File>,
124    pub signature: Option<Signature>,
125    pub x_signature: Option<Signature>,
126}
127
128impl XarToC {
129    /// Signatures present in the table of contents.
130    pub fn signatures(&self) -> Vec<&Signature> {
131        let mut res = vec![];
132        if let Some(sig) = &self.signature {
133            res.push(sig);
134        }
135        if let Some(sig) = &self.x_signature {
136            res.push(sig);
137        }
138
139        res
140    }
141
142    /// Attempt to find a signature given a signature style.
143    pub fn find_signature(&self, style: SignatureStyle) -> Option<&Signature> {
144        self.signatures().into_iter().find(|sig| sig.style == style)
145    }
146
147    pub fn visit_files_mut(&mut self, cb: &dyn Fn(&mut File)) {
148        for file in self.files.iter_mut() {
149            cb(file);
150            file.visit_files_mut(cb);
151        }
152    }
153}
154
155#[derive(Clone, Debug, Deserialize)]
156#[serde(deny_unknown_fields)]
157pub struct Checksum {
158    /// The digest format used.
159    pub style: ChecksumType,
160
161    /// Offset within heap of the checksum data.
162    pub offset: u64,
163
164    /// Size of checksum data.
165    pub size: u64,
166}
167
168impl Checksum {
169    pub fn write_xml<W: Write>(&self, writer: &mut EventWriter<W>) -> XarResult<()> {
170        writer.write(XmlEvent::start_element("checksum").attr("style", &self.style.to_string()))?;
171        writer.write(XmlEvent::start_element("offset"))?;
172        writer.write(XmlEvent::characters(&format!("{}", self.offset)))?;
173        writer.write(XmlEvent::end_element())?;
174        writer.write(XmlEvent::start_element("size"))?;
175        writer.write(XmlEvent::characters(&format!("{}", self.size)))?;
176        writer.write(XmlEvent::end_element())?;
177        writer.write(XmlEvent::end_element().name("checksum"))?;
178
179        Ok(())
180    }
181}
182
183#[derive(Clone, Copy, Debug, Deserialize)]
184#[serde(rename_all = "lowercase")]
185pub enum ChecksumType {
186    #[serde(alias = "NONE")]
187    None,
188    #[serde(alias = "SHA1")]
189    Sha1,
190    #[serde(alias = "SHA256")]
191    Sha256,
192    #[serde(alias = "SHA512")]
193    Sha512,
194    #[serde(alias = "MD5")]
195    Md5,
196}
197
198impl Display for ChecksumType {
199    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
200        match self {
201            Self::None => f.write_str("none"),
202            Self::Sha1 => f.write_str("sha1"),
203            Self::Sha256 => f.write_str("sha256"),
204            Self::Sha512 => f.write_str("sha512"),
205            Self::Md5 => f.write_str("md5"),
206        }
207    }
208}
209
210impl TryFrom<XarChecksum> for ChecksumType {
211    type Error = Error;
212
213    fn try_from(v: XarChecksum) -> Result<Self, Self::Error> {
214        match v {
215            XarChecksum::None => Ok(Self::None),
216            XarChecksum::Sha1 => Ok(Self::Sha1),
217            XarChecksum::Md5 => Ok(Self::Md5),
218            XarChecksum::Sha256 => Ok(Self::Sha256),
219            XarChecksum::Sha512 => Ok(Self::Sha512),
220            XarChecksum::Other(_) => Err(Error::Unsupported("unknown checksum type")),
221        }
222    }
223}
224
225impl From<ChecksumType> for XarChecksum {
226    fn from(v: ChecksumType) -> Self {
227        match v {
228            ChecksumType::None => Self::None,
229            ChecksumType::Sha1 => Self::Sha1,
230            ChecksumType::Sha256 => Self::Sha256,
231            ChecksumType::Sha512 => Self::Sha512,
232            ChecksumType::Md5 => Self::Md5,
233        }
234    }
235}
236
237impl ChecksumType {
238    /// Digest a slice of data.
239    pub fn digest_data(&self, data: &[u8]) -> XarResult<Vec<u8>> {
240        let mut h: Box<dyn DynDigest> = match self {
241            Self::None => return Err(Error::Unsupported("cannot digest None checksum")),
242            Self::Md5 => Box::<md5::Md5>::default(),
243            Self::Sha1 => Box::<sha1::Sha1>::default(),
244            Self::Sha256 => Box::<sha2::Sha256>::default(),
245            Self::Sha512 => Box::<sha2::Sha512>::default(),
246        };
247
248        h.update(data);
249
250        Ok(h.finalize().to_vec())
251    }
252}
253
254#[derive(Clone, Debug, Deserialize)]
255#[serde(deny_unknown_fields)]
256pub struct File {
257    pub id: u64,
258    pub ctime: Option<String>,
259    pub mtime: Option<String>,
260    pub atime: Option<String>,
261    /// Filename.
262    ///
263    /// There should only be a single element. However, some Apple tools can
264    /// emit multiple <name> elements.
265    #[serde(rename = "name")]
266    pub names: Vec<String>,
267    #[serde(rename = "type")]
268    pub file_type: FileType,
269    pub mode: Option<String>,
270    pub deviceno: Option<u32>,
271    pub inode: Option<u64>,
272    pub uid: Option<u32>,
273    pub gid: Option<u32>,
274    pub user: Option<String>,
275    pub group: Option<String>,
276    pub size: Option<u64>,
277    pub data: Option<FileData>,
278    pub ea: Option<Ea>,
279    #[serde(rename = "FinderCreateTime")]
280    pub finder_create_time: Option<FinderCreateTime>,
281    #[serde(default, rename = "file")]
282    pub files: Vec<File>,
283}
284
285impl File {
286    pub fn files(&self, directory: Option<&str>) -> XarResult<Vec<(String, File)>> {
287        let name = self
288            .names
289            .iter()
290            .last()
291            .ok_or(Error::TableOfContentsCorrupted("missing file name"))?;
292
293        let full_path = if let Some(d) = directory {
294            format!("{d}/{name}")
295        } else {
296            name.clone()
297        };
298
299        let mut files = vec![(full_path.clone(), self.clone())];
300
301        for f in &self.files {
302            files.extend(f.files(Some(&full_path))?);
303        }
304
305        Ok(files)
306    }
307
308    pub fn visit_files_mut(&mut self, cb: &dyn Fn(&mut File)) {
309        for f in self.files.iter_mut() {
310            cb(f);
311            f.visit_files_mut(cb)
312        }
313    }
314
315    pub fn write_xml<W: Write>(&self, writer: &mut EventWriter<W>) -> XarResult<()> {
316        writer.write(XmlEvent::start_element("file").attr("id", &format!("{}", self.id)))?;
317
318        if let Some(data) = &self.data {
319            data.write_xml(writer)?;
320        }
321
322        if let Some(fct) = &self.finder_create_time {
323            writer.write(XmlEvent::start_element("FinderCreateTime"))?;
324
325            writer.write(XmlEvent::start_element("nanoseconds"))?;
326            writer.write(XmlEvent::characters(&format!("{}", fct.nanoseconds)))?;
327            writer.write(XmlEvent::end_element())?;
328
329            writer.write(XmlEvent::start_element("time"))?;
330            writer.write(XmlEvent::characters(&fct.time))?;
331            writer.write(XmlEvent::end_element())?;
332
333            writer.write(XmlEvent::end_element())?;
334        }
335
336        if let Some(time) = &self.ctime {
337            writer.write(XmlEvent::start_element("ctime"))?;
338            writer.write(XmlEvent::characters(time))?;
339            writer.write(XmlEvent::end_element())?;
340        }
341
342        if let Some(time) = &self.mtime {
343            writer.write(XmlEvent::start_element("mtime"))?;
344            writer.write(XmlEvent::characters(time))?;
345            writer.write(XmlEvent::end_element())?;
346        }
347
348        if let Some(time) = &self.atime {
349            writer.write(XmlEvent::start_element("atime"))?;
350            writer.write(XmlEvent::characters(time))?;
351            writer.write(XmlEvent::end_element())?;
352        }
353
354        if let Some(v) = &self.group {
355            writer.write(XmlEvent::start_element("group"))?;
356            writer.write(XmlEvent::characters(v))?;
357            writer.write(XmlEvent::end_element())?;
358        }
359
360        if let Some(v) = &self.gid {
361            writer.write(XmlEvent::start_element("gid"))?;
362            writer.write(XmlEvent::characters(&format!("{v}")))?;
363            writer.write(XmlEvent::end_element())?;
364        }
365
366        if let Some(v) = &self.user {
367            writer.write(XmlEvent::start_element("user"))?;
368            writer.write(XmlEvent::characters(v))?;
369            writer.write(XmlEvent::end_element())?;
370        }
371
372        if let Some(v) = &self.uid {
373            writer.write(XmlEvent::start_element("uid"))?;
374            writer.write(XmlEvent::characters(&format!("{v}")))?;
375            writer.write(XmlEvent::end_element())?;
376        }
377
378        if let Some(v) = &self.mode {
379            writer.write(XmlEvent::start_element("mode"))?;
380            writer.write(XmlEvent::characters(v))?;
381            writer.write(XmlEvent::end_element())?;
382        }
383
384        if let Some(v) = &self.deviceno {
385            writer.write(XmlEvent::start_element("deviceno"))?;
386            writer.write(XmlEvent::characters(&format!("{v}")))?;
387            writer.write(XmlEvent::end_element())?;
388        }
389
390        if let Some(v) = &self.inode {
391            writer.write(XmlEvent::start_element("inode"))?;
392            writer.write(XmlEvent::characters(&format!("{v}")))?;
393            writer.write(XmlEvent::end_element())?;
394        }
395
396        if let Some(ea) = &self.ea {
397            ea.write_xml(writer)?;
398        }
399
400        writer.write(XmlEvent::start_element("type"))?;
401        writer.write(XmlEvent::characters(&self.file_type.to_string()))?;
402        writer.write(XmlEvent::end_element())?;
403
404        for name in &self.names {
405            writer.write(XmlEvent::start_element("name"))?;
406            writer.write(XmlEvent::characters(name))?;
407            writer.write(XmlEvent::end_element())?;
408        }
409
410        for file in &self.files {
411            file.write_xml(writer)?;
412        }
413
414        writer.write(XmlEvent::end_element().name("file"))?;
415
416        Ok(())
417    }
418}
419
420#[derive(Clone, Copy, Debug, Deserialize)]
421#[serde(rename_all = "lowercase")]
422pub enum FileType {
423    File,
424    Directory,
425    HardLink,
426    Link,
427}
428
429impl Display for FileType {
430    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
431        match self {
432            FileType::File => f.write_str("file"),
433            FileType::Directory => f.write_str("directory"),
434            FileType::HardLink => f.write_str("hardlink"),
435            FileType::Link => f.write_str("symlink"),
436        }
437    }
438}
439
440#[derive(Clone, Debug, Deserialize)]
441#[serde(deny_unknown_fields, rename_all = "kebab-case")]
442pub struct FileData {
443    pub offset: u64,
444    pub size: u64,
445    pub length: u64,
446    pub extracted_checksum: FileChecksum,
447    pub archived_checksum: FileChecksum,
448    pub encoding: FileEncoding,
449}
450
451impl FileData {
452    pub fn write_xml<W: Write>(&self, writer: &mut EventWriter<W>) -> XarResult<()> {
453        writer.write(XmlEvent::start_element("data"))?;
454
455        writer.write(XmlEvent::start_element("length"))?;
456        writer.write(XmlEvent::characters(&format!("{}", self.length)))?;
457        writer.write(XmlEvent::end_element())?;
458
459        writer.write(XmlEvent::start_element("offset"))?;
460        writer.write(XmlEvent::characters(&format!("{}", self.offset)))?;
461        writer.write(XmlEvent::end_element())?;
462
463        writer.write(XmlEvent::start_element("size"))?;
464        writer.write(XmlEvent::characters(&format!("{}", self.size)))?;
465        writer.write(XmlEvent::end_element())?;
466
467        writer.write(XmlEvent::start_element("encoding").attr("style", &self.encoding.style))?;
468        writer.write(XmlEvent::end_element())?;
469
470        self.extracted_checksum
471            .write_xml(writer, "extracted-checksum")?;
472        self.archived_checksum
473            .write_xml(writer, "archived-checksum")?;
474
475        writer.write(XmlEvent::end_element().name("data"))?;
476
477        Ok(())
478    }
479}
480
481#[derive(Clone, Debug, Deserialize)]
482#[serde(deny_unknown_fields)]
483pub struct FileChecksum {
484    pub style: ChecksumType,
485    #[serde(rename = "$value")]
486    pub checksum: String,
487}
488
489impl FileChecksum {
490    pub fn write_xml<W: Write>(&self, writer: &mut EventWriter<W>, name: &str) -> XarResult<()> {
491        writer.write(XmlEvent::start_element(name).attr("style", &self.style.to_string()))?;
492        writer.write(XmlEvent::characters(&self.checksum))?;
493        writer.write(XmlEvent::end_element())?;
494
495        Ok(())
496    }
497}
498
499#[derive(Clone, Debug, Deserialize)]
500#[serde(deny_unknown_fields)]
501pub struct FileEncoding {
502    pub style: String,
503}
504
505#[derive(Clone, Debug, Deserialize)]
506#[serde(deny_unknown_fields, rename_all = "kebab-case")]
507pub struct Ea {
508    #[serde(skip_serializing_if = "Option::is_none")]
509    pub id: Option<u64>,
510    pub name: String,
511    pub offset: u64,
512    pub size: u64,
513    pub length: u64,
514    pub extracted_checksum: FileChecksum,
515    pub archived_checksum: FileChecksum,
516    pub encoding: FileEncoding,
517}
518
519impl Ea {
520    pub fn write_xml<W: Write>(&self, writer: &mut EventWriter<W>) -> XarResult<()> {
521        let mut ea = XmlEvent::start_element("ea");
522
523        let id = self.id.map(|x| format!("{}", x));
524
525        if let Some(id) = &id {
526            ea = ea.attr("id", id.as_str());
527        }
528
529        writer.write(ea)?;
530
531        writer.write(XmlEvent::start_element("name"))?;
532        writer.write(XmlEvent::characters(&self.name))?;
533        writer.write(XmlEvent::end_element())?;
534
535        writer.write(XmlEvent::start_element("offset"))?;
536        writer.write(XmlEvent::characters(&format!("{}", self.offset)))?;
537        writer.write(XmlEvent::end_element())?;
538
539        writer.write(XmlEvent::start_element("size"))?;
540        writer.write(XmlEvent::characters(&format!("{}", self.size)))?;
541        writer.write(XmlEvent::end_element())?;
542
543        writer.write(XmlEvent::start_element("length"))?;
544        writer.write(XmlEvent::characters(&format!("{}", self.length)))?;
545        writer.write(XmlEvent::end_element())?;
546
547        self.extracted_checksum
548            .write_xml(writer, "extracted-checksum")?;
549        self.archived_checksum
550            .write_xml(writer, "archived-checksum")?;
551
552        writer.write(XmlEvent::start_element("encoding").attr("style", &self.encoding.style))?;
553        writer.write(XmlEvent::end_element())?;
554
555        writer.write(XmlEvent::end_element())?;
556
557        Ok(())
558    }
559}
560
561#[derive(Clone, Debug, Deserialize)]
562#[serde(deny_unknown_fields)]
563pub struct FinderCreateTime {
564    pub nanoseconds: u64,
565    pub time: String,
566}
567
568#[derive(Clone, Debug, Deserialize)]
569#[serde(deny_unknown_fields)]
570pub struct Signature {
571    pub style: SignatureStyle,
572    pub offset: u64,
573    pub size: u64,
574    #[serde(rename = "KeyInfo")]
575    pub key_info: KeyInfo,
576}
577
578impl Signature {
579    /// Obtained parsed X.509 certificates.
580    pub fn x509_certificates(&self) -> XarResult<Vec<CapturedX509Certificate>> {
581        self.key_info.x509_certificates()
582    }
583
584    pub fn write_xml<W: Write>(&self, writer: &mut EventWriter<W>, name: &str) -> XarResult<()> {
585        writer.write(XmlEvent::start_element(name).attr("style", &self.style.to_string()))?;
586
587        writer.write(XmlEvent::start_element("offset"))?;
588        writer.write(XmlEvent::characters(&format!("{}", &self.offset)))?;
589        writer.write(XmlEvent::end_element())?;
590
591        writer.write(XmlEvent::start_element("size"))?;
592        writer.write(XmlEvent::characters(&format!("{}", &self.size)))?;
593        writer.write(XmlEvent::end_element())?;
594
595        writer.write(
596            XmlEvent::start_element("KeyInfo").ns("", "http://www.w3.org/2000/09/xmldsig#"),
597        )?;
598        writer.write(XmlEvent::start_element("X509Data"))?;
599
600        for cert in &self.key_info.x509_data.x509_certificate {
601            writer.write(XmlEvent::start_element("X509Certificate"))?;
602            writer.write(XmlEvent::characters(cert))?;
603            writer.write(XmlEvent::end_element())?;
604        }
605
606        writer.write(XmlEvent::end_element().name("X509Data"))?;
607        writer.write(XmlEvent::end_element().name("KeyInfo"))?;
608
609        writer.write(XmlEvent::end_element().name(name))?;
610
611        Ok(())
612    }
613}
614
615#[derive(Clone, Copy, Debug, Eq, PartialEq, Deserialize)]
616#[serde(rename_all = "UPPERCASE")]
617pub enum SignatureStyle {
618    /// Cryptographic message syntax.
619    Cms,
620
621    /// RSA signature.
622    Rsa,
623}
624
625impl Display for SignatureStyle {
626    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
627        match self {
628            Self::Cms => f.write_str("CMS"),
629            Self::Rsa => f.write_str("RSA"),
630        }
631    }
632}
633
634#[derive(Clone, Debug, Deserialize)]
635#[serde(deny_unknown_fields)]
636pub struct KeyInfo {
637    #[serde(rename = "X509Data")]
638    pub x509_data: X509Data,
639}
640
641impl KeyInfo {
642    /// Construct an instance from an iterable of certificates.
643    pub fn from_certificates<'a>(
644        certs: impl Iterator<Item = &'a CapturedX509Certificate>,
645    ) -> XarResult<Self> {
646        Ok(Self {
647            x509_data: X509Data {
648                x509_certificate: certs
649                    .map(|cert| {
650                        let der = cert.encode_der()?;
651                        let s = STANDARD_ENGINE.encode(der);
652
653                        let mut lines = vec![];
654
655                        let mut remaining = s.as_str();
656
657                        loop {
658                            if remaining.len() > 72 {
659                                let res = remaining.split_at(72);
660                                lines.push(res.0);
661                                remaining = res.1;
662                            } else {
663                                lines.push(remaining);
664                                break;
665                            }
666                        }
667
668                        Ok(lines.join("\n"))
669                    })
670                    .collect::<XarResult<Vec<_>>>()?,
671            },
672        })
673    }
674
675    /// Obtain parsed X.509 certificates.
676    pub fn x509_certificates(&self) -> XarResult<Vec<CapturedX509Certificate>> {
677        self.x509_data.x509_certificates()
678    }
679}
680
681#[derive(Clone, Debug, Deserialize)]
682#[serde(deny_unknown_fields)]
683pub struct X509Data {
684    #[serde(rename = "X509Certificate")]
685    pub x509_certificate: Vec<String>,
686}
687
688impl X509Data {
689    /// Obtain parsed X.509 certificates.
690    pub fn x509_certificates(&self) -> XarResult<Vec<CapturedX509Certificate>> {
691        Ok(self
692            .x509_certificate
693            .iter()
694            .map(|data| {
695                // The data in the XML isn't armored. So we add armoring so it can
696                // be decoded by the pem crate.
697                let data = format!(
698                    "-----BEGIN CERTIFICATE-----\r\n{data}\r\n-----END CERTIFICATE-----\r\n"
699                );
700
701                CapturedX509Certificate::from_pem(data)
702            })
703            .collect::<Result<Vec<_>, X509CertificateError>>()?)
704    }
705}
706
707#[cfg(test)]
708mod test {
709    use super::*;
710
711    #[test]
712    fn file_checksum_serialize() -> XarResult<()> {
713        let xml = "<checksum style=\"sha1\">checksum</checksum>";
714
715        let _: FileChecksum = serde_xml_rs::from_reader(std::io::BufReader::new(xml.as_bytes()))?;
716
717        // Uppercase variant works.
718        let xml = "<checksum style=\"SHA1\">checksum</checksum>";
719        let _: FileChecksum = serde_xml_rs::from_reader(std::io::BufReader::new(xml.as_bytes()))?;
720
721        Ok(())
722    }
723}