sark-http 0.28.6

HTTP wire types, semantics, codecs, and ownership invariants for Sark
Documentation
use sark_http::{
    h1::codec::decode::response::{Decoder, Progress},
    payload::response::{Method, status::Code},
};

#[test]
fn partial_inputs() {
    for raw in [
        b"".as_slice(),
        b"H",
        b"hello world",
        b"HTTP/1.1 200 OK\r\nContent-Length: 5",
    ] {
        assert!(
            matches!(Decoder::new(Method::Other).decode(raw), Ok(Progress::More)),
            "completed {raw:?}"
        );
    }
}

#[test]
fn body_presence() {
    let fixed = b"HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n";
    {
        let reply =
            match (Decoder::new(Method::Other).decode(fixed)).expect("response must decode") {
                Progress::Complete(value) => Some(value),
                Progress::More => None,
            }
            .expect("test input must be complete");
        assert!(reply.body().is_empty());
    };

    let eof = b"HTTP/1.1 200 OK\r\n\r\n";
    assert!(matches!(
        Decoder::new(Method::Other).decode(eof),
        Ok(Progress::More)
    ));
}

#[test]
fn incomplete_bodies() {
    for raw in [
        b"HTTP/1.1 200 OK\r\nContent-Length: 100\r\n\r\nhello".as_slice(),
        b"HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n5\r\nhello\r\n0",
        b"HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n5\r\nhello\r\n0\r\n",
    ] {
        assert!(
            matches!(Decoder::new(Method::Other).decode(raw), Ok(Progress::More)),
            "completed {raw:?}"
        );
    }
}

#[test]
fn invalid_lengths() {
    for raw in [
        b"HTTP/1.1 200 OK\r\nContent-Length: abc\r\n\r\nhello".as_slice(),
        b"HTTP/1.1 200 OK\r\nContent-Length: -5\r\n\r\nhello",
        b"HTTP/1.1 200 OK\r\nContent-Length: 99999999999999999999999999999\r\n\r\nhello",
        b"HTTP/1.1 200 OK\r\nContent-Length: 5\r\nContent-Length: 10\r\n\r\nhello",
        b"HTTP/1.1 200 OK\r\nContent-Length: 100\r\nTransfer-Encoding: chunked\r\n\r\n5\r\nhello\r\n0\r\n\r\n",
    ] {
        assert!(Decoder::new(Method::Other).decode(raw).is_err());
    }
}

#[test]
fn invalid_chunks() {
    for raw in [
        b"HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\nGGG\r\nhello\r\n0\r\n\r\n".as_slice(),
        b"HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\nFFFFFFFFFFFFFFFF\r\nhello\r\n0\r\n\r\n",
        b"HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n  5  \r\nhello\r\n0\r\n\r\n",
    ] {
        assert!(Decoder::new(Method::Other).decode(raw).is_err());
    }
}

#[test]
fn invalid_status() {
    for raw in [
        b"HTTP/1.1  OK\r\n\r\n".as_slice(),
        b"HTTP/1.1 999 Custom\r\n\r\n",
        b"HTTP/1.1 1000 Custom\r\n\r\n",
        b"HTTP/1.1 200\0OK\r\n\r\n",
    ] {
        assert!(Decoder::new(Method::Other).decode(raw).is_err());
    }
}

#[test]
fn invalid_fields() {
    for raw in [
        b"HTTP/1.1 200 OK\r\n: value\r\n\r\n".as_slice(),
        b"HTTP/1.1 200 OK\r\nX-Binary: \0\x01\x02\xff\r\nContent-Length: 0\r\n\r\n",
        b"HTTP/1.1 200 OK\r\nX-Nu\0ll: value\r\nContent-Length: 0\r\n\r\n",
        b"HTTP/1.1 200 OK\r\nX-Test: val\0ue\r\nContent-Length: 0\r\n\r\n",
        b"HTTP/1.1 200 OK\r\nX-Long: first\r\n second\r\nContent-Length: 0\r\n\r\n",
    ] {
        assert!(Decoder::new(Method::Other).decode(raw).is_err());
    }
}

#[test]
fn empty_value() {
    let raw = b"HTTP/1.1 200 OK\r\nX-Empty:\r\nContent-Length: 0\r\n\r\n";
    let reply = match (Decoder::new(Method::Other).decode(raw)).expect("response must decode") {
        Progress::Complete(value) => Some(value),
        Progress::More => None,
    }
    .expect("test input must be complete");
    let value = reply.headers().get("x-empty").expect("x-empty field");
    assert_eq!(value.as_bytes(), b"");
}

#[test]
fn long_value() {
    let value = "x".repeat(10240);
    let raw = format!("HTTP/1.1 200 OK\r\nX-Long: {value}\r\nContent-Length: 0\r\n\r\n");
    let reply =
        match (Decoder::new(Method::Other).decode(raw.as_bytes())).expect("response must decode") {
            Progress::Complete(value) => Some(value),
            Progress::More => None,
        }
        .expect("test input must be complete");
    let value = reply.headers().get("x-long").expect("x-long field");
    assert_eq!(value.as_bytes().len(), 10240);
}

#[test]
fn header_limit() {
    let mut raw = String::from("HTTP/1.1 200 OK\r\n");
    for index in 0..99 {
        raw.push_str(&format!("X-Header-{index}: value\r\n"));
    }
    raw.push_str("Content-Length: 0\r\n\r\n");
    {
        let reply = match (Decoder::new(Method::Other).decode(raw.as_bytes()))
            .expect("response must decode")
        {
            Progress::Complete(value) => Some(value),
            Progress::More => None,
        }
        .expect("test input must be complete");
        assert_eq!(reply.headers().len(), 100);
    };

    let mut raw = String::from("HTTP/1.1 200 OK\r\n");
    for index in 0..101 {
        raw.push_str(&format!("X-Header-{index}: value\r\n"));
    }
    raw.push_str("\r\n");
    assert!(Decoder::new(Method::Other).decode(raw.as_bytes()).is_err());
}

#[test]
fn opaque_reason() {
    let raw = "HTTP/1.1 200 OK™\r\nContent-Length: 0\r\n\r\n".as_bytes();
    let reply = match (Decoder::new(Method::Other).decode(raw)).expect("response must decode") {
        Progress::Complete(value) => Some(value),
        Progress::More => None,
    }
    .expect("test input must be complete");
    assert!(reply.status() == Code::OK);
}

#[test]
fn pipelined_tail() {
    let raw = b"HTTP/1.1 200 OK\r\nContent-Length: 5\r\n\r\nhelloHTTP/1.1 200 OK\r\n";
    let reply = match (Decoder::new(Method::Other).decode(raw)).expect("response must decode") {
        Progress::Complete(value) => Some(value),
        Progress::More => None,
    }
    .expect("test input must be complete");
    assert_eq!(reply.body().as_slice(), b"hello");
}

#[test]
fn valid_lengths() {
    for name in ["Content-Length", "CONTENT-LENGTH"] {
        for value in ["0005", " 5 "] {
            let raw = format!("HTTP/1.1 200 OK\r\n{name}: {value}\r\n\r\nhello");
            let reply = match (Decoder::new(Method::Other).decode(raw.as_bytes()))
                .expect("response must decode")
            {
                Progress::Complete(value) => Some(value),
                Progress::More => None,
            }
            .expect("test input must be complete");
            assert_eq!(reply.body().as_slice(), b"hello");
        }
    }
}

#[test]
fn incomplete_eof() {
    let raw = b"HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n5\r\nhel";
    let reply = Decoder::new(Method::Other).finish(raw);
    assert!(reply.is_err());
}

#[test]
fn chunk_extensions() {
    let raw = b"HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n5;ext1=val1;ext2=val2\r\nhello\r\n0\r\n\r\n";
    let reply = match (Decoder::new(Method::Other).decode(raw)).expect("response must decode") {
        Progress::Complete(value) => Some(value),
        Progress::More => None,
    }
    .expect("test input must be complete");
    assert_eq!(reply.body().as_slice(), b"hello");
}

#[test]
fn chunk_case() {
    let raw =
        b"HTTP/1.1 200 OK\r\nTransfer-Encoding: CHUNKED\r\n\r\nf\r\n012345678901234\r\n0\r\n\r\n";
    let reply = match (Decoder::new(Method::Other).decode(raw)).expect("response must decode") {
        Progress::Complete(value) => Some(value),
        Progress::More => None,
    }
    .expect("test input must be complete");
    assert_eq!(reply.body().as_slice(), b"012345678901234");
}