Skip to content

Commit a4fd59e

Browse files
noxseanmonstar
authored andcommitted
feat(h1): implement obsolete line folding
1 parent 5ec094c commit a4fd59e

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
@@ -615,6 +615,49 @@ impl Builder {
615615
self
616616
}
617617

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

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)