Skip to content

Commit d7ab016

Browse files
committed
fix(server): correctly handle CONNECT requests
- In the higher-level `Server` API, since connection upgrades aren't yet supported, returning a 2xx response to a `CONNECT` request is a user error. A 500 response is written to the client, the connection is closed, and an error is reported back to the user. - In the lower-level `server::Connection` API, where upgrades *are* supported, a 2xx response correctly marks the response as the final one, instead of trying to parse more requests afterwards.
1 parent bc5e22f commit d7ab016

File tree

2 files changed

+111
-4
lines changed

2 files changed

+111
-4
lines changed

src/proto/h1/role.rs

+47-4
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,9 @@ where
191191
// This is because Service only allows returning a single Response, and
192192
// so if you try to reply with a e.g. 100 Continue, you have no way of
193193
// replying with the latter status code response.
194-
let (ret, mut is_last) = if StatusCode::SWITCHING_PROTOCOLS == msg.head.subject {
194+
let is_upgrade = msg.head.subject == StatusCode::SWITCHING_PROTOCOLS
195+
|| (msg.req_method == &Some(Method::CONNECT) && msg.head.subject.is_success());
196+
let (ret, mut is_last) = if is_upgrade {
195197
(T::on_encode_upgrade(&mut msg), true)
196198
} else if msg.head.subject.is_informational() {
197199
error!("response with 1xx status code not supported");
@@ -851,12 +853,20 @@ impl OnUpgrade for YesUpgrades {
851853

852854
impl OnUpgrade for NoUpgrades {
853855
fn on_encode_upgrade(msg: &mut Encode<StatusCode>) -> ::Result<()> {
854-
error!("response with 101 status code not supported");
855856
*msg.head = MessageHead::default();
856857
msg.head.subject = ::StatusCode::INTERNAL_SERVER_ERROR;
857858
msg.body = None;
858-
//TODO: replace with more descriptive error
859-
Err(::Error::new_status())
859+
860+
if msg.head.subject == StatusCode::SWITCHING_PROTOCOLS {
861+
error!("response with 101 status code not supported");
862+
Err(Parse::UpgradeNotSupported.into())
863+
} else if msg.req_method == &Some(Method::CONNECT) {
864+
error!("200 response to CONNECT request not supported");
865+
Err(::Error::new_user_unsupported_request_method())
866+
} else {
867+
debug_assert!(false, "upgrade incorrectly detected");
868+
Err(::Error::new_status())
869+
}
860870
}
861871

862872
fn on_decode_upgrade() -> Result<Decoder, Parse> {
@@ -1309,6 +1319,39 @@ mod tests {
13091319
assert_eq!(vec, b"GET / HTTP/1.1\r\nContent-Length: 10\r\nContent-Type: application/json\r\n\r\n".to_vec());
13101320
}
13111321

1322+
#[test]
1323+
fn test_server_no_upgrades_connect_method() {
1324+
let mut head = MessageHead::default();
1325+
1326+
let mut vec = Vec::new();
1327+
let err = Server::encode(Encode {
1328+
head: &mut head,
1329+
body: None,
1330+
keep_alive: true,
1331+
req_method: &mut Some(Method::CONNECT),
1332+
title_case_headers: false,
1333+
}, &mut vec).unwrap_err();
1334+
1335+
assert!(err.is_user());
1336+
assert_eq!(err.kind(), &::error::Kind::UnsupportedRequestMethod);
1337+
}
1338+
1339+
#[test]
1340+
fn test_server_yes_upgrades_connect_method() {
1341+
let mut head = MessageHead::default();
1342+
1343+
let mut vec = Vec::new();
1344+
let encoder = S::<YesUpgrades>::encode(Encode {
1345+
head: &mut head,
1346+
body: None,
1347+
keep_alive: true,
1348+
req_method: &mut Some(Method::CONNECT),
1349+
title_case_headers: false,
1350+
}, &mut vec).unwrap();
1351+
1352+
assert!(encoder.is_last());
1353+
}
1354+
13121355
#[cfg(feature = "nightly")]
13131356
use test::Bencher;
13141357

tests/server.rs

+64
Original file line numberDiff line numberDiff line change
@@ -1147,6 +1147,70 @@ fn upgrades() {
11471147
assert_eq!(vec, b"bar=foo");
11481148
}
11491149

1150+
#[test]
1151+
fn http_connect() {
1152+
use tokio_io::io::{read_to_end, write_all};
1153+
let _ = pretty_env_logger::try_init();
1154+
let runtime = Runtime::new().unwrap();
1155+
let listener = tcp_bind(&"127.0.0.1:0".parse().unwrap(), &runtime.reactor()).unwrap();
1156+
let addr = listener.local_addr().unwrap();
1157+
let (tx, rx) = oneshot::channel();
1158+
1159+
thread::spawn(move || {
1160+
let mut tcp = connect(&addr);
1161+
tcp.write_all(b"\
1162+
CONNECT localhost:80 HTTP/1.1\r\n\
1163+
\r\n\
1164+
eagerly optimistic\
1165+
").expect("write 1");
1166+
let mut buf = [0; 256];
1167+
tcp.read(&mut buf).expect("read 1");
1168+
1169+
let expected = "HTTP/1.1 200 OK\r\n";
1170+
assert_eq!(s(&buf[..expected.len()]), expected);
1171+
let _ = tx.send(());
1172+
1173+
let n = tcp.read(&mut buf).expect("read 2");
1174+
assert_eq!(s(&buf[..n]), "foo=bar");
1175+
tcp.write_all(b"bar=foo").expect("write 2");
1176+
});
1177+
1178+
let fut = listener.incoming()
1179+
.into_future()
1180+
.map_err(|_| -> hyper::Error { unreachable!() })
1181+
.and_then(|(item, _incoming)| {
1182+
let socket = item.unwrap();
1183+
let conn = Http::new()
1184+
.serve_connection(socket, service_fn(|_| {
1185+
let res = Response::builder()
1186+
.status(200)
1187+
.body(hyper::Body::empty())
1188+
.unwrap();
1189+
Ok::<_, hyper::Error>(res)
1190+
}));
1191+
1192+
let mut conn_opt = Some(conn);
1193+
future::poll_fn(move || {
1194+
try_ready!(conn_opt.as_mut().unwrap().poll_without_shutdown());
1195+
// conn is done with HTTP now
1196+
Ok(conn_opt.take().unwrap().into())
1197+
})
1198+
});
1199+
1200+
let conn = fut.wait().unwrap();
1201+
1202+
// wait so that we don't write until other side saw 101 response
1203+
rx.wait().unwrap();
1204+
1205+
let parts = conn.into_parts();
1206+
let io = parts.io;
1207+
assert_eq!(parts.read_buf, "eagerly optimistic");
1208+
1209+
let io = write_all(io, b"foo=bar").wait().unwrap().0;
1210+
let vec = read_to_end(io, vec![]).wait().unwrap().1;
1211+
assert_eq!(vec, b"bar=foo");
1212+
}
1213+
11501214
#[test]
11511215
fn parse_errors_send_4xx_response() {
11521216
let runtime = Runtime::new().unwrap();

0 commit comments

Comments
 (0)