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