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");
}
}