217 lines
6.8 KiB
Rust
217 lines
6.8 KiB
Rust
use std::fmt::Write;
|
|
use std::mem;
|
|
use std::sync::Arc;
|
|
|
|
use bytes::BytesMut;
|
|
use http;
|
|
use http::header::{CONNECTION, HOST, UPGRADE};
|
|
use http::uri::{Authority, Parts, Scheme, Uri};
|
|
|
|
use ctx::transport::{Server as ServerCtx};
|
|
use super::upgrade::HttpConnect;
|
|
|
|
/// Tries to make sure the `Uri` of the request is in a form needed by
|
|
/// hyper's Client.
|
|
pub fn normalize_our_view_of_uri<B>(req: &mut http::Request<B>) {
|
|
debug_assert!(
|
|
req.uri().scheme_part().is_none(),
|
|
"normalize_uri shouldn't be called with absolute URIs: {:?}",
|
|
req.uri()
|
|
);
|
|
|
|
// try to parse the Host header
|
|
if let Some(auth) = authority_from_host(&req) {
|
|
set_authority(req.uri_mut(), auth);
|
|
return;
|
|
}
|
|
|
|
// last resort is to use the so_original_dst
|
|
let orig_dst = req.extensions()
|
|
.get::<Arc<ServerCtx>>()
|
|
.and_then(|ctx| ctx.orig_dst_if_not_local());
|
|
if let Some(orig_dst) = orig_dst {
|
|
let mut bytes = BytesMut::with_capacity(31);
|
|
write!(&mut bytes, "{}", orig_dst)
|
|
.expect("socket address display is under 31 bytes");
|
|
let bytes = bytes.freeze();
|
|
let auth = Authority::from_shared(bytes)
|
|
.expect("socket address is valid authority");
|
|
set_authority(req.uri_mut(), auth);
|
|
}
|
|
}
|
|
|
|
/// Convert any URI into its origin-form (relative path part only).
|
|
pub fn set_origin_form(uri: &mut Uri) {
|
|
let mut parts = mem::replace(uri, Uri::default()).into_parts();
|
|
parts.scheme = None;
|
|
parts.authority = None;
|
|
*uri = Uri::from_parts(parts)
|
|
.expect("path only is valid origin-form uri")
|
|
}
|
|
|
|
/// Returns an Authority from a request's Host header.
|
|
pub fn authority_from_host<B>(req: &http::Request<B>) -> Option<Authority> {
|
|
req.headers().get(HOST)
|
|
.and_then(|host| {
|
|
host.to_str().ok()
|
|
.and_then(|s| {
|
|
if s.is_empty() {
|
|
None
|
|
} else {
|
|
s.parse::<Authority>().ok()
|
|
}
|
|
})
|
|
})
|
|
}
|
|
|
|
fn set_authority(uri: &mut http::Uri, auth: Authority) {
|
|
let mut parts = Parts::from(mem::replace(uri, Uri::default()));
|
|
|
|
parts.authority = Some(auth);
|
|
|
|
// If this was an origin-form target (path only),
|
|
// then we can't *only* set the authority, as that's
|
|
// an illegal target (such as `example.com/docs`).
|
|
//
|
|
// But don't set a scheme if this was authority-form (CONNECT),
|
|
// since that would change its meaning (like `https://example.com`).
|
|
if parts.path_and_query.is_some() {
|
|
parts.scheme = Some(Scheme::HTTP);
|
|
}
|
|
|
|
let new = Uri::from_parts(parts)
|
|
.expect("absolute uri");
|
|
|
|
*uri = new;
|
|
}
|
|
|
|
pub fn strip_connection_headers(headers: &mut http::HeaderMap) {
|
|
if let Some(val) = headers.remove(CONNECTION) {
|
|
if let Ok(conn_header) = val.to_str() {
|
|
// A `Connection` header may have a comma-separated list of
|
|
// names of other headers that are meant for only this specific
|
|
// connection.
|
|
//
|
|
// Iterate these names and remove them as headers.
|
|
for name in conn_header.split(',') {
|
|
let name = name.trim();
|
|
headers.remove(name);
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
// Additionally, strip these "connection-level" headers always, since
|
|
// they are otherwise illegal if upgraded to HTTP2.
|
|
headers.remove(UPGRADE);
|
|
headers.remove("proxy-connection");
|
|
headers.remove("keep-alive");
|
|
}
|
|
|
|
/// Checks requests to determine if they want to perform an HTTP upgrade.
|
|
pub fn wants_upgrade<B>(req: &http::Request<B>) -> bool {
|
|
// HTTP upgrades were added in 1.1, not 1.0.
|
|
if req.version() != http::Version::HTTP_11 {
|
|
return false;
|
|
}
|
|
|
|
if let Some(upgrade) = req.headers().get(UPGRADE) {
|
|
// If an `h2` upgrade over HTTP/1.1 were to go by the proxy,
|
|
// and it succeeded, there would an h2 connection, but it would
|
|
// be opaque-to-the-proxy, acting as just a TCP proxy.
|
|
//
|
|
// A user wouldn't be able to see any usual HTTP telemetry about
|
|
// requests going over that connection. Instead of that confusion,
|
|
// the proxy strips h2 upgrade headers.
|
|
//
|
|
// Eventually, the proxy will support h2 upgrades directly.
|
|
return upgrade != "h2c";
|
|
}
|
|
|
|
// HTTP/1.1 CONNECT requests are just like upgrades!
|
|
req.method() == &http::Method::CONNECT
|
|
}
|
|
|
|
/// Checks responses to determine if they are successful HTTP upgrades.
|
|
pub fn is_upgrade<B>(res: &http::Response<B>) -> bool {
|
|
// Upgrades were introduced in HTTP/1.1
|
|
if res.version() != http::Version::HTTP_11 {
|
|
return false;
|
|
}
|
|
|
|
// 101 Switching Protocols
|
|
if res.status() == http::StatusCode::SWITCHING_PROTOCOLS {
|
|
return true;
|
|
}
|
|
|
|
// CONNECT requests are complete if status code is 2xx.
|
|
if res.extensions().get::<HttpConnect>().is_some()
|
|
&& res.status().is_success() {
|
|
return true;
|
|
}
|
|
|
|
// Just a regular HTTP response...
|
|
false
|
|
}
|
|
|
|
/// Returns if the request target is in `absolute-form`.
|
|
///
|
|
/// This is `absolute-form`: `https://example.com/docs`
|
|
///
|
|
/// This is not:
|
|
///
|
|
/// - `/docs`
|
|
/// - `example.com`
|
|
pub fn is_absolute_form(uri: &Uri) -> bool {
|
|
// It's sufficient just to check for a scheme, since in HTTP1,
|
|
// it's required in absolute-form, and `http::Uri` doesn't
|
|
// allow URIs with the other parts missing when the scheme is set.
|
|
debug_assert!(
|
|
uri.scheme_part().is_none() ||
|
|
(
|
|
uri.authority_part().is_some() &&
|
|
uri.path_and_query().is_some()
|
|
),
|
|
"is_absolute_form http::Uri invariants: {:?}",
|
|
uri
|
|
);
|
|
|
|
uri.scheme_part().is_some()
|
|
}
|
|
|
|
/// Returns if the request target is in `origin-form`.
|
|
///
|
|
/// This is `origin-form`: `example.com`
|
|
fn is_origin_form(uri: &Uri) -> bool {
|
|
uri.scheme_part().is_none() &&
|
|
uri.path_and_query().is_none()
|
|
}
|
|
|
|
/// Returns if the received request is definitely bad.
|
|
///
|
|
/// Just because a request parses doesn't mean it's correct. For examples:
|
|
///
|
|
/// - `GET example.com`
|
|
/// - `CONNECT /just-a-path
|
|
pub fn is_bad_request<B>(req: &http::Request<B>) -> bool {
|
|
if req.method() == &http::Method::CONNECT {
|
|
// CONNECT is only valid over HTTP/1.1
|
|
if req.version() != http::Version::HTTP_11 {
|
|
debug!("CONNECT request not valid for HTTP/1.0: {:?}", req.uri());
|
|
return true;
|
|
}
|
|
|
|
// CONNECT requests are only valid in authority-form.
|
|
if !is_origin_form(req.uri()) {
|
|
debug!("CONNECT request with illegal URI: {:?}", req.uri());
|
|
return true;
|
|
}
|
|
// If not CONNECT, refuse any origin-form URIs
|
|
} else if is_origin_form(req.uri()) {
|
|
debug!("{} request with illegal URI: {:?}", req.method(), req.uri());
|
|
return true;
|
|
}
|
|
|
|
false
|
|
}
|