diff --git a/Program.cs b/Program.cs index 6d7dd25..8a2c57c 100644 --- a/Program.cs +++ b/Program.cs @@ -265,6 +265,12 @@ class Program 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)] static extern IntPtr WinHttpOpen(string agent, int accessType, string proxy, string bypass, int flags); @@ -278,38 +284,57 @@ static class WinHttpHelper [DllImport("winhttp.dll")] 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) { - IntPtr session = WinHttpOpen("proxy", 0, null, null, 0); + IntPtr session = WinHttpOpen("proxy", WINHTTP_ACCESS_TYPE_NO_PROXY, null, null, 0); if (session == IntPtr.Zero) { - Log.Error("WinHttpOpen failed"); + Log.Error($"WinHttpOpen failed (Win32={Marshal.GetLastWin32Error()})"); return url; } var options = new WINHTTP_AUTOPROXY_OPTIONS { - dwFlags = 0x00000001 | 0x00000002, - dwAutoDetectFlags = 0x00000001 | 0x00000002 + dwFlags = WINHTTP_AUTOPROXY_AUTO_DETECT | WINHTTP_AUTOPROXY_CONFIG_URL, + 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); - - if (!string.IsNullOrEmpty(proxy)) + var proxyUri = ParseProxyInfo(autoInfo); + if (proxyUri != null) { - var first = proxy.Split(';')[0]; WinHttpCloseHandle(session); - return new Uri(Normalize(first)); + return proxyUri; } } 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); + Log.Warn($"No upstream proxy resolved for {url}, fallback to direct"); return url; } @@ -319,11 +344,43 @@ static class WinHttpHelper return Marshal.PtrToStringUni(ptr); } - static string Normalize(string proxy) + static Uri ParseProxyInfo(WINHTTP_PROXY_INFO info) { - if (!proxy.StartsWith("http")) - return "http://" + proxy; - return proxy; + var raw = PtrToString(info.lpszProxy); + if (string.IsNullOrWhiteSpace(raw)) + 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