Skip to content

Commit f779500

Browse files
noxseanmonstar
authored andcommitted
feat(h1): implement obsolete line folding
1 parent f1b89c1 commit f779500

File tree

5 files changed

+156
-2
lines changed

5 files changed

+156
-2
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ futures-util = { version = "0.3", default-features = false }
3030
http = "0.2"
3131
http-body = "0.4"
3232
httpdate = "1.0"
33-
httparse = "1.5.1"
33+
httparse = "1.6"
3434
h2 = { version = "0.3.9", optional = true }
3535
itoa = "1"
3636
tracing = { version = "0.1", default-features = false, features = ["std"] }

src/client/client.rs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1000,6 +1000,9 @@ impl Builder {
10001000
/// Set whether HTTP/1 connections will accept spaces between header names
10011001
/// and the colon that follow them in responses.
10021002
///
1003+
/// Newline codepoints (`\r` and `\n`) will be transformed to spaces when
1004+
/// parsing.
1005+
///
10031006
/// You probably don't need this, here is what [RFC 7230 Section 3.2.4.] has
10041007
/// to say about it:
10051008
///
@@ -1022,6 +1025,43 @@ impl Builder {
10221025
self
10231026
}
10241027

1028+
/// Set whether HTTP/1 connections will accept obsolete line folding for
1029+
/// header values.
1030+
///
1031+
/// You probably don't need this, here is what [RFC 7230 Section 3.2.4.] has
1032+
/// to say about it:
1033+
///
1034+
/// > A server that receives an obs-fold in a request message that is not
1035+
/// > within a message/http container MUST either reject the message by
1036+
/// > sending a 400 (Bad Request), preferably with a representation
1037+
/// > explaining that obsolete line folding is unacceptable, or replace
1038+
/// > each received obs-fold with one or more SP octets prior to
1039+
/// > interpreting the field value or forwarding the message downstream.
1040+
///
1041+
/// > A proxy or gateway that receives an obs-fold in a response message
1042+
/// > that is not within a message/http container MUST either discard the
1043+
/// > message and replace it with a 502 (Bad Gateway) response, preferably
1044+
/// > with a representation explaining that unacceptable line folding was
1045+
/// > received, or replace each received obs-fold with one or more SP
1046+
/// > octets prior to interpreting the field value or forwarding the
1047+
/// > message downstream.
1048+
///
1049+
/// > A user agent that receives an obs-fold in a response message that is
1050+
/// > not within a message/http container MUST replace each received
1051+
/// > obs-fold with one or more SP octets prior to interpreting the field
1052+
/// > value.
1053+
///
1054+
/// Note that this setting does not affect HTTP/2.
1055+
///
1056+
/// Default is false.
1057+
///
1058+
/// [RFC 7230 Section 3.2.4.]: https://tools.ietf.org/html/rfc7230#section-3.2.4
1059+
pub fn http1_allow_obsolete_multiline_headers_in_responses(&mut self, val: bool) -> &mut Self {
1060+
self.conn_builder
1061+
.http1_allow_obsolete_multiline_headers_in_responses(val);
1062+
self
1063+
}
1064+
10251065
/// Set whether HTTP/1 connections should try to use vectored writes,
10261066
/// or always flatten into a single buffer.
10271067
///

src/client/conn.rs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -598,6 +598,49 @@ impl Builder {
598598
self
599599
}
600600

601+
/// Set whether HTTP/1 connections will accept obsolete line folding for
602+
/// header values.
603+
///
604+
/// Newline codepoints (`\r` and `\n`) will be transformed to spaces when
605+
/// parsing.
606+
///
607+
/// You probably don't need this, here is what [RFC 7230 Section 3.2.4.] has
608+
/// to say about it:
609+
///
610+
/// > A server that receives an obs-fold in a request message that is not
611+
/// > within a message/http container MUST either reject the message by
612+
/// > sending a 400 (Bad Request), preferably with a representation
613+
/// > explaining that obsolete line folding is unacceptable, or replace
614+
/// > each received obs-fold with one or more SP octets prior to
615+
/// > interpreting the field value or forwarding the message downstream.
616+
///
617+
/// > A proxy or gateway that receives an obs-fold in a response message
618+
/// > that is not within a message/http container MUST either discard the
619+
/// > message and replace it with a 502 (Bad Gateway) response, preferably
620+
/// > with a representation explaining that unacceptable line folding was
621+
/// > received, or replace each received obs-fold with one or more SP
622+
/// > octets prior to interpreting the field value or forwarding the
623+
/// > message downstream.
624+
///
625+
/// > A user agent that receives an obs-fold in a response message that is
626+
/// > not within a message/http container MUST replace each received
627+
/// > obs-fold with one or more SP octets prior to interpreting the field
628+
/// > value.
629+
///
630+
/// Note that this setting does not affect HTTP/2.
631+
///
632+
/// Default is false.
633+
///
634+
/// [RFC 7230 Section 3.2.4.]: https://tools.ietf.org/html/rfc7230#section-3.2.4
635+
pub fn http1_allow_obsolete_multiline_headers_in_responses(
636+
&mut self,
637+
enabled: bool,
638+
) -> &mut Builder {
639+
self.h1_parser_config
640+
.allow_obsolete_multiline_headers_in_responses(enabled);
641+
self
642+
}
643+
601644
/// Set whether HTTP/1 connections should try to use vectored writes,
602645
/// or always flatten into a single buffer.
603646
///

src/proto/h1/role.rs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -955,7 +955,21 @@ impl Http1Transaction for Client {
955955
}
956956
};
957957

958-
let slice = buf.split_to(len).freeze();
958+
let mut slice = buf.split_to(len);
959+
960+
if ctx.h1_parser_config.obsolete_multiline_headers_in_responses_are_allowed() {
961+
for header in &headers_indices[..headers_len] {
962+
// SAFETY: array is valid up to `headers_len`
963+
let header = unsafe { &*header.as_ptr() };
964+
for b in &mut slice[header.value.0..header.value.1] {
965+
if *b == b'\r' || *b == b'\n' {
966+
*b = b' ';
967+
}
968+
}
969+
}
970+
}
971+
972+
let slice = slice.freeze();
959973

960974
let mut headers = ctx.cached_headers.take().unwrap_or_else(HeaderMap::new);
961975

tests/client.rs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2214,6 +2214,63 @@ mod conn {
22142214
future::join(server, client).await;
22152215
}
22162216

2217+
#[tokio::test]
2218+
async fn get_obsolete_line_folding() {
2219+
let _ = ::pretty_env_logger::try_init();
2220+
let listener = TkTcpListener::bind(SocketAddr::from(([127, 0, 0, 1], 0)))
2221+
.await
2222+
.unwrap();
2223+
let addr = listener.local_addr().unwrap();
2224+
2225+
let server = async move {
2226+
let mut sock = listener.accept().await.unwrap().0;
2227+
let mut buf = [0; 4096];
2228+
let n = sock.read(&mut buf).await.expect("read 1");
2229+
2230+
// Notably:
2231+
// - Just a path, since just a path was set
2232+
// - No host, since no host was set
2233+
let expected = "GET /a HTTP/1.1\r\n\r\n";
2234+
assert_eq!(s(&buf[..n]), expected);
2235+
2236+
sock.write_all(b"HTTP/1.1 200 OK\r\nContent-Length: \r\n 0\r\nLine-Folded-Header: hello\r\n world \r\n \r\n\r\n")
2237+
.await
2238+
.unwrap();
2239+
};
2240+
2241+
let client = async move {
2242+
let tcp = tcp_connect(&addr).await.expect("connect");
2243+
let (mut client, conn) = conn::Builder::new()
2244+
.http1_allow_obsolete_multiline_headers_in_responses(true)
2245+
.handshake::<_, Body>(tcp)
2246+
.await
2247+
.expect("handshake");
2248+
2249+
tokio::task::spawn(async move {
2250+
conn.await.expect("http conn");
2251+
});
2252+
2253+
let req = Request::builder()
2254+
.uri("/a")
2255+
.body(Default::default())
2256+
.unwrap();
2257+
let mut res = client.send_request(req).await.expect("send_request");
2258+
assert_eq!(res.status(), hyper::StatusCode::OK);
2259+
assert_eq!(res.headers().len(), 2);
2260+
assert_eq!(
2261+
res.headers().get(http::header::CONTENT_LENGTH).unwrap(),
2262+
"0"
2263+
);
2264+
assert_eq!(
2265+
res.headers().get("line-folded-header").unwrap(),
2266+
"hello world"
2267+
);
2268+
assert!(res.body_mut().next().await.is_none());
2269+
};
2270+
2271+
future::join(server, client).await;
2272+
}
2273+
22172274
#[test]
22182275
fn incoming_content_length() {
22192276
use hyper::body::HttpBody;

0 commit comments

Comments
 (0)