diff --git a/test-files/index.html b/test-files/index.html
new file mode 100644
index 0000000..ef2c5cc
--- /dev/null
+++ b/test-files/index.html
@@ -0,0 +1 @@
+HTML!
diff --git a/tower-http/CHANGELOG.md b/tower-http/CHANGELOG.md
index d093dc2..1fd3b78 100644
--- a/tower-http/CHANGELOG.md
+++ b/tower-http/CHANGELOG.md
@@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
# Unreleased
+- `ServeDir` and `ServeFile`: Changed service types, both now return `ServeFileSystemResponseBody` and `ServeFileSystemResponseFuture` ([#187])
- `AddAuthorization`, `InFlightRequests`, `SetRequestHeader`, `SetResponseHeader`, `AddExtension`, `MapRequestBody` and `MapResponseBody`
now requires underlying service to use `http::Request` and `http::Response` as request and responses ([#182])
- `ServeDir` and `ServeFile`: Ability to serve precompressed files ([#156])
@@ -42,6 +43,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
[#170]: https://github.com/tower-rs/tower-http/pull/170
[#172]: https://github.com/tower-rs/tower-http/pull/172
[#182]: https://github.com/tower-rs/tower-http/pull/182
+[#187]: https://github.com/tower-rs/tower-http/pull/187
# 0.1.2 (November 13, 2021)
diff --git a/tower-http/src/services/fs/mod.rs b/tower-http/src/services/fs/mod.rs
index e087955..d0975d2 100644
--- a/tower-http/src/services/fs/mod.rs
+++ b/tower-http/src/services/fs/mod.rs
@@ -5,7 +5,7 @@ use http::{HeaderMap, Response, StatusCode};
use http_body::{combinators::BoxBody, Body, Empty};
use pin_project_lite::pin_project;
use std::fs::Metadata;
-use std::{ffi::OsStr, future::Future, path::PathBuf};
+use std::{ffi::OsStr, path::PathBuf};
use std::{
io,
pin::Pin,
@@ -27,11 +27,12 @@ use crate::content_encoding::{Encoding, SupportedEncodings};
pub use self::{
serve_dir::{
- ResponseBody as ServeDirResponseBody, ResponseFuture as ServeDirResponseFuture, ServeDir,
- },
- serve_file::{
- ResponseBody as ServeFileResponseBody, ResponseFuture as ServeFileResponseFuture, ServeFile,
+ // The response body and future are used for both ServeDir and ServeFile
+ ResponseBody as ServeFileSystemResponseBody,
+ ResponseFuture as ServeFileSystemResponseFuture,
+ ServeDir,
},
+ serve_file::ServeFile,
};
#[derive(Clone, Copy, Debug)]
@@ -65,9 +66,6 @@ impl SupportedEncodings for PrecompressedVariants {
}
}
-type FileFuture =
- Pin)>> + Send + Sync + 'static>>;
-
// Returns the preferred_encoding encoding and modifies the path extension
// to the corresponding file extension for the encoding.
fn preferred_encoding(
diff --git a/tower-http/src/services/fs/serve_dir.rs b/tower-http/src/services/fs/serve_dir.rs
index 9d2206f..0d11bc5 100644
--- a/tower-http/src/services/fs/serve_dir.rs
+++ b/tower-http/src/services/fs/serve_dir.rs
@@ -58,14 +58,14 @@ use tower_service::Service;
/// [`and_then`](tower::ServiceBuilder::and_then) to change the response:
///
/// ```
-/// use tower_http::services::fs::{ServeDir, ServeDirResponseBody};
+/// use tower_http::services::fs::{ServeDir, ServeFileSystemResponseBody};
/// use tower::ServiceBuilder;
/// use http::{StatusCode, Response};
/// use http_body::{Body as _, Full};
/// use std::io;
///
/// let service = ServiceBuilder::new()
-/// .and_then(|response: Response| async move {
+/// .and_then(|response: Response| async move {
/// let response = if response.status() == StatusCode::NOT_FOUND {
/// let body = Full::from("Not Found")
/// .map_err(|err| match err {})
@@ -92,9 +92,53 @@ use tower_service::Service;
#[derive(Clone, Debug)]
pub struct ServeDir {
base: PathBuf,
- append_index_html_on_directories: bool,
buf_chunk_size: usize,
precompressed_variants: Option,
+ // This is used to specialise implementation for
+ // single files
+ variant: ServeVariant,
+}
+
+// Allow the ServeDir service to be used in the ServeFile service
+// with almost no overhead
+#[derive(Clone, Debug)]
+enum ServeVariant {
+ Directory {
+ append_index_html_on_directories: bool,
+ },
+ SingleFile {
+ mime: HeaderValue,
+ },
+}
+
+impl ServeVariant {
+ fn full_path(&self, base_path: &Path, requested_path: &str) -> Option {
+ match self {
+ ServeVariant::Directory {
+ append_index_html_on_directories: _,
+ } => {
+ let full_path = build_and_validate_path(base_path, requested_path)?;
+ Some(full_path)
+ }
+ ServeVariant::SingleFile { mime: _ } => Some(base_path.to_path_buf()),
+ }
+ }
+}
+
+fn build_and_validate_path(base_path: &Path, requested_path: &str) -> Option {
+ // build and validate the path
+ let path = requested_path.trim_start_matches('/');
+
+ let path_decoded = percent_decode(path.as_ref()).decode_utf8().ok()?;
+
+ let mut full_path = base_path.to_path_buf();
+ for seg in path_decoded.split('/') {
+ if seg.starts_with("..") || seg.contains('\\') {
+ return None;
+ }
+ full_path.push(seg);
+ }
+ Some(full_path)
}
impl ServeDir {
@@ -105,9 +149,20 @@ impl ServeDir {
Self {
base,
- append_index_html_on_directories: true,
buf_chunk_size: DEFAULT_CAPACITY,
precompressed_variants: None,
+ variant: ServeVariant::Directory {
+ append_index_html_on_directories: true,
+ },
+ }
+ }
+
+ pub(crate) fn new_single_file>(path: P, mime: HeaderValue) -> Self {
+ Self {
+ base: path.as_ref().to_owned(),
+ buf_chunk_size: DEFAULT_CAPACITY,
+ precompressed_variants: None,
+ variant: ServeVariant::SingleFile { mime },
}
}
@@ -117,8 +172,15 @@ impl ServeDir {
///
/// Defaults to `true`.
pub fn append_index_html_on_directories(mut self, append: bool) -> Self {
- self.append_index_html_on_directories = append;
- self
+ match &mut self.variant {
+ ServeVariant::Directory {
+ append_index_html_on_directories,
+ } => {
+ *append_index_html_on_directories = append;
+ self
+ }
+ ServeVariant::SingleFile { mime: _ } => self,
+ }
}
/// Set a specific read buffer chunk size.
@@ -181,6 +243,29 @@ impl ServeDir {
}
}
+async fn maybe_redirect_or_append_path(
+ full_path: &mut PathBuf,
+ uri: Uri,
+ append_index_html_on_directories: bool,
+) -> Option