@@ -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\t world" ) . is_ok( ) ) ;
790+ assert ! ( HeaderValue :: from_bytes( b"hello\t world" ) . 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]
760800fn test_debug ( ) {
761801 let cases = & [
0 commit comments