Files
micro-http/src/common/mod.rs
AlexandruCihodaru ba4e5a0917 Added payload too large response
Signed-off-by: AlexandruCihodaru <cihodar@amazon.com>
Suggested-by: George Pisaltu <gpl@amazon.com>
2021-09-03 13:56:22 +03:00

563 lines
18 KiB
Rust

// 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)]
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)]
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)]
pub struct Body {
/// Body of the HTTP message as bytes.
pub body: Vec<u8>,
}
impl Body {
/// Creates a new `Body` from a `String` input.
pub fn new<T: Into<Vec<u8>>>(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<Self, RequestError> {
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)]
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<Self, RequestError> {
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");
}
}