safe_http_parser/parse/
request.rs1use 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}