Skip to content

Commit f20afba

Browse files
Josh Leeb-du Toitseanmonstar
Josh Leeb-du Toit
authored andcommitted
feat(http2): Strip connection headers before sending
Automatically removes "connection" headers before sending over HTTP2. These headers are illegal in HTTP2, and would otherwise cause errors. Closes: #1551
1 parent a0a0fcd commit f20afba

File tree

2 files changed

+132
-27
lines changed

2 files changed

+132
-27
lines changed

src/proto/h2/mod.rs

+54-27
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
use bytes::Buf;
22
use futures::{Async, Future, Poll};
33
use h2::{Reason, SendStream};
4+
use http::header::{
5+
HeaderName, CONNECTION, PROXY_AUTHENTICATE, PROXY_AUTHORIZATION, TE, TRAILER,
6+
TRANSFER_ENCODING, UPGRADE,
7+
};
48
use http::HeaderMap;
5-
use http::header::{CONNECTION, TRANSFER_ENCODING};
69

7-
use ::body::Payload;
10+
use body::Payload;
811

912
mod client;
1013
mod server;
@@ -13,13 +16,42 @@ pub(crate) use self::client::Client;
1316
pub(crate) use self::server::Server;
1417

1518
fn strip_connection_headers(headers: &mut HeaderMap) {
16-
if headers.remove(TRANSFER_ENCODING).is_some() {
17-
trace!("removed illegal Transfer-Encoding header");
19+
// List of connection headers from:
20+
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Connection
21+
let connection_headers = [
22+
HeaderName::from_lowercase(b"keep-alive").unwrap(),
23+
HeaderName::from_lowercase(b"proxy-connection").unwrap(),
24+
PROXY_AUTHENTICATE,
25+
PROXY_AUTHORIZATION,
26+
TE,
27+
TRAILER,
28+
TRANSFER_ENCODING,
29+
UPGRADE,
30+
];
31+
32+
for header in connection_headers.iter() {
33+
if headers.remove(header).is_some() {
34+
warn!("Connection header illegal in HTTP/2: {}", header.as_str());
35+
}
1836
}
19-
if headers.contains_key(CONNECTION) {
20-
warn!("Connection header illegal in HTTP/2");
21-
//TODO: actually remove it, after checking the value
22-
//and removing all related headers
37+
38+
if let Some(header) = headers.remove(CONNECTION) {
39+
warn!(
40+
"Connection header illegal in HTTP/2: {}",
41+
CONNECTION.as_str()
42+
);
43+
let header_contents = header.to_str().unwrap();
44+
45+
// A `Connection` header may have a comma-separated list of names of other headers that
46+
// are meant for only this specific connection.
47+
//
48+
// Iterate these names and remove them as headers. Connection-specific headers are
49+
// forbidden in HTTP2, as that information has been moved into frame types of the h2
50+
// protocol.
51+
for name in header_contents.split(',') {
52+
let name = name.trim();
53+
headers.remove(name);
54+
}
2355
}
2456
}
2557

@@ -55,7 +87,8 @@ where
5587

5688
fn send_eos_frame(&mut self) -> ::Result<()> {
5789
trace!("send body eos");
58-
self.body_tx.send_data(SendBuf(None), true)
90+
self.body_tx
91+
.send_data(SendBuf(None), true)
5992
.map_err(::Error::new_body_write)
6093
}
6194
}
@@ -94,13 +127,14 @@ where
94127
);
95128

96129
let buf = SendBuf(Some(chunk));
97-
self.body_tx.send_data(buf, is_eos)
130+
self.body_tx
131+
.send_data(buf, is_eos)
98132
.map_err(::Error::new_body_write)?;
99133

100134
if is_eos {
101-
return Ok(Async::Ready(()))
135+
return Ok(Async::Ready(()));
102136
}
103-
},
137+
}
104138
None => {
105139
let is_eos = self.stream.is_end_stream();
106140
if is_eos {
@@ -109,19 +143,20 @@ where
109143
self.data_done = true;
110144
// loop again to poll_trailers
111145
}
112-
},
146+
}
113147
}
114148
} else {
115149
match try_ready!(self.stream.poll_trailers().map_err(|e| self.on_err(e))) {
116150
Some(trailers) => {
117-
self.body_tx.send_trailers(trailers)
151+
self.body_tx
152+
.send_trailers(trailers)
118153
.map_err(::Error::new_body_write)?;
119154
return Ok(Async::Ready(()));
120-
},
155+
}
121156
None => {
122157
// There were no trailers, so send an empty DATA frame...
123158
return self.send_eos_frame().map(Async::Ready);
124-
},
159+
}
125160
}
126161
}
127162
}
@@ -133,24 +168,16 @@ struct SendBuf<B>(Option<B>);
133168
impl<B: Buf> Buf for SendBuf<B> {
134169
#[inline]
135170
fn remaining(&self) -> usize {
136-
self.0
137-
.as_ref()
138-
.map(|b| b.remaining())
139-
.unwrap_or(0)
171+
self.0.as_ref().map(|b| b.remaining()).unwrap_or(0)
140172
}
141173

142174
#[inline]
143175
fn bytes(&self) -> &[u8] {
144-
self.0
145-
.as_ref()
146-
.map(|b| b.bytes())
147-
.unwrap_or(&[])
176+
self.0.as_ref().map(|b| b.bytes()).unwrap_or(&[])
148177
}
149178

150179
#[inline]
151180
fn advance(&mut self, cnt: usize) {
152-
self.0
153-
.as_mut()
154-
.map(|b| b.advance(cnt));
181+
self.0.as_mut().map(|b| b.advance(cnt));
155182
}
156183
}

tests/integration.rs

+78
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,84 @@ t! {
6262
;
6363
}
6464

65+
t! {
66+
get_strip_connection_header,
67+
client:
68+
request:
69+
uri: "/",
70+
;
71+
response:
72+
status: 200,
73+
headers: {
74+
// h2 doesn't actually receive the connection header
75+
},
76+
body: "hello world",
77+
;
78+
server:
79+
request:
80+
uri: "/",
81+
;
82+
response:
83+
headers: {
84+
// http2 should strip this header
85+
"connection" => "close",
86+
},
87+
body: "hello world",
88+
;
89+
}
90+
91+
t! {
92+
get_strip_keep_alive_header,
93+
client:
94+
request:
95+
uri: "/",
96+
;
97+
response:
98+
status: 200,
99+
headers: {
100+
// h2 doesn't actually receive the keep-alive header
101+
},
102+
body: "hello world",
103+
;
104+
server:
105+
request:
106+
uri: "/",
107+
;
108+
response:
109+
headers: {
110+
// http2 should strip this header
111+
"keep-alive" => "timeout=5, max=1000",
112+
},
113+
body: "hello world",
114+
;
115+
}
116+
117+
t! {
118+
get_strip_upgrade_header,
119+
client:
120+
request:
121+
uri: "/",
122+
;
123+
response:
124+
status: 200,
125+
headers: {
126+
// h2 doesn't actually receive the upgrade header
127+
},
128+
body: "hello world",
129+
;
130+
server:
131+
request:
132+
uri: "/",
133+
;
134+
response:
135+
headers: {
136+
// http2 should strip this header
137+
"upgrade" => "h2c",
138+
},
139+
body: "hello world",
140+
;
141+
}
142+
65143
t! {
66144
get_body_chunked,
67145
client:

0 commit comments

Comments
 (0)