diff --git a/Program.cs b/Program.cs index 48317b4..2ef795f 100644 --- a/Program.cs +++ b/Program.cs @@ -63,6 +63,7 @@ class Program static async Task HandleHttp(NetworkStream clientStream, HttpRequestData req) { + DefaultProxyCredentials = CredentialCache.DefaultCredentials var handler = new SocketsHttpHandler { UseProxy = true, @@ -111,40 +112,47 @@ class Program // ================= CONNECT ================= - static async Task HandleConnect(NetworkStream clientStream, string target) +static async Task HandleConnect(NetworkStream clientStream, string target) +{ + var parts = target.Split(':'); + string host = parts[0]; + int port = int.Parse(parts[1]); + + var proxy = new WinHttpProxy(); + var proxyUri = proxy.GetProxy(new Uri($"https://{host}:{port}")); + + using var upstream = new TcpClient(); + await upstream.ConnectAsync(proxyUri.Host, proxyUri.Port); + + var upstreamStream = upstream.GetStream(); + + // CONNECT к upstream proxy + string connectReq = + $"CONNECT {host}:{port} HTTP/1.1\r\n" + + $"Host: {host}:{port}\r\n" + + $"Proxy-Connection: Keep-Alive\r\n\r\n"; + + await upstreamStream.WriteAsync(Encoding.ASCII.GetBytes(connectReq)); + + // читаем ответ upstream полностью до \r\n\r\n + var resp = await ReadHeaders(upstreamStream); + + if (!resp.HeadersRaw.Contains("200")) { - var parts = target.Split(':'); - string host = parts[0]; - int port = int.Parse(parts[1]); - - var proxy = new WinHttpProxy(); - var proxyUri = proxy.GetProxy(new Uri($"https://{host}:{port}")); - - using var upstream = new TcpClient(); - await upstream.ConnectAsync(proxyUri.Host, proxyUri.Port); - - var upstreamStream = upstream.GetStream(); - - // CONNECT к upstream - string connectReq = - $"CONNECT {host}:{port} HTTP/1.1\r\nHost: {host}:{port}\r\n\r\n"; - - await upstreamStream.WriteAsync(Encoding.ASCII.GetBytes(connectReq)); - - // читаем ответ от upstream - var buffer = new byte[8192]; - int read = await upstreamStream.ReadAsync(buffer); - - // отвечаем клиенту что туннель открыт - await clientStream.WriteAsync(Encoding.ASCII.GetBytes("HTTP/1.1 200 Connection Established\r\n\r\n")); - - // двунаправленный туннель - var t1 = upstreamStream.CopyToAsync(clientStream); - var t2 = clientStream.CopyToAsync(upstreamStream); - - await Task.WhenAny(t1, t2); + await clientStream.WriteAsync(Encoding.ASCII.GetBytes("HTTP/1.1 502 Bad Gateway\r\n\r\n")); + return; } + // говорим клиенту ОК + await clientStream.WriteAsync(Encoding.ASCII.GetBytes("HTTP/1.1 200 Connection Established\r\n\r\n")); + + // туннель + var t1 = Pump(upstreamStream, clientStream); + var t2 = Pump(clientStream, upstreamStream); + + await Task.WhenAny(t1, t2); +} + // ================= AUTH ================= static bool CheckAuth(string headers) @@ -233,80 +241,96 @@ class WinHttpProxy : IWebProxy public bool IsBypassed(Uri host) => false; } +using System.Runtime.InteropServices; + static class WinHttpHelper { [DllImport("winhttp.dll", SetLastError = true)] - static extern bool WinHttpGetIEProxyConfigForCurrentUser( - out WINHTTP_CURRENT_USER_IE_PROXY_CONFIG config); + static extern IntPtr WinHttpOpen(string agent, int accessType, string proxy, string bypass, int flags); + + [DllImport("winhttp.dll", SetLastError = true)] + static extern bool WinHttpGetProxyForUrl( + IntPtr hSession, + string url, + ref WINHTTP_AUTOPROXY_OPTIONS options, + out WINHTTP_PROXY_INFO proxyInfo); + + [DllImport("winhttp.dll")] + static extern bool WinHttpCloseHandle(IntPtr handle); public static Uri GetProxyForUrl(Uri url) { - if (!WinHttpGetIEProxyConfigForCurrentUser(out var cfg)) - return url; + IntPtr session = WinHttpOpen("proxy", 0, null, null, 0); - string proxy = PtrToString(cfg.lpszProxy); + var options = new WINHTTP_AUTOPROXY_OPTIONS + { + dwFlags = 0x00000001 | 0x00000002, // AUTO_DETECT + CONFIG_URL + dwAutoDetectFlags = 0x00000001 | 0x00000002 // DHCP + DNS_A + }; - if (string.IsNullOrWhiteSpace(proxy)) - return url; + if (WinHttpGetProxyForUrl(session, url.ToString(), ref options, out var info)) + { + string proxy = PtrToString(info.lpszProxy); - string selectedProxy = ParseProxy(proxy, url.Scheme); + if (!string.IsNullOrEmpty(proxy)) + { + var first = proxy.Split(';')[0]; + return new Uri(Normalize(first)); + } + } - if (string.IsNullOrEmpty(selectedProxy)) - return url; - - return new Uri(NormalizeProxy(selectedProxy)); + WinHttpCloseHandle(session); + return url; // direct fallback } - // ===== helpers ===== - static string PtrToString(IntPtr ptr) { - if (ptr == IntPtr.Zero) - return null; - + if (ptr == IntPtr.Zero) return null; return Marshal.PtrToStringUni(ptr); } - static string ParseProxy(string proxy, string scheme) + static string Normalize(string proxy) { - // варианты: - // "proxy:8080" - // "http=proxy:8080;https=proxy2:8080" - - if (!proxy.Contains("=")) - return proxy; - - var entries = proxy.Split(';', StringSplitOptions.RemoveEmptyEntries); - - foreach (var entry in entries) - { - var parts = entry.Split('=', 2); - if (parts.Length != 2) - continue; - - if (parts[0].Equals(scheme, StringComparison.OrdinalIgnoreCase)) - return parts[1]; - } - - // fallback — берём первый - return entries[0].Split('=')[1]; - } - - static string NormalizeProxy(string proxy) - { - if (!proxy.StartsWith("http://") && !proxy.StartsWith("https://")) + if (!proxy.StartsWith("http")) return "http://" + proxy; - return proxy; } - // ===== struct ===== - - struct WINHTTP_CURRENT_USER_IE_PROXY_CONFIG + struct WINHTTP_AUTOPROXY_OPTIONS { - public bool fAutoDetect; + public int dwFlags; + public int dwAutoDetectFlags; public IntPtr lpszAutoConfigUrl; + public IntPtr lpvReserved; + public int dwReserved; + public bool fAutoLogonIfChallenged; + } + + struct WINHTTP_PROXY_INFO + { + public int dwAccessType; public IntPtr lpszProxy; public IntPtr lpszProxyBypass; } +} + +static async Task Pump(Stream from, Stream to) +{ + var buffer = ArrayPool.Shared.Rent(8192); + + try + { + while (true) + { + int read = await from.ReadAsync(buffer); + if (read <= 0) break; + + await to.WriteAsync(buffer.AsMemory(0, read)); + } + } + catch { } + finally + { + ArrayPool.Shared.Return(buffer); + } } \ No newline at end of file