From 2faf89e5c5522cbf5ed80da3f3d0a110d28cd3f5 Mon Sep 17 00:00:00 2001 From: ldm0 Date: Fri, 18 Sep 2026 11:29:57 +0800 Subject: [PATCH] fix: restore HTTPS proxy default port --- moli-curl/src/proxy.rs | 79 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 70 insertions(+), 9 deletions(-) diff --git a/moli-curl/src/proxy.rs b/moli-curl/src/proxy.rs index 83f021923d..61d54e2d4f 100644 --- a/moli-curl/src/proxy.rs +++ b/moli-curl/src/proxy.rs @@ -88,12 +88,15 @@ impl SelectedProxy { if url.host().is_none() { bail!("proxy URL `{raw}` is missing a host"); } - // CURLOPT_PROXY defaults to port 1080 when the proxy URL omits a - // port, including for HTTP and HTTPS proxies. This differs from an - // origin URL's scheme-specific default port. Read the explicit port - // from the original authority because `url::Url` deliberately drops - // explicit default ports such as `:80` and `:443`. - let endpoint_port = explicit_proxy_port(&normalized)?.unwrap_or(1080); + // libcurl's parse_proxy() defaults to 443 for HTTPS and 1080 for + // HTTP/SOCKS (see CURLOPT_PROXYPORT). Read the explicit port from the + // original authority because `url::Url` deliberately drops explicit + // default ports such as `:80` and `:443`. + let default_port = match scheme { + ProxyScheme::Https => 443, + _ => 1080, + }; + let endpoint_port = explicit_proxy_port(&normalized)?.unwrap_or(default_port); let mut curl_url = url.clone(); curl_url .set_scheme(curl_scheme) @@ -551,8 +554,8 @@ mod tests { ( "https://proxy.test", ProxyScheme::Https, - 1080, - "https://proxy.test:1080/", + 443, + "https://proxy.test:443/", ), ( "socks://proxy.test", @@ -604,7 +607,7 @@ mod tests { fn proxy_parser_keeps_curl_endpoint_port_aligned_with_dns_endpoint() { for (raw, expected_port, expected_curl_url) in [ ("http://proxy.test", 1080, "http://proxy.test:1080/"), - ("https://proxy.test", 1080, "https://proxy.test:1080/"), + ("https://proxy.test", 443, "https://proxy.test:443/"), ("http://proxy.test:80", 80, "http://proxy.test:80/"), ("https://proxy.test:443", 443, "https://proxy.test:443/"), ("http://proxy.test:8080", 8080, "http://proxy.test:8080/"), @@ -616,6 +619,64 @@ mod tests { } } + #[test] + fn proxy_dns_pin_matches_libcurl_proxy_ports() { + use std::{ffi::c_int, time::Duration}; + + use curl::easy::{Easy2, Handler}; + + #[derive(Default)] + struct RefuseSockets { + attempts: usize, + } + + impl Handler for RefuseSockets { + fn open_socket( + &mut self, + _family: c_int, + _socktype: c_int, + _protocol: c_int, + ) -> Option { + self.attempts += 1; + None + } + } + + for raw in [ + "http://proxy.invalid", + "https://proxy.invalid", + "http://proxy.invalid:80", + "https://proxy.invalid:443", + "http://proxy.invalid:8080", + "https://proxy.invalid:8443", + "socks5h://proxy.invalid", + "socks4a://proxy.invalid", + ] { + let proxy = SelectedProxy::parse(raw).unwrap(); + for curl_proxy in [raw, proxy.curl_url()] { + let mut easy = Easy2::new(RefuseSockets::default()); + easy.url("http://origin.invalid/proxy-port").unwrap(); + easy.proxy(curl_proxy).unwrap(); + easy.noproxy("").unwrap(); + easy.timeout(Duration::from_secs(1)).unwrap(); + let mut dns = crate::CurlDnsResolution::resolve_endpoint( + DnsTarget::new(proxy.endpoint_host(), proxy.endpoint_port()), + Vec::new(), + ); + dns.install(&mut easy, &[IpAddr::from([127, 0, 0, 1])]) + .unwrap(); + + // Let the linked libcurl interpret both URLs. Only Moli's + // chosen port has a DNS pin for this reserved hostname, so + // reaching open_socket proves that libcurl selected it too. + // Refuse the socket before any connection or TLS handshake. + let error = easy.perform().expect_err("test refuses every socket"); + assert!(error.is_couldnt_connect(), "{curl_proxy}: {error}"); + assert_eq!(easy.get_ref().attempts, 1, "{curl_proxy}"); + } + } + } + #[test] fn route_selects_only_the_locally_connected_dns_endpoint() { let empty = HostResolveOverrides::default();