diff --git a/src/proto/h1/encode.rs b/src/proto/h1/encode.rs index 2df0c396b7..3be367f8fc 100644 --- a/src/proto/h1/encode.rs +++ b/src/proto/h1/encode.rs @@ -179,7 +179,7 @@ impl Encoder { } let name = cur_name.as_ref().expect("current header name"); - if allowed_trailer_field_map.contains_key(name.as_str()) { + if allowed_trailer_field_map.contains_key(name) { if is_valid_trailer_field(name) { allowed_trailers.insert(name, value); } else { @@ -279,15 +279,18 @@ fn is_valid_trailer_field(name: &HeaderName) -> bool { ) } -fn allowed_trailer_field_map(allowed_trailer_fields: &Vec) -> HashMap { +fn allowed_trailer_field_map(allowed_trailer_fields: &Vec) -> HashMap { let mut trailer_map = HashMap::new(); for header_value in allowed_trailer_fields { if let Ok(header_str) = header_value.to_str() { let items: Vec<&str> = header_str.split(',').map(|item| item.trim()).collect(); - for item in items { - trailer_map.entry(item.to_string()).or_insert(()); + for item in items + .into_iter() + .filter_map(|v| HeaderName::from_bytes(v.as_bytes()).ok()) + { + trailer_map.entry(item).or_insert(()); } } } diff --git a/tests/client.rs b/tests/client.rs index d5a0e4e005..5f976b1792 100644 --- a/tests/client.rs +++ b/tests/client.rs @@ -704,6 +704,44 @@ test! { body: None, } +test! { + name: client_post_req_body_chunked_with_trailer_titlecase, + + server: + expected: "\ + POST / HTTP/1.1\r\n\ + trailer: Chunky-Trailer\r\n\ + host: {addr}\r\n\ + transfer-encoding: chunked\r\n\ + \r\n\ + 5\r\n\ + hello\r\n\ + 0\r\n\ + chunky-trailer: header data\r\n\ + \r\n\ + ", + reply: REPLY_OK, + + client: + request: { + method: POST, + url: "http://{addr}/", + headers: { + "trailer" => "Chunky-Trailer", + }, + body_stream_with_trailers: ( + (futures_util::stream::once(async { Ok::<_, Infallible>(Bytes::from("hello"))})), + HeaderMap::from_iter(vec![( + HeaderName::from_static("chunky-trailer"), + HeaderValue::from_static("header data") + )].into_iter())), + }, + response: + status: OK, + headers: {}, + body: None, +} + test! { name: client_res_body_chunked_with_trailer, @@ -740,6 +778,42 @@ test! { }, } +test! { + name: client_res_body_chunked_with_trailer_titlecase, + + server: + expected: "GET / HTTP/1.1\r\nte: trailers\r\nhost: {addr}\r\n\r\n", + reply: "\ + HTTP/1.1 200 OK\r\n\ + transfer-encoding: chunked\r\n\ + trailer: Chunky-Trailer\r\n\ + \r\n\ + 5\r\n\ + hello\r\n\ + 0\r\n\ + chunky-trailer: header data\r\n\ + \r\n\ + ", + + client: + request: { + method: GET, + url: "http://{addr}/", + headers: { + "te" => "trailers", + }, + }, + response: + status: OK, + headers: { + "Transfer-Encoding" => "chunked", + }, + body: &b"hello"[..], + trailers: { + "chunky-trailer" => "header data", + }, +} + test! { name: client_res_body_chunked_with_pathological_trailers, diff --git a/tests/server.rs b/tests/server.rs index 485a10e049..1cd0eba570 100644 --- a/tests/server.rs +++ b/tests/server.rs @@ -2850,6 +2850,51 @@ fn http1_trailer_send_fields() { assert_eq!(body, expected_body); } +#[test] +fn http1_trailer_send_fields_titlecase() { + let body = futures_util::stream::once(async move { Ok("hello".into()) }); + let mut headers = HeaderMap::new(); + headers.insert("chunky-trailer", "header data".parse().unwrap()); + // Invalid trailer field that should not be sent + headers.insert("Host", "www.example.com".parse().unwrap()); + // Not specified in Trailer header, so should not be sent + headers.insert("foo", "bar".parse().unwrap()); + + let server = serve(); + server + .reply() + .header("transfer-encoding", "chunked") + .header("trailer", "Chunky-Trailer") + .body_stream_with_trailers(body, headers); + let mut req = connect(server.addr()); + req.write_all( + b"\ + GET / HTTP/1.1\r\n\ + Host: example.domain\r\n\ + Connection: keep-alive\r\n\ + TE: trailers\r\n\ + \r\n\ + ", + ) + .expect("writing"); + + let chunky_trailer_chunk = b"\r\nchunky-trailer: header data\r\n\r\n"; + let res = read_until(&mut req, |buf| buf.ends_with(chunky_trailer_chunk)).expect("reading"); + let sres = s(&res); + + let expected_head = + "HTTP/1.1 200 OK\r\ntransfer-encoding: chunked\r\ntrailer: Chunky-Trailer\r\n"; + assert_eq!(&sres[..expected_head.len()], expected_head); + + // skip the date header + let date_fragment = "GMT\r\n\r\n"; + let pos = sres.find(date_fragment).expect("find GMT"); + let body = &sres[pos + date_fragment.len()..]; + + let expected_body = "5\r\nhello\r\n0\r\nchunky-trailer: header data\r\n\r\n"; + assert_eq!(body, expected_body); +} + #[test] fn http1_trailer_fields_not_allowed() { let body = futures_util::stream::once(async move { Ok("hello".into()) });