From 77e4e5b86de025e3f87761282f0fdac286fa2750 Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Wed, 20 May 2026 10:30:25 +0200 Subject: [PATCH] websockets: auto-tunnel through http proxy When using a ws: or wss: url with a http proxy, automatically switch to tunneling operation mode. Add test_20_10 to check. Fixes #21663 Closes #21691 --- lib/protocol.c | 7 ++++--- lib/protocol.h | 2 ++ lib/url.c | 7 +++++-- tests/http/test_20_websockets.py | 14 ++++++++++++++ 4 files changed, 25 insertions(+), 5 deletions(-) diff --git a/lib/protocol.c b/lib/protocol.c index 36ad976181..8d57c058c6 100644 --- a/lib/protocol.c +++ b/lib/protocol.c @@ -153,7 +153,8 @@ const struct Curl_scheme Curl_scheme_https = { CURLPROTO_HTTPS, /* protocol */ CURLPROTO_HTTP, /* family */ PROTOPT_SSL | PROTOPT_CREDSPERREQUEST | PROTOPT_ALPN | /* flags */ - PROTOPT_USERPWDCTRL | PROTOPT_CONN_REUSE, + PROTOPT_USERPWDCTRL | PROTOPT_CONN_REUSE | + PROTOPT_HTTP_PROXY_TUNNEL, PORT_HTTPS, /* defport */ }; @@ -442,7 +443,7 @@ const struct Curl_scheme Curl_scheme_ws = { CURLPROTO_WS, /* protocol */ CURLPROTO_HTTP, /* family */ PROTOPT_CREDSPERREQUEST | /* flags */ - PROTOPT_USERPWDCTRL, + PROTOPT_USERPWDCTRL | PROTOPT_HTTP_PROXY_TUNNEL, PORT_HTTP /* defport */ }; @@ -457,7 +458,7 @@ const struct Curl_scheme Curl_scheme_wss = { CURLPROTO_WSS, /* protocol */ CURLPROTO_HTTP, /* family */ PROTOPT_SSL | PROTOPT_CREDSPERREQUEST | /* flags */ - PROTOPT_USERPWDCTRL, + PROTOPT_USERPWDCTRL | PROTOPT_HTTP_PROXY_TUNNEL, PORT_HTTPS /* defport */ }; diff --git a/lib/protocol.h b/lib/protocol.h index 8ae2155ee6..50e320d0f6 100644 --- a/lib/protocol.h +++ b/lib/protocol.h @@ -237,6 +237,8 @@ struct Curl_protocol { without having PROTOPT_SSL. */ #define PROTOPT_CONN_REUSE (1 << 16) /* this protocol can reuse connections */ #define PROTOPT_NO_TRANSFER (1 << 17) /* this protocol is not for transfers */ +#define PROTOPT_HTTP_PROXY_TUNNEL (1 << 18) /* Using this protocol with a + * HTTP proxy requires tunneling */ /* Everything about a URI scheme. */ struct Curl_scheme { diff --git a/lib/url.c b/lib/url.c index d6e98804b4..57b0639021 100644 --- a/lib/url.c +++ b/lib/url.c @@ -2018,8 +2018,11 @@ static CURLcode url_set_conn_proxies(struct Curl_easy *data, data->state.envproxy = curlx_strdup(proxy); } #endif - /* force this connection's protocol to become HTTP if compatible */ - if(!(conn->scheme->protocol & PROTO_FAMILY_HTTP)) { + if(conn->scheme->flags & PROTOPT_HTTP_PROXY_TUNNEL) { + conn->bits.tunnel_proxy = TRUE; + } + else if(!(conn->scheme->protocol & PROTO_FAMILY_HTTP)) { + /* force this connection's protocol to become HTTP if compatible */ if((conn->scheme->flags & PROTOPT_PROXY_AS_HTTP) && !conn->bits.tunnel_proxy) conn->scheme = &Curl_scheme_http; diff --git a/tests/http/test_20_websockets.py b/tests/http/test_20_websockets.py index fdc9df6eb7..416c342a60 100644 --- a/tests/http/test_20_websockets.py +++ b/tests/http/test_20_websockets.py @@ -206,3 +206,17 @@ class TestWebsockets: large = 0 r = client.run(args=[f'-{model}', '-c', str(count), '-m', str(large), url]) r.check_exit_code(0) + + # use ws:// url with HTTP proxy, check that it tunnels automatically + def test_20_10_proxy_http(self, env: Env, httpd, ws_echo): + curl = CurlClient(env=env) + url = f'ws://127.0.0.1:{env.ws_port}/' + xargs = curl.get_proxy_args(proxys=False) + xargs.extend([ + '--max-time', '2' + ]) + r = curl.http_download(urls=[url], alpn_proto='http/1.1', with_stats=True, + extra_args=xargs) + # The CONNECT through the proxy fails as it does not allow it + r.check_exit_code(7) # CURLE_COULDNT_CONNECT + assert r.stats[0]['http_connect'] == 403, f'{r}'