From 2459ec418b6bc89c8e98904805badee4948ec80d Mon Sep 17 00:00:00 2001 From: Jose Quintana Date: Mon, 9 Aug 2021 22:18:49 +0200 Subject: [PATCH] fix: return incorrect first bytes range when final bytes requested for example a request using 'Range: bytes=-10' header returns incorrectly the first 10 bytes rather than the last 10 ones according to https://datatracker.ietf.org/doc/html/rfc7233#section-2.1 note: this is borrowed from https://github.com/seanmonstar/warp/pull/872 and adapted to this project --- src/static_files.rs | 28 +++++++++++++--------------- tests/static_files.rs | 287 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 300 insertions(+), 15 deletions(-) diff --git a/src/static_files.rs b/src/static_files.rs index a9371d0..c1e4a7a 100644 --- a/src/static_files.rs +++ b/src/static_files.rs @@ -501,23 +501,21 @@ fn bytes_range(range: Option, max_len: u64) -> Result<(u64, u64), BadRang let ret = range .iter() .map(|(start, end)| { - let start = match start { - Bound::Unbounded => 0, - Bound::Included(s) => s, - Bound::Excluded(s) => s + 1, - }; - - let end = match end { - Bound::Unbounded => max_len, - Bound::Included(s) => { - // For the special case where s == the file size - if s == max_len { - s - } else { - s + 1 + let (start, end) = match (start, end) { + (Bound::Unbounded, Bound::Unbounded) => (0, max_len), + (Bound::Included(a), Bound::Included(b)) => { + // For the special case where e == the file size + let e = if b == max_len { b } else { b + 1 }; + (a, e) + } + (Bound::Included(s), Bound::Unbounded) => (s, max_len), + (Bound::Unbounded, Bound::Included(e)) => { + if e > max_len { + return Err(BadRange); } + (max_len - e, max_len) } - Bound::Excluded(s) => s, + _ => unreachable!(), }; if start < end && end <= max_len { diff --git a/tests/static_files.rs b/tests/static_files.rs index f6e4bdb..aa93c1b 100644 --- a/tests/static_files.rs +++ b/tests/static_files.rs @@ -209,4 +209,291 @@ mod tests { } } } + + #[tokio::test] + async fn handle_byte_ranges() { + let mut headers = HeaderMap::new(); + headers.insert("range", "bytes=100-200".parse().unwrap()); + + let buf = fs::read(root_dir().join("index.html")) + .expect("unexpected error during index.html reading"); + let buf = Bytes::from(buf); + + match static_files::handle(&Method::GET, &headers, root_dir(), "index.html", false).await { + Ok(mut res) => { + assert_eq!(res.status(), 206); + assert_eq!( + res.headers()["content-range"], + format!("bytes 100-200/{}", buf.len()) + ); + assert_eq!(res.headers()["content-length"], "101"); + let body = hyper::body::to_bytes(res.body_mut()) + .await + .expect("unexpected bytes error during `body` convertion"); + assert_eq!(body, &buf[100..=200]); + } + Err(_) => { + panic!("expected a normal response rather than a status error") + } + } + } + + #[tokio::test] + async fn handle_byte_ranges_out_of_range() { + let mut headers = HeaderMap::new(); + headers.insert("range", "bytes=100-100000".parse().unwrap()); + + let buf = fs::read(root_dir().join("index.html")) + .expect("unexpected error during index.html reading"); + let buf = Bytes::from(buf); + + match static_files::handle(&Method::GET, &headers, root_dir(), "index.html", false).await { + Ok(mut res) => { + assert_eq!(res.status(), 416); + assert_eq!( + res.headers()["content-range"], + format!("bytes */{}", buf.len()) + ); + assert_eq!(res.headers().get("content-length"), None); + let body = hyper::body::to_bytes(res.body_mut()) + .await + .expect("unexpected bytes error during `body` convertion"); + assert_eq!(body, ""); + } + Err(_) => { + panic!("expected a normal response rather than a status error") + } + } + } + + #[tokio::test] + async fn handle_byte_ranges_if_range_too_old() { + let mut headers = HeaderMap::new(); + headers.insert("range", "bytes=100-200".parse().unwrap()); + headers.insert("if-range", "Mon, 18 Nov 1974 00:00:00 GMT".parse().unwrap()); + + let buf = fs::read(root_dir().join("index.html")) + .expect("unexpected error during index.html reading"); + let buf = Bytes::from(buf); + + match static_files::handle(&Method::GET, &headers, root_dir(), "index.html", false).await { + Ok(res) => { + assert_eq!(res.status(), 200); + assert_eq!(res.headers()["content-length"], buf.len().to_string()); + assert_eq!(res.headers().get("content-range"), None); + } + Err(_) => { + panic!("expected a normal response rather than a status error") + } + } + } + + #[tokio::test] + async fn handle_byte_ranges_suffix() { + let mut headers = HeaderMap::new(); + headers.insert("range", "bytes=100-".parse().unwrap()); + + let buf = fs::read(root_dir().join("index.html")) + .expect("unexpected error during index.html reading"); + let buf = Bytes::from(buf); + + match static_files::handle(&Method::GET, &headers, root_dir(), "index.html", false).await { + Ok(mut res) => { + assert_eq!(res.status(), 206); + assert_eq!( + res.headers()["content-range"], + format!("bytes 100-{}/{}", buf.len() - 1, buf.len()) + ); + assert_eq!( + res.headers()["content-length"], + &buf[100..].len().to_string() + ); + let body = hyper::body::to_bytes(res.body_mut()) + .await + .expect("unexpected bytes error during `body` convertion"); + assert_eq!(body, &buf[100..]); + } + Err(_) => { + panic!("expected a normal response rather than a status error") + } + } + } + + #[tokio::test] + async fn handle_byte_ranges_suffix_2() { + let mut headers = HeaderMap::new(); + headers.insert("range", "bytes=-100".parse().unwrap()); + + let buf = fs::read(root_dir().join("index.html")) + .expect("unexpected error during index.html reading"); + let buf = Bytes::from(buf); + + match static_files::handle(&Method::GET, &headers, root_dir(), "index.html", false).await { + Ok(mut res) => { + assert_eq!(res.status(), 206); + assert_eq!( + res.headers()["content-range"], + format!("bytes {}-{}/{}", buf.len() - 100, buf.len() - 1, buf.len()) + ); + assert_eq!(res.headers()["content-length"], "100"); + let body = hyper::body::to_bytes(res.body_mut()) + .await + .expect("unexpected bytes error during `body` convertion"); + assert_eq!(body, &buf[buf.len() - 100..]); + } + Err(_) => { + panic!("expected a normal response rather than a status error") + } + } + } + + #[tokio::test] + async fn handle_byte_ranges_bad() { + let mut headers = HeaderMap::new(); + headers.insert("range", "bytes=100-10".parse().unwrap()); + + let buf = fs::read(root_dir().join("index.html")) + .expect("unexpected error during index.html reading"); + let buf = Bytes::from(buf); + + match static_files::handle(&Method::GET, &headers, root_dir(), "index.html", false).await { + Ok(mut res) => { + assert_eq!(res.status(), 416); + assert_eq!( + res.headers()["content-range"], + format!("bytes */{}", buf.len()) + ); + assert_eq!(res.headers().get("content-length"), None); + let body = hyper::body::to_bytes(res.body_mut()) + .await + .expect("unexpected bytes error during `body` convertion"); + assert_eq!(body, ""); + } + Err(_) => { + panic!("expected a normal response rather than a status error") + } + } + } + + #[tokio::test] + async fn handle_byte_ranges_bad_2() { + let buf = fs::read(root_dir().join("index.html")) + .expect("unexpected error during index.html reading"); + let buf = Bytes::from(buf); + + let mut headers = HeaderMap::new(); + headers.insert( + "range", + format!("bytes=-{}", buf.len() + 1).parse().unwrap(), + ); + + match static_files::handle(&Method::GET, &headers, root_dir(), "index.html", false).await { + Ok(mut res) => { + assert_eq!(res.status(), 416); + assert_eq!( + res.headers()["content-range"], + format!("bytes */{}", buf.len()) + ); + assert_eq!(res.headers().get("content-length"), None); + let body = hyper::body::to_bytes(res.body_mut()) + .await + .expect("unexpected bytes error during `body` convertion"); + assert_eq!(body, ""); + } + Err(_) => { + panic!("expected a normal response rather than a status error") + } + } + } + + #[tokio::test] + async fn handle_byte_ranges_bad_3() { + let buf = fs::read(root_dir().join("index.html")) + .expect("unexpected error during index.html reading"); + let buf = Bytes::from(buf); + + let mut headers = HeaderMap::new(); + // Range::Unbounded for beginning and end + headers.insert("range", "bytes=".parse().unwrap()); + + match static_files::handle(&Method::GET, &headers, root_dir(), "index.html", false).await { + Ok(mut res) => { + assert_eq!(res.status(), 200); + let body = hyper::body::to_bytes(res.body_mut()) + .await + .expect("unexpected bytes error during `body` convertion"); + assert_eq!(body, buf); + } + Err(_) => { + panic!("expected a normal response rather than a status error") + } + } + } + + #[tokio::test] + async fn handle_byte_ranges_exclude_file_size() { + let buf = fs::read(root_dir().join("index.html")) + .expect("unexpected error during index.html reading"); + let buf = Bytes::from(buf); + + let mut headers = HeaderMap::new(); + // range including end of file (non-inclusive result) + headers.insert("range", format!("bytes=100-{}", buf.len()).parse().unwrap()); + + match static_files::handle(&Method::GET, &headers, root_dir(), "index.html", false).await { + Ok(mut res) => { + assert_eq!(res.status(), 206); + assert_eq!( + res.headers()["content-range"], + format!("bytes 100-{}/{}", buf.len() - 1, buf.len()) + ); + assert_eq!( + res.headers()["content-length"], + format!("{}", buf.len() - 100) + ); + let body = hyper::body::to_bytes(res.body_mut()) + .await + .expect("unexpected bytes error during `body` convertion"); + assert_eq!(body, &buf[100..=buf.len() - 1]); + } + Err(_) => { + panic!("expected a normal response rather than a status error") + } + } + } + + #[tokio::test] + async fn handle_byte_ranges_exclude_file_size_2() { + let buf = fs::read(root_dir().join("index.html")) + .expect("unexpected error during index.html reading"); + let buf = Bytes::from(buf); + + let mut headers = HeaderMap::new(); + // range with 1 byte to end yields same result as above. (inclusive result) + headers.insert( + "range", + format!("bytes=100-{}", buf.len() - 1).parse().unwrap(), + ); + + match static_files::handle(&Method::GET, &headers, root_dir(), "index.html", false).await { + Ok(mut res) => { + assert_eq!(res.status(), 206); + assert_eq!( + res.headers()["content-range"], + format!("bytes 100-{}/{}", buf.len() - 1, buf.len()) + ); + assert_eq!( + res.headers()["content-length"], + format!("{}", buf.len() - 100) + ); + let body = hyper::body::to_bytes(res.body_mut()) + .await + .expect("unexpected bytes error during `body` convertion"); + assert_eq!(body, &buf[100..=buf.len() - 1]); + } + Err(_) => { + panic!("expected a normal response rather than a status error") + } + } + } } -- libgit2 1.7.2