Skip to main content

async_zip/base/read/
seek.rs

1// Copyright (c) 2022 Harry [Majored] [hello@majored.pw]
2// MIT License (https://github.com/Majored/rs-async-zip/blob/main/LICENSE)
3
4//! A ZIP reader which acts over a seekable source.
5//!
6//! ### Example
7//! ```no_run
8//! # use async_zip::base::read::seek::ZipFileReader;
9//! # use async_zip::error::Result;
10//! # use futures_lite::io::AsyncReadExt;
11//! # use tokio::fs::File;
12//! # use tokio_util::compat::TokioAsyncReadCompatExt;
13//! # use tokio::io::BufReader;
14//! #
15//! async fn run() -> Result<()> {
16//!     let mut data = BufReader::new(File::open("./foo.zip").await?);
17//!     let mut reader = ZipFileReader::new(data.compat()).await?;
18//!
19//!     let mut data = Vec::new();
20//!     let mut entry = reader.reader_without_entry(0).await?;
21//!     entry.read_to_end(&mut data).await?;
22//!
23//!     // Use data within current scope.
24//!
25//!     Ok(())
26//! }
27//! ```
28
29use crate::base::read::io::entry::ZipEntryReader;
30use crate::error::{Result, ZipError};
31use crate::file::ZipFile;
32use std::sync::Arc;
33
34#[cfg(feature = "tokio")]
35use crate::tokio::read::seek::ZipFileReader as TokioZipFileReader;
36
37use futures_lite::io::{AsyncBufRead, AsyncSeek};
38
39#[cfg(feature = "tokio")]
40use tokio_util::compat::{Compat, TokioAsyncReadCompatExt};
41
42use super::io::entry::{WithEntry, WithoutEntry};
43
44/// A ZIP reader which acts over a seekable source.
45#[derive(Clone)]
46pub struct ZipFileReader<R> {
47    reader: R,
48    file: Arc<ZipFile>,
49}
50
51impl<R> ZipFileReader<R>
52where
53    R: AsyncBufRead + AsyncSeek + Unpin,
54{
55    /// Constructs a new ZIP reader from a seekable source.
56    ///
57    /// The source must be finite or bounded to a single archive. At most 4 KiB of NUL padding is
58    /// permitted after the end record's declared comment. Local headers are not walked during construction.
59    /// Archive and entry comments containing bytes `0x01` through `0x08` are conservatively rejected to
60    /// exclude embedded ZIP records; this does not prohibit ZIP files stored as entries.
61    pub async fn new(mut reader: R) -> Result<ZipFileReader<R>> {
62        let file = crate::base::read::file(&mut reader).await?;
63        Ok(ZipFileReader::from_raw_parts(reader, file))
64    }
65
66    /// Constructs a ZIP reader from a seekable source and ZIP file information derived from that source.
67    ///
68    /// Providing a [`ZipFile`] that wasn't derived from that source may lead to inaccurate parsing.
69    pub fn from_raw_parts(reader: R, file: ZipFile) -> ZipFileReader<R> {
70        ZipFileReader { reader, file: Arc::new(file) }
71    }
72
73    /// Returns this ZIP file's information.
74    pub fn file(&self) -> &ZipFile {
75        &self.file
76    }
77
78    /// Returns a mutable reference to the inner seekable source.
79    ///
80    /// Swapping the source (eg. via std::mem operations) may lead to inaccurate parsing.
81    pub fn inner_mut(&mut self) -> &mut R {
82        &mut self.reader
83    }
84
85    /// Returns the inner seekable source by consuming self.
86    pub fn into_inner(self) -> R {
87        self.reader
88    }
89
90    /// Returns a new entry reader if the provided index is valid.
91    pub async fn reader_without_entry(&mut self, index: usize) -> Result<ZipEntryReader<'_, R, WithoutEntry>> {
92        let stored_entry = self.file.entries.get(index).ok_or(ZipError::EntryIndexOutOfBounds)?;
93        stored_entry.seek_to_data_offset(&mut self.reader).await?;
94
95        ZipEntryReader::new_with_borrow(
96            &mut self.reader,
97            stored_entry.entry.compression(),
98            stored_entry.entry.compressed_size(),
99        )
100    }
101
102    /// Returns a new entry reader if the provided index is valid.
103    pub async fn reader_with_entry(&mut self, index: usize) -> Result<ZipEntryReader<'_, R, WithEntry<'_>>> {
104        let stored_entry = self.file.entries.get(index).ok_or(ZipError::EntryIndexOutOfBounds)?;
105
106        stored_entry.seek_to_data_offset(&mut self.reader).await?;
107
108        let reader = ZipEntryReader::new_with_borrow(
109            &mut self.reader,
110            stored_entry.entry.compression(),
111            stored_entry.entry.compressed_size(),
112        )?;
113
114        Ok(reader.into_with_entry(stored_entry))
115    }
116
117    /// Returns a new entry reader if the provided index is valid.
118    /// Consumes self
119    pub async fn into_entry<'a>(mut self, index: usize) -> Result<ZipEntryReader<'a, R, WithoutEntry>>
120    where
121        R: 'a,
122    {
123        let stored_entry = self.file.entries.get(index).ok_or(ZipError::EntryIndexOutOfBounds)?;
124
125        stored_entry.seek_to_data_offset(&mut self.reader).await?;
126
127        ZipEntryReader::new_with_owned(
128            self.reader,
129            stored_entry.entry.compression(),
130            stored_entry.entry.compressed_size(),
131        )
132    }
133}
134
135#[cfg(test)]
136mod tests {
137    use std::sync::Arc;
138
139    use futures_lite::io::Cursor;
140
141    use super::ZipFileReader;
142    use crate::ZipFileBuilder;
143
144    #[test]
145    fn clone_shares_file_metadata() {
146        let reader = ZipFileReader::from_raw_parts(Cursor::new(Vec::new()), ZipFileBuilder::new().build());
147        let cloned_reader = reader.clone();
148
149        assert!(Arc::ptr_eq(&reader.file, &cloned_reader.file));
150    }
151}
152
153#[cfg(feature = "tokio")]
154impl<R> ZipFileReader<Compat<R>>
155where
156    R: tokio::io::AsyncBufRead + tokio::io::AsyncSeek + Unpin,
157{
158    /// Constructs a new tokio-specific ZIP reader from a seekable source.
159    pub async fn with_tokio(reader: R) -> Result<TokioZipFileReader<R>> {
160        let mut reader = reader.compat();
161        let file = crate::base::read::file(&mut reader).await?;
162        Ok(ZipFileReader::from_raw_parts(reader, file))
163    }
164}