Skip to main content

sark_core/http/codec/decode/
request_line.rs

1use 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}