xar/
toc.rs

1use chrono::NaiveDateTime;
2use failure::*;
3use libflate::zlib::Decoder;
4use std::fmt;
5use std::io::{Read, Write};
6use std::path::{Component, Path, PathBuf};
7use xmltree::Element;
8
9#[derive(Fail, Debug)]
10pub enum Errors {
11    #[fail(display = "<toc> element doesn't exist in Toc.")]
12    NoTocElement,
13    #[fail(display = "<creation-time> element doesn't exist in Toc.")]
14    NoCreationTime,
15    #[fail(display = "<checksum> element missing.")]
16    NoChecksumElement,
17    #[fail(display = "style attribute in <checksum> element missing.")]
18    NoChecksumType,
19    #[fail(display = "style attribute in <checksum> element missing.")]
20    NoFileTypeElement,
21    #[fail(display = "style attribute in <checksum> element missing.")]
22    NoFileNameElement,
23    #[fail(display = "style attribute in <checksum> element missing.")]
24    NoFileId,
25    #[fail(display = "style attribute in <checksum> element missing.")]
26    ChecksumOffsetInvalid,
27}
28
29/// Table of contents.
30#[derive(Debug, Clone)]
31pub struct Toc {
32    data: Element,
33}
34
35impl Toc {
36    /// Contstruct a toc from a reader pointed at the start of it.
37    pub fn from_read<T: Read>(reader: &mut T, _expected: usize) -> Result<Toc, Error> {
38        // TODO: check expected toc size.
39
40        let mut decoder = Decoder::new(reader)?;
41        let element = Element::parse(&mut decoder)?;
42
43        Ok(Toc { data: element })
44    }
45
46    pub fn data(&self) -> &Element {
47        &self.data
48    }
49
50    /// Print the toc as XML to writer.
51    pub fn write<W: Write>(&self, writer: W) -> Result<(), xmltree::Error> {
52        self.data.write(writer)
53    }
54
55    /// Compute creation time of Toc.
56    pub fn creation_time(&self) -> Result<NaiveDateTime, Error> {
57        let time = self.creation_time_element()?;
58        let text = time.text.as_ref().ok_or(Errors::NoCreationTime)?;
59        let parsed = NaiveDateTime::parse_from_str(&text, "%Y-%m-%dT%H:%M:%S")?;
60        Ok(parsed)
61    }
62
63    fn creation_time_element(&self) -> Result<&Element, Errors> {
64        self.toc_element()?
65            .get_child("creation-time")
66            .ok_or(Errors::NoCreationTime)
67    }
68
69    /// Get what type of checksum was used for the Toc.
70    pub fn checksum_type(&self) -> Result<&String, Errors> {
71        self.checksum_element()?
72            .attributes
73            .get("style")
74            .ok_or(Errors::NoChecksumType)
75    }
76
77    /// Find out at which offset the checksum is.
78    pub fn checksum_offset(&self) -> Result<usize, Error> {
79        let re = self
80            .checksum_element()?
81            .get_child("offset")
82            .ok_or(Errors::ChecksumOffsetInvalid)?
83            .text
84            .as_ref()
85            .ok_or(Errors::ChecksumOffsetInvalid)?
86            .parse::<usize>()?;
87        Ok(re)
88    }
89
90    /// Find out how many bytes the checksum is.
91    pub fn checksum_size(&self) -> Result<usize, Error> {
92        let re = self
93            .checksum_element()?
94            .get_child("size")
95            .ok_or(Errors::ChecksumOffsetInvalid)?
96            .text
97            .as_ref()
98            .ok_or(Errors::ChecksumOffsetInvalid)?
99            .parse::<usize>()?;
100        Ok(re)
101    }
102
103    fn checksum_element(&self) -> Result<&Element, Errors> {
104        self.toc_element()?
105            .get_child("checksum")
106            .ok_or(Errors::NoChecksumElement)
107    }
108
109    fn toc_element(&self) -> Result<&Element, Errors> {
110        self.data.get_child("toc").ok_or(Errors::NoTocElement)
111    }
112
113    pub fn files(&self) -> Result<Files, Errors> {
114        Ok(Files {
115            data: self.toc_element()?,
116            path: PathBuf::new(),
117        })
118    }
119}
120
121impl std::fmt::Display for Toc {
122    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
123        write!(f, "creation-time {:?}\n", self.creation_time())?;
124        write!(f, "checksum-kind {:?}\n", self.checksum_type())?;
125        write!(f, "checksum-offset {:?}\n", self.checksum_offset())?;
126        write!(f, "checksum-size {:?}\n", self.checksum_size())
127    }
128}
129
130#[derive(Clone, Debug, Copy)]
131pub enum FileElement {
132    Data,
133    CTime,
134    MTime,
135    ATime,
136    Group,
137    GID,
138    User,
139    UID,
140    Mode,
141    INode,
142    Type,
143    Name,
144    DeviceNo,
145}
146
147impl FileElement {
148    pub fn name(&self) -> &'static str {
149        use FileElement::*;
150        match self {
151            Data => "data",
152            CTime => "ctime",
153            MTime => "mtime",
154            ATime => "atime",
155            Group => "group",
156            GID => "gid",
157            User => "user",
158            UID => "uid",
159            Mode => "mode",
160            INode => "inode",
161            Type => "type",
162            Name => "name",
163            DeviceNo => "deviceno",
164        }
165    }
166
167    pub fn from_name(name: &str) -> Option<FileElement> {
168        use FileElement::*;
169        match name {
170            "data" => Some(Data),
171            "ctime" => Some(CTime),
172            "mtime" => Some(MTime),
173            "atime" => Some(ATime),
174            "group" => Some(Group),
175            "gid" => Some(GID),
176            "user" => Some(User),
177            "uid" => Some(UID),
178            "mode" => Some(Mode),
179            "inode" => Some(INode),
180            "type" => Some(Type),
181            "name" => Some(Name),
182            "deviceno" => Some(DeviceNo),
183            _ => None,
184        }
185    }
186
187    pub fn error(&self) -> Errors {
188        use FileElement::*;
189        match self {
190            _ => Errors::NoFileTypeElement,
191        }
192    }
193}
194
195#[derive(Debug, Clone, Copy)]
196pub enum FileDataElement {
197    Length,
198    Offset,
199    Size,
200    Encoding,
201    ExtractedChecksum,
202    ArchivedChecksum,
203}
204
205impl FileDataElement {
206    pub fn name(&self) -> &'static str {
207        use FileDataElement::*;
208        match self {
209            Length => "length",
210            Offset => "offset",
211            Size => "size",
212            Encoding => "encoding",
213            ExtractedChecksum => "extracted-checksum",
214            ArchivedChecksum => "archived-checksum",
215        }
216    }
217
218    pub fn error(&self) -> Errors {
219        use FileElement::*;
220        match self {
221            _ => Errors::NoFileTypeElement,
222        }
223    }
224}
225
226#[derive(Debug, Clone, Copy)]
227pub enum FileType {
228    File,
229    Directory,
230    CharacterSpecial,
231}
232
233impl FileType {
234    pub fn from_str(name: &str) -> Option<FileType> {
235        use FileType::*;
236        match name {
237            "file" => Some(File),
238            "directory" => Some(Directory),
239            "character special" => Some(CharacterSpecial),
240            _ => None,
241        }
242    }
243}
244
245#[derive(Debug, Clone)]
246pub struct FileAttr {
247    pub name: Option<String>,
248    pub id: Option<usize>,
249    pub ftype: Option<FileType>,
250    pub user: Option<String>,
251    pub group: Option<String>,
252    pub uid: Option<usize>,
253    pub gid: Option<usize>,
254    pub deviceno: Option<usize>,
255    pub inode: Option<usize>,
256}
257
258impl FileAttr {
259    pub fn new() -> Self {
260        FileAttr {
261            name: None,
262            id: None,
263            ftype: None,
264            user: None,
265            group: None,
266            uid: None,
267            gid: None,
268            deviceno: None,
269            inode: None,
270        }
271    }
272
273    pub fn parse(data: &Element) -> FileAttr {
274        let mut attrs = FileAttr::new();
275
276        for child in &data.children {
277            let _ = attrs.parse_child(child);
278        }
279
280        attrs
281    }
282
283    fn parse_child(&mut self, child: &Element) -> Result<(), Errors> {
284        let e = FileElement::from_name(&child.name).ok_or(Errors::NoTocElement)?;
285
286        use FileElement::*;
287        match e {
288            Group => Self::parse_text(e, child, &mut self.group),
289            User => Self::parse_text(e, child, &mut self.user),
290            Name => Self::parse_text(e, child, &mut self.name),
291            Type => Self::parse_type(e, child, &mut self.ftype),
292            Data => self.parse_dummy(child),
293            CTime => self.parse_dummy(child),
294            MTime => self.parse_dummy(child),
295            ATime => self.parse_dummy(child),
296            GID => Self::parse_usize(e, child, &mut self.gid),
297            UID => Self::parse_usize(e, child, &mut self.uid),
298            Mode => self.parse_dummy(child),
299            INode => Self::parse_usize(e, child, &mut self.inode),
300            DeviceNo => Self::parse_usize(e, child, &mut self.deviceno),
301        }
302    }
303
304    fn parse_text(
305        element: FileElement,
306        child: &Element,
307        text: &mut Option<String>,
308    ) -> Result<(), Errors> {
309        *text = child.text.clone();
310        Ok(())
311    }
312
313    fn parse_type(
314        element: FileElement,
315        child: &Element,
316        ftype: &mut Option<FileType>,
317    ) -> Result<(), Errors> {
318        if let Some(text) = &child.text {
319            if let Some(nftype) = FileType::from_str(text) {
320                *ftype = Some(nftype);
321            }
322        }
323
324        Ok(())
325    }
326
327    fn parse_usize(
328        element: FileElement,
329        child: &Element,
330        out: &mut Option<usize>,
331    ) -> Result<(), Errors> {
332        let amt = child
333            .text
334            .as_ref()
335            .ok_or(element.error())?
336            .parse::<usize>()
337            .or(Err(element.error()))?;
338        *out = Some(amt);
339        Ok(())
340    }
341
342    fn parse_dummy(&mut self, child: &Element) -> Result<(), Errors> {
343        Ok(())
344    }
345}
346
347/// File object.
348#[derive(Debug, Clone)]
349pub struct File<'a, 'b> {
350    data: &'a Element,
351    pub path: &'b Path,
352}
353
354impl<'a, 'b> File<'a, 'b> {
355    pub fn new(element: &'a Element, path: &'b Path) -> File<'a, 'b> {
356        File {
357            data: element,
358            path: path,
359        }
360    }
361
362    pub fn files(&self) -> Files<'a> {
363        let mut path = self.path.to_path_buf();
364        let attrs = self.attrs();
365        // TODO: what if no name?
366        if let Some(name) = attrs.name {
367            path.push(name)
368        }
369
370        Files {
371            data: self.data,
372            path: path,
373        }
374    }
375
376    pub fn attrs(&self) -> FileAttr {
377        FileAttr::parse(&self.data)
378    }
379}
380
381/// Iterator over the files (in the current level).
382#[derive(Debug, Clone)]
383pub struct Files<'a> {
384    data: &'a Element,
385    path: PathBuf,
386}
387
388impl<'a> Files<'a> {
389    pub fn iter(&self) -> FilesIter {
390        FilesIter {
391            data: self.data,
392            path: &self.path,
393            pos: 0,
394        }
395    }
396
397    pub fn find(&self, path: &Path) -> Option<Files> {
398        let mut files: Option<Files> = Some(self.clone());
399
400        files
401    }
402}
403
404/// Iterator over the files
405#[derive(Debug, Clone)]
406pub struct FilesIter<'a, 'b> {
407    data: &'a Element,
408    path: &'b Path,
409    pos: usize,
410}
411
412impl<'a, 'b> Iterator for FilesIter<'a, 'b> {
413    type Item = File<'a, 'b>;
414    fn next(&mut self) -> Option<Self::Item> {
415        for (i, child) in self.data.children.iter().enumerate().skip(self.pos) {
416            if child.name == "file" {
417                self.pos = i + 1;
418                return Some(File {
419                    data: child,
420                    path: self.path,
421                });
422            }
423        }
424        None
425    }
426}