Skip to main content

async_zip/base/read/
stream.rs

1// Copyright (c) 2023 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 non-seekable source.
5//!
6//! # API Design
7//! As opposed to other readers provided by this crate, it's important that the data of an entry is fully read before
8//! the proceeding entry is read. This is as a result of not being able to seek forwards or backwards, so we must end
9//! up at the start of the next entry.
10//!
11//! **We encode this invariant within Rust's type system so that it can be enforced at compile time.**
12//!
13//! This requires that any transition methods between these encoded types consume the reader and provide a new owned
14//! reader back. This is certainly something to keep in mind when working with this reader, but idiomatic code can
15//! still be produced nevertheless.
16//!
17//! # Considerations
18//! Entry iteration stops at the central directory; it does not validate the archive's footer.
19//! Continue with [`crate::base::read::cd::CentralDirectoryReader`] through its end record to
20//! validate the footer and trailing contents.
21//!
22//! As the central directory of a ZIP archive is stored at the end of it, a non-seekable reader doesn't have access
23//! to it. We have to rely on information provided within the local file header which may not be accurate or complete.
24//! This results in:
25//! - The inability to read ZIP entries using the combination of a data descriptor and the Stored compression method.
26//! - No file comment being available (defaults to an empty string).
27//! - No internal or external file attributes being available (defaults to 0).
28//! - The extra field data potentially being inconsistent with what's stored in the central directory.
29//! - None of the following being available when the entry was written with a data descriptor (defaults to 0):
30//!     - CRC
31//!     - compressed size
32//!     - uncompressed size
33//!
34//! # Example
35//! ```no_run
36//! # use futures_lite::io::Cursor;
37//! # use async_zip::error::Result;
38//! # use async_zip::base::read::stream::ZipFileReader;
39//! #
40//! # async fn run() -> Result<()> {
41//! let mut zip = ZipFileReader::new(Cursor::new([0; 0]));
42//!     
43//! // Print the name of every file in a ZIP archive.
44//! while let Some(entry) = zip.next_with_entry().await? {
45//!     println!("File: {}", entry.reader().entry().filename().as_str().unwrap());
46//!     (.., zip) = entry.skip().await?;
47//! }
48//! #
49//! #     Ok(())
50//! # }
51//! ```
52
53use crate::base::read::counting::Counting;
54use crate::base::read::io::entry::ZipEntryReader;
55use crate::error::Result;
56use crate::error::ZipError;
57use crate::spec::data_descriptor::{CombinedDataDescriptor, DataDescriptor, Zip64DataDescriptor};
58#[cfg(feature = "tokio")]
59use crate::tokio::read::stream::Ready as TokioReady;
60
61use futures_lite::io::AsyncBufRead;
62use futures_lite::io::AsyncReadExt;
63
64use super::io::entry::WithEntry;
65use super::io::entry::WithoutEntry;
66use crate::spec::header::HeaderId;
67#[cfg(feature = "tokio")]
68use tokio_util::compat::TokioAsyncReadCompatExt;
69
70/// A type which encodes that [`ZipFileReader`] is ready to open a new entry.
71pub struct Ready<R>(R);
72
73/// A type which encodes that [`ZipFileReader`] is currently reading an entry.
74pub struct Reading<'a, R, E>(ZipEntryReader<'a, R, E>, Option<Suffix>);
75
76#[derive(Copy, Clone, Debug)]
77enum Suffix {
78    /// The entry is followed by a data descriptor.
79    DataDescriptor,
80    /// The entry is followed by a ZIP64 data descriptor.
81    Zip64DataDescriptor,
82}
83
84/// A ZIP reader which acts over a non-seekable source.
85///
86/// See the [module-level docs](.) for more information.
87#[derive(Clone)]
88pub struct ZipFileReader<S>(S);
89
90impl<'a, R> ZipFileReader<Ready<Counting<R>>>
91where
92    R: AsyncBufRead + Unpin + 'a,
93{
94    /// Constructs a new ZIP reader from a non-seekable source.
95    pub fn new(reader: R) -> Self {
96        Self(Ready(Counting::new(reader)))
97    }
98
99    /// Opens the next entry for reading if the central directory hasn’t yet been reached.
100    pub async fn next_without_entry(mut self) -> Result<Option<ZipFileReader<Reading<'a, Counting<R>, WithoutEntry>>>> {
101        let file_offset = self.0 .0.bytes_read();
102        let entry = match crate::base::read::lfh(&mut self.0 .0, file_offset).await? {
103            Some(entry) => entry,
104            None => return Ok(None),
105        };
106
107        let length = if entry.data_descriptor { u64::MAX } else { entry.compressed_size };
108        let reader = ZipEntryReader::new_with_owned(self.0 .0, entry.compression, length)?;
109
110        let suffix = if entry.data_descriptor {
111            if entry.extra_fields.iter().any(|ef| ef.header_id() == HeaderId::ZIP64_EXTENDED_INFORMATION_EXTRA_FIELD) {
112                Some(Suffix::Zip64DataDescriptor)
113            } else {
114                Some(Suffix::DataDescriptor)
115            }
116        } else {
117            None
118        };
119
120        Ok(Some(ZipFileReader(Reading(reader, suffix))))
121    }
122
123    /// Opens the next entry for reading if the central directory hasn’t yet been reached.
124    pub async fn next_with_entry(mut self) -> Result<Option<ZipFileReader<Reading<'a, Counting<R>, WithEntry<'a>>>>> {
125        let file_offset = self.0 .0.bytes_read();
126        let entry = match crate::base::read::lfh(&mut self.0 .0, file_offset).await? {
127            Some(entry) => entry,
128            None => return Ok(None),
129        };
130
131        let length = if entry.data_descriptor { u64::MAX } else { entry.compressed_size };
132        let reader = ZipEntryReader::new_with_owned(self.0 .0, entry.compression, length)?;
133
134        let suffix = if entry.data_descriptor {
135            if entry.extra_fields.iter().any(|ef| ef.header_id() == HeaderId::ZIP64_EXTENDED_INFORMATION_EXTRA_FIELD) {
136                Some(Suffix::Zip64DataDescriptor)
137            } else {
138                Some(Suffix::DataDescriptor)
139            }
140        } else {
141            None
142        };
143
144        Ok(Some(ZipFileReader(Reading(reader.into_with_entry_owned(entry), suffix))))
145    }
146
147    /// Consumes the `ZipFileReader` returning the original `reader`
148    pub async fn into_inner(self) -> R {
149        self.0 .0.into_inner()
150    }
151
152    /// Returns the file offset of the current reader.
153    pub fn offset(&self) -> u64 {
154        self.0 .0.bytes_read()
155    }
156}
157
158#[cfg(feature = "tokio")]
159impl<R> ZipFileReader<TokioReady<R>>
160where
161    R: tokio::io::AsyncBufRead + Unpin,
162{
163    /// Constructs a new tokio-specific ZIP reader from a non-seekable source.
164    pub fn with_tokio(reader: R) -> ZipFileReader<TokioReady<R>> {
165        Self(Ready(reader.compat()))
166    }
167}
168
169type Next<R> = (Option<CombinedDataDescriptor>, ZipFileReader<Ready<R>>);
170
171impl<'a, R, E> ZipFileReader<Reading<'a, R, E>>
172where
173    R: AsyncBufRead + Unpin,
174{
175    /// Returns an immutable reference to the inner entry reader.
176    pub fn reader(&self) -> &ZipEntryReader<'a, R, E> {
177        &self.0 .0
178    }
179
180    /// Returns a mutable reference to the inner entry reader.
181    pub fn reader_mut(&mut self) -> &mut ZipEntryReader<'a, R, E> {
182        &mut self.0 .0
183    }
184
185    /// Converts the reader back into the Ready state if EOF has been reached.
186    pub async fn done(mut self) -> Result<Next<R>> {
187        if self.0 .0.read(&mut [0; 1]).await? != 0 {
188            return Err(ZipError::EOFNotReached);
189        }
190
191        let mut inner = self.0 .0.into_inner();
192
193        let data_descriptor = match self.0 .1 {
194            Some(Suffix::DataDescriptor) => {
195                Some(CombinedDataDescriptor::from(DataDescriptor::from_reader(&mut inner).await?))
196            }
197            Some(Suffix::Zip64DataDescriptor) => {
198                Some(CombinedDataDescriptor::from(Zip64DataDescriptor::from_reader(&mut inner).await?))
199            }
200            None => None,
201        };
202
203        let reader = ZipFileReader(Ready(inner));
204
205        Ok((data_descriptor, reader))
206    }
207
208    /// Reads until EOF and converts the reader back into the Ready state.
209    pub async fn skip(mut self) -> Result<Next<R>> {
210        let mut buf = [0u8; 8192];
211        while self.0 .0.read(&mut buf).await? != 0 {}
212        let mut inner = self.0 .0.into_inner();
213
214        let data_descriptor = match self.0 .1 {
215            Some(Suffix::DataDescriptor) => {
216                Some(CombinedDataDescriptor::from(DataDescriptor::from_reader(&mut inner).await?))
217            }
218            Some(Suffix::Zip64DataDescriptor) => {
219                Some(CombinedDataDescriptor::from(Zip64DataDescriptor::from_reader(&mut inner).await?))
220            }
221            None => None,
222        };
223
224        let reader = ZipFileReader(Ready(inner));
225
226        Ok((data_descriptor, reader))
227    }
228}