vegemite/
http_utils.rs

1//! This module provides http utility traits and functions for parsing and handling Requests and
2//! Responses
3
4use http::{Request, Response, StatusCode, Version};
5
6use std::io::{BufRead, Write};
7
8use crate::systems::RawResponse;
9
10/// Errors while parsing requests.
11#[derive(Debug, PartialEq)]
12pub enum ParseError {
13    MalformedRequest,
14    ReadError,
15
16    InvalidMethod,
17    InvalidProtocolVer,
18    InvalidRequestParts,
19}
20
21impl std::fmt::Display for ParseError {
22    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
23        match self {
24            ParseError::MalformedRequest => write!(f, "Malformed Request"),
25            ParseError::ReadError => write!(f, "Read Error"),
26
27            ParseError::InvalidMethod => write!(f, "Invalid Method"),
28            ParseError::InvalidProtocolVer => write!(f, "Invalid Protocol"),
29            ParseError::InvalidRequestParts => write!(f, "Invalid Request Parts"),
30        }
31    }
32}
33
34impl std::error::Error for ParseError {}
35
36pub trait VersionExt: Sized {
37    /// # Errors
38    ///
39    /// Returns `Err` if the `&str` isn't a valid version of the HTTP protocol
40    fn parse_version(s: &str) -> Result<Self, ParseError>;
41
42    fn to_string(&self) -> String;
43}
44
45impl VersionExt for Version {
46    fn parse_version(s: &str) -> Result<Version, ParseError> {
47        Ok(match s {
48            "HTTP/0.9" => Version::HTTP_09,
49            "HTTP/1.0" => Version::HTTP_10,
50            "HTTP/1.1" => Version::HTTP_11,
51            "HTTP/2.0" => Version::HTTP_2,
52            "HTTP/3.0" => Version::HTTP_3,
53            _ => return Err(ParseError::InvalidProtocolVer),
54        })
55    }
56
57    fn to_string(&self) -> String {
58        match *self {
59            Version::HTTP_09 => "HTTP/0.9".to_string(),
60            Version::HTTP_10 => "HTTP/1.0".to_string(),
61            Version::HTTP_11 => "HTTP/1.1".to_string(),
62            Version::HTTP_2 => "HTTP/2.0".to_string(),
63            Version::HTTP_3 => "HTTP/3.0".to_string(),
64            _ => unreachable!(),
65        }
66    }
67}
68
69fn validate_method(method: &str) -> bool {
70    matches!(
71        method,
72        "GET" | "POST" | "PUT" | "DELETE" | "HEAD" | "OPTIONS" | "CONNECT" | "TRACE" | "PATH"
73    )
74}
75
76/// the entirety of the header must be valid utf8
77pub fn take_request<R>(reader: &mut R) -> Result<Request<()>, ParseError>
78where
79    R: BufRead,
80{
81    let mut lines = reader.lines();
82
83    let line = lines
84        .next()
85        .ok_or(ParseError::MalformedRequest)?
86        .map_err(|_| ParseError::ReadError)?;
87
88    let mut parts = line.split(' ');
89
90    let method = parts.next().ok_or(ParseError::MalformedRequest)?;
91
92    if !validate_method(method) {
93        return Err(ParseError::InvalidMethod);
94    }
95
96    let uri = parts.next().ok_or(ParseError::MalformedRequest)?;
97
98    let version = parts.next().ok_or(ParseError::MalformedRequest)?;
99
100    let mut req = Request::builder()
101        .method(method)
102        .uri(uri)
103        .version(Version::parse_version(version)?);
104
105    while let Some(line) = lines
106        .next()
107        .transpose()
108        .map_err(|_| ParseError::ReadError)?
109    {
110        if line.is_empty() {
111            break;
112        }
113
114        let h = line.split_once(": ").ok_or(ParseError::MalformedRequest)?;
115
116        if h.1.is_empty() {
117            return Err(ParseError::MalformedRequest);
118        }
119
120        req = req.header(h.0, h.1);
121    }
122
123    req.body(()).map_err(|_| ParseError::MalformedRequest)
124}
125
126fn parse_response_line_into_buf<T>(
127    buf: &mut Vec<u8>,
128    request: &Response<T>,
129) -> Result<(), std::io::Error> {
130    write!(
131        buf,
132        "{} {}\r\n",
133        request.version().to_string(),
134        request.status()
135    )?;
136
137    for (key, value) in request.headers() {
138        let _ = buf.write(key.as_str().as_bytes())?;
139
140        write!(buf, ": ")?;
141
142        let _ = buf.write(value.as_bytes())?;
143
144        write!(buf, "\r\n")?;
145    }
146
147    write!(buf, "\r\n")?;
148
149    Ok(())
150}
151
152impl<T> IntoRawBytes for Response<T>
153where
154    T: IntoRawBytes,
155{
156    fn into_raw_bytes(self) -> Vec<u8> {
157        let mut buf = vec![];
158
159        // FIXME idk about not checking result
160        let _ = parse_response_line_into_buf(&mut buf, &self);
161
162        buf.extend_from_slice(self.map(IntoRawBytes::into_raw_bytes).body());
163
164        buf
165    }
166}
167
168pub trait IntoRawBytes {
169    fn into_raw_bytes(self) -> Vec<u8>;
170}
171
172impl IntoRawBytes for () {
173    fn into_raw_bytes(self) -> Vec<u8> {
174        vec![]
175    }
176}
177
178impl IntoRawBytes for Vec<u8> {
179    fn into_raw_bytes(self) -> Vec<u8> {
180        self
181    }
182}
183
184impl IntoRawBytes for String {
185    fn into_raw_bytes(self) -> Vec<u8> {
186        self.into_bytes()
187    }
188}
189
190pub trait ResponseExt: Sized {
191    fn base(code: StatusCode) -> Response<()>;
192
193    fn empty(code: impl Into<StatusCode>) -> Response<()>;
194
195    fn into_raw_response(self) -> RawResponse;
196}
197
198impl<T> ResponseExt for Response<T>
199where
200    T: IntoRawBytes,
201{
202    fn base(code: StatusCode) -> Response<()> {
203        Response::builder().status(code).body(()).unwrap()
204    }
205
206    fn empty(code: impl Into<StatusCode>) -> Response<()> {
207        Response::builder().status(code.into()).body(()).unwrap()
208    }
209
210    fn into_raw_response(self) -> RawResponse {
211        self.map(IntoRawBytes::into_raw_bytes)
212    }
213}
214
215#[cfg(test)]
216mod tests {
217    use std::io::BufReader;
218
219    use super::*;
220    use http::{HeaderValue, Method, Version};
221
222    #[test]
223    fn sanity_check() {
224        let bytes = b"POST /api/send-data HTTP/1.1 \r\nHost: example.com\r\nUser-Agent: My-HTTP-Client/1.0\r\nAccept: application/json\r\nContent-Type: application/json\r\nContent-Length: 87\r\nAuthorization: 82u27ydcfkjegh8jndnkzJJFFJRGHN\r\n\r\n{\"user_id\":12345, \"event_type\":\"page_view\",\"timestamp\":\"2023-09-23T15:30:00Z\",\"data\":{\"page_url\":\"https://example.com/some-page\",\"user_agent\":\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36\",\"referrer\":\"https://google.com/search?q=example\",\"browser_language\":\"en-US\",\"device_type\":\"desktop\"}}";
225        let mut reader = BufReader::new(&bytes[..]);
226
227        let req = take_request(&mut reader);
228        assert!(req.is_ok());
229
230        let req = req.unwrap();
231        assert_eq!(req.method(), Method::POST);
232        assert_eq!(req.uri(), "/api/send-data");
233        assert_eq!(req.version(), Version::HTTP_11);
234        assert_eq!(req.headers().len(), 6);
235        assert_eq!(
236            req.headers().get("Host"),
237            Some(&HeaderValue::from_str("example.com").unwrap())
238        );
239        assert_eq!(
240            req.headers().get("User-Agent"),
241            Some(&HeaderValue::from_str("My-HTTP-Client/1.0").unwrap())
242        );
243        assert_eq!(
244            req.headers().get("Accept"),
245            Some(&HeaderValue::from_str("application/json").unwrap())
246        );
247        assert_eq!(
248            req.headers().get("Content-Type"),
249            Some(&HeaderValue::from_str("application/json").unwrap())
250        );
251        assert_eq!(
252            req.headers().get("Content-Length"),
253            Some(&HeaderValue::from_str("87").unwrap())
254        );
255    }
256
257    #[test]
258    fn invalid_protocol() {
259        let bytes = b"GET / HTTP/1.32 \r\nContent-Type: text/html; charset=utf-8\r\nContent-Length: 123\r\n\r\n";
260        let mut reader = BufReader::new(&bytes[..]);
261
262        let resp = take_request(&mut reader);
263        assert!(matches!(resp, Err(ParseError::InvalidProtocolVer)));
264    }
265
266    #[test]
267    fn invalid_header() {
268        let bytes = b"GET / HTTP/1.1 22 3jklajs \r\nContent-Type: text/html; charset=utf-8\r\nContent-Length: 123\r\n\r\n";
269        let mut reader = BufReader::new(&bytes[..]);
270
271        let resp = take_request(&mut reader);
272
273        assert!(matches!(resp, Err(ParseError::MalformedRequest)));
274
275        let bytes = b"GET / jjshjudh HTTP/1.1 22 3jklajs \r\nContent-Type: text/html; charset=utf-8\r\nContent-Length: 123\r\n\r\n";
276
277        let mut reader = BufReader::new(&bytes[..]);
278
279        let resp = take_request(&mut reader);
280        assert!(matches!(resp, Err(ParseError::MalformedRequest)));
281    }
282
283    #[test]
284    fn invalid_method() {
285        let bytes = b"BAKLAVA / HTTP/1.1 \r\nContent-Type: text/html; charset=utf-8\r\nContent-Length: 123\r\n\r\n";
286
287        let mut reader = BufReader::new(&bytes[..]);
288
289        let resp = take_request(&mut reader);
290        assert!(matches!(resp, Err(ParseError::InvalidMethod)));
291    }
292
293    #[test]
294    fn not_enough_bytes() {
295        // these should all return MalformedRequest
296        // incomplete method
297        let bytes = b"GET / HT";
298        let mut reader = BufReader::new(&bytes[..]);
299
300        let resp = take_request(&mut reader);
301        // FIXME returns InvalidProtocolVer
302        assert!(matches!(resp, Err(ParseError::MalformedRequest)));
303
304        // incomplete header field
305        // let bytes = b"GET / HTTP/1.1 \r\nContent-Type: tex";
306        // let mut reader = BufReader::new(&bytes[..]);
307        //
308        // let resp = take_request(&mut reader);
309        // FIXME returns Ok. This behaviour may be acceptable considering the parser internally
310        // will wait until timeout and return Err on timeout
311        // assert!(matches!(resp, Err(ParseError::MalformedRequest)));
312
313        let bytes = b"GET / HTTP/1.1 \r\nContent-Type: \r\n\r\n";
314        let mut reader = BufReader::new(&bytes[..]);
315
316        let resp = take_request(&mut reader);
317        // FIXME returns malformed
318        assert!(matches!(resp, Err(ParseError::MalformedRequest)));
319
320        // incomplete header key
321        let bytes = b"GET / HTTP/1.1 \r\nContent-Type: \r\nContent-L";
322        let mut reader = BufReader::new(&bytes[..]);
323
324        let resp = take_request(&mut reader);
325        assert!(matches!(resp, Err(ParseError::MalformedRequest)));
326    }
327
328    #[test]
329    fn unicode_in_request() {
330        let bytes = b"GET / HTTP/1.1 \r\nContent-Type: \xE2\xA1\x91\xE2\xB4\x9D\xE2\x9B\xB6\r\nContent-Length: 123\r\n\r\n";
331        let mut reader = BufReader::new(&bytes[..]);
332
333        let resp = take_request(&mut reader);
334        assert!(resp.is_ok());
335    }
336
337    #[test]
338    fn malformed_request() {
339        // missing header field
340        let bytes = b"GET / HTTP/1.1 \r\nContent-Type: \r\nContent-Length: 123\r\n\r\n";
341        let mut reader = BufReader::new(&bytes[..]);
342
343        let resp = take_request(&mut reader);
344        assert!(matches!(resp, Err(ParseError::MalformedRequest)));
345
346        // incomplete header key
347        let bytes = b"GET / HTTP/1.1\r\nCey: text/html; charset=utf-8\r\nContent-Len\r\n\r\n";
348        let mut reader = BufReader::new(&bytes[..]);
349
350        let resp = take_request(&mut reader);
351        assert!(matches!(resp, Err(ParseError::MalformedRequest)));
352
353        let bytes = b"GET / HTTP/1.1\r\nContent-Type: text/html; charset=utf-8\rContent-Length: 123\r\n\r\n";
354        let mut reader = BufReader::new(&bytes[..]);
355
356        let resp = take_request(&mut reader);
357        assert!(matches!(resp, Err(ParseError::MalformedRequest)));
358
359        // missing separator
360        let bytes = b"GET / HTTP/1.1\r\nContent-Type: text/html; charset=utf-8\r\n\n";
361        let mut reader = BufReader::new(&bytes[..]);
362
363        let resp = take_request(&mut reader);
364        // FIXME returns Ok
365        assert!(matches!(resp, Err(ParseError::MalformedRequest)));
366
367        let bytes = b"GET / HTTP/1.1\r\nContent-Type: text/html; charset=utf-8\nContent-Length: 123\r\n\r\n";
368        let mut reader = BufReader::new(&bytes[..]);
369
370        let resp = take_request(&mut reader);
371        // FIXME returns Ok
372        assert!(matches!(resp, Err(ParseError::MalformedRequest)));
373    }
374
375    #[test]
376    fn nulls_in_request() {
377        let bytes = b"GET / HTTP/1.1\r\nContent-Type: \0\r\nContent-Length: 123\r\n\r\n";
378        let mut reader = BufReader::new(&bytes[..]);
379
380        let resp = take_request(&mut reader);
381        assert!(matches!(resp, Err(_)));
382
383        let bytes =
384            b"GET / HTTP/1.1\r\n\0: text/html; charset=utf-8\r\nContent-Length: 123\r\n\r\n";
385        let mut reader = BufReader::new(&bytes[..]);
386
387        let resp = take_request(&mut reader);
388        assert!(matches!(resp, Err(_)));
389
390        let bytes = b"\0 \0 \0\r\n\0: text/html; charset=utf-8\r\nContent-Length: 123\r\n\r\n";
391        let mut reader = BufReader::new(&bytes[..]);
392
393        let resp = take_request(&mut reader);
394        assert!(matches!(resp, Err(_)));
395    }
396}