From 31c7a8ea25ad0eb5e98362803c39b81c2fd3e599 Mon Sep 17 00:00:00 2001 From: David Pedersen Date: Sat, 22 Jan 2022 11:10:55 +0100 Subject: [PATCH] Backport #204 to 0.1.x (#213) --- tower-http/src/services/fs/serve_dir.rs | 48 ++++++++++++++++--------- 1 file changed, 31 insertions(+), 17 deletions(-) diff --git a/tower-http/src/services/fs/serve_dir.rs b/tower-http/src/services/fs/serve_dir.rs index dac2af31..de995389 100644 --- a/tower-http/src/services/fs/serve_dir.rs +++ b/tower-http/src/services/fs/serve_dir.rs @@ -8,7 +8,7 @@ use percent_encoding::percent_decode; use std::{ future::Future, io, - path::{Path, PathBuf}, + path::{Component, Path, PathBuf}, pin::Pin, task::{Context, Poll}, }; @@ -133,28 +133,15 @@ impl Service> for ServeDir { } fn call(&mut self, req: Request) -> Self::Future { - // build and validate the path let path = req.uri().path(); - let path = path.trim_start_matches('/'); - - let path_decoded = if let Ok(decoded_utf8) = percent_decode(path.as_ref()).decode_utf8() { - decoded_utf8 + let mut full_path = if let Some(path) = build_and_validate_path(&self.base, path) { + path } else { return ResponseFuture { inner: Inner::Invalid, }; }; - let mut full_path = self.base.clone(); - for seg in path_decoded.split('/') { - if seg.starts_with("..") || seg.contains('\\') { - return ResponseFuture { - inner: Inner::Invalid, - }; - } - full_path.push(seg); - } - let append_index_html_on_directories = self.append_index_html_on_directories; let buf_chunk_size = self.buf_chunk_size; let uri = req.uri().clone(); @@ -177,7 +164,7 @@ impl Service> for ServeDir { let guess = mime_guess::from_path(&full_path); let mime = guess .first_raw() - .map(|mime| HeaderValue::from_static(mime)) + .map(HeaderValue::from_static) .unwrap_or_else(|| { HeaderValue::from_str(mime::APPLICATION_OCTET_STREAM.as_ref()).unwrap() }); @@ -192,6 +179,33 @@ impl Service> for ServeDir { } } +fn build_and_validate_path(base_path: &Path, requested_path: &str) -> Option { + let path = requested_path.trim_start_matches('/'); + + let path_decoded = percent_decode(path.as_ref()).decode_utf8().ok()?; + let path_decoded = Path::new(&*path_decoded); + + let mut full_path = base_path.to_path_buf(); + for component in path_decoded.components() { + match component { + Component::Normal(comp) => { + // protect against paths like `/foo/c:/bar/baz` (#204) + if Path::new(&comp) + .components() + .all(|c| matches!(c, Component::Normal(_))) + { + full_path.push(comp) + } else { + return None; + } + } + Component::CurDir => {} + Component::Prefix(_) | Component::RootDir | Component::ParentDir => return None, + } + } + Some(full_path) +} + async fn is_dir(full_path: &Path) -> bool { tokio::fs::metadata(full_path) .await