sozu_lib/protocol/kawa_h1/
parser.rs

1use 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  // the domain name should not start with a hyphen or dot
102  // but is it important here, since we will match this to
103  // the list of accepted clusters?
104  // BTW each label between dots has a max of 63 chars,
105  // and the whole domain should not be larger than 253 chars
106  //
107  // this tolerant parser also allows underscore, which is wrong
108  // in domain names but accepted by some proxies and web servers
109  // see https://github.com/sozu-proxy/sozu/issues/480
110  b"-._".contains(&i)
111}
112
113#[cfg(not(feature = "tolerant-http1-parser"))]
114fn is_hostname_char(i: u8) -> bool {
115    is_alphanumeric(i) ||
116  // the domain name should not start with a hyphen or dot
117  // but is it important here, since we will match this to
118  // the list of accepted clusters?
119  // BTW each label between dots has a max of 63 chars,
120  // and the whole domain should not be larger than 253 chars
121  b"-.".contains(&i)
122}
123
124// FIXME: convert port to u16 here
125#[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}