1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
use std::{fmt, str::from_utf8_unchecked};

use nom::{
    bytes::{self, complete::take_while},
    character::{complete::digit1, is_alphanumeric},
    combinator::opt,
    error::{Error, ErrorKind},
    sequence::preceded,
    Err, IResult,
};

pub fn compare_no_case(left: &[u8], right: &[u8]) -> bool {
    if left.len() != right.len() {
        return false;
    }

    left.iter().zip(right).all(|(a, b)| match (*a, *b) {
        (0..=64, 0..=64) | (91..=96, 91..=96) | (123..=255, 123..=255) => a == b,
        (65..=90, 65..=90) | (97..=122, 97..=122) | (65..=90, 97..=122) | (97..=122, 65..=90) => {
            *a | 0b00_10_00_00 == *b | 0b00_10_00_00
        }
        _ => false,
    })
}

#[derive(PartialEq, Eq, Debug, Clone)]
pub enum Method {
    Get,
    Post,
    Head,
    Options,
    Put,
    Delete,
    Trace,
    Connect,
    Custom(String),
}

impl Method {
    pub fn new(s: &[u8]) -> Method {
        if compare_no_case(s, b"GET") {
            Method::Get
        } else if compare_no_case(s, b"POST") {
            Method::Post
        } else if compare_no_case(s, b"HEAD") {
            Method::Head
        } else if compare_no_case(s, b"OPTIONS") {
            Method::Options
        } else if compare_no_case(s, b"PUT") {
            Method::Put
        } else if compare_no_case(s, b"DELETE") {
            Method::Delete
        } else if compare_no_case(s, b"TRACE") {
            Method::Trace
        } else if compare_no_case(s, b"CONNECT") {
            Method::Connect
        } else {
            Method::Custom(String::from(unsafe { from_utf8_unchecked(s) }))
        }
    }
}

impl fmt::Display for Method {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            Method::Get => write!(f, "GET"),
            Method::Post => write!(f, "POST"),
            Method::Head => write!(f, "HEAD"),
            Method::Options => write!(f, "OPTIONS"),
            Method::Put => write!(f, "PUT"),
            Method::Delete => write!(f, "DELETE"),
            Method::Trace => write!(f, "TRACE"),
            Method::Connect => write!(f, "CONNECT"),
            Method::Custom(s) => write!(f, "{s}"),
        }
    }
}

#[cfg(feature = "tolerant-http1-parser")]
fn is_hostname_char(i: u8) -> bool {
    is_alphanumeric(i) ||
  // the domain name should not start with a hyphen or dot
  // but is it important here, since we will match this to
  // the list of accepted clusters?
  // BTW each label between dots has a max of 63 chars,
  // and the whole domain should not be larger than 253 chars
  //
  // this tolerant parser also allows underscore, which is wrong
  // in domain names but accepted by some proxies and web servers
  // see https://github.com/sozu-proxy/sozu/issues/480
  b"-._".contains(&i)
}

#[cfg(not(feature = "tolerant-http1-parser"))]
fn is_hostname_char(i: u8) -> bool {
    is_alphanumeric(i) ||
  // the domain name should not start with a hyphen or dot
  // but is it important here, since we will match this to
  // the list of accepted clusters?
  // BTW each label between dots has a max of 63 chars,
  // and the whole domain should not be larger than 253 chars
  b"-.".contains(&i)
}

// FIXME: convert port to u16 here
#[allow(clippy::type_complexity)]
pub fn hostname_and_port(i: &[u8]) -> IResult<&[u8], (&[u8], Option<&[u8]>)> {
    let (i, host) = take_while(is_hostname_char)(i)?;
    let (i, port) = opt(preceded(bytes::complete::tag(":"), digit1))(i)?;

    if !i.is_empty() {
        return Err(Err::Error(Error::new(i, ErrorKind::Eof)));
    }
    Ok((i, (host, port)))
}