From 5f13a7645e565c5c1a06f3ef86e97afb856fb364 Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Fri, 6 Mar 2026 14:54:09 +0100 Subject: [PATCH] proxy-auth: additional tests Also eliminate the special handling for socks proxy match. Closes #20837 --- lib/url.c | 28 +++++++--------------------- tests/http/test_13_proxy_auth.py | 20 ++++++++++++++++++++ tests/http/testenv/curl.py | 18 +++++++++++++++--- 3 files changed, 42 insertions(+), 24 deletions(-) diff --git a/lib/url.c b/lib/url.c index eabeb776ab..bdc183b45b 100644 --- a/lib/url.c +++ b/lib/url.c @@ -590,29 +590,15 @@ static bool proxy_info_matches(const struct proxy_info *data, { if((data->proxytype == needle->proxytype) && (data->port == needle->port) && - curl_strequal(data->host.name, needle->host.name)) + curl_strequal(data->host.name, needle->host.name)) { + + if(Curl_timestrcmp(data->user, needle->user) || + Curl_timestrcmp(data->passwd, needle->passwd)) + return FALSE; return TRUE; - + } return FALSE; } - -static bool socks_proxy_info_matches(const struct proxy_info *data, - const struct proxy_info *needle) -{ - if(!proxy_info_matches(data, needle)) - return FALSE; - - /* the user information is case-sensitive - or at least it is not defined as case-insensitive - see https://datatracker.ietf.org/doc/html/rfc3986#section-3.2.1 */ - - /* curl_strequal does a case insensitive comparison, - so do not use it here! */ - if(Curl_timestrcmp(data->user, needle->user) || - Curl_timestrcmp(data->passwd, needle->passwd)) - return FALSE; - return TRUE; -} #endif /* A connection has to have been idle for less than 'conn_max_idle_ms' @@ -923,7 +909,7 @@ static bool url_match_proxy_use(struct connectdata *conn, return FALSE; if(m->needle->bits.socksproxy && - !socks_proxy_info_matches(&m->needle->socks_proxy, &conn->socks_proxy)) + !proxy_info_matches(&m->needle->socks_proxy, &conn->socks_proxy)) return FALSE; if(m->needle->bits.httpproxy) { diff --git a/tests/http/test_13_proxy_auth.py b/tests/http/test_13_proxy_auth.py index 080adef187..33fb211e99 100644 --- a/tests/http/test_13_proxy_auth.py +++ b/tests/http/test_13_proxy_auth.py @@ -169,3 +169,23 @@ class TestProxyAuth: '--negotiate', '--proxy-user', 'proxy:proxy' ]) r1.check_response(count=1, http_status=200) + + def test_13_10_tunnels_mixed_auth(self, env: Env, httpd, configures_httpd): + self.httpd_configure(env, httpd) + curl = CurlClient(env=env) + url1 = f'http://localhost:{env.http_port}/data.json?1' + url2 = f'http://localhost:{env.http_port}/data.json?2' + url3 = f'http://localhost:{env.http_port}/data.json?3' + xargs1 = curl.get_proxy_args(proxys=False, tunnel=True) + xargs1.extend(['--proxy-user', 'proxy:proxy']) # good auth + xargs2 = curl.get_proxy_args(proxys=False, tunnel=True) + xargs2.extend(['--proxy-user', 'ungood:ungood']) # bad auth + xargs3 = curl.get_proxy_args(proxys=False, tunnel=True) + # no auth + r = curl.http_download(urls=[url1, url2, url3], alpn_proto='http/1.1', with_stats=True, + url_options={url1: xargs1, url2: xargs2, url3: xargs3}) + # only url1 succeeds, others fail, no connection reuse + assert r.stats[0]['http_code'] == 200, f'{r.dump_logs()}' + assert r.stats[1]['http_code'] == 0, f'{r.dump_logs()}' + assert r.stats[2]['http_code'] == 0, f'{r.dump_logs()}' + assert r.total_connects == 3, f'{r.dump_logs()}' diff --git a/tests/http/testenv/curl.py b/tests/http/testenv/curl.py index 4fc11c7923..1f812a1c2e 100644 --- a/tests/http/testenv/curl.py +++ b/tests/http/testenv/curl.py @@ -724,7 +724,8 @@ class CurlClient: with_tcpdump: bool = False, no_save: bool = False, limit_rate: Optional[str] = None, - extra_args: Optional[List[str]] = None): + extra_args: Optional[List[str]] = None, + url_options: Optional[Dict[str,List[str]]] = None): if extra_args is None: extra_args = [] if no_save: @@ -742,6 +743,7 @@ class CurlClient: ]) return self._raw(urls, alpn_proto=alpn_proto, options=extra_args, with_stats=with_stats, + url_options=url_options, with_headers=with_headers, with_profile=with_profile, with_tcpdump=with_tcpdump) @@ -1083,6 +1085,7 @@ class CurlClient: def _raw(self, urls, intext='', timeout=None, options=None, insecure=False, alpn_proto: Optional[str] = None, + url_options=None, force_resolve=True, with_stats=False, with_headers=True, @@ -1092,7 +1095,8 @@ class CurlClient: args = self._complete_args( urls=urls, timeout=timeout, options=options, insecure=insecure, alpn_proto=alpn_proto, force_resolve=force_resolve, - with_headers=with_headers, def_tracing=def_tracing) + with_headers=with_headers, def_tracing=def_tracing, + url_options=url_options) r = self._run(args, intext=intext, with_stats=with_stats, with_profile=with_profile, with_tcpdump=with_tcpdump) if r.exit_code == 0 and with_headers: @@ -1102,8 +1106,10 @@ class CurlClient: def _complete_args(self, urls, timeout=None, options=None, insecure=False, force_resolve=True, alpn_proto: Optional[str] = None, + url_options=None, with_headers: bool = True, def_tracing: bool = True): + url_sep = [] if not isinstance(urls, list): urls = [urls] @@ -1129,7 +1135,13 @@ class CurlClient: active_options = options[options.index('--next') + 1:] for url in urls: - u = urlparse(urls[0]) + args.extend(url_sep) + if url_options is not None: + url_sep = ['--next'] + + u = urlparse(url) + if url_options is not None and url in url_options: + args.extend(url_options[url]) if options: args.extend(options) if alpn_proto is not None: