Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions crates/wasi-http/src/authority.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
//! Shared validation for wasi-http outgoing request authorities (p2 and p3).
//! Shared validation for wasi-http request authorities (p2 and p3).

/// Parse an outgoing request `authority`, rejecting a malformed value.
/// Parse a request `authority`, rejecting a malformed value.
///
/// This is used both for authorities set by guests on outgoing requests and
/// for the `Host` header of incoming requests, which shares the same
/// `uri-host [":" port]` grammar.
///
/// `http::uri::Authority` accepts an authority whose port section is empty or
/// non-numeric (for example `example.com:` or `example.com:abc`), so the port
Expand Down
3 changes: 1 addition & 2 deletions crates/wasi-http/src/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1005,7 +1005,6 @@ impl<'a, T: Send> Prepared<'a, T> {
#[cfg(feature = "p3")]
Proxy::P3(guest) => {
use http::header::HOST;
use http::uri::Authority;

let (parts, body) = request.into_parts();
let http::request::Parts {
Expand All @@ -1026,7 +1025,7 @@ impl<'a, T: Send> Prepared<'a, T> {
None => request_headers
.get(HOST)
.and_then(|host| host.to_str().ok())
.and_then(|host| host.parse::<Authority>().ok()),
.and_then(|host| crate::parse_authority(host.to_string()).ok()),
};
let hooks = view(store.data_mut()).hooks;
let headers = crate::FieldMap::new_immutable(hooks, request_headers);
Expand Down
15 changes: 14 additions & 1 deletion crates/wasi-http/src/p2/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,20 @@ impl WasiHttpCtxView<'_> {
let authority = match parts.uri.authority() {
Some(authority) => authority.to_string(),
None => match parts.headers.get(http::header::HOST) {
Some(host) => host.to_str()?.to_string(),
// RFC 9112 section 3.2 requires a 400 for a `Host` header with
// an invalid field value, so don't pass one on to the guest as
// the request's authority.
Some(host) => match host
.to_str()
.ok()
.and_then(|host| crate::parse_authority(host.to_string()).ok())
{
Some(authority) => authority.to_string(),
None => {
return Err(wasmtime::Error::msg("invalid HTTP request host header")
.context(ErrorResponse::new(http::StatusCode::BAD_REQUEST)));
}
},
None => {
return Err(wasmtime::Error::msg(
"invalid HTTP request missing authority in URI and host header",
Expand Down
47 changes: 47 additions & 0 deletions crates/wasi-http/src/p2/types_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1448,4 +1448,51 @@ mod tests {
);
}
}

/// An incoming request's `Host` header becomes its authority, so it must be
/// a valid `uri-host [":" port]` rather than passed through verbatim.
#[test]
fn incoming_request_host_header() {
use crate::p2::bindings::http::types::{HostIncomingRequest as _, Scheme};

let mut table = wasmtime::component::ResourceTable::new();
let mut http = WasiHttpCtx::new();
let hooks: &mut dyn crate::WasiHttpHooks = crate::default_hooks();
let mut view = WasiHttpCtxView {
ctx: &mut http,
table: &mut table,
hooks,
};
let request = |host: &str| {
hyper::Request::builder()
.uri("/")
.header(http::header::HOST, host)
.body(http_body_util::Empty::<bytes::Bytes>::new())
.unwrap()
};

for host in ["example.com", "example.com:8080", "[::1]:443"] {
let id = view
.new_incoming_request(Scheme::Http, request(host))
.unwrap();
assert_eq!(view.authority(id).unwrap().as_deref(), Some(host));
}

for host in [
"evil.com#.example.com",
"evil.com/x?.example.com",
"user@evil.com",
"example.com:abc",
"a b",
] {
let r = view.new_incoming_request(Scheme::Http, request(host));
let err = r.expect_err(host);
assert_eq!(
err.downcast_ref::<crate::ErrorResponse>()
.map(|e| e.status()),
Some(http::StatusCode::BAD_REQUEST),
"host {host:?}"
);
}
}
}
Loading