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