1 use winnow::combinator::seq;
2 use winnow::{
3     ascii::line_ending, combinator::repeat, prelude::*, stream::Partial, token::take_while,
4 };
5 
6 pub type Stream<'i> = Partial<&'i [u8]>;
7 
8 #[rustfmt::skip]
9 #[derive(Debug)]
10 #[allow(dead_code)]
11 pub struct Request<'a> {
12   method:  &'a [u8],
13   uri:     &'a [u8],
14   version: &'a [u8],
15 }
16 
17 #[derive(Debug)]
18 #[allow(dead_code)]
19 pub struct Header<'a> {
20     name: &'a [u8],
21     value: Vec<&'a [u8]>,
22 }
23 
parse(data: &[u8]) -> Option<Vec<(Request<'_>, Vec<Header<'_>>)>>24 pub fn parse(data: &[u8]) -> Option<Vec<(Request<'_>, Vec<Header<'_>>)>> {
25     let mut buf = Partial::new(data);
26     let mut v = Vec::new();
27     loop {
28         match request(&mut buf) {
29             Ok(r) => {
30                 v.push(r);
31 
32                 if buf.is_empty() {
33                     //println!("{}", i);
34                     break;
35                 }
36             }
37             Err(e) => {
38                 println!("error: {:?}", e);
39                 return None;
40             }
41         }
42     }
43 
44     Some(v)
45 }
46 
request<'s>(input: &mut Stream<'s>) -> PResult<(Request<'s>, Vec<Header<'s>>)>47 fn request<'s>(input: &mut Stream<'s>) -> PResult<(Request<'s>, Vec<Header<'s>>)> {
48     let req = request_line(input)?;
49     let h = repeat(1.., message_header).parse_next(input)?;
50     let _ = line_ending.parse_next(input)?;
51 
52     Ok((req, h))
53 }
54 
request_line<'s>(input: &mut Stream<'s>) -> PResult<Request<'s>>55 fn request_line<'s>(input: &mut Stream<'s>) -> PResult<Request<'s>> {
56     seq!( Request {
57         method: take_while(1.., is_token),
58         _: take_while(1.., is_space),
59         uri: take_while(1.., is_not_space),
60         _: take_while(1.., is_space),
61         version: http_version,
62         _: line_ending,
63     })
64     .parse_next(input)
65 }
66 
http_version<'s>(input: &mut Stream<'s>) -> PResult<&'s [u8]>67 fn http_version<'s>(input: &mut Stream<'s>) -> PResult<&'s [u8]> {
68     let _ = "HTTP/".parse_next(input)?;
69     let version = take_while(1.., is_version).parse_next(input)?;
70 
71     Ok(version)
72 }
73 
message_header_value<'s>(input: &mut Stream<'s>) -> PResult<&'s [u8]>74 fn message_header_value<'s>(input: &mut Stream<'s>) -> PResult<&'s [u8]> {
75     let _ = take_while(1.., is_horizontal_space).parse_next(input)?;
76     let data = take_while(1.., till_line_ending).parse_next(input)?;
77     let _ = line_ending.parse_next(input)?;
78 
79     Ok(data)
80 }
81 
message_header<'s>(input: &mut Stream<'s>) -> PResult<Header<'s>>82 fn message_header<'s>(input: &mut Stream<'s>) -> PResult<Header<'s>> {
83     seq!(Header {
84         name: take_while(1.., is_token),
85         _: ':',
86         value: repeat(1.., message_header_value),
87     })
88     .parse_next(input)
89 }
90 
91 #[rustfmt::skip]
92 #[allow(clippy::match_same_arms)]
93 #[allow(clippy::match_like_matches_macro)]
is_token(c: u8) -> bool94 fn is_token(c: u8) -> bool {
95   match c {
96     128..=255 => false,
97     0..=31    => false,
98     b'('      => false,
99     b')'      => false,
100     b'<'      => false,
101     b'>'      => false,
102     b'@'      => false,
103     b','      => false,
104     b';'      => false,
105     b':'      => false,
106     b'\\'     => false,
107     b'"'      => false,
108     b'/'      => false,
109     b'['      => false,
110     b']'      => false,
111     b'?'      => false,
112     b'='      => false,
113     b'{'      => false,
114     b'}'      => false,
115     b' '      => false,
116     _         => true,
117   }
118 }
119 
is_version(c: u8) -> bool120 fn is_version(c: u8) -> bool {
121     c.is_ascii_digit() || c == b'.'
122 }
123 
till_line_ending(c: u8) -> bool124 fn till_line_ending(c: u8) -> bool {
125     c != b'\r' && c != b'\n'
126 }
127 
is_space(c: u8) -> bool128 fn is_space(c: u8) -> bool {
129     c == b' '
130 }
131 
is_not_space(c: u8) -> bool132 fn is_not_space(c: u8) -> bool {
133     c != b' '
134 }
135 
is_horizontal_space(c: u8) -> bool136 fn is_horizontal_space(c: u8) -> bool {
137     c == b' ' || c == b'\t'
138 }
139