Description
The simple_http transport in the jsonrpc crate constructs the HTTP Host header from the resolved socket address (<IP>:<port>) instead of the URL's authority component. This violates RFC 9112 §3.2:
A client MUST send a Host header field [...] in all HTTP/1.1 request messages. If the target URI includes an authority component, then a client MUST send a field value for Host that is identical to that authority component, excluding any userinfo subcomponent and its "@" delimiter [...].
Impact: any consumer of Client::simple_http cannot reach a server that sits behind a host-routed reverse proxy. The proxy receives Host: <ip>:<port>, which doesn't match any vhost rule, and serves its default backend (typically a 404).
Reproduce
# Terminal 1: tiny listener that prints what it receives
python3 -c '
import socket
s = socket.socket(socket.AF_INET6); s.bind(("::", 8080)); s.listen()
c, _ = s.accept()
print(c.recv(4096).decode(errors="replace"))
c.send(b"HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n")
'
# Terminal 2: minimal client
cargo new --quiet /tmp/host-bug && cd /tmp/host-bug
echo 'jsonrpc = "0.19"' >> Cargo.toml
echo 'fn main() {
let c = jsonrpc::Client::simple_http("http://localhost:8080/", None, None).unwrap();
let _ = c.send_request(c.build_request("ping", None));
}' > src/main.rs
cargo run --quiet
Terminal 1 prints the actual outgoing request:
POST / HTTP/1.1
host: [::1]:8080 <-- bug: resolved IP, not "localhost:8080"
Content-Type: application/json
Content-Length: 54
{"method":"ping","params":null,"id":1,"jsonrpc":"2.0"}
The host: value is [::1]:8080 (the address localhost resolved to), but RFC 9110 §7.2 requires it to be localhost:8080 (the host from the target URI). The bug only manifests when the URL hostname differs from its DNS resolution, exactly the case for any deployment behind a host-routed reverse proxy. (Switch to http://127.0.0.1:8080/ and the symptom hides because the URL hostname is the resolved IP.)
Expected behavior
The on-the-wire Host header should be the URL's authority component verbatim (with userinfo user:pass@ stripped, IPv6 brackets preserved per RFC 7230 §2.7.1, and explicit/omitted ports following the URL):
POST / HTTP/1.1
host: localhost:8080
...
Root cause
jsonrpc/src/http/simple_http.rs, three connected lines:
- Line 282 —
check_url() calls to_socket_addrs() on the URL's authority and returns only (SocketAddr, path). The hostname string exists transiently and is dropped after resolution.
- Lines 40-51 —
SimpleHttpTransport has no field for the URL hostname; only addr: SocketAddr and path: String.
- Lines 152-153 — the request serializer writes
self.addr.to_string() (resolved <IP>:<port>) to the host: header:
request_bytes.write_all(b"host: ")?;
request_bytes.write_all(self.addr.to_string().as_bytes())?;
The struct can't carry the hostname, so the parser has nowhere to put it, and the serializer has nothing but the IP to use.
Environment
jsonrpc 0.19.0 from crates.io (current code on master is identical at the affected lines).
- Reproduces on Linux (also on macOS — same
simple_http.rs codepath).
Description
The
simple_httptransport in thejsonrpccrate constructs the HTTPHostheader from the resolved socket address (<IP>:<port>) instead of the URL's authority component. This violates RFC 9112 §3.2:Impact: any consumer of
Client::simple_httpcannot reach a server that sits behind a host-routed reverse proxy. The proxy receivesHost: <ip>:<port>, which doesn't match any vhost rule, and serves its default backend (typically a 404).Reproduce
Terminal 1 prints the actual outgoing request:
The
host:value is[::1]:8080(the addresslocalhostresolved to), but RFC 9110 §7.2 requires it to belocalhost:8080(the host from the target URI). The bug only manifests when the URL hostname differs from its DNS resolution, exactly the case for any deployment behind a host-routed reverse proxy. (Switch tohttp://127.0.0.1:8080/and the symptom hides because the URL hostname is the resolved IP.)Expected behavior
The on-the-wire
Hostheader should be the URL's authority component verbatim (with userinfouser:pass@stripped, IPv6 brackets preserved per RFC 7230 §2.7.1, and explicit/omitted ports following the URL):Root cause
jsonrpc/src/http/simple_http.rs, three connected lines:check_url()callsto_socket_addrs()on the URL's authority and returns only(SocketAddr, path). The hostname string exists transiently and is dropped after resolution.SimpleHttpTransporthas no field for the URL hostname; onlyaddr: SocketAddrandpath: String.self.addr.to_string()(resolved<IP>:<port>) to thehost:header:The struct can't carry the hostname, so the parser has nowhere to put it, and the serializer has nothing but the IP to use.
Environment
jsonrpc 0.19.0from crates.io (current code onmasteris identical at the affected lines).simple_http.rscodepath).