diff --git a/Program.cs b/Program.cs index 8a2c57c..116ad4a 100644 --- a/Program.cs +++ b/Program.cs @@ -103,6 +103,13 @@ class Program static async Task HandleHttp(NetworkStream clientStream, HttpRequest req, long connectionId) { var proxyUri = WinHttpHelper.GetProxyForUrl(new Uri(req.Url)); + if (proxyUri == null) + { + Log.Error($"[conn:{connectionId}] no upstream proxy for {req.Url}"); + await Write502(clientStream); + return; + } + Log.Info($"[conn:{connectionId}] HTTP upstream {proxyUri.Host}:{proxyUri.Port} for {req.Url}"); using var upstream = new TcpClient(); @@ -130,6 +137,13 @@ class Program string host = parts[0]; var proxyUri = WinHttpHelper.GetProxyForUrl(new Uri($"https://{host}:{port}")); + if (proxyUri == null) + { + Log.Error($"[conn:{connectionId}] no upstream proxy for CONNECT {host}:{port}"); + await Write502(clientStream); + return; + } + Log.Info($"[conn:{connectionId}] CONNECT {host}:{port} via {proxyUri.Host}:{proxyUri.Port}"); using var upstream = new TcpClient(); @@ -176,6 +190,12 @@ class Program await stream.WriteAsync(Encoding.ASCII.GetBytes(resp)); } + static async Task Write502(NetworkStream stream) + { + const string resp = "HTTP/1.1 502 Bad Gateway\r\n\r\n"; + await stream.WriteAsync(Encoding.ASCII.GetBytes(resp)); + } + // ================= PARSER ================= class HttpRequest @@ -287,55 +307,97 @@ static class WinHttpHelper [DllImport("winhttp.dll", SetLastError = true)] static extern bool WinHttpGetDefaultProxyConfiguration(out WINHTTP_PROXY_INFO proxyInfo); + [DllImport("winhttp.dll", SetLastError = true)] + static extern bool WinHttpGetIEProxyConfigForCurrentUser(out WINHTTP_CURRENT_USER_IE_PROXY_CONFIG proxyConfig); + + [DllImport("kernel32.dll", SetLastError = true)] + static extern IntPtr GlobalFree(IntPtr hMem); + public static Uri GetProxyForUrl(Uri url) { IntPtr session = WinHttpOpen("proxy", WINHTTP_ACCESS_TYPE_NO_PROXY, null, null, 0); if (session == IntPtr.Zero) { Log.Error($"WinHttpOpen failed (Win32={Marshal.GetLastWin32Error()})"); - return url; + return null; } - var options = new WINHTTP_AUTOPROXY_OPTIONS - { - dwFlags = WINHTTP_AUTOPROXY_AUTO_DETECT | WINHTTP_AUTOPROXY_CONFIG_URL, - dwAutoDetectFlags = WINHTTP_AUTO_DETECT_TYPE_DHCP | WINHTTP_AUTO_DETECT_TYPE_DNS_A, - fAutoLogonIfChallenged = true - }; + WINHTTP_PROXY_INFO autoInfo = default; + WINHTTP_PROXY_INFO defaultInfo = default; + WINHTTP_CURRENT_USER_IE_PROXY_CONFIG ieConfig = default; - if (WinHttpGetProxyForUrl(session, url.ToString(), ref options, out var autoInfo)) + try { - var proxyUri = ParseProxyInfo(autoInfo); - if (proxyUri != null) + if (WinHttpGetIEProxyConfigForCurrentUser(out ieConfig)) { - WinHttpCloseHandle(session); - return proxyUri; - } - } - else - { - int err = Marshal.GetLastWin32Error(); - Log.Warn($"WinHttpGetProxyForUrl failed for {url} (Win32={err})"); - } + var autoConfigUrl = PtrToString(ieConfig.lpszAutoConfigUrl); + var options = new WINHTTP_AUTOPROXY_OPTIONS + { + dwFlags = 0, + dwAutoDetectFlags = WINHTTP_AUTO_DETECT_TYPE_DHCP | WINHTTP_AUTO_DETECT_TYPE_DNS_A, + lpszAutoConfigUrl = ieConfig.lpszAutoConfigUrl, + fAutoLogonIfChallenged = true + }; - if (WinHttpGetDefaultProxyConfiguration(out var defaultInfo)) - { - var fallbackProxy = ParseProxyInfo(defaultInfo); - if (fallbackProxy != null) + if (!string.IsNullOrWhiteSpace(autoConfigUrl)) + options.dwFlags |= WINHTTP_AUTOPROXY_CONFIG_URL; + if (ieConfig.fAutoDetect) + options.dwFlags |= WINHTTP_AUTOPROXY_AUTO_DETECT; + + if (options.dwFlags != 0) + { + if (WinHttpGetProxyForUrl(session, url.ToString(), ref options, out autoInfo)) + { + var proxyUri = ParseProxyInfo(autoInfo, url.Scheme); + if (proxyUri != null) + { + return proxyUri; + } + } + else + { + int err = Marshal.GetLastWin32Error(); + Log.Warn($"WinHttpGetProxyForUrl failed for {url} (Win32={err})"); + } + } + + var ieStaticProxyRaw = PtrToString(ieConfig.lpszProxy); + var ieStaticProxy = ParseProxyString(ieStaticProxyRaw, url.Scheme); + if (ieStaticProxy != null) + { + Log.Info($"Using IE static proxy {ieStaticProxy.Host}:{ieStaticProxy.Port} for {url}"); + return ieStaticProxy; + } + } + else { - Log.Info($"Using default WinHTTP proxy {fallbackProxy.Host}:{fallbackProxy.Port} for {url}"); - WinHttpCloseHandle(session); - return fallbackProxy; + Log.Warn($"WinHttpGetIEProxyConfigForCurrentUser failed (Win32={Marshal.GetLastWin32Error()})"); } - } - else - { - Log.Warn($"WinHttpGetDefaultProxyConfiguration failed (Win32={Marshal.GetLastWin32Error()})"); - } - WinHttpCloseHandle(session); - Log.Warn($"No upstream proxy resolved for {url}, fallback to direct"); - return url; + if (WinHttpGetDefaultProxyConfiguration(out defaultInfo)) + { + var fallbackProxy = ParseProxyInfo(defaultInfo, url.Scheme); + if (fallbackProxy != null) + { + Log.Info($"Using default WinHTTP proxy {fallbackProxy.Host}:{fallbackProxy.Port} for {url}"); + return fallbackProxy; + } + } + else + { + Log.Warn($"WinHttpGetDefaultProxyConfiguration failed (Win32={Marshal.GetLastWin32Error()})"); + } + + Log.Error($"No system upstream proxy resolved for {url}"); + return null; + } + finally + { + FreeWinHttpProxyInfo(autoInfo); + FreeWinHttpProxyInfo(defaultInfo); + FreeIeConfig(ieConfig); + WinHttpCloseHandle(session); + } } static string PtrToString(IntPtr ptr) @@ -344,37 +406,45 @@ static class WinHttpHelper return Marshal.PtrToStringUni(ptr); } - static Uri ParseProxyInfo(WINHTTP_PROXY_INFO info) + static Uri ParseProxyInfo(WINHTTP_PROXY_INFO info, string scheme) { var raw = PtrToString(info.lpszProxy); + return ParseProxyString(raw, scheme); + } + + static Uri ParseProxyString(string raw, string scheme) + { 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) + var parts = 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(); + .ToArray(); + string schemeToken = parts.FirstOrDefault(p => + p.StartsWith($"{scheme}=", StringComparison.OrdinalIgnoreCase)); + string genericToken = parts.FirstOrDefault(p => p.StartsWith("http=", StringComparison.OrdinalIgnoreCase)) + ?? parts.FirstOrDefault(p => p.StartsWith("https=", StringComparison.OrdinalIgnoreCase)); + string first = parts.FirstOrDefault(); + string token = schemeToken ?? genericToken ?? first; if (string.IsNullOrWhiteSpace(token)) return null; - var hostPort = token.Contains('=') + var value = token.Contains('=') ? token.Split('=', 2)[1].Trim() : token; - var normalized = hostPort.StartsWith("http", StringComparison.OrdinalIgnoreCase) - ? hostPort - : "http://" + hostPort; + if (value.StartsWith("PROXY ", StringComparison.OrdinalIgnoreCase)) + value = value.Substring(6).Trim(); + if (value.Equals("DIRECT", StringComparison.OrdinalIgnoreCase)) + return null; - if (!Uri.TryCreate(normalized, UriKind.Absolute, out var uri)) + var normalized = value.StartsWith("http://", StringComparison.OrdinalIgnoreCase) + || value.StartsWith("https://", StringComparison.OrdinalIgnoreCase) + ? value + : "http://" + value; + + if (!Uri.TryCreate(normalized, UriKind.Absolute, out var uri) || string.IsNullOrWhiteSpace(uri.Host)) { Log.Warn($"Cannot parse proxy value '{raw}'"); return null; @@ -383,6 +453,24 @@ static class WinHttpHelper return uri; } + static void FreeWinHttpProxyInfo(WINHTTP_PROXY_INFO info) + { + if (info.lpszProxy != IntPtr.Zero) + GlobalFree(info.lpszProxy); + if (info.lpszProxyBypass != IntPtr.Zero) + GlobalFree(info.lpszProxyBypass); + } + + static void FreeIeConfig(WINHTTP_CURRENT_USER_IE_PROXY_CONFIG config) + { + if (config.lpszAutoConfigUrl != IntPtr.Zero) + GlobalFree(config.lpszAutoConfigUrl); + if (config.lpszProxy != IntPtr.Zero) + GlobalFree(config.lpszProxy); + if (config.lpszProxyBypass != IntPtr.Zero) + GlobalFree(config.lpszProxyBypass); + } + struct WINHTTP_AUTOPROXY_OPTIONS { public int dwFlags; @@ -399,6 +487,15 @@ static class WinHttpHelper public IntPtr lpszProxy; public IntPtr lpszProxyBypass; } + + struct WINHTTP_CURRENT_USER_IE_PROXY_CONFIG + { + [MarshalAs(UnmanagedType.Bool)] + public bool fAutoDetect; + public IntPtr lpszAutoConfigUrl; + public IntPtr lpszProxy; + public IntPtr lpszProxyBypass; + } } static class Log