diff --git a/Cargo.lock b/Cargo.lock index a670506fa..311626827 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -406,6 +406,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "micro_http" version = "0.1.0" +source = "git+https://github.com/firecracker-microvm/firecracker#2b94334a6a2a5f7dd07ca94f37cc4d9bdbf57ce6" [[package]] name = "net_gen" @@ -1090,7 +1091,7 @@ dependencies = [ "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", "linux-loader 0.1.0 (git+https://github.com/rust-vmm/linux-loader)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "micro_http 0.1.0", + "micro_http 0.1.0 (git+https://github.com/firecracker-microvm/firecracker)", "net_util 0.1.0", "pci 0.1.0", "qcow 0.1.0", @@ -1202,6 +1203,7 @@ dependencies = [ "checksum log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" "checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" "checksum memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "88579771288728879b57485cc7d6b07d648c9f0141eb955f8ab7f9d45394468e" +"checksum micro_http 0.1.0 (git+https://github.com/firecracker-microvm/firecracker)" = "" "checksum nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9667ddcc6cc8a43afc9b7917599d7216aa09c463919ea32c59ed6cac8bc945" "checksum num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bcef43580c035376c0705c42792c294b66974abbfd2789b511784023f71f3273" "checksum openssl-sys 0.9.50 (registry+https://github.com/rust-lang/crates.io-index)" = "2c42dcccb832556b5926bc9ae61e8775f2a61e725ab07ab3d1e7fcf8ae62c3b6" diff --git a/micro_http/Cargo.toml b/micro_http/Cargo.toml deleted file mode 100644 index 2e5513f58..000000000 --- a/micro_http/Cargo.toml +++ /dev/null @@ -1,6 +0,0 @@ -[package] -name = "micro_http" -version = "0.1.0" -authors = ["Amazon Firecracker team "] - -[dependencies] diff --git a/micro_http/src/common/headers.rs b/micro_http/src/common/headers.rs deleted file mode 100644 index 1effae299..000000000 --- a/micro_http/src/common/headers.rs +++ /dev/null @@ -1,397 +0,0 @@ -// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -use std::result::Result; - -use RequestError; - -/// Wrapper over an HTTP Header type. -#[derive(Debug, Eq, Hash, PartialEq)] -pub enum Header { - /// Header `Content-Length`. - ContentLength, - /// Header `Content-Type`. - ContentType, - /// Header `Expect`. - Expect, - /// Header `Transfer-Encoding`. - TransferEncoding, - /// Header `Server`. - Server, -} - -impl Header { - pub fn raw(&self) -> &'static [u8] { - match self { - Header::ContentLength => b"Content-Length", - Header::ContentType => b"Content-Type", - Header::Expect => b"Expect", - Header::TransferEncoding => b"Transfer-Encoding", - Header::Server => b"Server", - } - } - - fn try_from(string: &[u8]) -> Result { - if let Ok(utf8_string) = String::from_utf8(string.to_vec()) { - match utf8_string.trim() { - "Content-Length" => Ok(Header::ContentLength), - "Content-Type" => Ok(Header::ContentType), - "Expect" => Ok(Header::Expect), - "Transfer-Encoding" => Ok(Header::TransferEncoding), - "Server" => Ok(Header::Server), - _ => Err(RequestError::InvalidHeader), - } - } else { - Err(RequestError::InvalidRequest) - } - } -} - -/// Wrapper over the list of headers associated with a Request that we need -/// in order to parse the request correctly and be able to respond to it. -/// -/// The only `Content-Type`s supported are `text/plain` and `application/json`, which are both -/// in plain text actually and don't influence our parsing process. -/// -/// All the other possible header fields are not necessary in order to serve this connection -/// and, thus, are not of interest to us. However, we still look for header fields that might -/// invalidate our request as we don't support the full set of HTTP/1.1 specification. -/// Such header entries are "Transfer-Encoding: identity; q=0", which means a compression -/// algorithm is applied to the body of the request, or "Expect: 103-checkpoint". -#[derive(Debug)] -pub struct Headers { - /// The `Content-Length` header field tells us how many bytes we need to receive - /// from the source after the headers. - content_length: i32, - /// The `Expect` header field is set when the headers contain the entry "Expect: 100-continue". - /// This means that, per HTTP/1.1 specifications, we must send a response with the status code - /// 100 after we have received the headers in order to receive the body of the request. This - /// field should be known immediately after parsing the headers. - expect: bool, - /// `Chunked` is a possible value of the `Transfer-Encoding` header field and every HTTP/1.1 - /// server must support it. It is useful only when receiving the body of the request and should - /// be known immediately after parsing the headers. - chunked: bool, -} - -impl Headers { - /// By default Requests are created with no headers. - pub fn default() -> Headers { - Headers { - content_length: 0, - expect: false, - chunked: false, - } - } - - /// Expects one header line and parses it, updating the header structure or returning an - /// error if the header is invalid. - /// - /// # Errors - /// `UnsupportedHeader` is returned when the parsed header line is not of interest - /// to us or when it is unrecognizable. - /// `InvalidHeader` is returned when the parsed header is formatted incorrectly or suggests - /// that the client is using HTTP features that we do not support in this implementation, - /// which invalidates the request. - pub fn parse_header_line(&mut self, header_line: &[u8]) -> Result<(), RequestError> { - // Headers must be ASCII, so also UTF-8 valid. - match std::str::from_utf8(header_line) { - Ok(headers_str) => { - let entry = headers_str.split(": ").collect::>(); - if entry.len() != 2 { - return Err(RequestError::InvalidHeader); - } - if let Ok(head) = Header::try_from(entry[0].as_bytes()) { - match head { - Header::ContentLength => { - let try_numeric: Result = - std::str::FromStr::from_str(entry[1].trim()); - if let Ok(content_length) = try_numeric { - self.content_length = content_length; - Ok(()) - } else { - Err(RequestError::InvalidHeader) - } - } - Header::ContentType => { - match MediaType::try_from(entry[1].trim().as_bytes()) { - Ok(_) => Ok(()), - Err(_) => Err(RequestError::InvalidHeader), - } - } - Header::TransferEncoding => match entry[1].trim() { - "chunked" => { - self.chunked = true; - Ok(()) - } - "identity; q=0" => Err(RequestError::InvalidHeader), - _ => Err(RequestError::UnsupportedHeader), - }, - Header::Expect => match entry[1].trim() { - "100-continue" => { - self.expect = true; - Ok(()) - } - _ => Err(RequestError::InvalidHeader), - }, - Header::Server => Ok(()), - } - } else { - Err(RequestError::UnsupportedHeader) - } - } - _ => Err(RequestError::InvalidHeader), - } - } - - /// Returns the content length of the body. - pub fn content_length(&self) -> i32 { - self.content_length - } - - /// Returns `true` if the transfer encoding is chunked. - #[allow(unused)] - pub fn chunked(&self) -> bool { - self.chunked - } - - /// Returns `true` if the client is expecting the code 100. - #[allow(unused)] - pub fn expect(&self) -> bool { - self.expect - } - - #[cfg(test)] - pub fn new(content_length: i32, expect: bool, chunked: bool) -> Self { - Headers { - content_length, - expect, - chunked, - } - } - - /// Parses a byte slice into a Headers structure for a HTTP request. - /// - /// The byte slice is expected to have the following format:
- /// * Request Header Lines " CRLF"- Optional
- /// There can be any number of request headers, including none, followed by - /// an extra sequence of Carriage Return and Line Feed. - /// All header fields are parsed. However, only the ones present in the - /// [`Headers`](struct.Headers.html) struct are relevant to us and stored - /// for future use. - /// - /// # Errors - /// The function returns `InvalidHeader` when parsing the byte stream fails. - /// - /// # Examples - /// - /// ``` - /// extern crate micro_http; - /// use micro_http::Headers; - /// - /// let request_headers = Headers::try_from(b"Content-Length: 55\r\n\r\n"); - /// ``` - pub fn try_from(bytes: &[u8]) -> Result { - // Headers must be ASCII, so also UTF-8 valid. - if let Ok(text) = std::str::from_utf8(bytes) { - let mut headers = Headers::default(); - - let header_lines = text.split("\r\n"); - for header_line in header_lines { - if header_line.is_empty() { - break; - } - match headers.parse_header_line(header_line.as_bytes()) { - Ok(_) | Err(RequestError::UnsupportedHeader) => continue, - Err(e) => return Err(e), - }; - } - return Ok(headers); - } - Err(RequestError::InvalidRequest) - } -} - -/// Wrapper over supported Media Types. -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum MediaType { - /// Media Type: "text/plain". - PlainText, - /// Media Type: "application/json". - ApplicationJson, -} - -impl Default for MediaType { - fn default() -> Self { - MediaType::PlainText - } -} - -impl MediaType { - fn try_from(bytes: &[u8]) -> Result { - if bytes.is_empty() { - return Err(RequestError::InvalidRequest); - } - let utf8_slice = - String::from_utf8(bytes.to_vec()).map_err(|_| RequestError::InvalidRequest)?; - match utf8_slice.as_str() { - "text/plain" => Ok(MediaType::PlainText), - "application/json" => Ok(MediaType::ApplicationJson), - _ => Err(RequestError::InvalidRequest), - } - } - - /// Returns a static string representation of the object. - pub fn as_str(self) -> &'static str { - match self { - MediaType::PlainText => "text/plain", - MediaType::ApplicationJson => "application/json", - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_default() { - let headers = Headers::default(); - assert_eq!(headers.content_length(), 0); - assert_eq!(headers.chunked(), false); - assert_eq!(headers.expect(), false); - } - - #[test] - fn test_try_from_media() { - assert_eq!( - MediaType::try_from(b"application/json").unwrap(), - MediaType::ApplicationJson - ); - - assert_eq!( - MediaType::try_from(b"text/plain").unwrap(), - MediaType::PlainText - ); - - assert_eq!( - MediaType::try_from(b"").unwrap_err(), - RequestError::InvalidRequest - ); - - assert_eq!( - MediaType::try_from(b"application/json-patch").unwrap_err(), - RequestError::InvalidRequest - ); - } - - #[test] - fn test_media_as_str() { - let media_type = MediaType::ApplicationJson; - assert_eq!(media_type.as_str(), "application/json"); - - let media_type = MediaType::PlainText; - assert_eq!(media_type.as_str(), "text/plain"); - } - - #[test] - fn test_try_from_headers() { - // Valid headers. - assert_eq!( - Headers::try_from( - b"Last-Modified: Tue, 15 Nov 1994 12:45:26 GMT\r\nContent-Length: 55\r\n\r\n" - ) - .unwrap() - .content_length, - 55 - ); - - let bytes: [u8; 10] = [130, 140, 150, 130, 140, 150, 130, 140, 150, 160]; - // Invalid headers. - assert!(Headers::try_from(&bytes[..]).is_err()); - } - - #[test] - fn test_parse_header_line() { - let mut header = Headers::default(); - - // Invalid header syntax. - assert_eq!( - header.parse_header_line(b"Expect"), - Err(RequestError::InvalidHeader) - ); - - // Invalid content length. - assert_eq!( - header.parse_header_line(b"Content-Length: five"), - Err(RequestError::InvalidHeader) - ); - - // Invalid transfer encoding. - assert_eq!( - header.parse_header_line(b"Transfer-Encoding: gzip"), - Err(RequestError::UnsupportedHeader) - ); - - // Invalid expect. - assert_eq!( - header - .parse_header_line(b"Expect: 102-processing") - .unwrap_err(), - RequestError::InvalidHeader - ); - - // Invalid media type. - assert_eq!( - header - .parse_header_line(b"Content-Type: application/json-patch") - .unwrap_err(), - RequestError::InvalidHeader - ); - - // Invalid input format. - let input: [u8; 10] = [130, 140, 150, 130, 140, 150, 130, 140, 150, 160]; - assert_eq!( - header.parse_header_line(&input[..]).unwrap_err(), - RequestError::InvalidHeader - ); - - // Test valid transfer encoding. - assert!(header - .parse_header_line(b"Transfer-Encoding: chunked") - .is_ok()); - assert!(header.chunked()); - - // Test valid expect. - assert!(header.parse_header_line(b"Expect: 100-continue").is_ok()); - assert!(header.expect()); - - // Test valid media type. - assert!(header - .parse_header_line(b"Content-Type: application/json") - .is_ok()); - } - - #[test] - fn test_header_try_from() { - // Bad header. - assert_eq!( - Header::try_from(b"Encoding").unwrap_err(), - RequestError::InvalidHeader - ); - - // Invalid encoding. - let input: [u8; 10] = [130, 140, 150, 130, 140, 150, 130, 140, 150, 160]; - assert_eq!( - Header::try_from(&input[..]).unwrap_err(), - RequestError::InvalidRequest - ); - - // Test valid headers. - let header = Header::try_from(b"Expect").unwrap(); - assert_eq!(header.raw(), b"Expect"); - - let header = Header::try_from(b"Transfer-Encoding").unwrap(); - assert_eq!(header.raw(), b"Transfer-Encoding"); - } -} diff --git a/micro_http/src/common/mod.rs b/micro_http/src/common/mod.rs deleted file mode 100644 index 4d902be60..000000000 --- a/micro_http/src/common/mod.rs +++ /dev/null @@ -1,236 +0,0 @@ -// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -pub mod headers; - -pub mod ascii { - pub const CR: u8 = b'\r'; - pub const COLON: u8 = b':'; - pub const LF: u8 = b'\n'; - pub const SP: u8 = b' '; - pub const CRLF_LEN: usize = 2; -} - -/// Errors associated with parsing the HTTP Request from a u8 slice. -#[derive(Debug, PartialEq)] -pub enum RequestError { - /// The HTTP Method is not supported or it is invalid. - InvalidHttpMethod(&'static str), - /// Request URI is invalid. - InvalidUri(&'static str), - /// The HTTP Version in the Request is not supported or it is invalid. - InvalidHttpVersion(&'static str), - /// The header specified may be valid, but is not supported by this HTTP implementation. - UnsupportedHeader, - /// Header specified is invalid. - InvalidHeader, - /// The Request is invalid and cannot be served. - InvalidRequest, -} - -/// Errors associated with a HTTP Connection. -#[derive(Debug)] -pub enum ConnectionError { - /// The request parsing has failed. - ParseError(RequestError), - /// Could not perform a stream operation successfully. - StreamError(std::io::Error), - /// Attempted to read or write on a closed connection. - ConnectionClosed, - /// Attempted to write on a stream when there was nothing to write. - InvalidWrite, -} - -/// The Body associated with an HTTP Request or Response. -/// -/// ## Examples -/// ``` -/// extern crate micro_http; -/// use micro_http::Body; -/// let body = Body::new("This is a test body.".to_string()); -/// assert_eq!(body.raw(), b"This is a test body."); -/// assert_eq!(body.len(), 20); -/// ``` -#[derive(Clone, Debug, PartialEq)] -pub struct Body { - /// Body of the HTTP message as bytes. - pub body: Vec, -} - -impl Body { - /// Creates a new `Body` from a `String` input. - pub fn new>>(body: T) -> Self { - Body { body: body.into() } - } - - /// Returns the body as an `u8 slice`. - pub fn raw(&self) -> &[u8] { - self.body.as_slice() - } - - /// Returns the length of the `Body`. - pub fn len(&self) -> usize { - self.body.len() - } - - /// Checks if the body is empty, ie with zero length - pub fn is_empty(&self) -> bool { - self.body.len() == 0 - } -} - -/// Supported HTTP Methods. -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum Method { - /// GET Method. - Get, - /// PUT Method. - Put, - /// PATCH Method. - Patch, -} - -impl Method { - /// Returns a `Method` object if the parsing of `bytes` is successful. - /// - /// The method is case sensitive. A call to try_from with the input b"get" will return - /// an error, but when using the input b"GET", it returns Method::Get. - /// - /// # Errors - /// Returns `RequestError` if the method specified by `bytes` is unsupported. - pub fn try_from(bytes: &[u8]) -> Result { - match bytes { - b"GET" => Ok(Method::Get), - b"PUT" => Ok(Method::Put), - b"PATCH" => Ok(Method::Patch), - _ => Err(RequestError::InvalidHttpMethod("Unsupported HTTP method.")), - } - } - - /// Returns an `u8 slice` corresponding to the Method. - pub fn raw(self) -> &'static [u8] { - match self { - Method::Get => b"GET", - Method::Put => b"PUT", - Method::Patch => b"PATCH", - } - } -} - -/// Supported HTTP Versions. -/// -/// # Examples -/// ``` -/// extern crate micro_http; -/// use micro_http::Version; -/// let version = Version::try_from(b"HTTP/1.1"); -/// assert!(version.is_ok()); -/// -/// let version = Version::try_from(b"http/1.1"); -/// assert!(version.is_err()); -/// ``` -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum Version { - /// HTTP/1.0 - Http10, - /// HTTP/1.1 - Http11, -} - -impl Version { - /// HTTP Version as an `u8 slice`. - pub fn raw(self) -> &'static [u8] { - match self { - Version::Http10 => b"HTTP/1.0", - Version::Http11 => b"HTTP/1.1", - } - } - - /// Creates a new HTTP Version from an `u8 slice`. - /// - /// The supported versions are HTTP/1.0 and HTTP/1.1. - /// The version is case sensitive and the accepted input is upper case. - /// - /// # Errors - /// Returns a `RequestError` when the version is not supported. - /// - pub fn try_from(bytes: &[u8]) -> Result { - match bytes { - b"HTTP/1.0" => Ok(Version::Http10), - b"HTTP/1.1" => Ok(Version::Http11), - _ => Err(RequestError::InvalidHttpVersion( - "Unsupported HTTP version.", - )), - } - } - - /// Returns the default HTTP version = HTTP/1.1. - pub fn default() -> Self { - Version::Http11 - } -} - -#[cfg(test)] -mod tests { - use super::*; - - impl PartialEq for ConnectionError { - fn eq(&self, other: &Self) -> bool { - use self::ConnectionError::*; - match (self, other) { - (ParseError(_), ParseError(_)) => true, - (ConnectionClosed, ConnectionClosed) => true, - (StreamError(_), StreamError(_)) => true, - (InvalidWrite, InvalidWrite) => true, - _ => false, - } - } - } - - #[test] - fn test_version() { - // Tests for raw() - assert_eq!(Version::Http10.raw(), b"HTTP/1.0"); - assert_eq!(Version::Http11.raw(), b"HTTP/1.1"); - - // Tests for try_from() - assert_eq!(Version::try_from(b"HTTP/1.0").unwrap(), Version::Http10); - assert_eq!(Version::try_from(b"HTTP/1.1").unwrap(), Version::Http11); - assert_eq!( - Version::try_from(b"HTTP/2.0").unwrap_err(), - RequestError::InvalidHttpVersion("Unsupported HTTP version.") - ); - - // Test for default() - assert_eq!(Version::default(), Version::Http11); - } - - #[test] - fn test_method() { - // Test for raw - assert_eq!(Method::Get.raw(), b"GET"); - assert_eq!(Method::Put.raw(), b"PUT"); - assert_eq!(Method::Patch.raw(), b"PATCH"); - - // Tests for try_from - assert_eq!(Method::try_from(b"GET").unwrap(), Method::Get); - assert_eq!(Method::try_from(b"PUT").unwrap(), Method::Put); - assert_eq!(Method::try_from(b"PATCH").unwrap(), Method::Patch); - assert_eq!( - Method::try_from(b"POST").unwrap_err(), - RequestError::InvalidHttpMethod("Unsupported HTTP method.") - ); - } - - #[test] - fn test_body() { - let body = Body::new("".to_string()); - // Test for is_empty - assert!(body.is_empty()); - let body = Body::new("This is a body.".to_string()); - // Test for len - assert_eq!(body.len(), 15); - // Test for raw - assert_eq!(body.raw(), b"This is a body."); - } -} diff --git a/micro_http/src/connection.rs b/micro_http/src/connection.rs deleted file mode 100644 index 9cd8971ab..000000000 --- a/micro_http/src/connection.rs +++ /dev/null @@ -1,760 +0,0 @@ -// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -use std::collections::VecDeque; -use std::io::{Read, Write}; - -use common::ascii::{CR, CRLF_LEN, LF}; -use common::Body; -pub use common::{ConnectionError, RequestError}; -use headers::Headers; -use request::{find, Request, RequestLine}; -use response::{Response, StatusCode}; - -const BUFFER_SIZE: usize = 1024; - -/// Describes the state machine of an HTTP connection. -pub enum ConnectionState { - WaitingForRequestLine, - WaitingForHeaders, - WaitingForBody, - RequestReady, -} - -/// A wrapper over a HTTP Connection. -pub struct HttpConnection { - /// A partial request that is still being received. - pending_request: Option, - /// Stream implementing `Read` and `Write`, capable of sending and - /// receiving bytes. - stream: T, - /// The state of the connection regarding the current request that - /// is being processed. - state: ConnectionState, - /// Buffer where we store the bytes we read from the stream. - buffer: [u8; BUFFER_SIZE], - /// The index in the buffer from where we have to start reading in - /// the next `try_read` call. - read_cursor: usize, - /// Contains all bytes pertaining to the body of the request that - /// is currently being processed. - body_vec: Vec, - /// Represents how many bytes from the body of the request are still - /// to be read. - body_bytes_to_be_read: i32, - /// A queue of all requests that have been fully received and parsed. - parsed_requests: VecDeque, - /// A queue of requests that are waiting to be sent. - response_queue: VecDeque, - /// A buffer containing the bytes of a response that is currently - /// being sent. - response_buffer: Option>, -} - -impl HttpConnection { - /// Creates an empty connection. - pub fn new(stream: T) -> Self { - HttpConnection { - pending_request: None, - stream, - state: ConnectionState::WaitingForRequestLine, - buffer: [0; BUFFER_SIZE], - read_cursor: 0, - body_vec: vec![], - body_bytes_to_be_read: 0, - parsed_requests: VecDeque::new(), - response_queue: VecDeque::new(), - response_buffer: None, - } - } - - /// Tries to read new bytes from the stream and automatically update the request. - /// Meant to be used only with non-blocking streams and an `EPOLL` structure. - /// Should be called whenever an `EPOLLIN` event is signaled. - pub fn try_read(&mut self) -> Result<(), ConnectionError> { - // Read some bytes from the stream, which will be appended to what is already - // present in the buffer from a previous call of `try_read`. There are already - // `read_cursor` bytes present in the buffer. - let end_cursor = self.read_bytes()?; - - let mut line_start_index = 0; - loop { - match self.state { - ConnectionState::WaitingForRequestLine => { - if !self.parse_request_line(&mut line_start_index, end_cursor)? { - return Ok(()); - } - } - ConnectionState::WaitingForHeaders => { - if !self.parse_headers(&mut line_start_index, end_cursor)? { - return Ok(()); - } - } - ConnectionState::WaitingForBody => { - if !self.parse_body(&mut line_start_index, end_cursor)? { - return Ok(()); - } - } - ConnectionState::RequestReady => { - // This request is ready to be passed for handling. - // Update the state machine to expect a new request and push this request into - // the `parsed_requests` queue. - self.state = ConnectionState::WaitingForRequestLine; - self.body_bytes_to_be_read = 0; - self.parsed_requests - .push_back(self.pending_request.take().unwrap()); - } - }; - } - } - - // Reads a maximum of 1024 bytes from the stream into `buffer`. - // The return value represents the end index of what we have just appended. - fn read_bytes(&mut self) -> Result { - // Append new bytes to what we already have in the buffer. - let bytes_read = self - .stream - .read(&mut self.buffer[self.read_cursor..]) - .map_err(ConnectionError::StreamError)?; - - // If the read returned 0 then the client has closed the connection. - if bytes_read == 0 { - return Err(ConnectionError::ConnectionClosed); - } - - Ok(bytes_read + self.read_cursor) - } - - // Parses bytes in `buffer` for a valid request line. - // Returns `false` if there are no more bytes to be parsed in the buffer. - fn parse_request_line( - &mut self, - start: &mut usize, - end: usize, - ) -> Result { - match find(&self.buffer[*start..end], &[CR, LF]) { - Some(line_end_index) => { - let line = &self.buffer[*start..(*start + line_end_index)]; - - *start = *start + line_end_index + CRLF_LEN; - let request_line = - RequestLine::try_from(line).map_err(ConnectionError::ParseError)?; - - // Form the request with a valid request line, which is the bare minimum - // for a valid request. - self.pending_request = Some(Request { - request_line, - headers: Headers::default(), - body: None, - }); - self.state = ConnectionState::WaitingForHeaders; - Ok(true) - } - None => { - // The request line is longer than BUFFER_SIZE bytes, so the request is invalid. - if end == BUFFER_SIZE && *start == 0 { - return Err(ConnectionError::ParseError(RequestError::InvalidRequest)); - } else { - // Move the incomplete request line to the beginning of the buffer and wait - // for the next `try_read` call to complete it. - // This can only happen if another request was sent before this one, as the - // limit for the length of a request line in this implementation is 1024 bytes. - self.shift_buffer_left(*start, end); - } - Ok(false) - } - } - } - - // Parses bytes in `buffer` for header fields. - // Returns `false` if there are no more bytes to be parsed in the buffer. - fn parse_headers( - &mut self, - line_start_index: &mut usize, - end_cursor: usize, - ) -> Result { - match find(&self.buffer[*line_start_index..end_cursor], &[CR, LF]) { - // We have found the end of the headers. - // `line_start_index` points to the end of the most recently found CR LF - // sequence. That means that if we found the next CR LF sequence at this index, - // they are, in fact, a CR LF CR LF sequence, which marks the end of the header - // fields, per HTTP specification. - Some(0) => { - // If our current state is `WaitingForHeaders`, it means that we already have - // a valid request formed from a request line, so it's safe to unwrap. - let request = self.pending_request.as_mut().unwrap(); - if request.headers.content_length() == 0 { - self.state = ConnectionState::RequestReady; - } else { - if request.headers.expect() { - // Send expect. - let expect_response = - Response::new(request.http_version(), StatusCode::Continue); - self.response_queue.push_back(expect_response); - } - - self.body_bytes_to_be_read = request.headers.content_length(); - request.body = Some(Body::new(vec![])); - self.state = ConnectionState::WaitingForBody; - } - - // Update the index for the next header. - *line_start_index += CRLF_LEN; - Ok(true) - } - // We have found the end of a header line. - Some(relative_line_end_index) => { - let request = self.pending_request.as_mut().unwrap(); - // The `line_end_index` relative to the whole buffer. - let line_end_index = relative_line_end_index + *line_start_index; - - // Get the line slice and parse it. - let line = &self.buffer[*line_start_index..line_end_index]; - match request.headers.parse_header_line(line) { - // If a header is unsupported we ignore it. - Ok(_) | Err(RequestError::UnsupportedHeader) => {} - // If parsing the header invalidates the request, we propagate - // the error. - Err(e) => return Err(ConnectionError::ParseError(e)), - }; - - // Update the `line_start_index` to where we finished parsing. - *line_start_index = line_end_index + CRLF_LEN; - Ok(true) - } - // If we have an incomplete header line. - None => { - // If we have parsed BUFFER_SIZE bytes and still haven't found the header - // line end sequence. - if *line_start_index == 0 && end_cursor == BUFFER_SIZE { - // Header line is longer than BUFFER_SIZE bytes, so it is invalid. - return Err(ConnectionError::ParseError(RequestError::InvalidHeader)); - } - // Move the incomplete header line from the end of the buffer to - // the beginning, so that we can append the rest of the line and - // parse it in the next `try_read` call. - self.shift_buffer_left(*line_start_index, end_cursor); - Ok(false) - } - } - } - - // Parses bytes in `buffer` to be put into the request body, if there should be one. - // Returns `false` if there are no more bytes to be parsed in the buffer. - fn parse_body( - &mut self, - line_start_index: &mut usize, - end_cursor: usize, - ) -> Result { - // If what we have just read is not enough to complete the request and - // there are more bytes pertaining to the body of the request. - if self.body_bytes_to_be_read > end_cursor as i32 - *line_start_index as i32 { - // Append everything that we read to our current incomplete body and update - // `body_bytes_to_be_read`. - self.body_vec - .extend_from_slice(&self.buffer[*line_start_index..end_cursor]); - self.body_bytes_to_be_read -= end_cursor as i32 - *line_start_index as i32; - - // Clear the buffer and reset the starting index. - for i in 0..BUFFER_SIZE { - self.buffer[i] = 0; - } - self.read_cursor = 0; - - return Ok(false); - } - - // Append only the remaining necessary bytes to the body of the request. - self.body_vec.extend_from_slice( - &self.buffer - [*line_start_index..(*line_start_index + self.body_bytes_to_be_read as usize)], - ); - *line_start_index += self.body_bytes_to_be_read as usize; - self.body_bytes_to_be_read = 0; - - let request = self.pending_request.as_mut().unwrap(); - // If there are no more bytes to be read for this request. - // Assign the body of the request. - let placeholder: Vec<_> = self - .body_vec - .drain(..request.headers.content_length() as usize) - .collect(); - request.body = Some(Body::new(placeholder)); - - // If we read more bytes than we should have into the body of the request. - if !self.body_vec.is_empty() { - return Err(ConnectionError::ParseError(RequestError::InvalidRequest)); - } - - self.state = ConnectionState::RequestReady; - Ok(true) - } - - /// Tries to write the first available response to the provided stream. - /// Meant to be used only with non-blocking streams and an `EPOLL` structure. - /// Should be called whenever an `EPOLLOUT` event is signaled. - pub fn try_write(&mut self) -> Result<(), ConnectionError> { - if self.response_buffer.is_none() { - if let Some(response) = self.response_queue.pop_front() { - let mut response_buffer_vec: Vec = Vec::new(); - response - .write_all(&mut response_buffer_vec) - .map_err(ConnectionError::StreamError)?; - self.response_buffer = Some(response_buffer_vec); - } else { - return Err(ConnectionError::InvalidWrite); - } - } - - let mut response_fully_written = false; - - if let Some(response_buffer_vec) = self.response_buffer.as_mut() { - let bytes_to_be_written = response_buffer_vec.len(); - match self.stream.write(response_buffer_vec.as_slice()) { - Ok(0) => { - return Err(ConnectionError::ConnectionClosed); - } - Ok(bytes_written) => { - if bytes_written != bytes_to_be_written { - response_buffer_vec.drain(..bytes_written); - } else { - response_fully_written = true; - } - } - Err(io_error) => { - return Err(ConnectionError::StreamError(io_error)); - } - } - } - - if response_fully_written { - self.response_buffer.take(); - } - - Ok(()) - } - - /// Send a response back to the source of a request. - pub fn enqueue_response(&mut self, response: Response) { - self.response_queue.push_back(response); - } - - fn shift_buffer_left(&mut self, line_start_index: usize, end_cursor: usize) { - // We don't want to shift something that is already at the beginning. - if line_start_index != 0 { - // Move the bytes from `line_start_index` to the beginning of the buffer. - for cursor in 0..(end_cursor - line_start_index) { - self.buffer[cursor] = self.buffer[line_start_index + cursor]; - } - - // Clear the rest of the buffer. - for cursor in (end_cursor - line_start_index)..end_cursor { - self.buffer[cursor] = 0; - } - } - - // Update `read_cursor`. - self.read_cursor = end_cursor - line_start_index; - } - - /// Returns the first parsed request in the queue. - pub fn pop_parsed_request(&mut self) -> Option { - self.parsed_requests.pop_front() - } -} - -#[cfg(test)] -mod tests { - use super::*; - use common::{Method, Version}; - use std::os::unix::net::UnixStream; - - #[test] - fn test_try_read_expect() { - // Test request with `Expect` header. - let (mut sender, receiver) = UnixStream::pair().unwrap(); - receiver.set_nonblocking(true).expect("Can't modify socket"); - let mut conn = HttpConnection::new(receiver); - sender - .write_all( - b"PATCH http://localhost/home HTTP/1.1\r\n \ - Expect: 100-continue\r\n \ - Content-Length: 26\r\n \ - Transfer-Encoding: chunked\r\n\r\n", - ) - .unwrap(); - assert!(conn.try_read().is_ok()); - - sender.write_all(b"this is not\n\r\na json \nbody").unwrap(); - conn.try_read().unwrap(); - let request = conn.pop_parsed_request().unwrap(); - - let expected_request = Request { - request_line: RequestLine::new(Method::Patch, "http://localhost/home", Version::Http11), - headers: Headers::new(26, true, true), - body: Some(Body::new(b"this is not\n\r\na json \nbody".to_vec())), - }; - - assert_eq!(request, expected_request); - } - - #[test] - fn test_try_read_long_headers() { - // Long request headers. - let (mut sender, receiver) = UnixStream::pair().unwrap(); - receiver.set_nonblocking(true).expect("Can't modify socket"); - let mut conn = HttpConnection::new(receiver); - sender - .write_all( - b"PATCH http://localhost/home HTTP/1.1\r\n \ - Expect: 100-continue\r\n \ - Transfer-Encoding: chunked\r\n", - ) - .unwrap(); - - for i in 0..90 { - sender.write_all(b"Custom-Header-Testing: 1").unwrap(); - sender.write_all(i.to_string().as_bytes()).unwrap(); - sender.write_all(b"\r\n").unwrap(); - } - sender - .write_all(b"Content-Length: 26\r\n\r\nthis is not\n\r\na json \nbody") - .unwrap(); - assert!(conn.try_read().is_ok()); - assert!(conn.try_read().is_ok()); - assert!(conn.try_read().is_ok()); - let request = conn.pop_parsed_request().unwrap(); - - let expected_request = Request { - request_line: RequestLine::new(Method::Patch, "http://localhost/home", Version::Http11), - headers: Headers::new(26, true, true), - body: Some(Body::new(b"this is not\n\r\na json \nbody".to_vec())), - }; - assert_eq!(request, expected_request); - } - - #[test] - fn test_try_read_split_ending() { - // Long request with '\r\n' on BUFFER_SIZEth and 1025th positions in the request. - let (mut sender, receiver) = UnixStream::pair().unwrap(); - receiver.set_nonblocking(true).expect("Can't modify socket"); - let mut conn = HttpConnection::new(receiver); - sender - .write_all( - b"PATCH http://localhost/home HTTP/1.1\r\n \ - Expect: 100-continue\r\n \ - Transfer-Encoding: chunked\r\n", - ) - .unwrap(); - - for i in 0..32 { - sender.write_all(b"Custom-Header-Testing: 1").unwrap(); - sender.write_all(i.to_string().as_bytes()).unwrap(); - sender.write_all(b"\r\n").unwrap(); - } - sender - .write_all(b"Head: aaaaa\r\nContent-Length: 26\r\n\r\nthis is not\n\r\na json \nbody") - .unwrap(); - assert!(conn.try_read().is_ok()); - conn.try_read().unwrap(); - let request = conn.pop_parsed_request().unwrap(); - let expected_request = Request { - request_line: RequestLine::new(Method::Patch, "http://localhost/home", Version::Http11), - headers: Headers::new(26, true, true), - body: Some(Body::new(b"this is not\n\r\na json \nbody".to_vec())), - }; - assert_eq!(request, expected_request); - } - - #[test] - fn test_try_read_invalid_request() { - // Invalid request. - let (mut sender, receiver) = UnixStream::pair().unwrap(); - receiver.set_nonblocking(true).expect("Can't modify socket"); - let mut conn = HttpConnection::new(receiver); - sender - .write_all( - b"PATCH http://localhost/home HTTP/1.1\r\n \ - Expect: 100-continue\r\n \ - Transfer-Encoding: chunked\r\n", - ) - .unwrap(); - - for i in 0..40 { - sender.write_all(b"Custom-Header-Testing: 1").unwrap(); - sender.write_all(i.to_string().as_bytes()).unwrap(); - sender.write_all(b"\r\n").unwrap(); - } - sender - .write_all(b"Content-Length: alpha\r\n\r\nthis is not\n\r\na json \nbody") - .unwrap(); - assert!(conn.try_read().is_ok()); - let request_error = conn.try_read().unwrap_err(); - assert_eq!( - request_error, - ConnectionError::ParseError(RequestError::InvalidHeader) - ); - } - - #[test] - fn test_try_read_long_request_body() { - // Long request body. - let (mut sender, receiver) = UnixStream::pair().unwrap(); - receiver.set_nonblocking(true).expect("Can't modify socket"); - let mut conn = HttpConnection::new(receiver); - sender - .write_all( - b"PATCH http://localhost/home HTTP/1.1\r\n \ - Expect: 100-continue\r\n \ - Transfer-Encoding: chunked\r\n \ - Content-Length: 1400\r\n\r\n", - ) - .unwrap(); - - let mut request_body: Vec = Vec::with_capacity(1400); - for _ in 0..100 { - request_body.write_all(b"This is a test").unwrap(); - } - sender.write_all(request_body.as_slice()).unwrap(); - assert!(conn.try_read().is_ok()); - conn.try_read().unwrap(); - let request = conn.pop_parsed_request().unwrap(); - let expected_request = Request { - request_line: RequestLine::new(Method::Patch, "http://localhost/home", Version::Http11), - headers: Headers::new(1400, true, true), - body: Some(Body::new(request_body)), - }; - assert_eq!(request, expected_request); - } - - #[test] - fn test_try_read_large_req_line() { - // Request line longer than BUFFER_SIZE bytes. - let (mut sender, receiver) = UnixStream::pair().unwrap(); - receiver.set_nonblocking(true).expect("Can't modify socket"); - let mut conn = HttpConnection::new(receiver); - sender.write_all(b"PATCH http://localhost/home").unwrap(); - - let mut request_body: Vec = Vec::with_capacity(1400); - for _ in 0..200 { - request_body.write_all(b"/home").unwrap(); - } - sender.write_all(request_body.as_slice()).unwrap(); - assert_eq!( - conn.try_read().unwrap_err(), - ConnectionError::ParseError(RequestError::InvalidRequest) - ); - } - - #[test] - fn test_try_read_large_header_line() { - // Header line longer than BUFFER_SIZE bytes. - let (mut sender, receiver) = UnixStream::pair().unwrap(); - receiver.set_nonblocking(true).expect("Can't modify socket"); - let mut conn = HttpConnection::new(receiver); - sender - .write_all(b"PATCH http://localhost/home HTTP/1.1\r\nhead: ") - .unwrap(); - - let mut request_body: Vec = Vec::with_capacity(1030); - for _ in 0..86 { - request_body.write_all(b"abcdefghijkl").unwrap(); - } - request_body.write_all(b"\r\n\r\n").unwrap(); - sender.write_all(request_body.as_slice()).unwrap(); - assert!(conn.try_read().is_ok()); - assert_eq!( - conn.try_read().unwrap_err(), - ConnectionError::ParseError(RequestError::InvalidHeader) - ); - } - - #[test] - fn test_try_read_no_body_request() { - // Request without body. - let (mut sender, receiver) = UnixStream::pair().unwrap(); - receiver.set_nonblocking(true).expect("Can't modify socket"); - let mut conn = HttpConnection::new(receiver); - sender - .write_all( - b"PATCH http://localhost/home HTTP/1.1\r\n \ - Expect: 100-continue\r\n \ - Transfer-Encoding: chunked\r\n\r\n", - ) - .unwrap(); - conn.try_read().unwrap(); - let request = conn.pop_parsed_request().unwrap(); - let expected_request = Request { - request_line: RequestLine::new(Method::Patch, "http://localhost/home", Version::Http11), - headers: Headers::new(0, true, true), - body: None, - }; - assert_eq!(request, expected_request); - } - - #[test] - fn test_try_read_segmented_req_line() { - // Segmented request line. - let (mut sender, receiver) = UnixStream::pair().unwrap(); - receiver.set_nonblocking(true).expect("Can't modify socket"); - let mut conn = HttpConnection::new(receiver); - sender.write_all(b"PATCH http://local").unwrap(); - assert!(conn.try_read().is_ok()); - - sender.write_all(b"host/home HTTP/1.1\r\n\r\n").unwrap(); - - conn.try_read().unwrap(); - let request = conn.pop_parsed_request().unwrap(); - let expected_request = Request { - request_line: RequestLine::new(Method::Patch, "http://localhost/home", Version::Http11), - headers: Headers::new(0, false, false), - body: None, - }; - assert_eq!(request, expected_request); - } - - #[test] - fn test_try_read_long_req_line_b2b() { - // Long request line after another request. - let (mut sender, receiver) = UnixStream::pair().unwrap(); - receiver.set_nonblocking(true).expect("Can't modify socket"); - let mut conn = HttpConnection::new(receiver); - // Req line 23 + 10*x + 13 = 36 + 10* x 984 free in first try read - sender - .write_all(b"PATCH http://localhost/home HTTP/1.1\r\n\r\nPATCH http://localhost/") - .unwrap(); - - let mut request_line: Vec = Vec::with_capacity(980); - for _ in 0..98 { - request_line.write_all(b"localhost/").unwrap(); - } - request_line.write_all(b" HTTP/1.1\r\n\r\n").unwrap(); - sender.write_all(request_line.as_slice()).unwrap(); - - conn.try_read().unwrap(); - let request = conn.pop_parsed_request().unwrap(); - let expected_request = Request { - request_line: RequestLine::new(Method::Patch, "http://localhost/home", Version::Http11), - headers: Headers::new(0, false, false), - body: None, - }; - assert_eq!(request, expected_request); - - conn.try_read().unwrap(); - let request = conn.pop_parsed_request().unwrap(); - let mut expected_request_as_bytes = Vec::new(); - expected_request_as_bytes - .write_all(b"http://localhost/") - .unwrap(); - expected_request_as_bytes.append(request_line.as_mut()); - let expected_request = Request { - request_line: RequestLine::new( - Method::Patch, - std::str::from_utf8(&expected_request_as_bytes[..997]).unwrap(), - Version::Http11, - ), - headers: Headers::new(0, false, false), - body: None, - }; - assert_eq!(request, expected_request); - } - - #[test] - fn test_try_read_double_request() { - // Double request in a single read. - let (mut sender, receiver) = UnixStream::pair().unwrap(); - receiver.set_nonblocking(true).expect("Can't modify socket"); - let mut conn = HttpConnection::new(receiver); - sender - .write_all( - b"PATCH http://localhost/home HTTP/1.1\r\n \ - Transfer-Encoding: chunked\r\n \ - Content-Length: 26\r\n\r\nthis is not\n\r\na json \nbody", - ) - .unwrap(); - sender - .write_all( - b"PUT http://farhost/away HTTP/1.1\r\nContent-Length: 23\r\n\r\nthis is another request", - ) - .unwrap(); - - let expected_request_first = Request { - request_line: RequestLine::new(Method::Patch, "http://localhost/home", Version::Http11), - headers: Headers::new(26, false, true), - body: Some(Body::new(b"this is not\n\r\na json \nbody".to_vec())), - }; - - conn.try_read().unwrap(); - let request_first = conn.pop_parsed_request().unwrap(); - let request_second = conn.pop_parsed_request().unwrap(); - - let expected_request_second = Request { - request_line: RequestLine::new(Method::Put, "http://farhost/away", Version::Http11), - headers: Headers::new(23, false, false), - body: Some(Body::new(b"this is another request".to_vec())), - }; - assert_eq!(request_first, expected_request_first); - assert_eq!(request_second, expected_request_second); - } - - #[test] - fn test_try_read_connection_closed() { - // Connection abruptly closed. - let (mut sender, receiver) = UnixStream::pair().unwrap(); - receiver.set_nonblocking(true).expect("Can't modify socket"); - let mut conn = HttpConnection::new(receiver); - sender - .write_all( - b"PATCH http://localhost/home HTTP/1.1\r\n \ - Transfer-Encoding: chunked\r\n \ - Content-Len", - ) - .unwrap(); - - conn.try_read().unwrap(); - sender.shutdown(std::net::Shutdown::Both).unwrap(); - - assert_eq!( - conn.try_read().unwrap_err(), - ConnectionError::ConnectionClosed - ); - } - - #[test] - fn test_enqueue_response() { - // Response without body. - let (sender, mut receiver) = UnixStream::pair().unwrap(); - receiver.set_nonblocking(true).expect("Can't modify socket"); - let mut conn = HttpConnection::new(sender); - - let response = Response::new(Version::Http11, StatusCode::OK); - let mut expected_response: Vec = vec![]; - response.write_all(&mut expected_response).unwrap(); - - conn.enqueue_response(response); - assert!(conn.try_write().is_ok()); - - let mut response_buffer = vec![0u8; expected_response.len()]; - receiver.read_exact(&mut response_buffer).unwrap(); - assert_eq!(response_buffer, expected_response); - - // Response with body. - let (sender, mut receiver) = UnixStream::pair().unwrap(); - receiver.set_nonblocking(true).expect("Can't modify socket"); - let mut conn = HttpConnection::new(sender); - let mut response = Response::new(Version::Http11, StatusCode::OK); - let mut body: Vec = vec![]; - body.write_all(br#"{ "json": "body", "hello": "world" }"#) - .unwrap(); - response.set_body(Body::new(body)); - let mut expected_response: Vec = vec![]; - response.write_all(&mut expected_response).unwrap(); - - conn.enqueue_response(response); - assert!(conn.try_write().is_ok()); - - let mut response_buffer = vec![0u8; expected_response.len()]; - receiver.read_exact(&mut response_buffer).unwrap(); - assert_eq!(response_buffer, expected_response); - } -} diff --git a/micro_http/src/lib.rs b/micro_http/src/lib.rs deleted file mode 100644 index 69a75246c..000000000 --- a/micro_http/src/lib.rs +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 -#![deny(missing_docs)] -//! Minimal implementation of the [HTTP/1.0](https://tools.ietf.org/html/rfc1945) -//! and [HTTP/1.1](https://www.ietf.org/rfc/rfc2616.txt) protocols. -//! -//! HTTP/1.1 has a mandatory header **Host**, but as this crate is only used -//! for parsing MMDS requests, this header (if present) is ignored. -//! -//! This HTTP implementation is stateless thus it does not support chunking or -//! compression. -//! -//! ## Supported Headers -//! The **micro_http** crate has support for parsing the following **Request** -//! headers: -//! - Content-Length -//! - Expect -//! - Transfer-Encoding -//! -//! The **Response** does not have a public interface for adding headers, but whenever -//! a write to the **Body** is made, the headers **ContentLength** and **MediaType** -//! are automatically updated. -//! -//! ### Media Types -//! The supported media types are: -//! - text/plain -//! - application/json -//! -//! ## Supported Methods -//! The supported HTTP Methods are: -//! - GET -//! - PUT -//! - PATCH -//! -//! ## Supported Status Codes -//! The supported status codes are: -//! -//! - Continue - 100 -//! - OK - 200 -//! - No Content - 204 -//! - Bad Request - 400 -//! - Not Found - 404 -//! - Internal Server Error - 500 -//! - Not Implemented - 501 -//! -//! ## Example for parsing an HTTP Request from a slice -//! ``` -//! extern crate micro_http; -//! use micro_http::{Request, Version}; -//! -//! let http_request = Request::try_from(b"GET http://localhost/home HTTP/1.0\r\n\r\n").unwrap(); -//! assert_eq!(http_request.http_version(), Version::Http10); -//! assert_eq!(http_request.uri().get_abs_path(), "/home"); -//! ``` -//! -//! ## Example for creating an HTTP Response -//! ``` -//! extern crate micro_http; -//! use micro_http::{Body, Response, StatusCode, Version}; -//! -//! let mut response = Response::new(Version::Http10, StatusCode::OK); -//! let body = String::from("This is a test"); -//! response.set_body(Body::new(body.clone())); -//! -//! assert!(response.status() == StatusCode::OK); -//! assert_eq!(response.body().unwrap(), Body::new(body)); -//! assert_eq!(response.http_version(), Version::Http10); -//! -//! let mut response_buf: [u8; 126] = [0; 126]; -//! assert!(response.write_all(&mut response_buf.as_mut()).is_ok()); -//! ``` - -mod common; -mod connection; -mod request; -mod response; -use common::ascii; -use common::headers; - -pub use connection::HttpConnection; -pub use request::{Request, RequestError}; -pub use response::{Response, StatusCode}; - -pub use common::headers::Headers; -pub use common::{Body, Method, Version}; diff --git a/micro_http/src/request.rs b/micro_http/src/request.rs deleted file mode 100644 index b533250dc..000000000 --- a/micro_http/src/request.rs +++ /dev/null @@ -1,474 +0,0 @@ -// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -use std::str::from_utf8; - -use common::ascii::{CR, CRLF_LEN, LF, SP}; -pub use common::RequestError; -use common::{Body, Method, Version}; -use headers::Headers; - -/// Finds the first occurence of `sequence` in the `bytes` slice. -/// -/// Returns the starting position of the `sequence` in `bytes` or `None` if the -/// `sequence` is not found. -pub 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 { - Uri { - 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(Uri::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. - pub fn try_from(request_line: &[u8]) -> Result { - let (method, uri, version) = RequestLine::parse_request_line(request_line); - - Ok(RequestLine { - 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 - } - - #[cfg(test)] - pub fn new(method: Method, uri: &str, http_version: Version) -> Self { - RequestLine { - method, - uri: Uri::new(uri), - http_version, - } - } -} - -/// Wrapper over an HTTP Request. -#[allow(unused)] -#[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 request headers and the entity body is not parsed and None is returned because - /// these are not used by the MMDS server. - /// 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"); - /// ``` - 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(Request { - 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(Request { - 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: &Request) -> 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() - } - } - - #[test] - fn test_uri() { - let uri = Uri::new("http://localhost/home"); - assert_eq!(uri.get_abs_path(), "/home"); - - let uri = Uri::new("/home"); - assert_eq!(uri.get_abs_path(), "/home"); - - let uri = Uri::new("home"); - assert_eq!(uri.get_abs_path(), ""); - - let uri = Uri::new("http://"); - assert_eq!(uri.get_abs_path(), ""); - - let uri = Uri::new("http://192.168.0.0"); - assert_eq!(uri.get_abs_path(), ""); - } - - #[test] - fn test_find() { - let bytes: &[u8; 13] = b"abcacrgbabsjl"; - let i = find(&bytes[..], b"ac"); - assert_eq!(i.unwrap(), 3); - - let i = find(&bytes[..], b"rgb"); - assert_eq!(i.unwrap(), 5); - - let i = find(&bytes[..], b"ab"); - assert_eq!(i.unwrap(), 0); - - let i = find(&bytes[..], b"l"); - assert_eq!(i.unwrap(), 12); - - let i = find(&bytes[..], b"jle"); - assert!(i.is_none()); - - let i = find(&bytes[..], b"asdkjhasjhdjhgsadg"); - assert!(i.is_none()); - - let i = find(&bytes[..], b"abcacrgbabsjl"); - assert_eq!(i.unwrap(), 0); - } - - #[test] - // Allow assertions on constants so we can have asserts on the values returned - // when result is Ok. - #[allow(clippy::assertions_on_constants)] - 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"; - match RequestLine::try_from(request_line) { - Ok(request) => assert_eq!(request, expected_request_line), - Err(_) => assert!(false), - }; - - 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"; - match RequestLine::try_from(request_line) { - Ok(request) => assert_eq!(request, expected_request_line), - Err(_) => assert!(false), - }; - - // Happy case with request line ending in LF instead of CRLF. - let request_line = b"GET http://localhost/home HTTP/1.1"; - match RequestLine::try_from(request_line) { - Ok(request) => assert_eq!(request, expected_request_line), - Err(_) => assert!(false), - }; - - // 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()); - } -} diff --git a/micro_http/src/response.rs b/micro_http/src/response.rs deleted file mode 100644 index 160c716c8..000000000 --- a/micro_http/src/response.rs +++ /dev/null @@ -1,284 +0,0 @@ -// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -use std::io::{Error as WriteError, Write}; - -use ascii::{COLON, CR, LF, SP}; -use common::{Body, Version}; -use headers::{Header, MediaType}; - -/// Wrapper over a response status code. -/// -/// The status code is defined as specified in the -/// [RFC](https://tools.ietf.org/html/rfc7231#section-6). -#[allow(dead_code)] -#[derive(Clone, Copy, PartialEq)] -pub enum StatusCode { - /// 100, Continue - Continue, - /// 200, OK - OK, - /// 204, No Content - NoContent, - /// 400, Bad Request - BadRequest, - /// 404, Not Found - NotFound, - /// 500, Internal Server Error - InternalServerError, - /// 501, Not Implemented - NotImplemented, -} - -impl StatusCode { - fn raw(self) -> &'static [u8; 3] { - match self { - StatusCode::Continue => b"100", - StatusCode::OK => b"200", - StatusCode::NoContent => b"204", - StatusCode::BadRequest => b"400", - StatusCode::NotFound => b"404", - StatusCode::InternalServerError => b"500", - StatusCode::NotImplemented => b"501", - } - } -} - -struct StatusLine { - http_version: Version, - status_code: StatusCode, -} - -impl StatusLine { - fn new(http_version: Version, status_code: StatusCode) -> Self { - StatusLine { - http_version, - status_code, - } - } - - fn write_all(&self, mut buf: T) -> Result<(), WriteError> { - buf.write_all(self.http_version.raw())?; - buf.write_all(&[SP])?; - buf.write_all(self.status_code.raw())?; - buf.write_all(&[SP, CR, LF])?; - - Ok(()) - } -} - -/// Wrapper over the list of headers associated with a HTTP Response. -/// When creating a ResponseHeaders object, the content type is initialized to `text/plain`. -/// The content type can be updated with a call to `set_content_type`. -pub struct ResponseHeaders { - content_length: i32, - content_type: MediaType, - server: String, -} - -impl Default for ResponseHeaders { - fn default() -> Self { - ResponseHeaders { - content_length: Default::default(), - content_type: Default::default(), - server: "Firecracker API".to_string(), - } - } -} - -impl ResponseHeaders { - /// Writes the headers to `buf` using the HTTP specification. - pub fn write_all(&self, buf: &mut T) -> Result<(), WriteError> { - buf.write_all(Header::Server.raw())?; - buf.write_all(&[COLON, SP])?; - buf.write_all(self.server.as_bytes())?; - - buf.write_all(&[CR, LF])?; - buf.write_all(b"Connection: keep-alive")?; - buf.write_all(&[CR, LF])?; - - buf.write_all(Header::ContentType.raw())?; - buf.write_all(&[COLON, SP])?; - buf.write_all(self.content_type.as_str().as_bytes())?; - buf.write_all(&[CR, LF])?; - - if self.content_length != 0 { - buf.write_all(Header::ContentLength.raw())?; - buf.write_all(&[COLON, SP])?; - buf.write_all(self.content_length.to_string().as_bytes())?; - buf.write_all(&[CR, LF])?; - } - - buf.write_all(&[CR, LF]) - } - - // Sets the content length to be written in the HTTP response. - fn set_content_length(&mut self, content_length: i32) { - self.content_length = content_length; - } - - /// Sets the HTTP response header server. - pub fn set_server(&mut self, server: &str) { - self.server = String::from(server); - } - - /// Sets the content type to be written in the HTTP response. - #[allow(unused)] - pub fn set_content_type(&mut self, content_type: MediaType) { - self.content_type = content_type; - } -} - -/// Wrapper over an HTTP Response. -/// -/// The Response is created using a `Version` and a `StatusCode`. When creating a Response object, -/// the body is initialized to `None`. The body can be updated with a call to `set_body`. -pub struct Response { - status_line: StatusLine, - headers: ResponseHeaders, - body: Option, -} - -impl Response { - /// Creates a new HTTP `Response` with an empty body. - pub fn new(http_version: Version, status_code: StatusCode) -> Response { - Response { - status_line: StatusLine::new(http_version, status_code), - headers: ResponseHeaders::default(), - body: None, - } - } - - /// Updates the body of the `Response`. - /// - /// This function has side effects because it also updates the headers: - /// - `ContentLength`: this is set to the length of the specified body. - /// - `MediaType`: this is set to "text/plain". - pub fn set_body(&mut self, body: Body) { - self.headers.set_content_length(body.len() as i32); - self.body = Some(body); - } - - /// Sets the HTTP response server. - pub fn set_server(&mut self, server: &str) { - self.headers.set_server(server); - } - - fn write_body(&self, mut buf: T) -> Result<(), WriteError> { - if let Some(ref body) = self.body { - buf.write_all(body.raw())?; - } - Ok(()) - } - - /// Writes the content of the `Response` to the specified `buf`. - /// - /// # Errors - /// Returns an error when the buffer is not large enough. - pub fn write_all(&self, mut buf: &mut T) -> Result<(), WriteError> { - self.status_line.write_all(&mut buf)?; - self.headers.write_all(&mut buf)?; - self.write_body(&mut buf)?; - - Ok(()) - } - - /// Returns the Status Code of the Response. - pub fn status(&self) -> StatusCode { - self.status_line.status_code - } - - /// Returns the Body of the response. If the response does not have a body, - /// it returns None. - pub fn body(&self) -> Option { - self.body.clone() - } - - /// Returns the HTTP Version of the response. - pub fn content_length(&self) -> i32 { - self.headers.content_length - } - - /// Returns the HTTP Version of the response. - pub fn content_type(&self) -> MediaType { - self.headers.content_type - } - - /// Returns the HTTP Version of the response. - pub fn http_version(&self) -> Version { - self.status_line.http_version - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_write_response() { - let mut response = Response::new(Version::Http10, StatusCode::OK); - let body = "This is a test"; - response.set_body(Body::new(body)); - - assert!(response.status() == StatusCode::OK); - assert_eq!(response.body().unwrap(), Body::new(body)); - assert_eq!(response.http_version(), Version::Http10); - assert_eq!(response.content_length(), 14); - assert_eq!(response.content_type(), MediaType::PlainText); - - let expected_response: &'static [u8] = b"HTTP/1.0 200 \r\n\ - Server: Firecracker API\r\n\ - Connection: keep-alive\r\n\ - Content-Type: text/plain\r\n\ - Content-Length: 14\r\n\r\n\ - This is a test"; - - let mut response_buf: [u8; 126] = [0; 126]; - assert!(response.write_all(&mut response_buf.as_mut()).is_ok()); - assert!(response_buf.as_ref() == expected_response); - - // Test write failed. - let mut response_buf: [u8; 1] = [0; 1]; - assert!(response.write_all(&mut response_buf.as_mut()).is_err()); - } - - #[test] - fn test_set_server() { - let mut response = Response::new(Version::Http10, StatusCode::OK); - let body = "This is a test"; - let server = "rust-vmm API"; - response.set_body(Body::new(body)); - response.set_server(server); - - assert!(response.status() == StatusCode::OK); - assert_eq!(response.body().unwrap(), Body::new(body)); - assert_eq!(response.http_version(), Version::Http10); - assert_eq!(response.content_length(), 14); - assert_eq!(response.content_type(), MediaType::PlainText); - - let expected_response = format!( - "HTTP/1.0 200 \r\n\ - Server: {}\r\n\ - Connection: keep-alive\r\n\ - Content-Type: text/plain\r\n\ - Content-Length: 14\r\n\r\n\ - This is a test", - server - ); - - let mut response_buf: [u8; 123] = [0; 123]; - assert!(response.write_all(&mut response_buf.as_mut()).is_ok()); - assert!(response_buf.as_ref() == expected_response.as_bytes()); - } - - #[test] - fn test_status_code() { - assert_eq!(StatusCode::Continue.raw(), b"100"); - assert_eq!(StatusCode::OK.raw(), b"200"); - assert_eq!(StatusCode::NoContent.raw(), b"204"); - assert_eq!(StatusCode::BadRequest.raw(), b"400"); - assert_eq!(StatusCode::NotFound.raw(), b"404"); - assert_eq!(StatusCode::InternalServerError.raw(), b"500"); - assert_eq!(StatusCode::NotImplemented.raw(), b"501"); - } -} diff --git a/vmm/Cargo.toml b/vmm/Cargo.toml index 6c4109ded..b18f3ec68 100644 --- a/vmm/Cargo.toml +++ b/vmm/Cargo.toml @@ -21,7 +21,7 @@ kvm-ioctls = { git = "https://github.com/rust-vmm/kvm-ioctls", branch = "master" lazy_static = "1.4.0" libc = "0.2.62" log = "0.4.8" -micro_http = { path = "../micro_http" } +micro_http = { git = "https://github.com/firecracker-microvm/firecracker", branch = "master" } net_util = { path = "../net_util" } pci = {path = "../pci", optional = true} qcow = { path = "../qcow" }