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
|
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
|
||||||
|
|||||||
Reference in New Issue
Block a user