Skip to main content

sark_core/http/codec/decode/
request_line.rs

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