index : static-web-server.git

ascending towards madness

author Jose Quintana <joseluisquintana20@gmail.com> 2021-08-09 20:18:49.0 +00:00:00
committer Jose Quintana <joseluisquintana20@gmail.com> 2021-08-09 20:18:49.0 +00:00:00
commit
2459ec418b6bc89c8e98904805badee4948ec80d [patch]
tree
19846ebf298fc8d91bc520e3b21b11e693912413
parent
fa130fa7a768a1c8f75bea17b8fdd712d78755a5
download
2459ec418b6bc89c8e98904805badee4948ec80d.tar.gz

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(-)

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<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) => {
                    // 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")
            }
        }
    }
}