Do more validation on status line. (#266)

Status code must be exactly three digits.
HTTP version must be "HTTP/", digit, dot, digit.

https://tools.ietf.org/html/rfc7230#section-3.1.2
status-line = HTTP-version SP status-code SP reason-phrase CRLF
This commit is contained in:
Jacob Hoffman-Andrews
2020-12-18 22:18:25 -08:00
committed by GitHub
parent 243b987110
commit de1805190e

View File

@@ -8,14 +8,12 @@ use std::{
use chunked_transfer::Decoder as ChunkDecoder; use chunked_transfer::Decoder as ChunkDecoder;
use url::Url; use url::Url;
use crate::error::{Error, ErrorKind::BadStatus};
use crate::header::Header; use crate::header::Header;
use crate::pool::PoolReturnRead; use crate::pool::PoolReturnRead;
use crate::stream::{DeadlineStream, Stream}; use crate::stream::{DeadlineStream, Stream};
use crate::unit::Unit; use crate::unit::Unit;
use crate::{ use crate::stream;
error::{Error, ErrorKind},
stream,
};
#[cfg(feature = "json")] #[cfg(feature = "json")]
use serde::de::DeserializeOwned; use serde::de::DeserializeOwned;
@@ -476,28 +474,43 @@ impl Response {
fn parse_status_line(line: &str) -> Result<(ResponseStatusIndex, u16), Error> { fn parse_status_line(line: &str) -> Result<(ResponseStatusIndex, u16), Error> {
// //
let mut split = line.splitn(3, ' '); if !line.is_ascii() {
return Err(BadStatus.msg("Status line not ASCII"));
let http_version = split.next().ok_or_else(|| ErrorKind::BadStatus.new())?;
if http_version.len() < 5 {
return Err(ErrorKind::BadStatus.new());
} }
let index1 = http_version.len(); // https://tools.ietf.org/html/rfc7230#section-3.1.2
// status-line = HTTP-version SP status-code SP reason-phrase CRLF
let status = split.next().ok_or_else(|| ErrorKind::BadStatus.new())?; let split: Vec<&str> = line.splitn(3, ' ').collect();
if status.len() < 2 { if split.len() != 3 {
return Err(ErrorKind::BadStatus.new()); return Err(BadStatus.msg("Wrong number of tokens in status line"));
} }
let index2 = index1 + status.len();
let status = status // https://tools.ietf.org/html/rfc7230#appendix-B
.parse::<u16>() // HTTP-name = %x48.54.54.50 ; HTTP
.map_err(|_| ErrorKind::BadStatus.new())?; // HTTP-version = HTTP-name "/" DIGIT "." DIGIT
let http_version = split[0];
if !http_version.starts_with("HTTP/") {
return Err(BadStatus.msg("HTTP version did not start with HTTP/"));
}
if http_version.len() != 8 {
return Err(BadStatus.msg("HTTP version was wrong length"));
}
if !http_version.as_bytes()[5].is_ascii_digit() || !http_version.as_bytes()[7].is_ascii_digit()
{
return Err(BadStatus.msg("HTTP version did not match format"));
}
let status_str: &str = split[1];
// status-code = 3DIGIT
if status_str.len() != 3 {
return Err(BadStatus.msg("Status code was wrong length"));
}
let status: u16 = status_str.parse().map_err(|_| BadStatus.new())?;
Ok(( Ok((
ResponseStatusIndex { ResponseStatusIndex {
http_version: index1, http_version: http_version.len(),
response_code: index2, response_code: http_version.len() + status_str.len(),
}, },
status, status,
)) ))
@@ -754,7 +767,7 @@ mod tests {
fn parse_borked_header() { fn parse_borked_header() {
let s = "HTTP/1.1 BORKED\r\n".to_string(); let s = "HTTP/1.1 BORKED\r\n".to_string();
let err = s.parse::<Response>().unwrap_err(); let err = s.parse::<Response>().unwrap_err();
assert_eq!(err.kind(), ErrorKind::BadStatus); assert_eq!(err.kind(), BadStatus);
} }
#[test] #[test]