From 987f5c9e2ef0d5347c56971490d679801f5e64a2 Mon Sep 17 00:00:00 2001 From: Denis Cornehl Date: Tue, 21 Feb 2023 13:24:19 +0100 Subject: [PATCH] ServeDir: convert io::ErrorKind::NotADirectory to not_found (#331) * ServeDir: convert io::ErrorKind::NotADirectory to not_found * only check raw OS error on unix OS, add comment. * mention "is a directory" handling in ServeDir in documentation --- .../src/services/fs/serve_dir/future.rs | 15 +++++++++++-- tower-http/src/services/fs/serve_dir/mod.rs | 2 ++ tower-http/src/services/fs/serve_dir/tests.rs | 21 +++++++++++++++++++ 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/tower-http/src/services/fs/serve_dir/future.rs b/tower-http/src/services/fs/serve_dir/future.rs index 73ee328e..1d7eed9e 100644 --- a/tower-http/src/services/fs/serve_dir/future.rs +++ b/tower-http/src/services/fs/serve_dir/future.rs @@ -124,8 +124,19 @@ where } Err(err) => { - if let io::ErrorKind::NotFound | io::ErrorKind::PermissionDenied = - err.kind() + #[cfg(unix)] + // 20 = libc::ENOTDIR => "not a directory + // when `io_error_more` landed, this can be changed + // to checking for `io::ErrorKind::NotADirectory`. + // https://github.com/rust-lang/rust/issues/86442 + let error_is_not_a_directory = err.raw_os_error() == Some(20); + #[cfg(not(unix))] + let error_is_not_a_directory = false; + + if matches!( + err.kind(), + io::ErrorKind::NotFound | io::ErrorKind::PermissionDenied + ) || error_is_not_a_directory { if let Some((mut fallback, request)) = fallback_and_request.take() { call_fallback(&mut fallback, request) diff --git a/tower-http/src/services/fs/serve_dir/mod.rs b/tower-http/src/services/fs/serve_dir/mod.rs index 6c869d19..7e0f7348 100644 --- a/tower-http/src/services/fs/serve_dir/mod.rs +++ b/tower-http/src/services/fs/serve_dir/mod.rs @@ -35,6 +35,8 @@ const DEFAULT_CAPACITY: usize = 65536; /// - The file doesn't exist /// - Any segment of the path contains `..` /// - Any segment of the path contains a backslash +/// - On unix, any segment of the path referenced as directory is actually an +/// existing file (`/file.html/something`) /// - We don't have necessary permissions to read the file /// /// # Example diff --git a/tower-http/src/services/fs/serve_dir/tests.rs b/tower-http/src/services/fs/serve_dir/tests.rs index cf215a47..9a25c6dd 100644 --- a/tower-http/src/services/fs/serve_dir/tests.rs +++ b/tower-http/src/services/fs/serve_dir/tests.rs @@ -289,6 +289,27 @@ async fn not_found() { assert!(body.is_empty()); } +#[cfg(unix)] +#[tokio::test] +async fn not_found_when_not_a_directory() { + let svc = ServeDir::new("../test-files"); + + // `index.html` is a file, and we are trying to request + // it as a directory. + let req = Request::builder() + .uri("/index.html/some_file") + .body(Body::empty()) + .unwrap(); + let res = svc.oneshot(req).await.unwrap(); + + // This should lead to a 404 + assert_eq!(res.status(), StatusCode::NOT_FOUND); + assert!(res.headers().get(header::CONTENT_TYPE).is_none()); + + let body = body_into_text(res.into_body()).await; + assert!(body.is_empty()); +} + #[tokio::test] async fn not_found_precompressed() { let svc = ServeDir::new("../test-files").precompressed_gzip();