fix(http1): reject content-lengths that have a plus sign prefix
The HTTP/1 content-length parser would accept lengths that were prefixed with a plus sign (for example, `+1234`). The specification restricts the content-length header to only allow DIGITs, making such a content-length illegal. Since some HTTP implementations protect against that, and others mis-interpret the length when the plus sign is present, this fixes hyper to always reject such content lengths. See GHSA-f3pg-qwvg-p99c
This commit is contained in:
@@ -30,7 +30,7 @@ fn connection_has(value: &HeaderValue, needle: &str) -> bool {
|
||||
|
||||
#[cfg(all(feature = "http1", feature = "server"))]
|
||||
pub(super) fn content_length_parse(value: &HeaderValue) -> Option<u64> {
|
||||
value.to_str().ok().and_then(|s| s.parse().ok())
|
||||
from_digits(value.as_bytes())
|
||||
}
|
||||
|
||||
pub(super) fn content_length_parse_all(headers: &HeaderMap) -> Option<u64> {
|
||||
@@ -46,7 +46,7 @@ pub(super) fn content_length_parse_all_values(values: ValueIter<'_, HeaderValue>
|
||||
for h in values {
|
||||
if let Ok(line) = h.to_str() {
|
||||
for v in line.split(',') {
|
||||
if let Some(n) = v.trim().parse().ok() {
|
||||
if let Some(n) = from_digits(v.trim().as_bytes()) {
|
||||
if content_length.is_none() {
|
||||
content_length = Some(n)
|
||||
} else if content_length != Some(n) {
|
||||
@@ -64,6 +64,33 @@ pub(super) fn content_length_parse_all_values(values: ValueIter<'_, HeaderValue>
|
||||
return content_length
|
||||
}
|
||||
|
||||
fn from_digits(bytes: &[u8]) -> Option<u64> {
|
||||
// cannot use FromStr for u64, since it allows a signed prefix
|
||||
let mut result = 0u64;
|
||||
const RADIX: u64 = 10;
|
||||
|
||||
if bytes.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
for &b in bytes {
|
||||
// can't use char::to_digit, since we haven't verified these bytes
|
||||
// are utf-8.
|
||||
match b {
|
||||
b'0'..=b'9' => {
|
||||
result = result.checked_mul(RADIX)?;
|
||||
result = result.checked_add((b - b'0') as u64)?;
|
||||
},
|
||||
_ => {
|
||||
// not a DIGIT, get outta here!
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some(result)
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "http2", feature = "client"))]
|
||||
pub(super) fn method_has_defined_payload_semantics(method: &Method) -> bool {
|
||||
match *method {
|
||||
|
||||
Reference in New Issue
Block a user