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
Diff
src/static_files.rs | 28 ++---
tests/static_files.rs | 287 +++++++++++++++++++++++++++++++++++++++++++++++++++-
2 files changed, 300 insertions(+), 15 deletions(-)
@@ -501,23 +501,21 @@ fn bytes_range(range: Option<Range>, 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) => {
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)) => {
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 {
@@ -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();
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();
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();
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")
}
}
}
}