safe_http_parser/parse/
request.rs

1use super::{headers::parse_header_map, version::version_from_bytes};
2use crate::{
3    ascii::str_from_ascii, chunks::ChunksSlice, error::Error, windows::IteratorExt,
4    ParseRequestError, Parsed,
5};
6use safe_http::{Method, RequestHead, RequestLine, Version};
7use safe_uri::{Scheme, Uri, UriRef};
8use shared_bytes::SharedBytes;
9
10pub fn parse_request_head(
11    chunks: &[SharedBytes],
12) -> Result<Parsed<RequestHead>, ParseRequestError> {
13    let slice = ChunksSlice::new(chunks);
14    let (line, remainder) = parse_request_line(slice).map_err(ParseRequestError)?;
15    let (headers, remainder) = parse_header_map(remainder).map_err(ParseRequestError)?;
16    Ok(Parsed {
17        value: RequestHead {
18            line,
19            headers,
20            ..Default::default()
21        },
22        remainder: remainder.to_continuous_shared(),
23    })
24}
25
26fn parse_request_line(slice: ChunksSlice) -> Result<(RequestLine, ChunksSlice), Error> {
27    let (method, remainder) = parse_method(slice)?;
28    let (uri, remainder) = parse_request_target(remainder)?;
29    let (version, remainder) = parse_version(remainder)?;
30    let line = RequestLine {
31        method,
32        uri,
33        version,
34        ..Default::default()
35    };
36    Ok((line, remainder))
37}
38
39fn parse_method(slice: ChunksSlice) -> Result<(Method, ChunksSlice), Error> {
40    let method_end = slice
41        .bytes_indexed()
42        .find_map(|(index, byte)| (byte == b' ').then(|| index))
43        .ok_or(Error::Message("missing method separator ' '"))?;
44    let method = method_from_slice(slice.index(..method_end))?;
45    let remainder_start = slice.next_chunks_index(method_end).ok_or(Error::Message(
46        "no request target (URI) after separator ' '",
47    ))?;
48    let remainder = slice.index(remainder_start..);
49    Ok((method, remainder))
50}
51
52fn method_from_slice(slice: ChunksSlice) -> Result<Method, Error> {
53    let string = str_from_ascii(slice)?;
54    string.try_into().map_err(Error::Method)
55}
56
57fn parse_request_target(slice: ChunksSlice) -> Result<(Uri, ChunksSlice), Error> {
58    let uri_end = slice
59        .bytes_indexed()
60        .find_map(|(index, byte)| (byte == b' ').then(|| index))
61        .ok_or(Error::Message("missing uri separator ' '"))?;
62    let uri = uri_from_slice(slice.index(..uri_end))?;
63    let remainder_start = slice
64        .next_chunks_index(uri_end)
65        .ok_or(Error::Message("no HTTP version after separator ' '"))?;
66    let remainder = slice.index(remainder_start..);
67    Ok((uri, remainder))
68}
69
70fn uri_from_slice(slice: ChunksSlice) -> Result<Uri, Error> {
71    let string = str_from_ascii(slice)?;
72    let uri_ref = UriRef::parse(string).map_err(Error::Uri)?;
73    Ok(uri_ref.into_uri_with_default_scheme(|| Scheme::HTTPS))
74}
75
76fn parse_version(slice: ChunksSlice) -> Result<(Version, ChunksSlice), Error> {
77    let [version_end, line_end] = slice
78        .bytes_indexed()
79        .windows::<2>()
80        .find_map(|[(index0, byte0), (index1, byte1)]| {
81            ([byte0, byte1] == *b"\r\n").then(|| [index0, index1])
82        })
83        .ok_or(Error::Message("missing version separator (CRLF)"))?;
84    let version = version_from_bytes(&slice.index(..version_end).to_continuous_cow())?;
85    let remainder_start = slice
86        .next_chunks_index(line_end)
87        .ok_or(Error::Message("no bytes following the request line"))?;
88    let remainder = slice.index(remainder_start..);
89    Ok((version, remainder))
90}
91
92#[cfg(test)]
93mod tests {
94    use super::*;
95    use safe_http::{HeaderMap, HeaderName, HeaderValue, Version};
96    use safe_uri::Path;
97    use tap::Tap;
98
99    #[test]
100    fn good_case() {
101        let request = "PUT /example HTTP/2.0\r\n\
102            content-type: text/plain\r\n\
103            content-length: 5\r\n\r\n\
104            hello";
105        let bytes = SharedBytes::from(request);
106        let parsed_request_head = parse_request_head(&[bytes]).unwrap();
107        let expected_head = RequestHead {
108            line: RequestLine {
109                method: Method::PUT,
110                uri: Uri::new().tap_mut(|u| {
111                    u.scheme = Scheme::HTTPS;
112                    u.resource.path = Path::from_static("/example");
113                }),
114                version: Version::HTTP_2_0,
115                ..Default::default()
116            },
117            headers: HeaderMap::from([
118                (
119                    HeaderName::CONTENT_TYPE,
120                    HeaderValue::from_static(b"text/plain"),
121                ),
122                (HeaderName::CONTENT_LENGTH, HeaderValue::from_static(b"5")),
123            ]),
124            ..Default::default()
125        };
126        assert_eq!(parsed_request_head.value, expected_head);
127        assert_eq!(parsed_request_head.remainder, "hello");
128    }
129}