feat(http1): implement obsolete line folding (#2734)
The client now has an option to allow parsing responses with obsolete line folding in headers. The option is off by default, since the spec recommends to reject such things if you can.
This commit is contained in:
@@ -30,7 +30,7 @@ futures-util = { version = "0.3", default-features = false }
|
||||
http = "0.2"
|
||||
http-body = "0.4"
|
||||
httpdate = "1.0"
|
||||
httparse = "1.5.1"
|
||||
httparse = "1.6"
|
||||
h2 = { version = "0.3.9", optional = true }
|
||||
itoa = "1"
|
||||
tracing = { version = "0.1", default-features = false, features = ["std"] }
|
||||
|
||||
@@ -1000,6 +1000,9 @@ impl Builder {
|
||||
/// Set whether HTTP/1 connections will accept spaces between header names
|
||||
/// and the colon that follow them in responses.
|
||||
///
|
||||
/// Newline codepoints (`\r` and `\n`) will be transformed to spaces when
|
||||
/// parsing.
|
||||
///
|
||||
/// You probably don't need this, here is what [RFC 7230 Section 3.2.4.] has
|
||||
/// to say about it:
|
||||
///
|
||||
@@ -1022,6 +1025,43 @@ impl Builder {
|
||||
self
|
||||
}
|
||||
|
||||
/// Set whether HTTP/1 connections will accept obsolete line folding for
|
||||
/// header values.
|
||||
///
|
||||
/// You probably don't need this, here is what [RFC 7230 Section 3.2.4.] has
|
||||
/// to say about it:
|
||||
///
|
||||
/// > A server that receives an obs-fold in a request message that is not
|
||||
/// > within a message/http container MUST either reject the message by
|
||||
/// > sending a 400 (Bad Request), preferably with a representation
|
||||
/// > explaining that obsolete line folding is unacceptable, or replace
|
||||
/// > each received obs-fold with one or more SP octets prior to
|
||||
/// > interpreting the field value or forwarding the message downstream.
|
||||
///
|
||||
/// > A proxy or gateway that receives an obs-fold in a response message
|
||||
/// > that is not within a message/http container MUST either discard the
|
||||
/// > message and replace it with a 502 (Bad Gateway) response, preferably
|
||||
/// > with a representation explaining that unacceptable line folding was
|
||||
/// > received, or replace each received obs-fold with one or more SP
|
||||
/// > octets prior to interpreting the field value or forwarding the
|
||||
/// > message downstream.
|
||||
///
|
||||
/// > A user agent that receives an obs-fold in a response message that is
|
||||
/// > not within a message/http container MUST replace each received
|
||||
/// > obs-fold with one or more SP octets prior to interpreting the field
|
||||
/// > value.
|
||||
///
|
||||
/// Note that this setting does not affect HTTP/2.
|
||||
///
|
||||
/// Default is false.
|
||||
///
|
||||
/// [RFC 7230 Section 3.2.4.]: https://tools.ietf.org/html/rfc7230#section-3.2.4
|
||||
pub fn http1_allow_obsolete_multiline_headers_in_responses(&mut self, val: bool) -> &mut Self {
|
||||
self.conn_builder
|
||||
.http1_allow_obsolete_multiline_headers_in_responses(val);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set whether HTTP/1 connections should try to use vectored writes,
|
||||
/// or always flatten into a single buffer.
|
||||
///
|
||||
|
||||
@@ -615,6 +615,49 @@ impl Builder {
|
||||
self
|
||||
}
|
||||
|
||||
/// Set whether HTTP/1 connections will accept obsolete line folding for
|
||||
/// header values.
|
||||
///
|
||||
/// Newline codepoints (`\r` and `\n`) will be transformed to spaces when
|
||||
/// parsing.
|
||||
///
|
||||
/// You probably don't need this, here is what [RFC 7230 Section 3.2.4.] has
|
||||
/// to say about it:
|
||||
///
|
||||
/// > A server that receives an obs-fold in a request message that is not
|
||||
/// > within a message/http container MUST either reject the message by
|
||||
/// > sending a 400 (Bad Request), preferably with a representation
|
||||
/// > explaining that obsolete line folding is unacceptable, or replace
|
||||
/// > each received obs-fold with one or more SP octets prior to
|
||||
/// > interpreting the field value or forwarding the message downstream.
|
||||
///
|
||||
/// > A proxy or gateway that receives an obs-fold in a response message
|
||||
/// > that is not within a message/http container MUST either discard the
|
||||
/// > message and replace it with a 502 (Bad Gateway) response, preferably
|
||||
/// > with a representation explaining that unacceptable line folding was
|
||||
/// > received, or replace each received obs-fold with one or more SP
|
||||
/// > octets prior to interpreting the field value or forwarding the
|
||||
/// > message downstream.
|
||||
///
|
||||
/// > A user agent that receives an obs-fold in a response message that is
|
||||
/// > not within a message/http container MUST replace each received
|
||||
/// > obs-fold with one or more SP octets prior to interpreting the field
|
||||
/// > value.
|
||||
///
|
||||
/// Note that this setting does not affect HTTP/2.
|
||||
///
|
||||
/// Default is false.
|
||||
///
|
||||
/// [RFC 7230 Section 3.2.4.]: https://tools.ietf.org/html/rfc7230#section-3.2.4
|
||||
pub fn http1_allow_obsolete_multiline_headers_in_responses(
|
||||
&mut self,
|
||||
enabled: bool,
|
||||
) -> &mut Builder {
|
||||
self.h1_parser_config
|
||||
.allow_obsolete_multiline_headers_in_responses(enabled);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set whether HTTP/1 connections should try to use vectored writes,
|
||||
/// or always flatten into a single buffer.
|
||||
///
|
||||
|
||||
@@ -955,7 +955,21 @@ impl Http1Transaction for Client {
|
||||
}
|
||||
};
|
||||
|
||||
let slice = buf.split_to(len).freeze();
|
||||
let mut slice = buf.split_to(len);
|
||||
|
||||
if ctx.h1_parser_config.obsolete_multiline_headers_in_responses_are_allowed() {
|
||||
for header in &headers_indices[..headers_len] {
|
||||
// SAFETY: array is valid up to `headers_len`
|
||||
let header = unsafe { &*header.as_ptr() };
|
||||
for b in &mut slice[header.value.0..header.value.1] {
|
||||
if *b == b'\r' || *b == b'\n' {
|
||||
*b = b' ';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let slice = slice.freeze();
|
||||
|
||||
let mut headers = ctx.cached_headers.take().unwrap_or_else(HeaderMap::new);
|
||||
|
||||
|
||||
@@ -2214,6 +2214,63 @@ mod conn {
|
||||
future::join(server, client).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn get_obsolete_line_folding() {
|
||||
let _ = ::pretty_env_logger::try_init();
|
||||
let listener = TkTcpListener::bind(SocketAddr::from(([127, 0, 0, 1], 0)))
|
||||
.await
|
||||
.unwrap();
|
||||
let addr = listener.local_addr().unwrap();
|
||||
|
||||
let server = async move {
|
||||
let mut sock = listener.accept().await.unwrap().0;
|
||||
let mut buf = [0; 4096];
|
||||
let n = sock.read(&mut buf).await.expect("read 1");
|
||||
|
||||
// Notably:
|
||||
// - Just a path, since just a path was set
|
||||
// - No host, since no host was set
|
||||
let expected = "GET /a HTTP/1.1\r\n\r\n";
|
||||
assert_eq!(s(&buf[..n]), expected);
|
||||
|
||||
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")
|
||||
.await
|
||||
.unwrap();
|
||||
};
|
||||
|
||||
let client = async move {
|
||||
let tcp = tcp_connect(&addr).await.expect("connect");
|
||||
let (mut client, conn) = conn::Builder::new()
|
||||
.http1_allow_obsolete_multiline_headers_in_responses(true)
|
||||
.handshake::<_, Body>(tcp)
|
||||
.await
|
||||
.expect("handshake");
|
||||
|
||||
tokio::task::spawn(async move {
|
||||
conn.await.expect("http conn");
|
||||
});
|
||||
|
||||
let req = Request::builder()
|
||||
.uri("/a")
|
||||
.body(Default::default())
|
||||
.unwrap();
|
||||
let mut res = client.send_request(req).await.expect("send_request");
|
||||
assert_eq!(res.status(), hyper::StatusCode::OK);
|
||||
assert_eq!(res.headers().len(), 2);
|
||||
assert_eq!(
|
||||
res.headers().get(http::header::CONTENT_LENGTH).unwrap(),
|
||||
"0"
|
||||
);
|
||||
assert_eq!(
|
||||
res.headers().get("line-folded-header").unwrap(),
|
||||
"hello world"
|
||||
);
|
||||
assert!(res.body_mut().next().await.is_none());
|
||||
};
|
||||
|
||||
future::join(server, client).await;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn incoming_content_length() {
|
||||
use hyper::body::HttpBody;
|
||||
|
||||
Reference in New Issue
Block a user