diff --git a/serve_zip/src/handler.rs b/serve_zip/src/handler.rs index 21452ef..a574945 100644 --- a/serve_zip/src/handler.rs +++ b/serve_zip/src/handler.rs @@ -1,3 +1,4 @@ +use rc_zip_tokio::ReadZip; use std::convert::Infallible; use std::sync::Arc; @@ -5,6 +6,7 @@ use axum::body::Body; use http::{Method, Request, Response, StatusCode}; use crate::index::Entries; +use crate::read_zip_for_arc::ArcBytes; use crate::serve_zip::ZipServeConfig; pub async fn serve( @@ -57,13 +59,65 @@ fn not_found() -> Response { } async fn serve_entry( - _data: Arc<[u8]>, - _config: ZipServeConfig, - _entry_name: String, - _method: Method, + data: Arc<[u8]>, + config: ZipServeConfig, + entry_name: String, + method: Method, ) -> Result, Infallible> { + let data = ArcBytes(data); + let archive = match data.read_zip().await { + Ok(a) => a, + Err(e) => { + eprintln!("Failed to open archive: {}", e); + return Ok(internal_error("Failed to open archive")); + } + }; + + let entry = match archive.by_name(&entry_name) { + Some(e) => e, + None => return Ok(not_found()), + }; + + let content_type = mime_for(&entry_name, &config); + let content_length = entry.uncompressed_size; + + if method == Method::HEAD { + return Ok(Response::builder() + .status(StatusCode::OK) + .header("content-type", content_type) + .header("content-length", content_length.to_string()) + .body(Body::empty()) + .unwrap()); + } + + let mut reader = entry.reader(); + let mut buffer = Vec::with_capacity(content_length as usize); + if let Err(e) = tokio::io::AsyncReadExt::read_to_end(&mut reader, &mut buffer).await { + eprintln!("Failed to read entry: {}", e); + return Ok(internal_error("Failed to read entry")); + } + + let body = Body::from(buffer); + Ok(Response::builder() - .status(StatusCode::NOT_IMPLEMENTED) - .body(Body::empty()) + .status(StatusCode::OK) + .header("content-type", content_type) + .header("content-length", content_length.to_string()) + .body(body) .unwrap()) } + +fn mime_for(entry_name: &str, config: &ZipServeConfig) -> String { + mime_guess::from_path(entry_name) + .first_raw() + .unwrap_or(&config.fallback_content_type) + .to_string() +} + +fn internal_error(msg: &str) -> Response { + Response::builder() + .status(StatusCode::INTERNAL_SERVER_ERROR) + .header("content-type", "text/plain; charset=utf-8") + .body(Body::from(msg.to_string())) + .unwrap() +} diff --git a/serve_zip/src/lib.rs b/serve_zip/src/lib.rs index 4c281ff..4faedc1 100644 --- a/serve_zip/src/lib.rs +++ b/serve_zip/src/lib.rs @@ -1,5 +1,6 @@ pub mod handler; pub mod index; +pub mod read_zip_for_arc; pub mod serve_zip; pub mod service; pub mod source; diff --git a/serve_zip/src/read_zip_for_arc.rs b/serve_zip/src/read_zip_for_arc.rs new file mode 100644 index 0000000..10b1337 --- /dev/null +++ b/serve_zip/src/read_zip_for_arc.rs @@ -0,0 +1,26 @@ +use rc_zip::Error; +use rc_zip_tokio::{ArchiveHandle, HasCursor, ReadZip, ReadZipWithSize}; +use std::io::Cursor; +use std::sync::Arc; + +pub struct ArcBytes(pub Arc<[u8]>); + +impl HasCursor for ArcBytes { + type Cursor<'a> + = Cursor> + where + Self: 'a; + + fn cursor_at(&self, offset: u64) -> Self::Cursor<'_> { + let mut c = Cursor::new(Arc::clone(&self.0)); + c.set_position(offset); + c + } +} + +impl ReadZip for ArcBytes { + type File = Self; + async fn read_zip(&self) -> Result, Error> { + self.read_zip_with_size(self.0.len() as u64).await + } +}