diff --git a/tower-http/src/services/fs/serve_dir/future.rs b/tower-http/src/services/fs/serve_dir/future.rs index 7df32b0d..3712c055 100644 --- a/tower-http/src/services/fs/serve_dir/future.rs +++ b/tower-http/src/services/fs/serve_dir/future.rs @@ -13,6 +13,7 @@ use http::{ HeaderValue, Request, Response, StatusCode, }; use http_body_util::{BodyExt, Empty, Full}; +use http_range_header::RangeUnsatisfiableError; use pin_project_lite::pin_project; use std::{ convert::Infallible, @@ -297,6 +298,14 @@ fn build_response(output: FileOpened) -> Response { } } + Some(Err(RangeUnsatisfiableError::OverlappingRanges)) => builder + .header(header::CONTENT_RANGE, format!("bytes */{}", size)) + .status(StatusCode::RANGE_NOT_SATISFIABLE) + .body(body_from_bytes(Bytes::from( + "Cannot serve multipart range requests", + ))) + .unwrap(), + Some(Err(_)) => builder .header(header::CONTENT_RANGE, format!("bytes */{}", size)) .status(StatusCode::RANGE_NOT_SATISFIABLE) diff --git a/tower-http/src/services/fs/serve_dir/open_file.rs b/tower-http/src/services/fs/serve_dir/open_file.rs index 9dd1e012..fbe67265 100644 --- a/tower-http/src/services/fs/serve_dir/open_file.rs +++ b/tower-http/src/services/fs/serve_dir/open_file.rs @@ -309,8 +309,15 @@ fn try_parse_range( file_size: u64, ) -> Option>, RangeUnsatisfiableError>> { maybe_range_ref.map(|header_value| { - http_range_header::parse_range_header(header_value) - .and_then(|first_pass| first_pass.validate(file_size)) + let parsed = http_range_header::parse_range_header(header_value)?; + + if parsed.ranges.len() > 1 { + // ServeDir/ServeFile do not support multipart responses, so reject + // multi-range requests before validate() runs overlap checks. + return Err(RangeUnsatisfiableError::OverlappingRanges); + } + + parsed.validate(file_size) }) } diff --git a/tower-http/src/services/fs/serve_dir/tests.rs b/tower-http/src/services/fs/serve_dir/tests.rs index e220197a..1896ea67 100644 --- a/tower-http/src/services/fs/serve_dir/tests.rs +++ b/tower-http/src/services/fs/serve_dir/tests.rs @@ -627,6 +627,27 @@ async fn read_partial_errs_on_bad_range() { ) } +#[tokio::test] +async fn multipart_range_valid_returns_multipart_error_body() { + let svc = ServeDir::new(".."); + let req = Request::builder() + .uri("/README.md") + .header("Range", "bytes=0-0,2-2") + .body(Body::empty()) + .unwrap(); + let res = svc.oneshot(req).await.unwrap(); + + assert_eq!(res.status(), StatusCode::RANGE_NOT_SATISFIABLE); + let file_contents = std::fs::read("../README.md").unwrap(); + assert_eq!( + res.headers()["content-range"], + &format!("bytes */{}", file_contents.len()) + ); + + let body = body_into_text(res.into_body()).await; + assert_eq!(body, "Cannot serve multipart range requests"); +} + #[tokio::test] async fn accept_encoding_identity() { let svc = ServeDir::new("..");