sozu_lib/protocol/kawa_h1/
parser.rs1use std::{
9 cmp::min,
10 fmt::{self, Write},
11 ops::Deref,
12};
13
14use nom::{
15 AsChar, Err, IResult, Parser,
16 bytes::{self, complete::take_while},
17 character::complete::digit1,
18 combinator::opt,
19 error::{Error, ErrorKind},
20 sequence::preceded,
21};
22
23pub fn compare_no_case(left: &[u8], right: &[u8]) -> bool {
24 if left.len() != right.len() {
25 return false;
26 }
27
28 left.iter().zip(right).all(|(a, b)| match (*a, *b) {
29 (0..=64, 0..=64) | (91..=96, 91..=96) | (123..=255, 123..=255) => a == b,
30 (65..=90, 65..=90) | (97..=122, 97..=122) | (65..=90, 97..=122) | (97..=122, 65..=90) => {
31 *a | 0b00_10_00_00 == *b | 0b00_10_00_00
32 }
33 _ => false,
34 })
35}
36
37#[derive(PartialEq, Eq, Clone)]
38pub enum Method {
39 Get,
40 Post,
41 Head,
42 Options,
43 Put,
44 Delete,
45 Trace,
46 Connect,
47 Custom(String),
48}
49
50impl fmt::Debug for Method {
51 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
52 match self {
53 Self::Get => f.write_str("Get"),
54 Self::Post => f.write_str("Post"),
55 Self::Head => f.write_str("Head"),
56 Self::Options => f.write_str("Options"),
57 Self::Put => f.write_str("Put"),
58 Self::Delete => f.write_str("Delete"),
59 Self::Trace => f.write_str("Trace"),
60 Self::Connect => f.write_str("Connect"),
61 Self::Custom(custom) => write!(f, "Custom(bytes={})", custom.len()),
62 }
63 }
64}
65
66impl Method {
67 pub fn new(s: &[u8]) -> Method {
68 if compare_no_case(s, b"GET") {
69 Method::Get
70 } else if compare_no_case(s, b"POST") {
71 Method::Post
72 } else if compare_no_case(s, b"HEAD") {
73 Method::Head
74 } else if compare_no_case(s, b"OPTIONS") {
75 Method::Options
76 } else if compare_no_case(s, b"PUT") {
77 Method::Put
78 } else if compare_no_case(s, b"DELETE") {
79 Method::Delete
80 } else if compare_no_case(s, b"TRACE") {
81 Method::Trace
82 } else if compare_no_case(s, b"CONNECT") {
83 Method::Connect
84 } else {
85 Method::Custom(String::from_utf8_lossy(s).into_owned())
86 }
87 }
88}
89
90impl AsRef<str> for Method {
91 fn as_ref(&self) -> &str {
92 match self {
93 Self::Get => "GET",
94 Self::Post => "POST",
95 Self::Head => "HEAD",
96 Self::Options => "OPTIONS",
97 Self::Put => "PUT",
98 Self::Delete => "DELETE",
99 Self::Trace => "TRACE",
100 Self::Connect => "CONNECT",
101 Self::Custom(custom) => custom,
102 }
103 }
104}
105
106impl Deref for Method {
107 type Target = str;
108
109 fn deref(&self) -> &Self::Target {
110 self.as_ref()
111 }
112}
113
114impl fmt::Display for Method {
115 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
116 write!(f, "{}", self.as_ref())
117 }
118}
119
120#[cfg(feature = "tolerant-http1-parser")]
121fn is_hostname_char(i: u8) -> bool {
122 i.is_alphanum() ||
123 b"-._".contains(&i)
133}
134
135#[cfg(not(feature = "tolerant-http1-parser"))]
136fn is_hostname_char(i: u8) -> bool {
137 i.is_alphanum() ||
138 b"-.".contains(&i)
144}
145
146pub fn hostname_and_port(i: &[u8]) -> IResult<&[u8], (&[u8], Option<u16>)> {
147 let (i, host) = take_while(is_hostname_char)(i)?;
148 if host.is_empty() {
149 return Err(Err::Error(Error::new(i, ErrorKind::Alpha)));
150 }
151 let (i, port_bytes) = opt(preceded(bytes::complete::tag(":"), digit1)).parse(i)?;
152 let port = match port_bytes {
153 Some(bytes) => match std::str::from_utf8(bytes).unwrap().parse::<u16>() {
158 Ok(p) if p != 0 => Some(p),
159 _ => return Err(Err::Error(Error::new(bytes, ErrorKind::Digit))),
160 },
161 None => None,
162 };
163
164 if !i.is_empty() {
165 return Err(Err::Error(Error::new(i, ErrorKind::Eof)));
166 }
167 Ok((i, (host, port)))
168}
169
170pub fn view(buf: &[u8], size: usize, points: &[usize]) -> String {
171 let mut view = format!("{points:?} => ");
172 let mut end = 0;
173 for (i, point) in points.iter().enumerate() {
174 if *point > buf.len() {
175 break;
176 }
177 let start = if end + size < *point {
178 view.push_str("... ");
179 point - size
180 } else {
181 end
182 };
183 let stop = if i + 1 < points.len() {
184 min(buf.len(), points[i + 1])
185 } else {
186 buf.len()
187 };
188 end = if point + size > stop {
189 stop
190 } else {
191 point + size
192 };
193 for element in &buf[start..*point] {
194 let _ = view.write_fmt(format_args!("{element:02X} "));
195 }
196 view.push_str("| ");
197 for element in &buf[*point..end] {
198 let _ = view.write_fmt(format_args!("{element:02X} "));
199 }
200 }
201 if end < buf.len() {
202 view.push_str("...")
203 }
204 view
205}
206
207#[test]
208fn test_view_out_of_bound() {
209 println!(
210 "{}",
211 view(
212 &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
213 2,
214 &[5, 5, 8, 9, 9, 13, 80]
215 )
216 );
217}
218
219#[test]
220fn custom_method_does_not_assume_utf8() {
221 let method = Method::new(b"\xFFBAD");
222
223 assert_eq!(method, Method::Custom("\u{FFFD}BAD".to_owned()));
224}
225
226#[test]
227fn custom_method_debug_is_bounded() {
228 const METHOD_SECRET: &str = "CUSTOM_METHOD_DEBUG_SECRET_SENTINEL";
229
230 let custom = format!("{METHOD_SECRET}{}", "x".repeat(4096));
231 let custom_len = custom.len();
232 let output = format!("{:?}", Method::Custom(custom));
233
234 assert!(
235 !output.contains(METHOD_SECRET),
236 "custom Method Debug leaked its payload: {output}"
237 );
238 assert!(
239 output.contains(&format!("bytes={custom_len}")),
240 "custom Method Debug omitted its byte length: {output}"
241 );
242 assert!(
243 output.len() <= 128,
244 "custom Method Debug is not bounded: {} bytes",
245 output.len()
246 );
247}
248
249#[test]
250fn hostname_and_port_rejects_empty_host() {
251 assert!(hostname_and_port(b":80").is_err());
252}
253
254#[test]
255fn hostname_and_port_rejects_out_of_range_port() {
256 assert!(hostname_and_port(b"example.com:65536").is_err());
257}
258
259#[test]
260fn hostname_and_port_rejects_port_zero() {
261 assert!(hostname_and_port(b"example.com:0").is_err());
263}
264
265#[test]
266fn hostname_and_port_accepts_u16_port() {
267 let (remaining, (host, port)) = hostname_and_port(b"example.com:65535").unwrap();
268
269 assert!(remaining.is_empty());
270 assert_eq!(host, b"example.com");
271 assert_eq!(port, Some(65535));
272}
273
274#[test]
275fn hostname_and_port_returns_no_port_when_absent() {
276 let (remaining, (host, port)) = hostname_and_port(b"example.com").unwrap();
277
278 assert!(remaining.is_empty());
279 assert_eq!(host, b"example.com");
280 assert_eq!(port, None);
281}