sark_core/http/codec/decode/
request_line.rs1use std::ops::Range;
2use std::{error, fmt};
3
4const ALL_METHODS: u8 = (1 << 7) - 1;
5const SPACE_WORD: u64 = u64::from_le_bytes(*b" ");
6const CR_WORD: u64 = u64::from_le_bytes(*b"\r\r\r\r\r\r\r\r");
7const BYTE_LO: u64 = 0x0101_0101_0101_0101;
8const BYTE_HI: u64 = 0x8080_8080_8080_8080;
9const VERSION_BASE: u64 = u64::from_ne_bytes(*b"HTTP/1.0");
10const VERSION_MASK: u64 = u64::from_ne_bytes(*b"\xff\xff\xff\xff\xff\xff\xff\xfe");
11
12#[derive(Clone, Copy, Debug, PartialEq, Eq)]
13#[repr(u8)]
14pub enum MethodKey {
15 Get,
16 Post,
17 Put,
18 Patch,
19 Delete,
20 Head,
21 Options,
22}
23
24#[derive(Clone, Copy, Debug, PartialEq, Eq)]
25pub struct RequestLineError;
26
27impl fmt::Display for RequestLineError {
28 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
29 formatter.write_str("malformed HTTP request line")
30 }
31}
32
33impl error::Error for RequestLineError {}
34
35impl MethodKey {
36 #[doc(hidden)]
37 pub const fn bit(self) -> u8 {
38 1 << self as u8
39 }
40}
41
42#[derive(Clone, Copy, Debug)]
43pub struct RequestLine<'a> {
44 pub method: &'a [u8],
45 pub target: &'a [u8],
46 pub version: &'a [u8],
47 pub headers_start: usize,
48}
49
50impl RequestLine<'_> {
51 pub fn parse(buf: &[u8]) -> Result<Option<RequestLine<'_>>, RequestLineError> {
52 let mut method = None;
53 Self::parse_for::<ALL_METHODS>(buf, &mut method)
54 }
55
56 #[doc(hidden)]
57 pub fn parse_for<'a, const METHODS: u8>(
58 buf: &'a [u8],
59 method_slot: &mut Option<MethodKey>,
60 ) -> Result<Option<RequestLine<'a>>, RequestLineError> {
61 let parsed = 'parse: {
62 let (method_end, method_key) = match known_method::<METHODS>(buf) {
63 Some(known) => (known.0, Some(known.1)),
64 None => {
65 let Some(end) = buf.iter().position(|&byte| byte == b' ' || byte == b'\r')
66 else {
67 break 'parse None;
68 };
69 if buf[end] == b'\r' {
70 break 'parse None;
71 }
72 (end, None)
73 }
74 };
75 if method_end == 0 {
76 break 'parse None;
77 }
78
79 let target_start = method_end + 1;
80 let Some(target_bytes) = buf.get(target_start..) else {
81 break 'parse None;
82 };
83 let target_len = if target_bytes.get(1) == Some(&b' ') {
84 if target_bytes[0] == b'\r' {
85 break 'parse None;
86 }
87 1
88 } else if let Some(word) = target_bytes.first_chunk::<8>() {
89 let word = u64::from_le_bytes(*word);
90 let space_x = word ^ SPACE_WORD;
91 let spaces = space_x.wrapping_sub(BYTE_LO) & !space_x & BYTE_HI;
92 let cr_x = word ^ CR_WORD;
93 let crs = cr_x.wrapping_sub(BYTE_LO) & !cr_x & BYTE_HI;
94 let separators = spaces | crs;
95 if separators != 0 {
96 let end = separators.trailing_zeros() as usize / 8;
97 if target_bytes[end] == b'\r' {
98 break 'parse None;
99 }
100 end
101 } else {
102 let Some(rest) = memchr::memchr2(b' ', b'\r', &target_bytes[8..]) else {
103 break 'parse None;
104 };
105 if target_bytes[rest + 8] == b'\r' {
106 break 'parse None;
107 }
108 rest + 8
109 }
110 } else {
111 let Some(end) = target_bytes
112 .iter()
113 .position(|&byte| byte == b' ' || byte == b'\r')
114 else {
115 break 'parse None;
116 };
117 if target_bytes[end] == b'\r' {
118 break 'parse None;
119 }
120 end
121 };
122 if target_len == 0 {
123 break 'parse None;
124 }
125
126 let version_start = target_start + target_len + 1;
127 let Some(suffix) = buf.get(version_start..version_start + 10) else {
128 break 'parse None;
129 };
130 let Some(version) = suffix.first_chunk::<8>() else {
131 break 'parse None;
132 };
133 let version_word = u64::from_ne_bytes(*version);
134 if ((version_word ^ VERSION_BASE) & VERSION_MASK) != 0
135 || suffix[8] != b'\r'
136 || suffix[9] != b'\n'
137 {
138 break 'parse None;
139 }
140
141 *method_slot = method_key;
142 Some(RequestLine {
143 method: &buf[..method_end],
144 target: &buf[target_start..target_start + target_len],
145 version,
146 headers_start: version_start + 10,
147 })
148 };
149
150 match parsed {
151 Some(request) => Ok(Some(request)),
152 None => {
153 *method_slot = None;
154 reject(buf)
155 }
156 }
157 }
158}
159
160fn known_method<const METHODS: u8>(buf: &[u8]) -> Option<(usize, MethodKey)> {
161 if METHODS & MethodKey::Get.bit() != 0 && buf.starts_with(b"GET ") {
162 Some((3, MethodKey::Get))
163 } else if METHODS & MethodKey::Post.bit() != 0 && buf.starts_with(b"POST ") {
164 Some((4, MethodKey::Post))
165 } else if METHODS & MethodKey::Put.bit() != 0 && buf.starts_with(b"PUT ") {
166 Some((3, MethodKey::Put))
167 } else if METHODS & MethodKey::Patch.bit() != 0 && buf.starts_with(b"PATCH ") {
168 Some((5, MethodKey::Patch))
169 } else if METHODS & MethodKey::Delete.bit() != 0 && buf.starts_with(b"DELETE ") {
170 Some((6, MethodKey::Delete))
171 } else if METHODS & MethodKey::Head.bit() != 0 && buf.starts_with(b"HEAD ") {
172 Some((4, MethodKey::Head))
173 } else if METHODS & MethodKey::Options.bit() != 0 && buf.starts_with(b"OPTIONS ") {
174 Some((7, MethodKey::Options))
175 } else {
176 None
177 }
178}
179
180fn reject<T>(buf: &[u8]) -> Result<Option<T>, RequestLineError> {
181 match memchr::memchr(b'\r', buf) {
182 None => Ok(None),
183 Some(end) if end + 1 == buf.len() => Ok(None),
184 Some(_) => Err(RequestLineError),
185 }
186}
187
188pub fn request_head_end(bytes: &[u8]) -> Option<Range<usize>> {
189 memchr::memmem::find(bytes, b"\r\n\r\n").map(|s| s..s + 4)
190}