sozu_lib/protocol/kawa_h1/
parser.rs1use std::{
2 cmp::min,
3 fmt::{self, Write},
4 ops::Deref,
5 str::from_utf8_unchecked,
6};
7
8use nom::{
9 bytes::{self, complete::take_while},
10 character::{complete::digit1, is_alphanumeric},
11 combinator::opt,
12 error::{Error, ErrorKind},
13 sequence::preceded,
14 Err, IResult,
15};
16
17pub fn compare_no_case(left: &[u8], right: &[u8]) -> bool {
18 if left.len() != right.len() {
19 return false;
20 }
21
22 left.iter().zip(right).all(|(a, b)| match (*a, *b) {
23 (0..=64, 0..=64) | (91..=96, 91..=96) | (123..=255, 123..=255) => a == b,
24 (65..=90, 65..=90) | (97..=122, 97..=122) | (65..=90, 97..=122) | (97..=122, 65..=90) => {
25 *a | 0b00_10_00_00 == *b | 0b00_10_00_00
26 }
27 _ => false,
28 })
29}
30
31#[derive(PartialEq, Eq, Debug, Clone)]
32pub enum Method {
33 Get,
34 Post,
35 Head,
36 Options,
37 Put,
38 Delete,
39 Trace,
40 Connect,
41 Custom(String),
42}
43
44impl Method {
45 pub fn new(s: &[u8]) -> Method {
46 if compare_no_case(s, b"GET") {
47 Method::Get
48 } else if compare_no_case(s, b"POST") {
49 Method::Post
50 } else if compare_no_case(s, b"HEAD") {
51 Method::Head
52 } else if compare_no_case(s, b"OPTIONS") {
53 Method::Options
54 } else if compare_no_case(s, b"PUT") {
55 Method::Put
56 } else if compare_no_case(s, b"DELETE") {
57 Method::Delete
58 } else if compare_no_case(s, b"TRACE") {
59 Method::Trace
60 } else if compare_no_case(s, b"CONNECT") {
61 Method::Connect
62 } else {
63 Method::Custom(String::from(unsafe { from_utf8_unchecked(s) }))
64 }
65 }
66}
67
68impl AsRef<str> for Method {
69 fn as_ref(&self) -> &str {
70 match self {
71 Self::Get => "GET",
72 Self::Post => "POST",
73 Self::Head => "HEAD",
74 Self::Options => "OPTIONS",
75 Self::Put => "PUT",
76 Self::Delete => "DELETE",
77 Self::Trace => "TRACE",
78 Self::Connect => "CONNECT",
79 Self::Custom(custom) => custom,
80 }
81 }
82}
83
84impl Deref for Method {
85 type Target = str;
86
87 fn deref(&self) -> &Self::Target {
88 self.as_ref()
89 }
90}
91
92impl fmt::Display for Method {
93 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
94 write!(f, "{}", self.as_ref())
95 }
96}
97
98#[cfg(feature = "tolerant-http1-parser")]
99fn is_hostname_char(i: u8) -> bool {
100 is_alphanumeric(i) ||
101 b"-._".contains(&i)
111}
112
113#[cfg(not(feature = "tolerant-http1-parser"))]
114fn is_hostname_char(i: u8) -> bool {
115 is_alphanumeric(i) ||
116 b"-.".contains(&i)
122}
123
124#[allow(clippy::type_complexity)]
126pub fn hostname_and_port(i: &[u8]) -> IResult<&[u8], (&[u8], Option<&[u8]>)> {
127 let (i, host) = take_while(is_hostname_char)(i)?;
128 let (i, port) = opt(preceded(bytes::complete::tag(":"), digit1))(i)?;
129
130 if !i.is_empty() {
131 return Err(Err::Error(Error::new(i, ErrorKind::Eof)));
132 }
133 Ok((i, (host, port)))
134}
135
136pub fn view(buf: &[u8], size: usize, points: &[usize]) -> String {
137 let mut view = format!("{points:?} => ");
138 let mut end = 0;
139 for (i, point) in points.iter().enumerate() {
140 if *point > buf.len() {
141 break;
142 }
143 let start = if end + size < *point {
144 view.push_str("... ");
145 point - size
146 } else {
147 end
148 };
149 let stop = if i + 1 < points.len() {
150 min(buf.len(), points[i + 1])
151 } else {
152 buf.len()
153 };
154 end = if point + size > stop {
155 stop
156 } else {
157 point + size
158 };
159 for element in &buf[start..*point] {
160 let _ = view.write_fmt(format_args!("{element:02X} "));
161 }
162 view.push_str("| ");
163 for element in &buf[*point..end] {
164 let _ = view.write_fmt(format_args!("{element:02X} "));
165 }
166 }
167 if end < buf.len() {
168 view.push_str("...")
169 }
170 view
171}
172
173#[test]
174fn test_view_out_of_bound() {
175 println!(
176 "{}",
177 view(
178 &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
179 2,
180 &[5, 5, 8, 9, 9, 13, 80]
181 )
182 );
183}