fuchsia_archive/
utf8_reader.rs

1// Copyright 2022 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::error::Error;
6
7/// A struct to open and read FAR-formatted archive.
8/// Requires that all paths are valid UTF-8.
9#[derive(Debug)]
10pub struct Utf8Reader<T>
11where
12    T: std::io::Read + std::io::Seek,
13{
14    reader: crate::read::Reader<T>,
15}
16
17impl<T> Utf8Reader<T>
18where
19    T: std::io::Read + std::io::Seek,
20{
21    /// Create a new Utf8Reader for the provided source.
22    pub fn new(source: T) -> Result<Self, Error> {
23        let ret = Self { reader: crate::read::Reader::new(source)? };
24        let () = ret.try_list().try_for_each(|r| r.map(|_| ()))?;
25        Ok(ret)
26    }
27
28    /// Return a list of the items in the archive.
29    fn try_list(&self) -> impl ExactSizeIterator<Item = Result<crate::Utf8Entry<'_>, Error>> {
30        self.reader.list().map(|e| {
31            Ok(crate::Utf8Entry {
32                path: std::str::from_utf8(e.path).map_err(|err| Error::PathDataInvalidUtf8 {
33                    source: err,
34                    path: e.path.into(),
35                })?,
36                offset: e.offset,
37                length: e.length,
38            })
39        })
40    }
41
42    /// Return a list of the items in the archive.
43    pub fn list(&self) -> impl ExactSizeIterator<Item = crate::Utf8Entry<'_>> {
44        self.try_list().map(|r| {
45            r.expect("Utf8Reader::new only succeeds if try_list succeeds for every element")
46        })
47    }
48
49    /// Read the entire contents of an entry with the specified path.
50    /// O(log(# directory entries))
51    pub fn read_file(&mut self, path: &str) -> Result<Vec<u8>, Error> {
52        self.reader.read_file(path.as_bytes())
53    }
54}
55
56#[cfg(test)]
57mod tests {
58    use super::*;
59    use assert_matches::assert_matches;
60    use fuchsia_async as fasync;
61    use std::io::Cursor;
62
63    #[fasync::run_singlethreaded(test)]
64    async fn new_rejects_non_utf8_path() {
65        let mut far_bytes = vec![];
66        let () = crate::write::write(
67            &mut far_bytes,
68            std::collections::BTreeMap::from_iter([(
69                b"\xff",
70                (0, Box::new("".as_bytes()) as Box<dyn std::io::Read>),
71            )]),
72        )
73        .unwrap();
74
75        assert_matches!(
76            Utf8Reader::new(Cursor::new(far_bytes)),
77            Err(crate::Error::PathDataInvalidUtf8{source: _, path}) if path == b"\xff".to_vec()
78        );
79    }
80
81    #[fasync::run_singlethreaded(test)]
82    async fn list_does_not_panic() {
83        let mut far_bytes = vec![];
84        let () = crate::write::write(
85            &mut far_bytes,
86            std::collections::BTreeMap::from_iter([(
87                "valid-utf8",
88                (0, Box::new("".as_bytes()) as Box<dyn std::io::Read>),
89            )]),
90        )
91        .unwrap();
92
93        itertools::assert_equal(
94            Utf8Reader::new(Cursor::new(far_bytes)).unwrap().list(),
95            [crate::Utf8Entry { path: "valid-utf8", offset: 4096, length: 0 }],
96        );
97    }
98
99    #[fasync::run_singlethreaded(test)]
100    async fn read_file() {
101        let mut far_bytes = vec![];
102        let () = crate::write::write(
103            &mut far_bytes,
104            std::collections::BTreeMap::from_iter([(
105                "valid-utf8",
106                (12, Box::new("test-content".as_bytes()) as Box<dyn std::io::Read>),
107            )]),
108        )
109        .unwrap();
110
111        assert_eq!(
112            Utf8Reader::new(Cursor::new(far_bytes)).unwrap().read_file("valid-utf8").unwrap(),
113            b"test-content".to_vec()
114        );
115    }
116}