async_mtzip/zip/
file.rs

1use std::io::{Seek, Write};
2use std::path::PathBuf;
3
4use cfg_if::cfg_if;
5use tokio::fs::read_dir;
6use tokio::io::{AsyncSeek, AsyncWrite};
7
8use super::extra_field::ExtraFields;
9use crate::CompressionType;
10
11const LOCAL_FILE_HEADER_SIGNATURE: u32 = 0x04034B50;
12const CENTRAL_FILE_HEADER_SIGNATURE: u32 = 0x02014B50;
13
14const VERSION_NEEDED_TO_EXTRACT: u16 = 20;
15#[cfg(not(target_os = "windows"))]
16/// OS - Unix assumed, id 3
17/// Specification version 6.2
18const VERSION_MADE_BY: u16 = (3 << 8) + 62;
19#[cfg(target_os = "windows")]
20/// OS - Windows, id 11 per Info-Zip spec
21/// Specification version 6.2
22const VERSION_MADE_BY: u16 = (11 << 8) + 62;
23
24#[cfg(any(target_os = "linux", unix))]
25#[allow(unused)]
26pub(crate) const DEFAULT_UNIX_FILE_ATTRS: u16 = 0o100644;
27#[cfg(any(target_os = "linux", unix))]
28#[allow(unused)]
29pub(crate) const DEFAULT_UNIX_DIR_ATTRS: u16 = 0o040755;
30
31#[cfg(target_os = "windows")]
32pub(crate) const DEFAULT_WINDOWS_FILE_ATTRS: u16 = 128;
33#[cfg(target_os = "windows")]
34pub(crate) const DEFAULT_WINDOWS_DIR_ATTRS: u16 = 16;
35
36/// Set bit 11 to indicate that the file names are in UTF-8, because all strings in rust are valid
37/// UTF-8
38const GENERAL_PURPOSE_BIT_FLAG: u16 = 1 << 11;
39
40#[derive(Debug)]
41pub struct ZipFile {
42    pub header: ZipFileHeader,
43    pub data: Vec<u8>,
44}
45
46#[derive(Debug)]
47pub struct TokioReceiveZipFile(pub tokio::sync::mpsc::Receiver<ZipFile>);
48
49#[derive(Debug)]
50pub struct ZipFileHeader {
51    pub compression_type: CompressionType,
52    pub crc: u32,
53    pub uncompressed_size: u32,
54    pub filename: String,
55    pub external_file_attributes: u32,
56    pub extra_fields: ExtraFields,
57}
58
59#[derive(Debug)]
60pub struct ZipFileNoData {
61    pub header: ZipFileHeader,
62    pub local_header_offset: u32,
63    pub compressed_size: u32,
64}
65
66pub async fn dirs(dir: PathBuf) -> Result<Vec<PathBuf>, String> {
67    let mut dirs = vec![dir];
68    let mut files = vec![];
69    while !dirs.is_empty() {
70        let mut dir_iter = read_dir(dirs.remove(0))
71            .await
72            .map_err(|e| format!("read_dir error: {}", e))?;
73        while let Some(entry) = dir_iter
74            .next_entry()
75            .await
76            .map_err(|e| format!("next_entry error: {}", e))?
77        {
78            let entry_path_buf = entry.path();
79            if entry_path_buf.is_dir() {
80                dirs.push(entry_path_buf);
81            } else {
82                files.push(entry_path_buf);
83            }
84        }
85    }
86    Ok(files)
87}
88
89impl ZipFile {
90    pub(crate) const fn default_dir_attrs() -> u16 {
91        cfg_if! {
92            if #[cfg(target_os = "windows")] {
93                DEFAULT_WINDOWS_DIR_ATTRS
94            } else if #[cfg(any(target_os = "linux", unix))] {
95                DEFAULT_UNIX_DIR_ATTRS
96            } else {
97                0
98            }
99        }
100    }
101
102    pub fn write_local_file_header_with_data_consuming<W: Write + Seek>(
103        self,
104        buf: &mut W,
105    ) -> std::io::Result<ZipFileNoData> {
106        let local_header_offset = super::stream_position_u32(buf)?;
107        self.write_local_file_header_and_data(buf)?;
108        let Self { header, data } = self;
109        Ok(ZipFileNoData {
110            header,
111            local_header_offset,
112            compressed_size: data.len() as u32,
113        })
114    }
115
116    pub async fn write_local_file_header_with_data_consuming_with_tokio<
117        W: AsyncWrite + AsyncSeek + Unpin,
118    >(
119        self,
120        buf: &mut W,
121    ) -> std::io::Result<ZipFileNoData> {
122        let local_header_offset = super::stream_position_u32_with_tokio(buf).await?;
123        self.write_local_file_header_and_data_with_tokio(buf)
124            .await?;
125        let Self { header, data } = self;
126        Ok(ZipFileNoData {
127            header,
128            local_header_offset,
129            compressed_size: data.len() as u32,
130        })
131    }
132
133    const LOCAL_FILE_HEADER_LEN: usize = 30;
134
135    pub fn write_local_file_header_and_data<W: Write>(&self, buf: &mut W) -> std::io::Result<()> {
136        // Writing to a temporary in-memory statically sized array first
137        let mut header = [0; Self::LOCAL_FILE_HEADER_LEN];
138        {
139            let mut header_buf: &mut [u8] = &mut header;
140
141            // signature
142            header_buf.write_all(&LOCAL_FILE_HEADER_SIGNATURE.to_le_bytes())?;
143            // version needed to extract
144            header_buf.write_all(&VERSION_NEEDED_TO_EXTRACT.to_le_bytes())?;
145            // general purpose bit flag
146            header_buf.write_all(&GENERAL_PURPOSE_BIT_FLAG.to_le_bytes())?;
147            // compression type
148            header_buf.write_all(&(self.header.compression_type as u16).to_le_bytes())?;
149            // Last modification time // moved to extra fields
150            header_buf.write_all(&0_u16.to_le_bytes())?;
151            // Last modification date // moved to extra fields
152            header_buf.write_all(&0_u16.to_le_bytes())?;
153            // crc
154            header_buf.write_all(&self.header.crc.to_le_bytes())?;
155            // Compressed size
156            debug_assert!(self.data.len() <= u32::MAX as usize);
157            header_buf.write_all(&(self.data.len() as u32).to_le_bytes())?;
158            // Uncompressed size
159            header_buf.write_all(&self.header.uncompressed_size.to_le_bytes())?;
160            // Filename size
161            debug_assert!(self.header.filename.len() <= u16::MAX as usize);
162            header_buf.write_all(&(self.header.filename.len() as u16).to_le_bytes())?;
163            // extra field size
164            header_buf.write_all(
165                &self
166                    .header
167                    .extra_fields
168                    .data_length::<false>()
169                    .to_le_bytes(),
170            )?;
171        }
172
173        buf.write_all(&header)?;
174
175        // Filename
176        buf.write_all(self.header.filename.as_bytes())?;
177        // Extra field
178        self.header.extra_fields.write::<_, false>(buf)?;
179
180        // Data
181        buf.write_all(&self.data)?;
182
183        Ok(())
184    }
185
186    pub async fn write_local_file_header_and_data_with_tokio<W: AsyncWrite + Unpin>(
187        &self,
188        buf: &mut W,
189    ) -> std::io::Result<()> {
190        // Writing to a temporary in-memory statically sized array first
191        let mut header = [0; Self::LOCAL_FILE_HEADER_LEN];
192        {
193            let mut header_buf: &mut [u8] = &mut header;
194
195            // signature
196            header_buf.write_all(&LOCAL_FILE_HEADER_SIGNATURE.to_le_bytes())?;
197            // version needed to extract
198            header_buf.write_all(&VERSION_NEEDED_TO_EXTRACT.to_le_bytes())?;
199            // general purpose bit flag
200            header_buf.write_all(&GENERAL_PURPOSE_BIT_FLAG.to_le_bytes())?;
201            // compression type
202            header_buf.write_all(&(self.header.compression_type as u16).to_le_bytes())?;
203            // Last modification time // moved to extra fields
204            header_buf.write_all(&0_u16.to_le_bytes())?;
205            // Last modification date // moved to extra fields
206            header_buf.write_all(&0_u16.to_le_bytes())?;
207            // crc
208            header_buf.write_all(&self.header.crc.to_le_bytes())?;
209            // Compressed size
210            debug_assert!(self.data.len() <= u32::MAX as usize);
211            header_buf.write_all(&(self.data.len() as u32).to_le_bytes())?;
212            // Uncompressed size
213            header_buf.write_all(&self.header.uncompressed_size.to_le_bytes())?;
214            // Filename size
215            debug_assert!(self.header.filename.len() <= u16::MAX as usize);
216            header_buf.write_all(&(self.header.filename.len() as u16).to_le_bytes())?;
217            // extra field size
218            header_buf.write_all(
219                &self
220                    .header
221                    .extra_fields
222                    .data_length::<false>()
223                    .to_le_bytes(),
224            )?;
225        }
226
227        {
228            use tokio::io::AsyncWriteExt;
229            buf.write_all(&header).await?;
230
231            // Filename
232            buf.write_all(self.header.filename.as_bytes()).await?;
233
234            // Extra field
235            self.header
236                .extra_fields
237                .write_with_tokio::<_, false>(buf)
238                .await?;
239
240            // Data
241            buf.write_all(&self.data).await?;
242        }
243
244        Ok(())
245    }
246
247    #[inline]
248    pub fn directory(
249        mut name: String,
250        extra_fields: ExtraFields,
251        external_attributes: u16,
252    ) -> Self {
253        if !(name.ends_with('/') || name.ends_with('\\')) {
254            name += "/"
255        };
256        Self {
257            header: ZipFileHeader {
258                compression_type: CompressionType::Stored,
259                crc: 0,
260                uncompressed_size: 0,
261                filename: name,
262                external_file_attributes: (external_attributes as u32) << 16,
263                extra_fields,
264            },
265            data: vec![],
266        }
267    }
268}
269
270impl ZipFileNoData {
271    const CENTRAL_DIR_ENTRY_LEN: usize = 46;
272
273    pub fn write_central_directory_entry<W: Write>(&self, buf: &mut W) -> std::io::Result<()> {
274        // Writing to a temporary in-memory statically sized array first
275        let mut central_dir_entry_header = [0; Self::CENTRAL_DIR_ENTRY_LEN];
276        {
277            let mut central_dir_entry_buf: &mut [u8] = &mut central_dir_entry_header;
278
279            // signature
280            central_dir_entry_buf.write_all(&CENTRAL_FILE_HEADER_SIGNATURE.to_le_bytes())?;
281            // version made by
282            central_dir_entry_buf.write_all(&VERSION_MADE_BY.to_le_bytes())?;
283            // version needed to extract
284            central_dir_entry_buf.write_all(&VERSION_NEEDED_TO_EXTRACT.to_le_bytes())?;
285            // general purpose bit flag
286            central_dir_entry_buf.write_all(&GENERAL_PURPOSE_BIT_FLAG.to_le_bytes())?;
287            // compression type
288            central_dir_entry_buf
289                .write_all(&(self.header.compression_type as u16).to_le_bytes())?;
290            // Last modification time // moved to extra fields
291            central_dir_entry_buf.write_all(&0_u16.to_le_bytes())?;
292            // Last modification date // moved to extra fields
293            central_dir_entry_buf.write_all(&0_u16.to_le_bytes())?;
294            // crc
295            central_dir_entry_buf.write_all(&self.header.crc.to_le_bytes())?;
296            // Compressed size
297            central_dir_entry_buf.write_all(&self.compressed_size.to_le_bytes())?;
298            // Uncompressed size
299            central_dir_entry_buf.write_all(&self.header.uncompressed_size.to_le_bytes())?;
300            // Filename size
301            debug_assert!(self.header.filename.len() <= u16::MAX as usize);
302            central_dir_entry_buf.write_all(&(self.header.filename.len() as u16).to_le_bytes())?;
303            // extra field size
304            central_dir_entry_buf
305                .write_all(&self.header.extra_fields.data_length::<true>().to_le_bytes())?;
306            // comment size
307            central_dir_entry_buf.write_all(&0_u16.to_le_bytes())?;
308            // disk number start
309            central_dir_entry_buf.write_all(&0_u16.to_le_bytes())?;
310            // internal file attributes
311            central_dir_entry_buf.write_all(&0_u16.to_le_bytes())?;
312            // external file attributes
313            central_dir_entry_buf.write_all(&self.header.external_file_attributes.to_le_bytes())?;
314            // relative offset of local header
315            central_dir_entry_buf.write_all(&self.local_header_offset.to_le_bytes())?;
316        }
317
318        buf.write_all(&central_dir_entry_header)?;
319
320        // Filename
321        buf.write_all(self.header.filename.as_bytes())?;
322        // Extra field
323        self.header.extra_fields.write::<_, true>(buf)?;
324
325        Ok(())
326    }
327
328    pub async fn write_central_directory_entry_with_tokio<W: AsyncWrite + Unpin>(
329        &self,
330        buf: &mut W,
331    ) -> std::io::Result<()> {
332        // Writing to a temporary in-memory statically sized array first
333        let mut central_dir_entry_header = [0; Self::CENTRAL_DIR_ENTRY_LEN];
334        {
335            let mut central_dir_entry_buf: &mut [u8] = &mut central_dir_entry_header;
336
337            // signature
338            central_dir_entry_buf.write_all(&CENTRAL_FILE_HEADER_SIGNATURE.to_le_bytes())?;
339            // version made by
340            central_dir_entry_buf.write_all(&VERSION_MADE_BY.to_le_bytes())?;
341            // version needed to extract
342            central_dir_entry_buf.write_all(&VERSION_NEEDED_TO_EXTRACT.to_le_bytes())?;
343            // general purpose bit flag
344            central_dir_entry_buf.write_all(&GENERAL_PURPOSE_BIT_FLAG.to_le_bytes())?;
345            // compression type
346            central_dir_entry_buf
347                .write_all(&(self.header.compression_type as u16).to_le_bytes())?;
348            // Last modification time // moved to extra fields
349            central_dir_entry_buf.write_all(&0_u16.to_le_bytes())?;
350            // Last modification date // moved to extra fields
351            central_dir_entry_buf.write_all(&0_u16.to_le_bytes())?;
352            // crc
353            central_dir_entry_buf.write_all(&self.header.crc.to_le_bytes())?;
354            // Compressed size
355            central_dir_entry_buf.write_all(&self.compressed_size.to_le_bytes())?;
356            // Uncompressed size
357            central_dir_entry_buf.write_all(&self.header.uncompressed_size.to_le_bytes())?;
358            // Filename size
359            debug_assert!(self.header.filename.len() <= u16::MAX as usize);
360            central_dir_entry_buf.write_all(&(self.header.filename.len() as u16).to_le_bytes())?;
361            // extra field size
362            central_dir_entry_buf
363                .write_all(&self.header.extra_fields.data_length::<true>().to_le_bytes())?;
364            // comment size
365            central_dir_entry_buf.write_all(&0_u16.to_le_bytes())?;
366            // disk number start
367            central_dir_entry_buf.write_all(&0_u16.to_le_bytes())?;
368            // internal file attributes
369            central_dir_entry_buf.write_all(&0_u16.to_le_bytes())?;
370            // external file attributes
371            central_dir_entry_buf.write_all(&self.header.external_file_attributes.to_le_bytes())?;
372            // relative offset of local header
373            central_dir_entry_buf.write_all(&self.local_header_offset.to_le_bytes())?;
374        }
375
376        {
377            use tokio::io::AsyncWriteExt;
378            buf.write_all(&central_dir_entry_header).await?;
379
380            // Filename
381            buf.write_all(self.header.filename.as_bytes()).await?;
382            // Extra field
383        }
384        self.header
385            .extra_fields
386            .write_with_tokio::<_, true>(buf)
387            .await?;
388
389        Ok(())
390    }
391}