use {
crate::{
checksums::crc32,
never,
padding::{write_aligned_pad_end, write_aligned_pad_start},
panic,
},
bstr::ByteSlice,
serde::{Deserialize, Serialize},
std::io::{Read, Write},
};
mod configuration;
mod to_zip;
pub use self::{configuration::*, to_zip::*};
#[derive(Debug, Clone, Default)]
pub struct Zip {
pub configuration: ZipConfiguration,
pub files: Vec<(Vec<u8>, Vec<u8>)>,
}
impl Zip {
pub fn new(data: &impl ToZip) -> Self {
data.to_zip().into_owned()
}
pub fn write(&self, output: &mut impl Write) -> Result<usize, panic> {
todo!()
}
pub fn read(input: &impl Read) -> Result<Self, panic> {
todo!()
}
pub fn write_vec(&self) -> Result<Vec<u8>, never> {
let mut output = Vec::new();
self.write(&mut output)?;
Ok(output)
}
pub fn read_slice(input: &[u8]) -> Result<Self, never> {
Ok(Self::read(&input)?)
}
}
impl Serialize for Zip {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where S: serde::Serializer {
serializer.serialize_bytes(
&self
.write_vec()
.expect("serializing Zip to bytes should not fail"),
)
}
}
impl<'de> Deserialize<'de> for Zip {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where D: serde::Deserializer<'de> {
let bytes: &[u8] = serde_bytes::deserialize(deserializer)?;
Self::read_slice(bytes).map_err(serde::de::Error::custom)
}
}
fn zip<'files, Files>(files: Files) -> Vec<u8>
where Files: 'files + IntoIterator<Item = (&'files [u8], &'files [u8])> {
let mut files: Vec<(&[u8], &[u8])> = files.into_iter().collect();
files.sort_by_cached_key(|(path, body)| {
(
path != b"mimetype",
!body.is_empty(),
path.iter().filter(|&&b| b == b'/').count(),
*path,
*body,
)
});
zip_with(&files, Vec::new(), b"")
}
const BLOCK_SIZE: usize = 1024;
pub(crate) fn zip_with(files: &[(&[u8], &[u8])], prefix: Vec<u8>, suffix: &[u8]) -> Vec<u8> {
let mut output = prefix;
if suffix.find(b"PK\x05\x06").is_some() {
panic!("Zip file suffix must not contain zip file terminator signature PK\\x05\\x06");
}
let mut files_with_offsets = Vec::with_capacity(files.len());
for (name, body) in files.iter() {
let mut header = Vec::new();
header.extend_from_slice(b"PK\x03\x04");
header.extend_from_slice(&1_0_u16.to_le_bytes());
header.extend_from_slice(&[0x00; 2]);
header.extend_from_slice(&[0x00; 2]);
header.extend_from_slice(b"PK");
header.extend_from_slice(b"PK");
header.extend_from_slice(&crc32(body).to_le_bytes());
header.extend_from_slice(
&u32::try_from(body.len())
.expect("file size larger than 4GiB")
.to_le_bytes(),
);
header.extend_from_slice(
&u32::try_from(body.len())
.expect("file size larger than 4GiB")
.to_le_bytes(),
);
header.extend_from_slice(
&u16::try_from(name.len())
.expect("file name larger than 64KiB")
.to_le_bytes(),
);
header.extend_from_slice(&[0x00; 2]);
header.extend_from_slice(name);
let range = if !body.is_empty() && name != b"mimetype" {
let header_range = write_aligned_pad_start(&mut output, &header, BLOCK_SIZE);
let body_range = write_aligned_pad_end(&mut output, body, BLOCK_SIZE);
header_range.start..body_range.end
} else {
let before = output.len();
output.extend_from_slice(&header);
output.extend_from_slice(body);
let after = output.len();
before..after
};
files_with_offsets.push((*name, *body, range.start));
}
let mut central_directory = Vec::new();
for (name, body, header_offset) in files_with_offsets.iter() {
let name = name.to_vec();
let name_length = u16::try_from(name.len()).expect("file name larger than 64KiB");
let body_length = u32::try_from(body.len()).expect("file size larger than 4GiB");
let header_offset = u32::try_from(*header_offset).expect("archive larger than 4GiB");
let crc = crc32(body).to_le_bytes();
let mut header = Vec::new();
header.extend_from_slice(b"PK\x01\x02");
header.extend_from_slice(&1_0_u16.to_le_bytes());
header.extend_from_slice(&1_0_u16.to_le_bytes());
header.extend_from_slice(&[0x00; 2]);
header.extend_from_slice(&[0x00; 2]);
header.extend_from_slice(b"PK");
header.extend_from_slice(b"PK");
header.extend_from_slice(&crc);
header.extend_from_slice(&body_length.to_le_bytes());
header.extend_from_slice(&body_length.to_le_bytes());
header.extend_from_slice(&name_length.to_le_bytes());
header.extend_from_slice(&[0x00; 2]);
header.extend_from_slice(&[0x00; 2]);
header.extend_from_slice(&[0x00; 2]);
header.extend_from_slice(&[0x00; 2]);
header.extend_from_slice(&[0x00; 4]);
header.extend_from_slice(&header_offset.to_le_bytes());
header.extend_from_slice(&name);
central_directory.extend_from_slice(&header);
}
let archive_terminator = vec![0; 22 + suffix.len()];
central_directory.extend_from_slice(&archive_terminator);
let central_directory_range =
write_aligned_pad_start(&mut output, ¢ral_directory, BLOCK_SIZE);
let final_len = output.len();
let mut archive_terminator = &mut output[final_len - archive_terminator.len()..];
let directory_offset =
u32::try_from(central_directory_range.start).expect("archive larger than 4GiB");
let directory_count = u16::try_from(files.len()).expect("more than 64Ki files");
let directory_length =
u32::try_from(central_directory_range.len() - archive_terminator.len()).unwrap();
let suffix_length = u16::try_from(suffix.len()).expect("comment longer than 64KiB");
archive_terminator.write_all(b"PK\x05\x06").unwrap();
archive_terminator.write_all(&[0x00; 2]).unwrap();
archive_terminator.write_all(&[0x00; 2]).unwrap();
archive_terminator
.write_all(&directory_count.to_le_bytes())
.unwrap();
archive_terminator
.write_all(&directory_count.to_le_bytes())
.unwrap();
archive_terminator
.write_all(&directory_length.to_le_bytes())
.unwrap();
archive_terminator
.write_all(&directory_offset.to_le_bytes())
.unwrap();
archive_terminator
.write_all(&suffix_length.to_le_bytes())
.unwrap();
archive_terminator.write_all(suffix).unwrap();
output
}