// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use std::fmt::{Display, Error, Formatter}; use std::str::Utf8Error; 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 a header that is invalid. #[derive(Debug, PartialEq, Eq)] pub enum HttpHeaderError { /// The header is misformatted. InvalidFormat(String), /// The specified header contains illegal characters. InvalidUtf8String(Utf8Error), ///The value specified is not valid. InvalidValue(String, String), /// The content length specified is longer than the limit imposed by Micro Http. SizeLimitExceeded(String), /// The requested feature is not currently supported. UnsupportedFeature(String, String), /// The header specified is not supported. UnsupportedName(String), /// The value for the specified header is not supported. UnsupportedValue(String, String), } impl Display for HttpHeaderError { fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { match self { Self::InvalidFormat(header_key) => { write!(f, "Header is incorrectly formatted. Key: {}", header_key) } Self::InvalidUtf8String(header_key) => { write!(f, "Header contains invalid characters. Key: {}", header_key) } Self::InvalidValue(header_name, value) => { write!(f, "Invalid value. Key:{}; Value:{}", header_name, value) } Self::SizeLimitExceeded(inner) => { write!(f, "Invalid content length. Header: {}", inner) } Self::UnsupportedFeature(header_key, header_value) => write!( f, "Unsupported feature. Key: {}; Value: {}", header_key, header_value ), Self::UnsupportedName(inner) => write!(f, "Unsupported header name. Key: {}", inner), Self::UnsupportedValue(header_key, header_value) => write!( f, "Unsupported value. Key:{}; Value:{}", header_key, header_value ), } } } /// Errors associated with parsing the HTTP Request from a u8 slice. #[derive(Debug, PartialEq, Eq)] pub enum RequestError { /// No request was pending while the request body was being parsed. BodyWithoutPendingRequest, /// Header specified is either invalid or not supported by this HTTP implementation. HeaderError(HttpHeaderError), /// No request was pending while the request headers were being parsed. HeadersWithoutPendingRequest, /// The HTTP Method is not supported or it is invalid. InvalidHttpMethod(&'static str), /// The HTTP Version in the Request is not supported or it is invalid. InvalidHttpVersion(&'static str), /// The Request is invalid and cannot be served. InvalidRequest, /// Request URI is invalid. InvalidUri(&'static str), /// Overflow occurred when parsing a request. Overflow, /// Underflow occurred when parsing a request. Underflow, /// Payload too large. SizeLimitExceeded(usize, usize), } impl Display for RequestError { fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { match self { Self::BodyWithoutPendingRequest => write!( f, "No request was pending while the request body was being parsed." ), Self::HeaderError(inner) => write!(f, "Invalid header. Reason: {}", inner), Self::HeadersWithoutPendingRequest => write!( f, "No request was pending while the request headers were being parsed." ), Self::InvalidHttpMethod(inner) => write!(f, "Invalid HTTP Method: {}", inner), Self::InvalidHttpVersion(inner) => write!(f, "Invalid HTTP Version: {}", inner), Self::InvalidRequest => write!(f, "Invalid request."), Self::InvalidUri(inner) => write!(f, "Invalid URI: {}", inner), Self::Overflow => write!(f, "Overflow occurred when parsing a request."), Self::Underflow => write!(f, "Underflow occurred when parsing a request."), Self::SizeLimitExceeded(limit, size) => write!( f, "Request payload with size {} is larger than the limit of {} \ allowed by server.", size, limit ), } } } /// Errors associated with a HTTP Connection. #[derive(Debug)] pub enum ConnectionError { /// Attempted to read or write on a closed connection. ConnectionClosed, /// Attempted to write on a stream when there was nothing to write. InvalidWrite, /// The request parsing has failed. ParseError(RequestError), /// Could not perform a read operation from stream successfully. StreamReadError(vmm_sys_util::errno::Error), /// Could not perform a write operation to stream successfully. StreamWriteError(std::io::Error), } impl Display for ConnectionError { fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { match self { Self::ConnectionClosed => write!(f, "Connection closed."), Self::InvalidWrite => write!(f, "Invalid write attempt."), Self::ParseError(inner) => write!(f, "Parsing error: {}", inner), Self::StreamReadError(inner) => write!(f, "Reading stream error: {}", inner), Self::StreamWriteError(inner) => write!(f, "Writing stream error: {}", inner), } } } /// Errors pertaining to `HttpRoute`. #[derive(Debug)] #[allow(dead_code)] pub enum RouteError { /// Handler for http routing path already exists. HandlerExist(String), } impl Display for RouteError { fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { match self { RouteError::HandlerExist(p) => write!(f, "handler for {} already exists", p), } } } /// Errors pertaining to `HttpServer`. #[derive(Debug)] pub enum ServerError { /// Error from one of the connections. ConnectionError(ConnectionError), /// Epoll operations failed. IOError(std::io::Error), /// Overflow occured while processing messages. Overflow, /// Server maximum capacity has been reached. ServerFull, /// Underflow occured while processing mesagges. Underflow, } impl Display for ServerError { fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { match self { Self::ConnectionError(inner) => write!(f, "Connection error: {}", inner), Self::IOError(inner) => write!(f, "IO error: {}", inner), Self::Overflow => write!(f, "Overflow occured while processing messages."), Self::ServerFull => write!(f, "Server is full."), Self::Underflow => write!(f, "Underflow occured while processing messages."), } } } /// The Body associated with an HTTP Request or Response. /// /// ## Examples /// ``` /// 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, Eq)] 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 { Self { 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, Eq, Hash)] 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 /// `InvalidHttpMethod` is returned if the specified HTTP method is unsupported. pub fn try_from(bytes: &[u8]) -> Result { match bytes { b"GET" => Ok(Self::Get), b"PUT" => Ok(Self::Put), b"PATCH" => Ok(Self::Patch), _ => Err(RequestError::InvalidHttpMethod("Unsupported HTTP method.")), } } /// Returns an `u8 slice` corresponding to the Method. pub fn raw(self) -> &'static [u8] { match self { Self::Get => b"GET", Self::Put => b"PUT", Self::Patch => b"PATCH", } } /// Returns an &str corresponding to the Method. pub fn to_str(self) -> &'static str { match self { Method::Get => "GET", Method::Put => "PUT", Method::Patch => "PATCH", } } } /// Supported HTTP Versions. /// /// # Examples /// ``` /// 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, Eq)] pub enum Version { /// HTTP/1.0 Http10, /// HTTP/1.1 Http11, } impl Default for Version { /// Returns the default HTTP version = HTTP/1.1. fn default() -> Self { Self::Http11 } } impl Version { /// HTTP Version as an `u8 slice`. pub fn raw(self) -> &'static [u8] { match self { Self::Http10 => b"HTTP/1.0", Self::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 `InvalidHttpVersion` when the HTTP version is not supported. pub fn try_from(bytes: &[u8]) -> Result { match bytes { b"HTTP/1.0" => Ok(Self::Http10), b"HTTP/1.1" => Ok(Self::Http11), _ => Err(RequestError::InvalidHttpVersion( "Unsupported HTTP version.", )), } } } #[cfg(test)] mod tests { use super::*; impl PartialEq for ConnectionError { fn eq(&self, other: &Self) -> bool { use self::ConnectionError::*; match (self, other) { (ParseError(ref e), ParseError(ref other_e)) => e.eq(other_e), (ConnectionClosed, ConnectionClosed) => true, (StreamReadError(ref e), StreamReadError(ref other_e)) => { format!("{}", e).eq(&format!("{}", other_e)) } (StreamWriteError(ref e), StreamWriteError(ref other_e)) => { format!("{}", e).eq(&format!("{}", other_e)) } (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."); } #[test] fn test_display_request_error() { assert_eq!( format!("{}", RequestError::BodyWithoutPendingRequest), "No request was pending while the request body was being parsed." ); assert_eq!( format!("{}", RequestError::HeadersWithoutPendingRequest), "No request was pending while the request headers were being parsed." ); assert_eq!( format!("{}", RequestError::InvalidHttpMethod("test")), "Invalid HTTP Method: test" ); assert_eq!( format!("{}", RequestError::InvalidHttpVersion("test")), "Invalid HTTP Version: test" ); assert_eq!( format!("{}", RequestError::InvalidRequest), "Invalid request." ); assert_eq!( format!("{}", RequestError::InvalidUri("test")), "Invalid URI: test" ); assert_eq!( format!("{}", RequestError::Overflow), "Overflow occurred when parsing a request." ); assert_eq!( format!("{}", RequestError::Underflow), "Underflow occurred when parsing a request." ); assert_eq!( format!("{}", RequestError::SizeLimitExceeded(4, 10)), "Request payload with size 10 is larger than the limit of 4 allowed by server." ); } #[test] fn test_display_header_error() { assert_eq!( format!( "{}", RequestError::HeaderError(HttpHeaderError::InvalidFormat("test".to_string())) ), "Invalid header. Reason: Header is incorrectly formatted. Key: test" ); let value = String::from_utf8(vec![0, 159]); assert_eq!( format!( "{}", RequestError::HeaderError(HttpHeaderError::InvalidUtf8String( value.unwrap_err().utf8_error() )) ), "Invalid header. Reason: Header contains invalid characters. Key: invalid utf-8 sequence of 1 bytes from index 1" ); assert_eq!( format!( "{}", RequestError::HeaderError(HttpHeaderError::SizeLimitExceeded("test".to_string())) ), "Invalid header. Reason: Invalid content length. Header: test" ); assert_eq!( format!( "{}", RequestError::HeaderError(HttpHeaderError::UnsupportedFeature( "test".to_string(), "test".to_string() )) ), "Invalid header. Reason: Unsupported feature. Key: test; Value: test" ); assert_eq!( format!( "{}", RequestError::HeaderError(HttpHeaderError::UnsupportedName("test".to_string())) ), "Invalid header. Reason: Unsupported header name. Key: test" ); assert_eq!( format!( "{}", RequestError::HeaderError(HttpHeaderError::UnsupportedValue( "test".to_string(), "test".to_string() )) ), "Invalid header. Reason: Unsupported value. Key:test; Value:test" ); } #[test] fn test_display_connection_error() { assert_eq!( format!("{}", ConnectionError::ConnectionClosed), "Connection closed." ); assert_eq!( format!( "{}", ConnectionError::ParseError(RequestError::InvalidRequest) ), "Parsing error: Invalid request." ); assert_eq!( format!("{}", ConnectionError::InvalidWrite), "Invalid write attempt." ); assert_eq!( format!( "{}", ConnectionError::StreamWriteError(std::io::Error::from_raw_os_error(11)) ), "Writing stream error: Resource temporarily unavailable (os error 11)" ); } #[test] fn test_display_server_error() { assert_eq!( format!( "{}", ServerError::ConnectionError(ConnectionError::ConnectionClosed) ), "Connection error: Connection closed." ); assert_eq!( format!( "{}", ServerError::IOError(std::io::Error::from_raw_os_error(11)) ), "IO error: Resource temporarily unavailable (os error 11)" ); assert_eq!( format!("{}", ServerError::Overflow), "Overflow occured while processing messages." ); assert_eq!(format!("{}", ServerError::ServerFull), "Server is full."); assert_eq!( format!("{}", ServerError::Underflow), "Underflow occured while processing messages." ); } #[test] fn test_display_route_error() { assert_eq!( format!("{}", RouteError::HandlerExist("test".to_string())), "handler for test already exists" ); } #[test] fn test_method_to_str() { let val = Method::Get; assert_eq!(val.to_str(), "GET"); let val = Method::Put; assert_eq!(val.to_str(), "PUT"); let val = Method::Patch; assert_eq!(val.to_str(), "PATCH"); } }