1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
use super::{headers::parse_header_map, version::version_from_bytes};
use crate::{
    ascii::str_from_ascii, chunks::ChunksSlice, error::Error, windows::IteratorExt,
    ParseResponseError, Parsed,
};
use safe_http::{ResponseHead, ResponseLine, StatusCode, Version};
use shared_bytes::SharedBytes;

pub fn parse_response_head(
    chunks: &[SharedBytes],
) -> Result<Parsed<ResponseHead>, ParseResponseError> {
    let slice = ChunksSlice::new(chunks);
    let (line, remainder) = parse_response_line(slice).map_err(ParseResponseError)?;
    let (headers, remainder) = parse_header_map(remainder).map_err(ParseResponseError)?;
    Ok(Parsed {
        value: ResponseHead {
            line,
            headers,
            ..Default::default()
        },
        remainder: remainder.to_continuous_shared(),
    })
}

fn parse_response_line(slice: ChunksSlice) -> Result<(ResponseLine, ChunksSlice), Error> {
    let (version, remainder) = parse_version(slice)?;
    let (status_code, remainder) = parse_status_code(remainder)?;
    let remainder = skip_reason_phrase(remainder)?;
    let line = ResponseLine {
        version,
        status_code,
        ..Default::default()
    };
    Ok((line, remainder))
}

fn parse_version(slice: ChunksSlice) -> Result<(Version, ChunksSlice), Error> {
    let version_end = slice
        .bytes_indexed()
        .find_map(|(index, byte)| (byte == b' ').then(|| index))
        .ok_or(Error::Message("missing version separator ' '"))?;
    let version = version_from_bytes(&slice.index(..version_end).to_continuous_cow())?;
    let remainder_start = slice
        .next_chunks_index(version_end)
        .ok_or(Error::Message("no bytes after response HTTP version"))?;
    let remainder = slice.index(remainder_start..);
    Ok((version, remainder))
}

fn parse_status_code(slice: ChunksSlice) -> Result<(StatusCode, ChunksSlice), Error> {
    let status_code_end = slice
        .bytes_indexed()
        .find_map(|(index, byte)| (byte == b' ').then(|| index))
        .ok_or(Error::Message("missing status code separator ' '"))?;
    let method = status_code_from_slice(slice.index(..status_code_end))?;
    let remainder_start = slice
        .next_chunks_index(status_code_end)
        .ok_or(Error::Message("no bytes after status code"))?;
    let remainder = slice.index(remainder_start..);
    Ok((method, remainder))
}

fn status_code_from_slice(slice: ChunksSlice) -> Result<StatusCode, Error> {
    let string = str_from_ascii(slice)?;
    let code: u16 = string.parse().map_err(Error::StatusCodeParse)?;
    StatusCode::try_from_u16(code).map_err(Error::StatusCode)
}

fn skip_reason_phrase(slice: ChunksSlice) -> Result<ChunksSlice, Error> {
    let line_end = slice
        .bytes_indexed()
        .windows::<2>()
        .find_map(|[(_index0, byte0), (index1, byte1)]| {
            ([byte0, byte1] == *b"\r\n").then(|| index1)
        })
        .ok_or(Error::Message("missing version separator (CRLF)"))?;
    let remainder_start = slice
        .next_chunks_index(line_end)
        .ok_or(Error::Message("no bytes following the request line"))?;
    let remainder = slice.index(remainder_start..);
    Ok(remainder)
}

#[cfg(test)]
mod tests {
    use super::*;
    use safe_http::{HeaderMap, HeaderName, HeaderValue, Version};

    #[test]
    fn good_case() {
        let response = "HTTP/1.1 200 OK \r\n\
            content-type: text/plain\r\n\
            content-length: 5\r\n\r\n\
            hello";
        let bytes = SharedBytes::from(response);
        let parsed_response_head = parse_response_head(&[bytes]).unwrap();
        let expected_head = ResponseHead {
            line: ResponseLine {
                version: Version::HTTP_1_1,
                status_code: StatusCode::OK,
                ..Default::default()
            },
            headers: HeaderMap::from([
                (
                    HeaderName::CONTENT_TYPE,
                    HeaderValue::from_static(b"text/plain"),
                ),
                (HeaderName::CONTENT_LENGTH, HeaderValue::from_static(b"5")),
            ]),
            ..Default::default()
        };
        assert_eq!(parsed_response_head.value, expected_head);
        assert_eq!(parsed_response_head.remainder, "hello");
    }
}