use super::*;
use std::io::Write;
fn make_zip(path: &Path) {
let mut w = zip::ZipWriter::new(File::create(path).unwrap());
let opts = zip::write::SimpleFileOptions::default();
w.add_directory("docs/", opts).unwrap();
w.start_file("docs/readme.txt", opts).unwrap();
w.write_all(b"hello zip").unwrap();
w.start_file("top.txt", opts).unwrap();
w.write_all(b"x").unwrap();
w.finish().unwrap();
}
fn make_tar(path: &Path, gz: bool) {
let file = File::create(path).unwrap();
let writer: Box<dyn Write> = if gz {
Box::new(flate2::write::GzEncoder::new(
file,
flate2::Compression::default(),
))
} else {
Box::new(file)
};
let mut b = tar::Builder::new(writer);
let data = b"hello tar";
let mut h = tar::Header::new_gnu();
h.set_size(data.len() as u64);
h.set_mode(0o644);
h.set_cksum();
b.append_data(&mut h, "dir/file.txt", &data[..]).unwrap();
b.finish().unwrap();
}
#[test]
fn detects_formats_case_insensitively() {
for (name, want) in [
("a.zip", Some(ArchiveFormat::Zip)),
("A.ZIP", Some(ArchiveFormat::Zip)),
("a.tar", Some(ArchiveFormat::Tar)),
("a.tar.gz", Some(ArchiveFormat::TarGz)),
("a.tgz", Some(ArchiveFormat::TarGz)),
("a.txt", None),
("gz", None),
] {
assert_eq!(ArchiveFormat::detect(Path::new(name)), want, "{name}");
}
}
#[test]
fn default_destination_strips_archive_extension() {
assert_eq!(
default_destination(Path::new("/x/foo.tar.gz")),
PathBuf::from("/x/foo")
);
assert_eq!(
default_destination(Path::new("/x/foo.zip")),
PathBuf::from("/x/foo")
);
assert_eq!(
default_destination(Path::new("/x/foo.TGZ")),
PathBuf::from("/x/foo")
);
assert_eq!(
default_destination(Path::new("/x/.zip")),
PathBuf::from("/x/extracted")
);
}
#[test]
fn lists_zip_contents() {
let tmp = tempfile::tempdir().unwrap();
let p = tmp.path().join("a.zip");
make_zip(&p);
let l = list(&p).unwrap();
assert_eq!(l.format, ArchiveFormat::Zip);
assert_eq!(l.total_entries, 3);
assert_eq!(l.total_size, 10);
assert!(l.entries.iter().any(|e| e.name == "docs/" && e.is_dir));
assert!(l
.entries
.iter()
.any(|e| e.name == "docs/readme.txt" && e.size == 9));
}
#[test]
fn lists_tar_and_targz_contents() {
let tmp = tempfile::tempdir().unwrap();
for (name, gz) in [("a.tar", false), ("a.tar.gz", true)] {
let p = tmp.path().join(name);
make_tar(&p, gz);
let l = list(&p).unwrap();
assert_eq!(l.total_entries, 1, "{name}");
assert_eq!(l.entries[0].name, "dir/file.txt");
assert_eq!(l.entries[0].size, 9);
}
}
#[test]
fn list_rejects_unsupported_and_corrupt_files() {
let tmp = tempfile::tempdir().unwrap();
let txt = tmp.path().join("a.txt");
std::fs::write(&txt, "x").unwrap();
assert!(list(&txt).is_err());
let bad = tmp.path().join("bad.zip");
std::fs::write(&bad, "not a zip").unwrap();
assert!(list(&bad).is_err());
}
#[test]
fn extracts_zip_tar_and_targz() {
let tmp = tempfile::tempdir().unwrap();
let z = tmp.path().join("a.zip");
make_zip(&z);
let out = tmp.path().join("zout");
assert_eq!(extract(&z, &out).unwrap(), 3);
assert_eq!(
std::fs::read_to_string(out.join("docs/readme.txt")).unwrap(),
"hello zip"
);
for (name, gz) in [("a.tar", false), ("a.tgz", true)] {
let t = tmp.path().join(name);
make_tar(&t, gz);
let out = tmp.path().join(format!("out-{name}"));
assert_eq!(extract(&t, &out).unwrap(), 1);
assert_eq!(
std::fs::read_to_string(out.join("dir/file.txt")).unwrap(),
"hello tar"
);
}
}
#[test]
fn tar_path_traversal_is_refused() {
let tmp = tempfile::tempdir().unwrap();
let p = tmp.path().join("evil.tar");
{
let mut b = tar::Builder::new(File::create(&p).unwrap());
let data = b"pwn";
let mut h = tar::Header::new_gnu();
h.set_size(3);
h.set_mode(0o644);
let name = b"../escaped.txt";
h.as_old_mut().name[..name.len()].copy_from_slice(name);
h.set_cksum();
b.append(&h, &data[..]).unwrap();
b.finish().unwrap();
}
let out = tmp.path().join("out");
assert!(extract(&p, &out).is_err());
assert!(!tmp.path().join("escaped.txt").exists());
}