Harden WinHTTP proxy resolution and add fallback
This commit is contained in:
87
Program.cs
87
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
|
||||
|
||||
Reference in New Issue
Block a user