Made InvalidHeader error more verbose.

Changed the way we handle TransferEncoding
and added new test cases.

Signed-off-by: cihodar <cihodar@amazon.com>
Signed-off-by: YUAN LYU <lyuyuan92@gmail.com>
This commit is contained in:
cihodar
2020-08-10 14:17:05 +03:00
committed by Adrian Catangiu
parent eeae4ac11d
commit 8a8d7bb5b1
6 changed files with 217 additions and 45 deletions

View File

@@ -3,6 +3,7 @@
use std::result::Result;
use crate::HttpHeaderError;
use crate::RequestError;
/// Wrapper over an HTTP Header type.
@@ -51,7 +52,9 @@ impl Header {
"transfer-encoding" => Ok(Self::TransferEncoding),
"server" => Ok(Self::Server),
"accept" => Ok(Self::Accept),
_ => Err(RequestError::InvalidHeader),
invalid_key => Err(RequestError::HeaderError(HttpHeaderError::UnsupportedName(
invalid_key.to_string(),
))),
}
} else {
Err(RequestError::InvalidRequest)
@@ -129,7 +132,9 @@ impl Headers {
Ok(headers_str) => {
let entry = headers_str.splitn(2, ':').collect::<Vec<&str>>();
if entry.len() != 2 {
return Err(RequestError::InvalidHeader);
return Err(RequestError::HeaderError(HttpHeaderError::InvalidFormat(
entry[0].to_string(),
)));
}
if let Ok(head) = Header::try_from(entry[0].as_bytes()) {
match head {
@@ -138,12 +143,22 @@ impl Headers {
self.content_length = content_length;
Ok(())
}
Err(_) => Err(RequestError::InvalidHeader),
Err(_) => {
Err(RequestError::HeaderError(HttpHeaderError::InvalidValue(
entry[0].to_string(),
entry[1].to_string(),
)))
}
},
Header::ContentType => {
match MediaType::try_from(entry[1].trim().as_bytes()) {
Ok(_) => Ok(()),
Err(_) => Err(RequestError::UnsupportedHeader),
Err(_) => Err(RequestError::HeaderError(
HttpHeaderError::UnsupportedValue(
entry[0].to_string(),
entry[1].to_string(),
),
)),
}
}
Header::Accept => match MediaType::try_from(entry[1].trim().as_bytes()) {
@@ -151,30 +166,52 @@ impl Headers {
self.accept = accept_type;
Ok(())
}
Err(_) => Err(RequestError::UnsupportedHeader),
Err(_) => Err(RequestError::HeaderError(
HttpHeaderError::UnsupportedValue(
entry[0].to_string(),
entry[1].to_string(),
),
)),
},
Header::TransferEncoding => match entry[1].trim() {
"chunked" => {
self.chunked = true;
Ok(())
}
"identity; q=0" => Err(RequestError::InvalidHeader),
_ => Err(RequestError::UnsupportedHeader),
"identity" => Ok(()),
_ => Err(RequestError::HeaderError(
HttpHeaderError::UnsupportedValue(
entry[0].to_string(),
entry[1].to_string(),
),
)),
},
Header::Expect => match entry[1].trim() {
"100-continue" => {
self.expect = true;
Ok(())
}
_ => Err(RequestError::InvalidHeader),
_ => Err(RequestError::HeaderError(
HttpHeaderError::UnsupportedValue(
entry[0].to_string(),
entry[1].to_string(),
),
)),
},
Header::Server => Ok(()),
}
} else {
Err(RequestError::UnsupportedHeader)
Err(RequestError::HeaderError(
HttpHeaderError::UnsupportedValue(
entry[0].to_string(),
entry[1].to_string(),
),
))
}
}
_ => Err(RequestError::InvalidHeader),
Err(utf8_err) => Err(RequestError::HeaderError(
HttpHeaderError::InvalidUtf8String(utf8_err),
)),
}
}
@@ -231,7 +268,10 @@ impl Headers {
break;
}
match headers.parse_header_line(header_line.as_bytes()) {
Ok(_) | Err(RequestError::UnsupportedHeader) => continue,
Ok(_)
| Err(RequestError::HeaderError(HttpHeaderError::UnsupportedValue(_, _))) => {
continue
}
Err(e) => return Err(e),
};
}
@@ -411,19 +451,29 @@ mod tests {
// Invalid header syntax.
assert_eq!(
header.parse_header_line(b"Expect"),
Err(RequestError::InvalidHeader)
Err(RequestError::HeaderError(HttpHeaderError::InvalidFormat(
"Expect".to_string()
)))
);
// Invalid content length.
assert_eq!(
header.parse_header_line(b"Content-Length: five"),
Err(RequestError::InvalidHeader)
Err(RequestError::HeaderError(HttpHeaderError::InvalidValue(
"Content-Length".to_string(),
" five".to_string()
)))
);
// Invalid transfer encoding.
assert_eq!(
header.parse_header_line(b"Transfer-Encoding: gzip"),
Err(RequestError::UnsupportedHeader)
Err(RequestError::HeaderError(
HttpHeaderError::UnsupportedValue(
"Transfer-Encoding".to_string(),
" gzip".to_string()
)
))
);
// Invalid expect.
@@ -431,7 +481,10 @@ mod tests {
header
.parse_header_line(b"Expect: 102-processing")
.unwrap_err(),
RequestError::InvalidHeader
RequestError::HeaderError(HttpHeaderError::UnsupportedValue(
"Expect".to_string(),
" 102-processing".to_string()
))
);
// Unsupported media type.
@@ -439,14 +492,19 @@ mod tests {
header
.parse_header_line(b"Content-Type: application/json-patch")
.unwrap_err(),
RequestError::UnsupportedHeader
RequestError::HeaderError(HttpHeaderError::UnsupportedValue(
"Content-Type".to_string(),
" application/json-patch".to_string()
))
);
// 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
RequestError::HeaderError(HttpHeaderError::InvalidUtf8String(
String::from_utf8(input.to_vec()).unwrap_err().utf8_error()
))
);
// Test valid transfer encoding.
@@ -480,7 +538,10 @@ mod tests {
// Invalid content length.
assert_eq!(
header.parse_header_line(b"Content-Length: -1"),
Err(RequestError::InvalidHeader)
Err(RequestError::HeaderError(HttpHeaderError::InvalidValue(
"Content-Length".to_string(),
" -1".to_string()
)))
);
}
@@ -530,7 +591,7 @@ mod tests {
// Bad header.
assert_eq!(
Header::try_from(b"Encoding").unwrap_err(),
RequestError::InvalidHeader
RequestError::HeaderError(HttpHeaderError::UnsupportedName("encoding".to_string()))
);
// Invalid encoding.

View File

@@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
use std::fmt::{Display, Error, Formatter};
use std::str::Utf8Error;
pub mod headers;
@@ -13,6 +14,55 @@ pub mod ascii {
pub const CRLF_LEN: usize = 2;
}
///Errors associated with a header that is invalid.
#[derive(Debug, PartialEq)]
pub enum HttpHeaderError {
/// The content length specified is longer than the limit imposed by Micro Http.
SizeLimitExceeded(String),
/// The header specified is not supported.
UnsupportedName(String),
/// The value for the specified header is not supported.
UnsupportedValue(String, String),
/// The specified header contains illegal characters.
InvalidUtf8String(Utf8Error),
/// The requested feature is not currently supported.
UnsupportedFeature(String, String),
/// The header is misformatted.
InvalidFormat(String),
///The value specified is not valid.
InvalidValue(String, String),
}
impl Display for HttpHeaderError {
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
match self {
Self::SizeLimitExceeded(inner) => {
write!(f, "Invalid content length. Header: {}", inner)
}
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
),
Self::InvalidUtf8String(header_key) => {
write!(f, "Header contains invalid characters. Key: {}", header_key)
}
Self::UnsupportedFeature(header_key, header_value) => write!(
f,
"Unsupported feature. Key: {}; Value: {}",
header_key, header_value
),
Self::InvalidFormat(header_key) => {
write!(f, "Header is incorrectly formatted. Key: {}", header_key)
}
Self::InvalidValue(header_name, value) => {
write!(f, "Invalid value. Key:{}; Value:{}", header_name, value)
}
}
}
}
/// Errors associated with parsing the HTTP Request from a u8 slice.
#[derive(Debug, PartialEq)]
pub enum RequestError {
@@ -26,10 +76,8 @@ pub enum RequestError {
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,
/// Header specified is either invalid or not supported by this HTTP implementation.
HeaderError(HttpHeaderError),
/// The Request is invalid and cannot be served.
InvalidRequest,
/// Overflow occurred when parsing a request.
@@ -52,8 +100,7 @@ impl Display for RequestError {
Self::InvalidHttpMethod(inner) => write!(f, "Invalid HTTP Method: {}", inner),
Self::InvalidUri(inner) => write!(f, "Invalid URI: {}", inner),
Self::InvalidHttpVersion(inner) => write!(f, "Invalid HTTP Version: {}", inner),
Self::UnsupportedHeader => write!(f, "Unsupported header."),
Self::InvalidHeader => write!(f, "Invalid header."),
Self::HeaderError(inner) => write!(f, "Invalid header. Reason: {}", inner),
Self::InvalidRequest => write!(f, "Invalid request."),
Self::Overflow => write!(f, "Overflow occurred when parsing a request."),
Self::Underflow => write!(f, "Underflow occurred when parsing a request."),
@@ -340,10 +387,6 @@ mod tests {
format!("{}", RequestError::HeadersWithoutPendingRequest),
"No request was pending while the request headers were being parsed."
);
assert_eq!(
format!("{}", RequestError::InvalidHeader),
"Invalid header."
);
assert_eq!(
format!("{}", RequestError::InvalidHttpMethod("test")),
"Invalid HTTP Method: test"
@@ -368,9 +411,60 @@ mod tests {
format!("{}", RequestError::Underflow),
"Underflow occurred when parsing a request."
);
}
#[test]
fn test_display_header_error() {
assert_eq!(
format!("{}", RequestError::UnsupportedHeader),
"Unsupported header."
format!(
"{}",
RequestError::HeaderError(HttpHeaderError::SizeLimitExceeded("test".to_string()))
),
"Invalid header. Reason: Invalid content length. Header: 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"
);
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::UnsupportedFeature(
"test".to_string(),
"test".to_string()
))
),
"Invalid header. Reason: Unsupported feature. Key: test; Value: test"
);
assert_eq!(
format!(
"{}",
RequestError::HeaderError(HttpHeaderError::InvalidFormat("test".to_string()))
),
"Invalid header. Reason: Header is incorrectly formatted. Key: test"
);
}