client: Extract proxy handshakes into a new proxy_handshake crate (#61427)

This consolidates the proxy code in `client/src/proxy` into a new
`proxy_handshake` crate that implements the client side of HTTP CONNECT
and SOCKS4/4a/5/5h handshakes sans-IO: the protocol logic is a pure
state machine that never touches a socket, with thin drivers behind
`futures-io` and `tokio` feature flags. `client` now drives the
websocket's proxy tunnel through the tokio driver, and the `tokio-socks`
dependency goes away.

The main win is testability: the handshakes are now covered by
synchronous byte-level tests on both sides of each exchange, plus a
property test that the outcome is invariant under how the proxy's bytes
are chunked across reads — the class of partial-read bug that's hard to
hit with hand-written tests but easy to hit in production behind a
buffering proxy. The crate is written as if it were standalone (no
zed-internal dependencies, structured errors instead of `anyhow`), which
is also why it grows both driver flavors rather than just the one
`client` needs today.

Two deliberate behavior changes, both converging on spec/curl semantics:

- Proxy credentials embedded in URLs are now percent-decoded before use,
per RFC 3986 §3.2.1. Previously they were sent in their encoded form,
which only worked for credentials with no reserved characters. A
password that literally contains something like `%40` and relied on the
old behavior now needs the RFC-correct `%2540` spelling.
- `NO_PROXY` is now honored for the collaboration connection, matching
the exclusions reqwest already applies to the rest of Zed's HTTP
traffic.

TLS to the proxy itself (for `https://` proxy URLs) and DNS policy stay
in `client`, since those are I/O concerns the sans-IO crate deliberately
doesn't own.

Release Notes:

- Improved proxy support: the collaboration connection now respects
`NO_PROXY` and percent-encoded proxy credentials.
dd3a94e2e5Eric Holk committed on 7/23/2026, 10:46:06 PM· committed by GitHubparent97d854b
13 files changedLine totals unavailable