#![allow(clippy::unwrap_used, clippy::missing_panics_doc, dead_code)]
use std::io::Cursor;
use std::io::Write;
#[must_use]
pub fn create_test_tar(entries: Vec<(&str, &[u8])>) -> Vec<u8> {
let mut ar = tar::Builder::new(Vec::new());
for (path, data) in entries {
let mut header = tar::Header::new_gnu();
header.set_size(data.len() as u64);
header.set_mode(0o644);
header.set_cksum();
ar.append_data(&mut header, path, data).unwrap();
}
ar.into_inner().unwrap()
}
#[must_use]
pub fn create_test_zip(entries: Vec<(&str, &[u8])>) -> Vec<u8> {
use zip::write::SimpleFileOptions;
use zip::write::ZipWriter;
let buffer = Vec::new();
let mut zip = ZipWriter::new(Cursor::new(buffer));
let options = SimpleFileOptions::default()
.compression_method(zip::CompressionMethod::Stored)
.unix_permissions(0o644);
for (path, data) in entries {
zip.start_file(path, options).unwrap();
zip.write_all(data).unwrap();
}
zip.finish().unwrap().into_inner()
}
#[must_use]
#[allow(clippy::cast_possible_truncation)]
pub fn create_raw_zip_entry(entry_name: &str, content: &[u8]) -> Vec<u8> {
let name_bytes = entry_name.as_bytes();
let name_len = name_bytes.len() as u16;
let content_len = content.len() as u32;
let mut buf: Vec<u8> = Vec::new();
let local_offset = buf.len() as u32;
buf.extend_from_slice(b"PK\x03\x04");
buf.extend_from_slice(&20u16.to_le_bytes());
buf.extend_from_slice(&0u16.to_le_bytes()); buf.extend_from_slice(&0u16.to_le_bytes()); buf.extend_from_slice(&0u16.to_le_bytes());
buf.extend_from_slice(&0u16.to_le_bytes());
buf.extend_from_slice(&0u32.to_le_bytes()); buf.extend_from_slice(&content_len.to_le_bytes());
buf.extend_from_slice(&content_len.to_le_bytes());
buf.extend_from_slice(&name_len.to_le_bytes());
buf.extend_from_slice(&0u16.to_le_bytes()); buf.extend_from_slice(name_bytes);
buf.extend_from_slice(content);
let central_offset = buf.len() as u32;
buf.extend_from_slice(b"PK\x01\x02");
buf.extend_from_slice(&0x031eu16.to_le_bytes());
buf.extend_from_slice(&20u16.to_le_bytes());
buf.extend_from_slice(&0u16.to_le_bytes());
buf.extend_from_slice(&0u16.to_le_bytes());
buf.extend_from_slice(&0u16.to_le_bytes());
buf.extend_from_slice(&0u16.to_le_bytes());
buf.extend_from_slice(&0u32.to_le_bytes());
buf.extend_from_slice(&content_len.to_le_bytes());
buf.extend_from_slice(&content_len.to_le_bytes());
buf.extend_from_slice(&name_len.to_le_bytes());
buf.extend_from_slice(&0u16.to_le_bytes()); buf.extend_from_slice(&0u16.to_le_bytes()); buf.extend_from_slice(&0u16.to_le_bytes()); buf.extend_from_slice(&0u16.to_le_bytes()); buf.extend_from_slice(&(0o100_644u32 << 16).to_le_bytes());
buf.extend_from_slice(&local_offset.to_le_bytes());
buf.extend_from_slice(name_bytes);
let central_size = (buf.len() as u32) - central_offset;
buf.extend_from_slice(b"PK\x05\x06");
buf.extend_from_slice(&0u16.to_le_bytes());
buf.extend_from_slice(&0u16.to_le_bytes());
buf.extend_from_slice(&1u16.to_le_bytes());
buf.extend_from_slice(&1u16.to_le_bytes());
buf.extend_from_slice(¢ral_size.to_le_bytes());
buf.extend_from_slice(¢ral_offset.to_le_bytes());
buf.extend_from_slice(&0u16.to_le_bytes());
buf
}
pub struct TarTestBuilder {
builder: tar::Builder<Vec<u8>>,
}
impl TarTestBuilder {
#[must_use]
pub fn new() -> Self {
Self {
builder: tar::Builder::new(Vec::new()),
}
}
#[must_use]
pub fn add_file(mut self, path: &str, data: &[u8]) -> Self {
let mut header = tar::Header::new_gnu();
header.set_size(data.len() as u64);
header.set_mode(0o644);
header.set_cksum();
self.builder.append_data(&mut header, path, data).unwrap();
self
}
#[must_use]
pub fn add_file_with_mode(mut self, path: &str, data: &[u8], mode: u32) -> Self {
let mut header = tar::Header::new_gnu();
header.set_size(data.len() as u64);
header.set_mode(mode);
header.set_cksum();
self.builder.append_data(&mut header, path, data).unwrap();
self
}
#[must_use]
pub fn add_directory(mut self, path: &str) -> Self {
let mut header = tar::Header::new_gnu();
header.set_size(0);
header.set_mode(0o755);
header.set_entry_type(tar::EntryType::Directory);
header.set_cksum();
self.builder
.append_data(&mut header, path, std::io::empty())
.unwrap();
self
}
#[must_use]
pub fn add_symlink(mut self, path: &str, target: &str) -> Self {
let mut header = tar::Header::new_gnu();
header.set_size(0);
header.set_mode(0o777);
header.set_entry_type(tar::EntryType::Symlink);
header.set_link_name(target).unwrap();
header.set_cksum();
self.builder
.append_data(&mut header, path, std::io::empty())
.unwrap();
self
}
#[must_use]
pub fn add_hardlink(mut self, path: &str, target: &str) -> Self {
let mut header = tar::Header::new_gnu();
header.set_size(0);
header.set_mode(0o644);
header.set_entry_type(tar::EntryType::Link);
header.set_link_name(target).unwrap();
header.set_cksum();
self.builder
.append_data(&mut header, path, std::io::empty())
.unwrap();
self
}
#[must_use]
pub fn build(self) -> Vec<u8> {
self.builder.into_inner().unwrap()
}
}
impl Default for TarTestBuilder {
fn default() -> Self {
Self::new()
}
}
#[must_use]
pub fn create_zip_with_symlink(link_path: &str, target: &str) -> Vec<u8> {
let content = target.as_bytes();
let crc = {
let mut c: u32 = 0xFFFF_FFFF;
for &b in content {
c ^= u32::from(b);
for _ in 0..8 {
if c & 1 != 0 {
c = (c >> 1) ^ 0xEDB8_8320;
} else {
c >>= 1;
}
}
}
c ^ 0xFFFF_FFFF
};
let external_attributes: u32 = 0o120_777 << 16;
let name_bytes = link_path.as_bytes();
let name_len = u16::try_from(name_bytes.len()).unwrap();
let content_len = u32::try_from(content.len()).unwrap();
let mut buf: Vec<u8> = Vec::new();
let local_offset = u32::try_from(buf.len()).unwrap();
buf.extend_from_slice(b"PK\x03\x04");
buf.extend_from_slice(&20u16.to_le_bytes());
buf.extend_from_slice(&0u16.to_le_bytes()); buf.extend_from_slice(&0u16.to_le_bytes()); buf.extend_from_slice(&0u16.to_le_bytes()); buf.extend_from_slice(&0u16.to_le_bytes()); buf.extend_from_slice(&crc.to_le_bytes());
buf.extend_from_slice(&content_len.to_le_bytes());
buf.extend_from_slice(&content_len.to_le_bytes());
buf.extend_from_slice(&name_len.to_le_bytes());
buf.extend_from_slice(&0u16.to_le_bytes()); buf.extend_from_slice(name_bytes);
buf.extend_from_slice(content);
let central_offset = u32::try_from(buf.len()).unwrap();
buf.extend_from_slice(b"PK\x01\x02");
buf.extend_from_slice(&0x031eu16.to_le_bytes()); buf.extend_from_slice(&20u16.to_le_bytes());
buf.extend_from_slice(&0u16.to_le_bytes()); buf.extend_from_slice(&0u16.to_le_bytes()); buf.extend_from_slice(&0u16.to_le_bytes()); buf.extend_from_slice(&0u16.to_le_bytes()); buf.extend_from_slice(&crc.to_le_bytes());
buf.extend_from_slice(&content_len.to_le_bytes());
buf.extend_from_slice(&content_len.to_le_bytes());
buf.extend_from_slice(&name_len.to_le_bytes());
buf.extend_from_slice(&0u16.to_le_bytes()); buf.extend_from_slice(&0u16.to_le_bytes()); buf.extend_from_slice(&0u16.to_le_bytes()); buf.extend_from_slice(&0u16.to_le_bytes()); buf.extend_from_slice(&external_attributes.to_le_bytes());
buf.extend_from_slice(&local_offset.to_le_bytes());
buf.extend_from_slice(name_bytes);
let central_size = u32::try_from(buf.len()).unwrap() - central_offset;
buf.extend_from_slice(b"PK\x05\x06");
buf.extend_from_slice(&0u16.to_le_bytes()); buf.extend_from_slice(&0u16.to_le_bytes()); buf.extend_from_slice(&1u16.to_le_bytes()); buf.extend_from_slice(&1u16.to_le_bytes()); buf.extend_from_slice(¢ral_size.to_le_bytes());
buf.extend_from_slice(¢ral_offset.to_le_bytes());
buf.extend_from_slice(&0u16.to_le_bytes());
buf
}
pub struct ZipTestBuilder {
zip: zip::ZipWriter<Cursor<Vec<u8>>>,
}
impl ZipTestBuilder {
#[must_use]
pub fn new() -> Self {
Self {
zip: zip::ZipWriter::new(Cursor::new(Vec::new())),
}
}
#[must_use]
pub fn add_file(mut self, path: &str, data: &[u8]) -> Self {
use zip::write::SimpleFileOptions;
let options = SimpleFileOptions::default()
.compression_method(zip::CompressionMethod::Stored)
.unix_permissions(0o644);
self.zip.start_file(path, options).unwrap();
self.zip.write_all(data).unwrap();
self
}
#[must_use]
pub fn add_file_with_mode(mut self, path: &str, data: &[u8], mode: u32) -> Self {
use zip::write::SimpleFileOptions;
let options = SimpleFileOptions::default()
.compression_method(zip::CompressionMethod::Stored)
.unix_permissions(mode);
self.zip.start_file(path, options).unwrap();
self.zip.write_all(data).unwrap();
self
}
#[must_use]
pub fn add_directory(mut self, path: &str) -> Self {
use zip::write::SimpleFileOptions;
let options = SimpleFileOptions::default().unix_permissions(0o755);
self.zip.add_directory(path, options).unwrap();
self
}
#[cfg(unix)]
#[must_use]
pub fn add_symlink(mut self, path: &str, target: &str) -> Self {
use zip::write::SimpleFileOptions;
let options = SimpleFileOptions::default().unix_permissions(0o120_777);
self.zip.start_file(path, options).unwrap();
self.zip.write_all(target.as_bytes()).unwrap();
self
}
#[must_use]
pub fn build(self) -> Vec<u8> {
self.zip.finish().unwrap().into_inner()
}
}
impl Default for ZipTestBuilder {
fn default() -> Self {
Self::new()
}
}
fn raw_tar_header(name: &[u8], size: usize, typeflag: u8) -> [u8; 512] {
let mut h = [0u8; 512];
let name_len = name.len().min(100);
h[..name_len].copy_from_slice(&name[..name_len]);
h[100..108].copy_from_slice(b"0000644\0");
h[108..116].copy_from_slice(b"0000000\0");
h[116..124].copy_from_slice(b"0000000\0");
h[124..136].copy_from_slice(format!("{size:011o}\0").as_bytes());
h[136..148].copy_from_slice(b"00000000000\0");
h[156] = typeflag;
h[257..263].copy_from_slice(b"ustar\0");
h[263..265].copy_from_slice(b"00");
h[148..156].copy_from_slice(b" ");
let checksum: u32 = h.iter().map(|&b| u32::from(b)).sum();
h[148..156].copy_from_slice(format!("{checksum:06o}\0 ").as_bytes());
h
}
fn pad_tar_block(out: &mut Vec<u8>, data: &[u8]) {
out.extend_from_slice(data);
let rem = data.len() % 512;
if rem != 0 {
out.extend(std::iter::repeat_n(0u8, 512 - rem));
}
}
#[must_use]
pub fn tar_with_pax_record(
pax_key: &str,
pax_value: &[u8],
entry_name: &[u8],
typeflag: u8,
data: &[u8],
) -> Vec<u8> {
let base = pax_key.len() + pax_value.len() + 3;
let mut len = base + 1;
loop {
let candidate = len.to_string().len() + base;
if candidate == len {
break;
}
len = candidate;
}
let mut record = format!("{len} {pax_key}=").into_bytes();
record.extend_from_slice(pax_value);
record.push(b'\n');
let mut tar = Vec::new();
tar.extend_from_slice(&raw_tar_header(b"PaxHeaders/entry", record.len(), b'x'));
pad_tar_block(&mut tar, &record);
tar.extend_from_slice(&raw_tar_header(entry_name, data.len(), typeflag));
pad_tar_block(&mut tar, data);
tar.extend(std::iter::repeat_n(0u8, 1024));
tar
}
#[must_use]
pub fn tar_with_pax_nul_path(pax_path: &[u8], data: &[u8]) -> Vec<u8> {
tar_with_pax_record("path", pax_path, b"ustar_name.txt", b'0', data)
}
#[must_use]
pub fn tar_with_pax_linkpath(pax_linkpath: &[u8], typeflag: u8) -> Vec<u8> {
tar_with_pax_record("linkpath", pax_linkpath, b"link_entry", typeflag, b"")
}
#[must_use]
pub fn tar_with_nonutf8_name(name_bytes: &[u8], data: &[u8]) -> Vec<u8> {
let mut tar = Vec::new();
tar.extend_from_slice(&raw_tar_header(name_bytes, data.len(), b'0'));
pad_tar_block(&mut tar, data);
tar.extend(std::iter::repeat_n(0u8, 1024));
tar
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_create_test_tar() {
let tar_data = create_test_tar(vec![("file.txt", b"hello")]);
assert!(!tar_data.is_empty());
}
#[test]
fn test_create_test_zip() {
let zip_data = create_test_zip(vec![("file.txt", b"hello")]);
assert!(!zip_data.is_empty());
}
#[test]
fn test_tar_builder() {
let tar_data = TarTestBuilder::new()
.add_file("file.txt", b"content")
.add_directory("dir/")
.build();
assert!(!tar_data.is_empty());
}
#[test]
fn test_zip_builder() {
let zip_data = ZipTestBuilder::new()
.add_file("file.txt", b"content")
.add_directory("dir/")
.build();
assert!(!zip_data.is_empty());
}
}