diff --git a/Program.cs b/Program.cs index 2ef795f..fb60d23 100644 --- a/Program.cs +++ b/Program.cs @@ -1,13 +1,13 @@ using System; +using System.Buffers; +using System.Collections.Generic; +using System.IO; +using System.Linq; using System.Net; -using System.Net.Http; using System.Net.Sockets; +using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; -using System.Collections.Generic; -using System.Linq; -using System.IO; -using System.Runtime.InteropServices; class Program { @@ -18,10 +18,10 @@ class Program { int port = 8888; - Console.WriteLine($"Local proxy:"); - Console.WriteLine($" http://127.0.0.1:{port}"); - Console.WriteLine($" login: {USER}"); - Console.WriteLine($" pass : {PASS}"); + Console.WriteLine("=== LOCAL PROXY ==="); + Console.WriteLine($"http://127.0.0.1:{port}"); + Console.WriteLine($"login: {USER}"); + Console.WriteLine($"pass : {PASS}"); var listener = new TcpListener(IPAddress.Any, port); listener.Start(); @@ -38,127 +38,81 @@ class Program using (client) { var stream = client.GetStream(); - var request = await ReadHeaders(stream); + if (request == null) return; - if (!CheckAuth(request.HeadersRaw)) + if (!CheckAuth(request.Raw)) { await Write407(stream); return; } if (request.Method == "CONNECT") - { await HandleConnect(stream, request.Target); - } else - { await HandleHttp(stream, request); - } } } // ================= HTTP ================= - static async Task HandleHttp(NetworkStream clientStream, HttpRequestData req) + static async Task HandleHttp(NetworkStream clientStream, HttpRequest req) { - DefaultProxyCredentials = CredentialCache.DefaultCredentials - var handler = new SocketsHttpHandler - { - UseProxy = true, - Proxy = new WinHttpProxy(), - DefaultProxyCredentials = CredentialCache.DefaultCredentials, - AllowAutoRedirect = false - }; + var proxyUri = WinHttpHelper.GetProxyForUrl(new Uri(req.Url)); - using var http = new HttpClient(handler); + using var upstream = new TcpClient(); + await upstream.ConnectAsync(proxyUri.Host, proxyUri.Port); - var message = new HttpRequestMessage(new HttpMethod(req.Method), req.Url); + var upstreamStream = upstream.GetStream(); - foreach (var h in req.Headers) - { - if (!h.Key.Equals("Proxy-Authorization", StringComparison.OrdinalIgnoreCase)) - message.Headers.TryAddWithoutValidation(h.Key, h.Value); - } + await upstreamStream.WriteAsync(req.RawBytes); - if (req.Body != null) - message.Content = new StreamContent(req.Body); - - var resp = await http.SendAsync(message, HttpCompletionOption.ResponseHeadersRead); - - // статус - var statusLine = $"HTTP/1.1 {(int)resp.StatusCode} {resp.ReasonPhrase}\r\n"; - await clientStream.WriteAsync(Encoding.ASCII.GetBytes(statusLine)); - - // заголовки - foreach (var h in resp.Headers) - { - foreach (var v in h.Value) - await WriteHeader(clientStream, h.Key, v); - } - - foreach (var h in resp.Content.Headers) - { - foreach (var v in h.Value) - await WriteHeader(clientStream, h.Key, v); - } - - await clientStream.WriteAsync(Encoding.ASCII.GetBytes("\r\n")); - - // тело (стриминг) - await resp.Content.CopyToAsync(clientStream); + await Pump(upstreamStream, clientStream); } // ================= CONNECT ================= -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")) + static async Task HandleConnect(NetworkStream clientStream, string target) { - await clientStream.WriteAsync(Encoding.ASCII.GetBytes("HTTP/1.1 502 Bad Gateway\r\n\r\n")); - return; + var parts = target.Split(':'); + string host = parts[0]; + int port = int.Parse(parts[1]); + + var proxyUri = WinHttpHelper.GetProxyForUrl(new Uri($"https://{host}:{port}")); + + using var upstream = new TcpClient(); + await upstream.ConnectAsync(proxyUri.Host, proxyUri.Port); + + var upstreamStream = upstream.GetStream(); + + string connectReq = + $"CONNECT {host}:{port} HTTP/1.1\r\nHost: {host}:{port}\r\n\r\n"; + + await upstreamStream.WriteAsync(Encoding.ASCII.GetBytes(connectReq)); + + var resp = await ReadHeaders(upstreamStream); + + if (resp == null || !resp.Raw.Contains("200")) + { + 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); } - // говорим клиенту ОК - 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) + static bool CheckAuth(string raw) { string expected = Convert.ToBase64String(Encoding.ASCII.GetBytes($"{USER}:{PASS}")); - return headers.Contains($"Proxy-Authorization: Basic {expected}"); + return raw.Contains($"Proxy-Authorization: Basic {expected}"); } static async Task Write407(NetworkStream stream) @@ -170,78 +124,73 @@ static async Task HandleConnect(NetworkStream clientStream, string target) await stream.WriteAsync(Encoding.ASCII.GetBytes(resp)); } - // ================= REQUEST PARSER ================= + // ================= PARSER ================= - class HttpRequestData + class HttpRequest { public string Method; - public string Url; public string Target; - public string HeadersRaw; - public Dictionary Headers = new(); - public Stream Body; + public string Url; + public string Raw; + public byte[] RawBytes; } - static async Task ReadHeaders(NetworkStream stream) + static async Task ReadHeaders(Stream stream) { var buffer = new byte[8192]; int read = await stream.ReadAsync(buffer); if (read <= 0) return null; - string text = Encoding.ASCII.GetString(buffer, 0, read); + string raw = Encoding.ASCII.GetString(buffer, 0, read); - var lines = text.Split("\r\n"); + var lines = raw.Split("\r\n"); var first = lines[0].Split(' '); - var data = new HttpRequestData + var req = new HttpRequest { Method = first[0], Target = first[1], - HeadersRaw = text + Raw = raw, + RawBytes = buffer.Take(read).ToArray() }; - foreach (var line in lines.Skip(1)) + if (req.Method != "CONNECT") { - if (string.IsNullOrWhiteSpace(line)) break; - - int idx = line.IndexOf(':'); - if (idx > 0) - data.Headers[line[..idx]] = line[(idx + 1)..].Trim(); + string host = lines.FirstOrDefault(l => l.StartsWith("Host:", StringComparison.OrdinalIgnoreCase))?.Split(':')[1].Trim(); + req.Url = req.Target.StartsWith("http") + ? req.Target + : $"http://{host}{req.Target}"; } - if (data.Method != "CONNECT") - { - data.Url = data.Target.StartsWith("http") - ? data.Target - : $"http://{data.Headers["Host"]}{data.Target}"; - } - - return data; + return req; } - static async Task WriteHeader(NetworkStream stream, string key, string value) + // ================= STREAM ================= + + static async Task Pump(Stream from, Stream to) { - string line = $"{key}: {value}\r\n"; - await stream.WriteAsync(Encoding.ASCII.GetBytes(line)); + 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); + } } } -// ================= WINHTTP PROXY ================= - -class WinHttpProxy : IWebProxy -{ - public ICredentials Credentials { get; set; } - - public Uri GetProxy(Uri destination) - { - return WinHttpHelper.GetProxyForUrl(destination); - } - - public bool IsBypassed(Uri host) => false; -} - -using System.Runtime.InteropServices; +// ================= WINHTTP ================= static class WinHttpHelper { @@ -264,8 +213,8 @@ static class WinHttpHelper var options = new WINHTTP_AUTOPROXY_OPTIONS { - dwFlags = 0x00000001 | 0x00000002, // AUTO_DETECT + CONFIG_URL - dwAutoDetectFlags = 0x00000001 | 0x00000002 // DHCP + DNS_A + dwFlags = 0x00000001 | 0x00000002, + dwAutoDetectFlags = 0x00000001 | 0x00000002 }; if (WinHttpGetProxyForUrl(session, url.ToString(), ref options, out var info)) @@ -275,12 +224,13 @@ static class WinHttpHelper if (!string.IsNullOrEmpty(proxy)) { var first = proxy.Split(';')[0]; + WinHttpCloseHandle(session); return new Uri(Normalize(first)); } } WinHttpCloseHandle(session); - return url; // direct fallback + return url; } static string PtrToString(IntPtr ptr) @@ -312,25 +262,4 @@ static class WinHttpHelper 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