// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use std::str::from_utf8; use crate::common::ascii::{CR, CRLF_LEN, LF, SP}; use crate::common::headers::Headers; use crate::common::{Body, Method, Version}; pub use crate::common::RequestError; /// Finds the first occurrence of `sequence` in the `bytes` slice. /// /// Returns the starting position of the `sequence` in `bytes` or `None` if the /// `sequence` is not found. pub(crate) fn find(bytes: &[u8], sequence: &[u8]) -> Option { bytes .windows(sequence.len()) .position(|window| window == sequence) } /// Wrapper over HTTP URIs. /// /// The `Uri` can not be used directly and it is only accessible from an HTTP Request. #[derive(Clone, Debug, PartialEq)] pub struct Uri { string: String, } impl Uri { fn new(slice: &str) -> Self { Self { string: String::from(slice), } } fn try_from(bytes: &[u8]) -> Result { if bytes.is_empty() { return Err(RequestError::InvalidUri("Empty URI not allowed.")); } let utf8_slice = from_utf8(bytes).map_err(|_| RequestError::InvalidUri("Cannot parse URI as UTF-8."))?; Ok(Self::new(utf8_slice)) } /// Returns the absolute path of the `Uri`. /// /// URIs can be represented in absolute form or relative form. The absolute form includes /// the HTTP scheme, followed by the absolute path as follows: /// "http:" "//" host [ ":" port ] [ abs_path ] /// The relative URIs can be one of net_path | abs_path | rel_path. /// This method only handles absolute URIs and relative URIs specified by abs_path. /// The abs_path is expected to start with '/'. /// /// # Errors /// Returns an empty byte array when the host or the path are empty/invalid. pub fn get_abs_path(&self) -> &str { const HTTP_SCHEME_PREFIX: &str = "http://"; if self.string.starts_with(HTTP_SCHEME_PREFIX) { let without_scheme = &self.string[HTTP_SCHEME_PREFIX.len()..]; if without_scheme.is_empty() { return ""; } // The host in this case includes the port and contains the bytes after http:// up to // the next '/'. match without_scheme.bytes().position(|byte| byte == b'/') { Some(len) => &without_scheme[len..], None => "", } } else { if self.string.starts_with('/') { return self.string.as_str(); } "" } } } /// Wrapper over an HTTP Request Line. #[derive(Debug, PartialEq)] pub struct RequestLine { method: Method, uri: Uri, http_version: Version, } impl RequestLine { fn parse_request_line(request_line: &[u8]) -> (&[u8], &[u8], &[u8]) { if let Some(method_end) = find(request_line, &[SP]) { let method = &request_line[..method_end]; let uri_and_version = &request_line[(method_end + 1)..]; if let Some(uri_end) = find(uri_and_version, &[SP]) { let uri = &uri_and_version[..uri_end]; let version = &uri_and_version[(uri_end + 1)..]; return (method, uri, version); } return (method, uri_and_version, b""); } (b"", b"", b"") } /// Tries to parse a byte stream in a request line. Fails if the request line is malformed. /// /// # Errors /// `InvalidHttpMethod` is returned if the specified HTTP method is unsupported. /// `InvalidHttpVersion` is returned if the specified HTTP version is unsupported. /// `InvalidUri` is returned if the specified Uri is not valid. pub fn try_from(request_line: &[u8]) -> Result { let (method, uri, version) = Self::parse_request_line(request_line); Ok(Self { method: Method::try_from(method)?, uri: Uri::try_from(uri)?, http_version: Version::try_from(version)?, }) } // Returns the minimum length of a valid request. The request must contain // the method (GET), the URI (minmum 1 character), the HTTP version(HTTP/DIGIT.DIGIT) and // 2 separators (SP). fn min_len() -> usize { Method::Get.raw().len() + 1 + Version::Http10.raw().len() + 2 } } /// Wrapper over an HTTP Request. #[derive(Debug)] pub struct Request { /// The request line of the request. pub request_line: RequestLine, /// The headers of the request. pub headers: Headers, /// The body of the request. pub body: Option, } impl Request { /// Parses a byte slice into a HTTP Request. /// /// The byte slice is expected to have the following format:
/// * Request Line: "GET SP Request-uri SP HTTP/1.0 CRLF" - Mandatory
/// * Request Headers " CRLF"- Optional
/// * Entity Body - Optional
/// The only supported method is GET and the HTTP protocol is expected to be HTTP/1.0 /// or HTTP/1.1. /// /// # Errors /// The function returns InvalidRequest when parsing the byte stream fails. /// /// # Examples /// /// ``` /// extern crate micro_http; /// use micro_http::Request; /// /// let http_request = Request::try_from(b"GET http://localhost/home HTTP/1.0\r\n\r\n").unwrap(); /// ``` pub fn try_from(byte_stream: &[u8]) -> Result { // The first line of the request is the Request Line. The line ending is CR LF. let request_line_end = match find(byte_stream, &[CR, LF]) { Some(len) => len, // If no CR LF is found in the stream, the request format is invalid. None => return Err(RequestError::InvalidRequest), }; let request_line_bytes = &byte_stream[..request_line_end]; if request_line_bytes.len() < RequestLine::min_len() { return Err(RequestError::InvalidRequest); } let request_line = RequestLine::try_from(request_line_bytes)?; // Find the next CR LF CR LF sequence in our buffer starting at the end on the Request // Line, including the trailing CR LF previously found. match find(&byte_stream[request_line_end..], &[CR, LF, CR, LF]) { // If we have found a CR LF CR LF at the end of the Request Line, the request // is complete. Some(0) => Ok(Self { request_line, headers: Headers::default(), body: None, }), Some(headers_end) => { // Parse the request headers. // Start by removing the leading CR LF from them. let headers_and_body = &byte_stream[(request_line_end + CRLF_LEN)..]; let headers_end = headers_end - CRLF_LEN; let headers = Headers::try_from(&headers_and_body[..headers_end])?; // Parse the body of the request. // Firstly check if we have a body. let body = match headers.content_length() { 0 => { // No request body. None } content_length => { // Headers suggest we have a body, but the buffer is shorter than the specified // content length. if headers_and_body.len() - (headers_end + 2 * CRLF_LEN) < content_length as usize { return Err(RequestError::InvalidRequest); } let body_as_bytes = &headers_and_body[(headers_end + 2 * CRLF_LEN)..]; // If the actual length of the body is different than the `Content-Length` value // in the headers then this request is invalid. if body_as_bytes.len() == content_length as usize { Some(Body::new(body_as_bytes)) } else { return Err(RequestError::InvalidRequest); } } }; Ok(Self { request_line, headers, body, }) } // If we can't find a CR LF CR LF even though the request should have headers // the request format is invalid. None => Err(RequestError::InvalidRequest), } } /// Returns the `Uri` from the parsed `Request`. /// /// The return value can be used to get the absolute path of the URI. pub fn uri(&self) -> &Uri { &self.request_line.uri } /// Returns the HTTP `Version` of the `Request`. pub fn http_version(&self) -> Version { self.request_line.http_version } /// Returns the HTTP `Method` of the `Request`. pub fn method(&self) -> Method { self.request_line.method } } #[cfg(test)] mod tests { use super::*; impl PartialEq for Request { fn eq(&self, other: &Self) -> bool { // Ignore the other fields of Request for now because they are not used. self.request_line == other.request_line && self.headers.content_length() == other.headers.content_length() && self.headers.expect() == other.headers.expect() && self.headers.chunked() == other.headers.chunked() } } impl RequestLine { pub fn new(method: Method, uri: &str, http_version: Version) -> Self { Self { method, uri: Uri::new(uri), http_version, } } } #[test] fn test_uri() { for tc in &vec![ ("http://localhost/home", "/home"), ("http://localhost:8080/home", "/home"), ("http://localhost/home/sub", "/home/sub"), ("/home", "/home"), ("home", ""), ("http://", ""), ("http://192.168.0.0", ""), ] { assert_eq!(Uri::new(tc.0).get_abs_path(), tc.1); } } #[test] fn test_find() { let bytes: &[u8; 13] = b"abcacrgbabsjl"; for tc in &vec![ ("ac", Some(3)), ("rgb", Some(5)), ("ab", Some(0)), ("l", Some(12)), ("abcacrgbabsjl", Some(0)), ("jle", None), ("asdkjhasjhdjhgsadg", None), ] { assert_eq!(find(&bytes[..], tc.0.as_bytes()), tc.1); } } #[test] fn test_into_request_line() { let expected_request_line = RequestLine { http_version: Version::Http10, method: Method::Get, uri: Uri::new("http://localhost/home"), }; let request_line = b"GET http://localhost/home HTTP/1.0"; assert_eq!( RequestLine::try_from(request_line).unwrap(), expected_request_line ); let expected_request_line = RequestLine { http_version: Version::Http11, method: Method::Get, uri: Uri::new("http://localhost/home"), }; // Happy case with request line ending in CRLF. let request_line = b"GET http://localhost/home HTTP/1.1"; assert_eq!( RequestLine::try_from(request_line).unwrap(), expected_request_line ); // Happy case with request line ending in LF instead of CRLF. let request_line = b"GET http://localhost/home HTTP/1.1"; assert_eq!( RequestLine::try_from(request_line).unwrap(), expected_request_line ); // Test for invalid method. let request_line = b"POST http://localhost/home HTTP/1.0"; assert_eq!( RequestLine::try_from(request_line).unwrap_err(), RequestError::InvalidHttpMethod("Unsupported HTTP method.") ); // Test for invalid uri. let request_line = b"GET HTTP/1.0"; assert_eq!( RequestLine::try_from(request_line).unwrap_err(), RequestError::InvalidUri("Empty URI not allowed.") ); // Test for invalid HTTP version. let request_line = b"GET http://localhost/home HTTP/2.0"; assert_eq!( RequestLine::try_from(request_line).unwrap_err(), RequestError::InvalidHttpVersion("Unsupported HTTP version.") ); // Test for invalid format with no method, uri or version. let request_line = b"nothing"; assert_eq!( RequestLine::try_from(request_line).unwrap_err(), RequestError::InvalidHttpMethod("Unsupported HTTP method.") ); // Test for invalid format with no version. let request_line = b"GET /"; assert_eq!( RequestLine::try_from(request_line).unwrap_err(), RequestError::InvalidHttpVersion("Unsupported HTTP version.") ); } #[test] fn test_into_request() { let expected_request = Request { request_line: RequestLine { http_version: Version::Http10, method: Method::Get, uri: Uri::new("http://localhost/home"), }, body: None, headers: Headers::default(), }; let request_bytes = b"GET http://localhost/home HTTP/1.0\r\n\ Last-Modified: Tue, 15 Nov 1994 12:45:26 GMT\r\n\r\n"; let request = Request::try_from(request_bytes).unwrap(); assert_eq!(request, expected_request); assert_eq!(request.uri(), &Uri::new("http://localhost/home")); assert_eq!(request.http_version(), Version::Http10); assert!(request.body.is_none()); // Test for invalid Request (length is less than minimum). let request_bytes = b"GET"; assert_eq!( Request::try_from(request_bytes).unwrap_err(), RequestError::InvalidRequest ); // Test for a request with the headers we are looking for. let request = Request::try_from( b"PATCH http://localhost/home HTTP/1.1\r\n\ Expect: 100-continue\r\n\ Transfer-Encoding: chunked\r\n\ Content-Length: 26\r\n\r\nthis is not\n\r\na json \nbody", ) .unwrap(); assert_eq!(request.uri(), &Uri::new("http://localhost/home")); assert_eq!(request.http_version(), Version::Http11); assert_eq!(request.method(), Method::Patch); assert_eq!(request.headers.chunked(), true); assert_eq!(request.headers.expect(), true); assert_eq!(request.headers.content_length(), 26); assert_eq!( request.body.unwrap().body, String::from("this is not\n\r\na json \nbody") .as_bytes() .to_vec() ); // Test for an invalid request format. Request::try_from(b"PATCH http://localhost/home HTTP/1.1\r\n").unwrap_err(); // Test for an invalid encoding. let request = Request::try_from( b"PATCH http://localhost/home HTTP/1.1\r\n\ Expect: 100-continue\r\n\ Transfer-Encoding: identity; q=0\r\n\ Content-Length: 26\r\n\r\nthis is not\n\r\na json \nbody", ) .unwrap_err(); assert_eq!(request, RequestError::InvalidHeader); // Test for an invalid content length. let request = Request::try_from( b"PATCH http://localhost/home HTTP/1.1\r\n\ Expect: 100-continue\r\n\ Content-Length: 5000\r\n\r\nthis is a short body", ) .unwrap_err(); assert_eq!(request, RequestError::InvalidRequest); // Test for a request without a body and an optional header. let request = Request::try_from( b"GET http://localhost/ HTTP/1.0\r\n\ Accept-Encoding: gzip\r\n\r\n", ) .unwrap(); assert_eq!(request.uri(), &Uri::new("http://localhost/")); assert_eq!(request.http_version(), Version::Http10); assert_eq!(request.method(), Method::Get); assert_eq!(request.headers.chunked(), false); assert_eq!(request.headers.expect(), false); assert_eq!(request.headers.content_length(), 0); assert!(request.body.is_none()); } }