diff --git a/README.md b/README.md index 816a1c95..fed129d5 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,9 @@ api_bind_addr = "[::1]:3900" # the S3 API port, HTTP without TLS. Add a reverse s3_region = "garage" # set this to anything. S3 API calls will fail if they are not made against the region set here. [s3_web] -web_bind_addr = "[::1]:3902" +bind_addr = "[::1]:3902" +root_domain = ".garage.tld" +index = "index.html" ``` Build Garage using `cargo build --release`. diff --git a/config.dev.toml b/config.dev.toml index 88378e50..215bc50c 100644 --- a/config.dev.toml +++ b/config.dev.toml @@ -19,3 +19,4 @@ s3_region = "garage" # set this to anything. S3 API calls will fail if they a [s3_web] bind_addr = "[::1]:3902" root_domain = ".garage.tld" +index = "index.html" diff --git a/src/util/config.rs b/src/util/config.rs index 72f7c319..f4c841b7 100644 --- a/src/util/config.rs +++ b/src/util/config.rs @@ -56,6 +56,7 @@ pub struct ApiConfig { pub struct WebConfig { pub bind_addr: SocketAddr, pub root_domain: String, + pub index: String, } fn default_max_concurrent_rpc_requests() -> usize { diff --git a/src/web/web_server.rs b/src/web/web_server.rs index cbb2aaac..16b27cef 100644 --- a/src/web/web_server.rs +++ b/src/web/web_server.rs @@ -1,3 +1,4 @@ +use std::borrow::Cow; use std::net::SocketAddr; use std::sync::Arc; @@ -55,17 +56,18 @@ async fn handler( // Get path let path = req.uri().path().to_string(); - let key = percent_encoding::percent_decode_str(&path).decode_utf8()?; + let index = &garage.config.s3_web.index; + let key = path_to_key(&path, &index)?; - // Get bucket descriptor + info!("Selected bucket: \"{}\", selected key: \"{}\"", bucket, key); + + // Get bucket descriptor let object = garage .object_table .get(&bucket.to_string(), &key.to_string()) .await? .ok_or(Error::NotFound)?; - info!("Selected bucket: \"{}\", selected key: \"{}\"", bucket, key); - Ok(Response::new(Body::from("hello world\n"))) } @@ -121,6 +123,27 @@ fn host_to_bucket<'a>(host: &'a str, root: &str) -> &'a str { &host[..cursor] } +/// Path to key +/// +/// Convert the provided path to the internal key +/// When a path ends with "/", we append the index name to match traditional web server behavior +/// which is also AWS S3 behavior. +fn path_to_key<'a>(path: &'a str, index: &str) -> Result, Error> { + let path_utf8 = percent_encoding::percent_decode_str(&path).decode_utf8()?; + match path_utf8.chars().last() { + None => Err(Error::BadRequest(format!( + "Path must have at least a character" + ))), + Some('/') => { + let mut key = String::with_capacity(path_utf8.len() + index.len()); + key.push_str(&path_utf8); + key.push_str(index); + Ok(key.into()) + } + Some(_) => Ok(path_utf8.into()), + } +} + #[cfg(test)] mod tests { use super::*; @@ -170,4 +193,13 @@ mod tests { assert_eq!(host_to_bucket("garage.tld", ".garage.tld"), "garage.tld"); } + + #[test] + fn path_to_key_test() -> Result<(), Error> { + assert_eq!(path_to_key("/file%20.jpg", "index.html")?, "/file .jpg"); + assert_eq!(path_to_key("/%20t/", "index.html")?, "/ t/index.html"); + assert_eq!(path_to_key("/", "index.html")?, "/index.html"); + assert!(path_to_key("", "index.html").is_err()); + Ok(()) + } }