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
mod util;

use http::Request;
use http::Uri;

/// Parses a buffer and returns a Request whose body is a byte slice, if everything goes successful.
/// An error is returned in case the buffer content doesn't conform with HTTP message structure.
/// # Examples
///
/// ```
/// use saf_httparser::request_from_bytes;
///
/// let buffer = b"GET /somepath HTTP/1.1\r\nHost: www.awesomehost.com\r\n\r\nRequest body";
/// let request = request_from_bytes(buffer).unwrap();
/// ```
pub fn request_from_bytes(buffer: &[u8]) -> Result<Request<&[u8]>, String> {
  let buffer = util::normalize_buffer(buffer);
  let parts = util::parse_first_line(buffer);
  let (method, version, path, end) = match parts {
    (Some(method), Some(version), Some(path), end) => (method, version, path, end),
    _ => return Err(String::from("Error processing request line"))
  };

  let (headers, end) = match util::parse_headers(buffer, end) {
    (Some(headers), end) => (headers, end),
    _ => return Err(String::from("Error processig headers"))
  };

  let mut request = Request::builder()
    .method(method)
    .version(version);

  let uri  = Uri::builder()
    .scheme("http")
    .authority(headers.get("Host").unwrap().as_bytes())
    .path_and_query(path).build();
  
  let uri = match uri {
    Ok(u) => u,
    Err(err) => return Err(err.to_string())
  };

  for (name, value) in headers {
    match name {
      Some(name) => {
        request = request.header(name, value);
      },
      None => return Err(String::from("Error processing header"))
    }
  };

  let body = util::parse_body(buffer, end);

  match request.uri(uri).body::<&[u8]>(body) {
    Ok(req) => Ok(req),
    Err(_) => Err(String::from("Error building request body"))
  }
}

/// Same as `request_from_bytes`, except that this function takes the message content as a string slice. More convenient if you
/// don't want to call it like `request_from_bytes(buffer.as_bytes())`.
pub fn request_from_str(buffer: &str) -> Result<Request<&[u8]>, String> {
  request_from_bytes(buffer.as_bytes())
}

#[cfg(test)]
mod tests {

  use http::Version;

  #[test]
  fn parse_request() {
    let example_request = b"GET /bora/is HTTP/1.1\r\n\
                                    Host:www.bora-is-awesome.com\r\n\
                                    Content-Type:text/plain\r\n\r\nbora is awesome";
    let request = super::request_from_bytes(example_request).unwrap();

    assert_eq!(request.method().as_str(), "GET");
    assert_eq!(request.uri().path(), "/bora/is");
    assert_eq!(request.version(), Version::HTTP_11);
    assert_eq!(request.headers().get("host").unwrap(), &"www.bora-is-awesome.com");
    assert_eq!(request.headers().get("content-type").unwrap(), &"text/plain");
    assert_eq!(request.body(), b"bora is awesome");
  }

  #[test]
  fn parse_null_terminated_request() {
    let example_request = b"GET / HTTP/1.1\r\n\
                                    User-Agent: PostmanRuntime/7.26.1\r\n\
                                    Accept: */*\r\n\
                                    Postman-Token: 048859cd-7a99-470b-a48f-29004152bd5c\r\n\
                                    Host: localhost:7878\r\n\
                                    Accept-Encoding: gzip, deflate, br\r\n\
                                    Connection: keep-alive\r\n\r\nBora is awesome";

    let mut big_buffer = [0u8; 1024];
    for (index, byte) in example_request.iter().enumerate() {
      big_buffer[index] = *byte;
    }

    let request = super::request_from_bytes(&big_buffer).unwrap();

    assert_eq!(request.method().as_str(), "GET");
    assert_eq!(request.uri().path(), "/");
    assert_eq!(request.version(), Version::HTTP_11);
    assert_eq!(request.headers().get("host").unwrap(), &"localhost:7878");
    assert_eq!(request.headers().get("Accept-Encoding").unwrap(), &"gzip, deflate, br");
    assert_eq!(request.body(), b"Bora is awesome");
  }
}