eazip/write/
mod.rs

1//! Utilities to write an archive.
2
3use crate::{
4    CompressionMethod,
5    compression::Compressor,
6    utils::{Counter, Crc32Writer},
7};
8use std::io;
9
10mod raw;
11
12/// The options used when adding a file to an archive.
13#[derive(Debug, Default, Clone)]
14#[non_exhaustive]
15pub struct FileOptions {
16    /// The compression method.
17    pub compression_method: CompressionMethod,
18    /// The compression level.
19    pub level: Option<i32>,
20}
21
22/// Wraps a writer to create a ZIP archive.
23///
24/// You need to call `self.finish()` when done.
25///
26/// This type only does minimal validation on the file names for now, so untrusted
27/// input may produce dangerous ZIP archives. This will be improved in a future
28/// version.
29///
30/// # Example
31///
32/// ```no_run
33/// use std::io::prelude::*;
34///
35/// let mut archive = eazip::ArchiveWriter::create("example.zip")?;
36/// let options = eazip::write::FileOptions::default();
37///
38/// // Add a file
39/// archive.add_file("hello.txt", b"hello\n".as_slice(), &options)?;
40///
41/// // Add a directory
42/// archive.add_directory("dir/")?;
43///
44/// // Stream a file
45/// let mut writer = archive.stream_file("dir/streaming.txt", &options)?;
46/// writer.write_all(b"some data\n")?;
47/// writer.finish()?;
48///
49/// // Finish writing the archive
50/// archive.finish()?;
51/// # Ok::<(), std::io::Error>(())
52/// ```
53#[derive(Default)]
54pub struct ArchiveWriter<W: io::Write> {
55    writer: W,
56    raw: raw::RawArchiveWriter,
57}
58
59impl ArchiveWriter<std::fs::File> {
60    /// Creates a new `ArchiveWriter` that writes to the given file.
61    ///
62    /// The file will be created if it does not exist, and will be truncated if
63    /// it does.
64    pub fn create(path: impl AsRef<std::path::Path>) -> io::Result<Self> {
65        std::fs::File::create(path).map(Self::new)
66    }
67
68    /// Creates a new `ArchiveWriter` that writes to the given file; error if
69    /// the file exists.
70    pub fn create_new(path: impl AsRef<std::path::Path>) -> io::Result<Self> {
71        std::fs::File::create_new(path).map(Self::new)
72    }
73}
74
75impl<W: io::Write> ArchiveWriter<W> {
76    /// Creates a new `ArchiveWriter` that writes to the given writer.
77    #[inline]
78    pub fn new(writer: W) -> Self {
79        ArchiveWriter {
80            writer,
81            raw: raw::RawArchiveWriter::default(),
82        }
83    }
84
85    /// Writes a file to the archive.
86    ///
87    /// The entire compressed content of the file must fit in memory.
88    pub fn add_file<R: io::Read>(
89        &mut self,
90        name: &str,
91        mut content: R,
92        options: &FileOptions,
93    ) -> io::Result<()> {
94        let mut w = Crc32Writer::new(Compressor::new(
95            Vec::new(),
96            options.compression_method,
97            options.level,
98        )?);
99        let uncompressed_size = io::copy(&mut content, &mut w)?;
100        let crc32 = w.result();
101        let compressed = w.into_inner().finish()?;
102
103        self.raw.write_file_raw(
104            &mut self.writer,
105            name,
106            &compressed,
107            raw::Metadata {
108                compression_method: options.compression_method,
109                uncompressed_size,
110                crc32,
111                typ: crate::FileType::File,
112            },
113        )?;
114
115        Ok(())
116    }
117
118    /// Starts streaming a file to the archive.
119    ///
120    /// This is useful for (but not limited to) very large files that may not
121    /// fit in memory.
122    ///
123    /// This method returns a `FileStreamer` that can be written to.
124    pub fn stream_file(
125        &mut self,
126        name: &str,
127        options: &FileOptions,
128    ) -> io::Result<FileStreamer<'_, W>> {
129        let writer = self.raw.start_stream_raw(&mut self.writer, name, options)?;
130
131        Ok(FileStreamer {
132            writer: Counter::new(Crc32Writer::new(Compressor::new(
133                writer,
134                options.compression_method,
135                options.level,
136            )?)),
137        })
138    }
139
140    /// Adds a directory to the archive.
141    pub fn add_directory(&mut self, name: &str) -> io::Result<()> {
142        if !name.ends_with('/') {
143            return Err(io::Error::new(
144                io::ErrorKind::InvalidInput,
145                "directory name must end with '/'",
146            ));
147        }
148
149        self.raw.write_file_raw(
150            &mut self.writer,
151            name,
152            &[],
153            raw::Metadata {
154                compression_method: CompressionMethod::STORE,
155                uncompressed_size: 0,
156                crc32: 0,
157                typ: crate::FileType::Directory,
158            },
159        )
160    }
161
162    /// Adds a symlink to the archive.
163    pub fn add_symlink(&mut self, name: &str, target: &str) -> io::Result<()> {
164        self.raw.write_file_raw(
165            &mut self.writer,
166            name,
167            target.as_bytes(),
168            raw::Metadata {
169                compression_method: CompressionMethod::STORE,
170                uncompressed_size: target.len() as _,
171                crc32: crc32fast::hash(target.as_bytes()),
172                typ: crate::FileType::Symlink,
173            },
174        )
175    }
176
177    /// Tries to recover from an error by erasing the last entry.
178    ///
179    /// Note that this requires a seeking writer. Calling this when no error
180    /// needs recovery does nothing.
181    ///
182    /// **Footgun**: this requires the user to properly truncate the writer after
183    /// using this method.
184    #[inline]
185    pub fn recover(&mut self) -> io::Result<()>
186    where
187        W: io::Seek,
188    {
189        self.raw.recover(&mut self.writer)
190    }
191
192    /// Gets a shared reference to the underlying writer.
193    #[inline]
194    pub fn get_ref(&self) -> &W {
195        &self.writer
196    }
197
198    /// Gets a mutable reference to the underlying writer.
199    ///
200    /// It is inadvisable to directly write to the underlying writer.
201    #[inline]
202    pub fn get_mut(&mut self) -> &mut W {
203        &mut self.writer
204    }
205
206    /// Flushes the underlying stream.
207    #[inline]
208    pub fn flush(&mut self) -> io::Result<()> {
209        self.writer.flush()
210    }
211
212    /// Finishes writing the archive and get the writer back.
213    ///
214    /// It is necessary to call this method or the resulting archive will not
215    /// be readable.
216    #[inline]
217    pub fn finish(mut self) -> io::Result<W> {
218        self.raw.finish(&mut self.writer)?;
219        Ok(self.writer)
220    }
221}
222
223/// An adapter to stream a ZIP file.
224///
225/// Writing to this value will write to a file in an archive.
226///
227/// It is necessary to call `finish` when done.
228pub struct FileStreamer<'a, W: io::Write> {
229    writer: Counter<Crc32Writer<Compressor<raw::RawFileStreamer<'a, &'a mut W>>>>,
230}
231
232impl<W: io::Write> io::Write for FileStreamer<'_, W> {
233    #[inline]
234    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
235        self.writer.write(buf)
236    }
237
238    #[inline]
239    fn flush(&mut self) -> io::Result<()> {
240        self.writer.flush()
241    }
242}
243
244impl<W: io::Write> FileStreamer<'_, W> {
245    /// Finishes writing the current file.
246    pub fn finish(self) -> io::Result<()> {
247        let uncompressed_size = self.writer.amt;
248        let crc32 = self.writer.inner.result();
249
250        let raw_writer = self.writer.inner.into_inner().finish()?;
251
252        raw_writer.finish(uncompressed_size, crc32)
253    }
254}