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"))]
16const VERSION_MADE_BY: u16 = (3 << 8) + 62;
19#[cfg(target_os = "windows")]
20const 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
36const 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 let mut header = [0; Self::LOCAL_FILE_HEADER_LEN];
138 {
139 let mut header_buf: &mut [u8] = &mut header;
140
141 header_buf.write_all(&LOCAL_FILE_HEADER_SIGNATURE.to_le_bytes())?;
143 header_buf.write_all(&VERSION_NEEDED_TO_EXTRACT.to_le_bytes())?;
145 header_buf.write_all(&GENERAL_PURPOSE_BIT_FLAG.to_le_bytes())?;
147 header_buf.write_all(&(self.header.compression_type as u16).to_le_bytes())?;
149 header_buf.write_all(&0_u16.to_le_bytes())?;
151 header_buf.write_all(&0_u16.to_le_bytes())?;
153 header_buf.write_all(&self.header.crc.to_le_bytes())?;
155 debug_assert!(self.data.len() <= u32::MAX as usize);
157 header_buf.write_all(&(self.data.len() as u32).to_le_bytes())?;
158 header_buf.write_all(&self.header.uncompressed_size.to_le_bytes())?;
160 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 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 buf.write_all(self.header.filename.as_bytes())?;
177 self.header.extra_fields.write::<_, false>(buf)?;
179
180 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 let mut header = [0; Self::LOCAL_FILE_HEADER_LEN];
192 {
193 let mut header_buf: &mut [u8] = &mut header;
194
195 header_buf.write_all(&LOCAL_FILE_HEADER_SIGNATURE.to_le_bytes())?;
197 header_buf.write_all(&VERSION_NEEDED_TO_EXTRACT.to_le_bytes())?;
199 header_buf.write_all(&GENERAL_PURPOSE_BIT_FLAG.to_le_bytes())?;
201 header_buf.write_all(&(self.header.compression_type as u16).to_le_bytes())?;
203 header_buf.write_all(&0_u16.to_le_bytes())?;
205 header_buf.write_all(&0_u16.to_le_bytes())?;
207 header_buf.write_all(&self.header.crc.to_le_bytes())?;
209 debug_assert!(self.data.len() <= u32::MAX as usize);
211 header_buf.write_all(&(self.data.len() as u32).to_le_bytes())?;
212 header_buf.write_all(&self.header.uncompressed_size.to_le_bytes())?;
214 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 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 buf.write_all(self.header.filename.as_bytes()).await?;
233
234 self.header
236 .extra_fields
237 .write_with_tokio::<_, false>(buf)
238 .await?;
239
240 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 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 central_dir_entry_buf.write_all(&CENTRAL_FILE_HEADER_SIGNATURE.to_le_bytes())?;
281 central_dir_entry_buf.write_all(&VERSION_MADE_BY.to_le_bytes())?;
283 central_dir_entry_buf.write_all(&VERSION_NEEDED_TO_EXTRACT.to_le_bytes())?;
285 central_dir_entry_buf.write_all(&GENERAL_PURPOSE_BIT_FLAG.to_le_bytes())?;
287 central_dir_entry_buf
289 .write_all(&(self.header.compression_type as u16).to_le_bytes())?;
290 central_dir_entry_buf.write_all(&0_u16.to_le_bytes())?;
292 central_dir_entry_buf.write_all(&0_u16.to_le_bytes())?;
294 central_dir_entry_buf.write_all(&self.header.crc.to_le_bytes())?;
296 central_dir_entry_buf.write_all(&self.compressed_size.to_le_bytes())?;
298 central_dir_entry_buf.write_all(&self.header.uncompressed_size.to_le_bytes())?;
300 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 central_dir_entry_buf
305 .write_all(&self.header.extra_fields.data_length::<true>().to_le_bytes())?;
306 central_dir_entry_buf.write_all(&0_u16.to_le_bytes())?;
308 central_dir_entry_buf.write_all(&0_u16.to_le_bytes())?;
310 central_dir_entry_buf.write_all(&0_u16.to_le_bytes())?;
312 central_dir_entry_buf.write_all(&self.header.external_file_attributes.to_le_bytes())?;
314 central_dir_entry_buf.write_all(&self.local_header_offset.to_le_bytes())?;
316 }
317
318 buf.write_all(¢ral_dir_entry_header)?;
319
320 buf.write_all(self.header.filename.as_bytes())?;
322 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 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 central_dir_entry_buf.write_all(&CENTRAL_FILE_HEADER_SIGNATURE.to_le_bytes())?;
339 central_dir_entry_buf.write_all(&VERSION_MADE_BY.to_le_bytes())?;
341 central_dir_entry_buf.write_all(&VERSION_NEEDED_TO_EXTRACT.to_le_bytes())?;
343 central_dir_entry_buf.write_all(&GENERAL_PURPOSE_BIT_FLAG.to_le_bytes())?;
345 central_dir_entry_buf
347 .write_all(&(self.header.compression_type as u16).to_le_bytes())?;
348 central_dir_entry_buf.write_all(&0_u16.to_le_bytes())?;
350 central_dir_entry_buf.write_all(&0_u16.to_le_bytes())?;
352 central_dir_entry_buf.write_all(&self.header.crc.to_le_bytes())?;
354 central_dir_entry_buf.write_all(&self.compressed_size.to_le_bytes())?;
356 central_dir_entry_buf.write_all(&self.header.uncompressed_size.to_le_bytes())?;
358 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 central_dir_entry_buf
363 .write_all(&self.header.extra_fields.data_length::<true>().to_le_bytes())?;
364 central_dir_entry_buf.write_all(&0_u16.to_le_bytes())?;
366 central_dir_entry_buf.write_all(&0_u16.to_le_bytes())?;
368 central_dir_entry_buf.write_all(&0_u16.to_le_bytes())?;
370 central_dir_entry_buf.write_all(&self.header.external_file_attributes.to_le_bytes())?;
372 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(¢ral_dir_entry_header).await?;
379
380 buf.write_all(self.header.filename.as_bytes()).await?;
382 }
384 self.header
385 .extra_fields
386 .write_with_tokio::<_, true>(buf)
387 .await?;
388
389 Ok(())
390 }
391}