Harden WinHTTP proxy resolution and add fallback

This commit is contained in:
2026-04-09 19:48:38 +03:00
parent e5d40e881a
commit a9f1a2c84e

View File

@@ -265,6 +265,12 @@ class Program
static class WinHttpHelper static class WinHttpHelper
{ {
const int WINHTTP_ACCESS_TYPE_NO_PROXY = 1;
const int WINHTTP_AUTOPROXY_AUTO_DETECT = 0x00000001;
const int WINHTTP_AUTOPROXY_CONFIG_URL = 0x00000002;
const int WINHTTP_AUTO_DETECT_TYPE_DHCP = 0x00000001;
const int WINHTTP_AUTO_DETECT_TYPE_DNS_A = 0x00000002;
[DllImport("winhttp.dll", SetLastError = true)] [DllImport("winhttp.dll", SetLastError = true)]
static extern IntPtr WinHttpOpen(string agent, int accessType, string proxy, string bypass, int flags); static extern IntPtr WinHttpOpen(string agent, int accessType, string proxy, string bypass, int flags);
@@ -278,38 +284,57 @@ static class WinHttpHelper
[DllImport("winhttp.dll")] [DllImport("winhttp.dll")]
static extern bool WinHttpCloseHandle(IntPtr handle); static extern bool WinHttpCloseHandle(IntPtr handle);
[DllImport("winhttp.dll", SetLastError = true)]
static extern bool WinHttpGetDefaultProxyConfiguration(out WINHTTP_PROXY_INFO proxyInfo);
public static Uri GetProxyForUrl(Uri url) public static Uri GetProxyForUrl(Uri url)
{ {
IntPtr session = WinHttpOpen("proxy", 0, null, null, 0); IntPtr session = WinHttpOpen("proxy", WINHTTP_ACCESS_TYPE_NO_PROXY, null, null, 0);
if (session == IntPtr.Zero) if (session == IntPtr.Zero)
{ {
Log.Error("WinHttpOpen failed"); Log.Error($"WinHttpOpen failed (Win32={Marshal.GetLastWin32Error()})");
return url; return url;
} }
var options = new WINHTTP_AUTOPROXY_OPTIONS var options = new WINHTTP_AUTOPROXY_OPTIONS
{ {
dwFlags = 0x00000001 | 0x00000002, dwFlags = WINHTTP_AUTOPROXY_AUTO_DETECT | WINHTTP_AUTOPROXY_CONFIG_URL,
dwAutoDetectFlags = 0x00000001 | 0x00000002 dwAutoDetectFlags = WINHTTP_AUTO_DETECT_TYPE_DHCP | WINHTTP_AUTO_DETECT_TYPE_DNS_A,
fAutoLogonIfChallenged = true
}; };
if (WinHttpGetProxyForUrl(session, url.ToString(), ref options, out var info)) if (WinHttpGetProxyForUrl(session, url.ToString(), ref options, out var autoInfo))
{ {
string proxy = PtrToString(info.lpszProxy); var proxyUri = ParseProxyInfo(autoInfo);
if (proxyUri != null)
if (!string.IsNullOrEmpty(proxy))
{ {
var first = proxy.Split(';')[0];
WinHttpCloseHandle(session); WinHttpCloseHandle(session);
return new Uri(Normalize(first)); return proxyUri;
} }
} }
else else
{ {
Log.Error($"WinHttpGetProxyForUrl failed for {url} (Win32={Marshal.GetLastWin32Error()})"); int err = Marshal.GetLastWin32Error();
Log.Warn($"WinHttpGetProxyForUrl failed for {url} (Win32={err})");
}
if (WinHttpGetDefaultProxyConfiguration(out var defaultInfo))
{
var fallbackProxy = ParseProxyInfo(defaultInfo);
if (fallbackProxy != null)
{
Log.Info($"Using default WinHTTP proxy {fallbackProxy.Host}:{fallbackProxy.Port} for {url}");
WinHttpCloseHandle(session);
return fallbackProxy;
}
}
else
{
Log.Warn($"WinHttpGetDefaultProxyConfiguration failed (Win32={Marshal.GetLastWin32Error()})");
} }
WinHttpCloseHandle(session); WinHttpCloseHandle(session);
Log.Warn($"No upstream proxy resolved for {url}, fallback to direct");
return url; return url;
} }
@@ -319,11 +344,43 @@ static class WinHttpHelper
return Marshal.PtrToStringUni(ptr); return Marshal.PtrToStringUni(ptr);
} }
static string Normalize(string proxy) static Uri ParseProxyInfo(WINHTTP_PROXY_INFO info)
{ {
if (!proxy.StartsWith("http")) var raw = PtrToString(info.lpszProxy);
return "http://" + proxy; if (string.IsNullOrWhiteSpace(raw))
return proxy; return null;
// Common WinHTTP formats:
// "proxy.local:8080"
// "http=proxy.local:8080;https=proxy.local:8443"
var token = raw.Split(';', StringSplitOptions.RemoveEmptyEntries)
.Select(p => p.Trim())
.FirstOrDefault(p => p.StartsWith("https=", StringComparison.OrdinalIgnoreCase))
?? raw.Split(';', StringSplitOptions.RemoveEmptyEntries)
.Select(p => p.Trim())
.FirstOrDefault(p => p.StartsWith("http=", StringComparison.OrdinalIgnoreCase))
?? raw.Split(';', StringSplitOptions.RemoveEmptyEntries)
.Select(p => p.Trim())
.FirstOrDefault();
if (string.IsNullOrWhiteSpace(token))
return null;
var hostPort = token.Contains('=')
? token.Split('=', 2)[1].Trim()
: token;
var normalized = hostPort.StartsWith("http", StringComparison.OrdinalIgnoreCase)
? hostPort
: "http://" + hostPort;
if (!Uri.TryCreate(normalized, UriKind.Absolute, out var uri))
{
Log.Warn($"Cannot parse proxy value '{raw}'");
return null;
}
return uri;
} }
struct WINHTTP_AUTOPROXY_OPTIONS struct WINHTTP_AUTOPROXY_OPTIONS