From e85da1cf340cb607cae7a1e73b9ed6d2b887210f Mon Sep 17 00:00:00 2001 From: "Elf M. Sternberg" Date: Sun, 10 May 2026 12:10:49 -0700 Subject: [PATCH] Hey, some tests are passing. I can open a zip file and check it for validity, presence, file size, and kind. --- .gitignore | 1 + Cargo.lock | 1 + demo/assets/demo.zip | Bin 0 -> 844 bytes serve_zip/Cargo.toml | 1 + serve_zip/src/index.rs | 92 ++++++++++++++++++++++++++++++++++++++++ serve_zip/src/lib.rs | 16 +------ serve_zip/src/source.rs | 20 +++++++++ 7 files changed, 117 insertions(+), 14 deletions(-) create mode 100644 demo/assets/demo.zip create mode 100644 serve_zip/src/index.rs create mode 100644 serve_zip/src/source.rs diff --git a/.gitignore b/.gitignore index 2f05f19..3e83fe1 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ target # rustc will dump stack traces when hitting an internal compiler error to PWD rustc-ice-*.txt +todo.md diff --git a/Cargo.lock b/Cargo.lock index 7298829..13d4ea4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -806,6 +806,7 @@ dependencies = [ "futures-util", "http", "mime_guess", + "rc-zip", "rc-zip-tokio", "tokio", "tokio-util", diff --git a/demo/assets/demo.zip b/demo/assets/demo.zip new file mode 100644 index 0000000000000000000000000000000000000000..0246f3e141eed77fa9dfb43ef589badf37b13459 GIT binary patch literal 844 zcmWIWW@h1H0D)EBt75 z5e6m(4zTvTb1y&o1GPs0u?S3iR%vmGZlZ2lW=^VJNks`h(_~L?#X+X>cG&?gdCAC5?qa-&6 z>|JeOz=1HD;kM^C@-Zn2uw1yeKB8KAXWizl*V0puAB=PO-cr!rwtu=ul69rAB+o9+ z#Dv$JrmGC@R)o)Z{V8F2hFe=&hw9I3UtPq-JU2|+a`4R4v)^a*%lfv=T&KMy8 zkQ9LC0f, +} + +impl Entries { + pub async fn new(data: &[u8]) -> Result, rc_zip::Error> { + // The only thing zip cares about is can it be read as a zip file? Does it have a Cursor? + let records = data.read_zip().await?; + let mut entries = HashMap::::new(); + for record in records.entries() { + let name = record.sanitized_name(); + if name.is_none() { + continue; + } + + let kind = record.kind(); + if kind == EntryKind::Symlink { + continue; + } + + let name = name.unwrap(); + entries.insert( + name.to_string(), + EntryMeta { + is_dir: kind == EntryKind::Directory, + size: record.uncompressed_size, + }, + ); + } + Ok(Arc::new(Entries { entries })) + } + + pub fn get(&self, path: &str) -> Option<&EntryMeta> { + path.strip_prefix('/') + .and_then(|stripped| self.entries.get(stripped)) + .or_else(|| self.entries.get(path)) + } +} + +#[cfg(test)] +mod tests { + const ZIPPATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../demo/assets/demo.zip"); + + use super::Entries; + use crate::source::ZipSource; + + #[tokio::test] + async fn finds_zip_file_by_zip_path() { + let zipfile = ZipSource::File(ZIPPATH.into()); + let archive = zipfile.load().await.unwrap(); + let entries = Entries::new(&archive).await.unwrap(); + let index = entries.get("index.html"); + assert!(index.is_some()); + let indexfile = index.unwrap(); + assert!(indexfile.size == 150); + } + + #[tokio::test] + async fn finds_zip_file_with_rooted_path() { + let zipfile = ZipSource::File(ZIPPATH.into()); + let archive = zipfile.load().await.unwrap(); + let entries = Entries::new(&archive).await.unwrap(); + assert!(entries.get("/index.html").is_some()); + } + + #[tokio::test] + async fn returns_none_on_bad_filename() { + let zipfile = ZipSource::File(ZIPPATH.into()); + let archive = zipfile.load().await.unwrap(); + let entries = Entries::new(&archive).await.unwrap(); + assert!(entries.get("index.garbage").is_none()); + } + + #[tokio::test] + async fn returns_isdir_when_expected() { + let zipfile = ZipSource::File(ZIPPATH.into()); + let archive = zipfile.load().await.unwrap(); + let entries = Entries::new(&archive).await.unwrap(); + assert!(entries.get("docs/").is_some()); + assert!(entries.get("docs/").unwrap().is_dir); + } +} diff --git a/serve_zip/src/lib.rs b/serve_zip/src/lib.rs index b93cf3f..7f9c127 100644 --- a/serve_zip/src/lib.rs +++ b/serve_zip/src/lib.rs @@ -1,14 +1,2 @@ -pub fn add(left: u64, right: u64) -> u64 { - left + right -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn it_works() { - let result = add(2, 2); - assert_eq!(result, 4); - } -} +pub mod index; +pub mod source; diff --git a/serve_zip/src/source.rs b/serve_zip/src/source.rs new file mode 100644 index 0000000..f02ad9b --- /dev/null +++ b/serve_zip/src/source.rs @@ -0,0 +1,20 @@ +use std::path::PathBuf; +use std::sync::Arc; + +#[derive(Clone, Debug)] +pub enum ZipSource { + Local(&'static [u8]), + File(PathBuf), +} + +impl ZipSource { + pub async fn load(self) -> Result, std::io::Error> { + match self { + ZipSource::Local(bytes) => Ok(Arc::from(bytes)), + ZipSource::File(path) => { + let bytes = tokio::fs::read(&path).await?; + Ok(Arc::from(bytes.as_slice())) + } + } + } +}