Skip to content

Commit 4d18d3e

Browse files
authored
fix(header): enforce string constructors to only allow ASCII (#860)
1 parent 16fc9a7 commit 4d18d3e

2 files changed

Lines changed: 55 additions & 15 deletions

File tree

src/header/value.rs

Lines changed: 54 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ impl HeaderValue {
4444
///
4545
/// This function will not perform any copying, however the string is
4646
/// checked to ensure that no invalid characters are present. Only visible
47-
/// ASCII characters (32-127) are permitted.
47+
/// ASCII characters (32-126) and horizontal tab are permitted.
4848
///
4949
/// # Panics
5050
///
@@ -63,7 +63,7 @@ impl HeaderValue {
6363
let bytes = src.as_bytes();
6464
let mut i = 0;
6565
while i < bytes.len() {
66-
if !is_visible_ascii(bytes[i]) {
66+
if !is_valid_ascii(bytes[i]) {
6767
panic!("HeaderValue::from_static with invalid bytes")
6868
}
6969
i += 1;
@@ -78,7 +78,8 @@ impl HeaderValue {
7878
/// Attempt to convert a string to a `HeaderValue`.
7979
///
8080
/// If the argument contains invalid header value characters, an error is
81-
/// returned. Only visible ASCII characters (32-127) are permitted. Use
81+
/// returned. Only visible ASCII characters (32-126) and horizontal tab are
82+
/// permitted. Use
8283
/// `from_bytes` to create a `HeaderValue` that includes opaque octets
8384
/// (128-255).
8485
///
@@ -103,7 +104,11 @@ impl HeaderValue {
103104
#[inline]
104105
#[allow(clippy::should_implement_trait)]
105106
pub fn from_str(src: &str) -> Result<HeaderValue, InvalidHeaderValue> {
106-
HeaderValue::try_from_generic(src, |s| Bytes::copy_from_slice(s.as_bytes()))
107+
HeaderValue::try_from_generic(
108+
src,
109+
|s| Bytes::copy_from_slice(s.as_bytes()),
110+
is_valid_ascii,
111+
)
107112
}
108113

109114
/// Converts a HeaderName into a HeaderValue
@@ -149,7 +154,7 @@ impl HeaderValue {
149154
/// ```
150155
#[inline]
151156
pub fn from_bytes(src: &[u8]) -> Result<HeaderValue, InvalidHeaderValue> {
152-
HeaderValue::try_from_generic(src, Bytes::copy_from_slice)
157+
HeaderValue::try_from_generic(src, Bytes::copy_from_slice, is_valid_ascii_or_opaque_byte)
153158
}
154159

155160
/// Attempt to convert a `Bytes` buffer to a `HeaderValue`.
@@ -206,12 +211,13 @@ impl HeaderValue {
206211
}
207212

208213
fn from_shared(src: Bytes) -> Result<HeaderValue, InvalidHeaderValue> {
209-
HeaderValue::try_from_generic(src, std::convert::identity)
214+
HeaderValue::try_from_generic(src, std::convert::identity, is_valid_ascii_or_opaque_byte)
210215
}
211216

212-
fn try_from_generic<T: AsRef<[u8]>, F: FnOnce(T) -> Bytes>(
217+
fn try_from_generic<T: AsRef<[u8]>, F: FnOnce(T) -> Bytes, V: Fn(u8) -> bool>(
213218
src: T,
214219
into: F,
220+
is_valid: V,
215221
) -> Result<HeaderValue, InvalidHeaderValue> {
216222
// Avoid an early return so the loop vectorizes.
217223
let mut bad = false;
@@ -246,7 +252,7 @@ impl HeaderValue {
246252
// Avoid an early return so the loop vectorizes.
247253
let mut bad = false;
248254
for &b in bytes {
249-
bad |= !is_visible_ascii(b);
255+
bad |= !is_valid_ascii(b);
250256
}
251257
if bad {
252258
return Err(ToStrError { _priv: () });
@@ -369,7 +375,7 @@ impl fmt::Debug for HeaderValue {
369375
let mut from = 0;
370376
let bytes = self.as_bytes();
371377
for (i, &b) in bytes.iter().enumerate() {
372-
if !is_visible_ascii(b) || b == b'"' {
378+
if !is_valid_ascii(b) || b == b'"' {
373379
if from != i {
374380
f.write_str(unsafe { str::from_utf8_unchecked(&bytes[from..i]) })?;
375381
}
@@ -417,7 +423,7 @@ macro_rules! from_integers {
417423
let val = HeaderValue::from(n);
418424
assert_eq!(val, &n.to_string());
419425

420-
let n = ::std::$t::MAX;
426+
let n = <$t>::MAX;
421427
let val = HeaderValue::from(n);
422428
assert_eq!(val, &n.to_string());
423429
}
@@ -510,7 +516,7 @@ impl TryFrom<&String> for HeaderValue {
510516
type Error = InvalidHeaderValue;
511517
#[inline]
512518
fn try_from(s: &String) -> Result<Self, Self::Error> {
513-
Self::from_bytes(s.as_bytes())
519+
Self::from_str(s)
514520
}
515521
}
516522

@@ -528,7 +534,7 @@ impl TryFrom<String> for HeaderValue {
528534

529535
#[inline]
530536
fn try_from(t: String) -> Result<Self, Self::Error> {
531-
HeaderValue::from_shared(t.into())
537+
HeaderValue::try_from_generic(t, |s| s.into(), is_valid_ascii)
532538
}
533539
}
534540

@@ -555,12 +561,15 @@ mod try_from_header_name_tests {
555561
}
556562
}
557563

558-
const fn is_visible_ascii(b: u8) -> bool {
564+
const fn is_valid_ascii(b: u8) -> bool {
559565
b >= 32 && b < 127 || b == b'\t'
560566
}
561567

568+
// This validator is only for byte-oriented constructors. HTTP field values
569+
// may contain opaque bytes, even though those bytes cannot be exposed by
570+
// `HeaderValue::to_str`.
562571
#[inline]
563-
fn is_valid(b: u8) -> bool {
572+
fn is_valid_ascii_or_opaque_byte(b: u8) -> bool {
564573
b >= 32 && b != 127 || b == b'\t'
565574
}
566575

@@ -756,6 +765,37 @@ fn test_try_from() {
756765
HeaderValue::try_from(vec![127]).unwrap_err();
757766
}
758767

768+
#[test]
769+
fn test_string_constructors_reject_non_ascii() {
770+
let value = String::from("hello \u{e9}");
771+
772+
assert!(HeaderValue::from_str(&value).is_err());
773+
assert!(HeaderValue::try_from(value.as_str()).is_err());
774+
assert!(HeaderValue::try_from(&value).is_err());
775+
assert!(HeaderValue::try_from(value).is_err());
776+
}
777+
778+
#[test]
779+
fn test_byte_constructors_allow_opaque_bytes_but_reject_del() {
780+
assert!(HeaderValue::from_bytes(b"hello\xff").is_ok());
781+
assert!(HeaderValue::try_from(&b"hello\xff"[..]).is_ok());
782+
assert!(HeaderValue::try_from(b"hello\xff".to_vec()).is_ok());
783+
784+
assert!(HeaderValue::from_bytes(b"hello\x7f").is_err());
785+
}
786+
787+
#[test]
788+
fn test_string_and_byte_constructors_allow_horizontal_tab() {
789+
assert!(HeaderValue::from_str("hello\tworld").is_ok());
790+
assert!(HeaderValue::from_bytes(b"hello\tworld").is_ok());
791+
}
792+
793+
#[test]
794+
#[should_panic(expected = "HeaderValue::from_static with invalid bytes")]
795+
fn test_static_constructor_rejects_non_ascii() {
796+
HeaderValue::from_static("hello \u{e9}");
797+
}
798+
759799
#[test]
760800
fn test_debug() {
761801
let cases = &[

tests/header_map.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ fn extend_size_hint_above_capacity() {
7777
fn reserve_overflow() {
7878
// See https://github.com/hyperium/http/issues/352
7979
let mut headers = HeaderMap::<u32>::with_capacity(0);
80-
headers.reserve(std::usize::MAX); // next_power_of_two overflows
80+
headers.reserve(usize::MAX); // next_power_of_two overflows
8181
}
8282

8383
#[test]

0 commit comments

Comments
 (0)