saf_httparser/
lib.rs

1mod util;
2
3use http::Request;
4use http::Uri;
5
6/// Parses a buffer and returns a Request whose body is a byte slice, if everything goes successful.
7/// An error is returned in case the buffer content doesn't conform with HTTP message structure.
8/// # Examples
9///
10/// ```
11/// use saf_httparser::request_from_bytes;
12///
13/// let buffer = b"GET /somepath HTTP/1.1\r\nHost: www.awesomehost.com\r\n\r\nRequest body";
14/// let request = request_from_bytes(buffer).unwrap();
15/// ```
16pub fn request_from_bytes(buffer: &[u8]) -> Result<Request<&[u8]>, String> {
17  let buffer = util::normalize_buffer(buffer);
18  let parts = util::parse_first_line(buffer);
19  let (method, version, path, end) = match parts {
20    (Some(method), Some(version), Some(path), end) => (method, version, path, end),
21    _ => return Err(String::from("Error processing request line"))
22  };
23
24  let (headers, end) = match util::parse_headers(buffer, end) {
25    (Some(headers), end) => (headers, end),
26    _ => return Err(String::from("Error processig headers"))
27  };
28
29  let mut request = Request::builder()
30    .method(method)
31    .version(version);
32
33  let uri  = Uri::builder()
34    .scheme("http")
35    .authority(headers.get("Host").unwrap().as_bytes())
36    .path_and_query(path).build();
37  
38  let uri = match uri {
39    Ok(u) => u,
40    Err(err) => return Err(err.to_string())
41  };
42
43  for (name, value) in headers {
44    match name {
45      Some(name) => {
46        request = request.header(name, value);
47      },
48      None => return Err(String::from("Error processing header"))
49    }
50  };
51
52  let body = util::parse_body(buffer, end);
53
54  match request.uri(uri).body::<&[u8]>(body) {
55    Ok(req) => Ok(req),
56    Err(_) => Err(String::from("Error building request body"))
57  }
58}
59
60/// Same as `request_from_bytes`, except that this function takes the message content as a string slice. More convenient if you
61/// don't want to call it like `request_from_bytes(buffer.as_bytes())`.
62pub fn request_from_str(buffer: &str) -> Result<Request<&[u8]>, String> {
63  request_from_bytes(buffer.as_bytes())
64}
65
66#[cfg(test)]
67mod tests {
68
69  use http::Version;
70
71  #[test]
72  fn parse_request() {
73    let example_request = b"GET /bora/is HTTP/1.1\r\n\
74                                    Host:www.bora-is-awesome.com\r\n\
75                                    Content-Type:text/plain\r\n\r\nbora is awesome";
76    let request = super::request_from_bytes(example_request).unwrap();
77
78    assert_eq!(request.method().as_str(), "GET");
79    assert_eq!(request.uri().path(), "/bora/is");
80    assert_eq!(request.version(), Version::HTTP_11);
81    assert_eq!(request.headers().get("host").unwrap(), &"www.bora-is-awesome.com");
82    assert_eq!(request.headers().get("content-type").unwrap(), &"text/plain");
83    assert_eq!(request.body(), b"bora is awesome");
84  }
85
86  #[test]
87  fn parse_null_terminated_request() {
88    let example_request = b"GET / HTTP/1.1\r\n\
89                                    User-Agent: PostmanRuntime/7.26.1\r\n\
90                                    Accept: */*\r\n\
91                                    Postman-Token: 048859cd-7a99-470b-a48f-29004152bd5c\r\n\
92                                    Host: localhost:7878\r\n\
93                                    Accept-Encoding: gzip, deflate, br\r\n\
94                                    Connection: keep-alive\r\n\r\nBora is awesome";
95
96    let mut big_buffer = [0u8; 1024];
97    for (index, byte) in example_request.iter().enumerate() {
98      big_buffer[index] = *byte;
99    }
100
101    let request = super::request_from_bytes(&big_buffer).unwrap();
102
103    assert_eq!(request.method().as_str(), "GET");
104    assert_eq!(request.uri().path(), "/");
105    assert_eq!(request.version(), Version::HTTP_11);
106    assert_eq!(request.headers().get("host").unwrap(), &"localhost:7878");
107    assert_eq!(request.headers().get("Accept-Encoding").unwrap(), &"gzip, deflate, br");
108    assert_eq!(request.body(), b"Bora is awesome");
109  }
110}