diff --git a/Cargo.toml b/Cargo.toml index 3a0f7ee9..3d59efba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,6 +39,7 @@ async-session = { version = "3.0", optional = true } async-sse = { version = "5.1.0", optional = true } async-std = { version = "1.6.5", features = ["unstable"] } async-trait = "0.1.41" +cap-async-std = "0.25.0" femme = { version = "2.1.1", optional = true } futures-util = "0.3.6" http-client = { version = "6.1.0", default-features = false } diff --git a/src/fs/serve_dir.rs b/src/fs/serve_dir.rs index 9b464153..cc9fea39 100644 --- a/src/fs/serve_dir.rs +++ b/src/fs/serve_dir.rs @@ -1,19 +1,18 @@ use crate::log; use crate::{Body, Endpoint, Request, Response, Result, StatusCode}; -use async_std::path::PathBuf as AsyncPathBuf; +use async_std::io::BufReader; -use std::path::{Path, PathBuf}; -use std::{ffi::OsStr, io}; +use cap_async_std::fs; pub(crate) struct ServeDir { prefix: String, - dir: PathBuf, + dir: fs::Dir, } impl ServeDir { /// Create a new instance of `ServeDir`. - pub(crate) fn new(prefix: String, dir: PathBuf) -> Self { + pub(crate) fn new(prefix: String, dir: fs::Dir) -> Self { Self { prefix, dir } } } @@ -29,33 +28,26 @@ where .strip_prefix(&self.prefix.trim_end_matches('*')) .unwrap(); let path = path.trim_start_matches('/'); - let mut file_path = self.dir.clone(); - for p in Path::new(path) { - if p == OsStr::new(".") { - continue; - } else if p == OsStr::new("..") { - file_path.pop(); - } else { - file_path.push(&p); + + log::info!("Requested file: {:?}", path); + + let file = match self.dir.open(path).await { + Ok(file) => file, + Err(e) if e.kind() == std::io::ErrorKind::PermissionDenied => { + log::warn!("Unauthorized attempt to read: {:?}", path); + return Ok(Response::new(StatusCode::Forbidden)); } - } - - log::info!("Requested file: {:?}", file_path); - - let file_path = AsyncPathBuf::from(file_path); - if !file_path.starts_with(&self.dir) { - log::warn!("Unauthorized attempt to read: {:?}", file_path); - Ok(Response::new(StatusCode::Forbidden)) - } else { - match Body::from_file(&file_path).await { - Ok(body) => Ok(Response::builder(StatusCode::Ok).body(body).build()), - Err(e) if e.kind() == io::ErrorKind::NotFound => { - log::warn!("File not found: {:?}", &file_path); - Ok(Response::new(StatusCode::NotFound)) - } - Err(e) => Err(e.into()), + Err(e) if e.kind() == std::io::ErrorKind::NotFound => { + log::warn!("File not found: {:?}", path); + return Ok(Response::new(StatusCode::NotFound)); } - } + Err(e) => return Err(e.into()), + }; + + // TODO: This always uses `mime::BYTE_STREAM`; with http-types 3.0 + // we'll be able to use `Body::from_open_file` which fixes this. + let body = Body::from_reader(BufReader::new(file), None); + Ok(Response::builder(StatusCode::Ok).body(body).build()) } } @@ -63,16 +55,12 @@ where mod test { use super::*; - use std::fs::{self, File}; - use std::io::Write; + use async_std::io::WriteExt; + use cap_async_std::ambient_authority; + use cap_async_std::fs::Dir; fn serve_dir(tempdir: &tempfile::TempDir) -> crate::Result { - let static_dir = tempdir.path().join("static"); - fs::create_dir(&static_dir)?; - - let file_path = static_dir.join("foo"); - let mut file = File::create(&file_path)?; - write!(file, "Foobar")?; + let static_dir = async_std::task::block_on(async { setup_static_dir(tempdir).await })?; Ok(ServeDir { prefix: "/static/".to_string(), @@ -80,6 +68,16 @@ mod test { }) } + async fn setup_static_dir(tempdir: &tempfile::TempDir) -> crate::Result { + let static_dir = tempdir.path().join("static"); + Dir::create_ambient_dir_all(&static_dir, ambient_authority()).await?; + + let static_dir = Dir::open_ambient_dir(static_dir, ambient_authority()).await?; + let mut file = static_dir.create("foo").await?; + write!(file, "Foobar").await?; + Ok(static_dir) + } + fn request(path: &str) -> crate::Request<()> { let request = crate::http::Request::get( crate::http::Url::parse(&format!("http://localhost/{}", path)).unwrap(), diff --git a/src/route.rs b/src/route.rs index 8b827c1d..48e73e6f 100644 --- a/src/route.rs +++ b/src/route.rs @@ -1,3 +1,6 @@ +use async_std::task; +use cap_async_std::ambient_authority; +use cap_async_std::fs::Dir; use std::fmt::Debug; use std::io; use std::path::Path; @@ -165,7 +168,8 @@ impl<'a, State: Clone + Send + Sync + 'static> Route<'a, State> { /// ``` pub fn serve_dir(&mut self, dir: impl AsRef) -> io::Result<()> { // Verify path exists, return error if it doesn't. - let dir = dir.as_ref().to_owned().canonicalize()?; + let path = dir.as_ref().to_owned().canonicalize()?; + let dir = task::block_on(async { Dir::open_ambient_dir(path, ambient_authority()).await })?; let prefix = self.path().to_string(); self.get(ServeDir::new(prefix, dir)); Ok(())