1- use std:: collections:: HashMap ;
1+ use std:: collections:: HashSet ;
22use std:: fmt;
33use std:: io:: IoSlice ;
44
@@ -9,7 +9,7 @@ use http::{
99 AUTHORIZATION , CACHE_CONTROL , CONTENT_ENCODING , CONTENT_LENGTH , CONTENT_RANGE ,
1010 CONTENT_TYPE , HOST , MAX_FORWARDS , SET_COOKIE , TE , TRAILER , TRANSFER_ENCODING ,
1111 } ,
12- HeaderMap , HeaderName , HeaderValue ,
12+ HeaderMap , HeaderName ,
1313} ;
1414
1515use super :: io:: WriteBuf ;
@@ -35,7 +35,7 @@ pub(crate) struct NotEof(u64);
3535#[ derive( Debug , PartialEq , Clone ) ]
3636enum Kind {
3737 /// An Encoder for when Transfer-Encoding includes `chunked`.
38- Chunked ( Option < Vec < HeaderValue > > ) ,
38+ Chunked ( Option < Vec < HeaderName > > ) ,
3939 /// An Encoder for when Content-Length is set.
4040 ///
4141 /// Enforces that the body is not longer than the Content-Length header.
@@ -77,7 +77,7 @@ impl Encoder {
7777 Encoder :: new ( Kind :: CloseDelimited )
7878 }
7979
80- pub ( crate ) fn into_chunked_with_trailing_fields ( self , trailers : Vec < HeaderValue > ) -> Encoder {
80+ pub ( crate ) fn into_chunked_with_trailing_fields ( self , trailers : Vec < HeaderName > ) -> Encoder {
8181 match self . kind {
8282 Kind :: Chunked ( _) => Encoder {
8383 kind : Kind :: Chunked ( Some ( trailers) ) ,
@@ -168,7 +168,7 @@ impl Encoder {
168168 trace ! ( "encoding trailers" ) ;
169169 match & self . kind {
170170 Kind :: Chunked ( Some ( allowed_trailer_fields) ) => {
171- let allowed_trailer_field_map = allowed_trailer_field_map ( allowed_trailer_fields) ;
171+ let allowed_set : HashSet < & HeaderName > = allowed_trailer_fields. iter ( ) . collect ( ) ;
172172
173173 let mut cur_name = None ;
174174 let mut allowed_trailers = HeaderMap :: new ( ) ;
@@ -179,7 +179,7 @@ impl Encoder {
179179 }
180180 let name = cur_name. as_ref ( ) . expect ( "current header name" ) ;
181181
182- if allowed_trailer_field_map . contains_key ( name. as_str ( ) ) {
182+ if allowed_set . contains ( name) {
183183 if is_valid_trailer_field ( name) {
184184 allowed_trailers. insert ( name, value) ;
185185 } else {
@@ -279,22 +279,6 @@ fn is_valid_trailer_field(name: &HeaderName) -> bool {
279279 )
280280}
281281
282- fn allowed_trailer_field_map ( allowed_trailer_fields : & Vec < HeaderValue > ) -> HashMap < String , ( ) > {
283- let mut trailer_map = HashMap :: new ( ) ;
284-
285- for header_value in allowed_trailer_fields {
286- if let Ok ( header_str) = header_value. to_str ( ) {
287- let items: Vec < & str > = header_str. split ( ',' ) . map ( |item| item. trim ( ) ) . collect ( ) ;
288-
289- for item in items {
290- trailer_map. entry ( item. to_string ( ) ) . or_insert ( ( ) ) ;
291- }
292- }
293- }
294-
295- trailer_map
296- }
297-
298282impl < B > Buf for EncodedBuf < B >
299283where
300284 B : Buf ,
@@ -532,7 +516,7 @@ mod tests {
532516 #[ test]
533517 fn chunked_with_valid_trailers ( ) {
534518 let encoder = Encoder :: chunked ( ) ;
535- let trailers = vec ! [ HeaderValue :: from_static( "chunky-trailer" ) ] ;
519+ let trailers = vec ! [ HeaderName :: from_static( "chunky-trailer" ) ] ;
536520 let encoder = encoder. into_chunked_with_trailing_fields ( trailers) ;
537521
538522 let headers = HeaderMap :: from_iter ( vec ! [
@@ -557,8 +541,8 @@ mod tests {
557541 fn chunked_with_multiple_trailer_headers ( ) {
558542 let encoder = Encoder :: chunked ( ) ;
559543 let trailers = vec ! [
560- HeaderValue :: from_static( "chunky-trailer" ) ,
561- HeaderValue :: from_static( "chunky-trailer-2" ) ,
544+ HeaderName :: from_static( "chunky-trailer" ) ,
545+ HeaderName :: from_static( "chunky-trailer-2" ) ,
562546 ] ;
563547 let encoder = encoder. into_chunked_with_trailing_fields ( trailers) ;
564548
@@ -606,8 +590,7 @@ mod tests {
606590 fn chunked_with_invalid_trailers ( ) {
607591 let encoder = Encoder :: chunked ( ) ;
608592
609- let trailers = format ! (
610- "{},{},{},{},{},{},{},{},{},{},{},{}" ,
593+ let trailers = vec ! [
611594 AUTHORIZATION ,
612595 CACHE_CONTROL ,
613596 CONTENT_ENCODING ,
@@ -620,8 +603,7 @@ mod tests {
620603 TRAILER ,
621604 TRANSFER_ENCODING ,
622605 TE ,
623- ) ;
624- let trailers = vec ! [ HeaderValue :: from_str( & trailers) . unwrap( ) ] ;
606+ ] ;
625607 let encoder = encoder. into_chunked_with_trailing_fields ( trailers) ;
626608
627609 let mut headers = HeaderMap :: new ( ) ;
@@ -644,7 +626,7 @@ mod tests {
644626 #[ test]
645627 fn chunked_with_title_case_headers ( ) {
646628 let encoder = Encoder :: chunked ( ) ;
647- let trailers = vec ! [ HeaderValue :: from_static( "chunky-trailer" ) ] ;
629+ let trailers = vec ! [ HeaderName :: from_static( "chunky-trailer" ) ] ;
648630 let encoder = encoder. into_chunked_with_trailing_fields ( trailers) ;
649631
650632 let headers = HeaderMap :: from_iter ( vec ! [ (
@@ -657,4 +639,34 @@ mod tests {
657639 dst. put ( buf1) ;
658640 assert_eq ! ( dst, b"0\r \n Chunky-Trailer: header data\r \n \r \n " ) ;
659641 }
642+
643+ #[ test]
644+ fn chunked_trailers_case_insensitive_matching ( ) {
645+ // Regression test for issue #4010: HTTP/1.1 trailers are case-sensitive
646+ //
647+ // Previously, the Trailer header values were stored as HeaderValue (preserving case)
648+ // and compared against HeaderName (which is always lowercase). This caused trailers
649+ // declared as "Chunky-Trailer" to not match actual trailers sent as "chunky-trailer".
650+ //
651+ // The fix converts Trailer header values to HeaderName during parsing, which
652+ // normalizes the case and enables proper case-insensitive matching.
653+ //
654+ // Note: HeaderName::from_static() requires lowercase input. In real usage,
655+ // HeaderName::from_bytes() is used to parse the Trailer header value, which
656+ // normalizes mixed-case input like "Chunky-Trailer" to "chunky-trailer".
657+ let encoder = Encoder :: chunked ( ) ;
658+ let trailers = vec ! [ HeaderName :: from_static( "chunky-trailer" ) ] ;
659+ let encoder = encoder. into_chunked_with_trailing_fields ( trailers) ;
660+
661+ // The actual trailer being sent
662+ let headers = HeaderMap :: from_iter ( vec ! [ (
663+ HeaderName :: from_static( "chunky-trailer" ) ,
664+ HeaderValue :: from_static( "trailer value" ) ,
665+ ) ] ) ;
666+
667+ let buf = encoder. encode_trailers :: < & [ u8 ] > ( headers, false ) . unwrap ( ) ;
668+ let mut dst = Vec :: new ( ) ;
669+ dst. put ( buf) ;
670+ assert_eq ! ( dst, b"0\r \n chunky-trailer: trailer value\r \n \r \n " ) ;
671+ }
660672}
0 commit comments