commit 83da281cbe67cad5e8e225b41156eb15f87992fa Author: kosipov Date: Thu Apr 9 18:17:42 2026 +0200 Добавить Program.cs diff --git a/Program.cs b/Program.cs new file mode 100644 index 0000000..e2c3d74 --- /dev/null +++ b/Program.cs @@ -0,0 +1,261 @@ +using System; +using System.Buffers; +using System.IO; +using System.Net; +using System.Net.Http; +using System.Net.Sockets; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +class Program +{ + static string USER = "user"; + static string PASS = "pass"; + + static async Task Main() + { + int port = 8888; + + 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(); + + while (true) + { + var client = await listener.AcceptTcpClientAsync(); + _ = Task.Run(() => HandleClient(client)); + } + } + + static async Task HandleClient(TcpClient client) + { + using (client) + { + var stream = client.GetStream(); + + var request = await ReadHeaders(stream); + if (request == null) return; + + if (!CheckAuth(request.HeadersRaw)) + { + 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) + { + var handler = new SocketsHttpHandler + { + UseProxy = true, + Proxy = new WinHttpProxy(), + DefaultProxyCredentials = CredentialCache.DefaultCredentials, + AllowAutoRedirect = false + }; + + using var http = new HttpClient(handler); + + var message = new HttpRequestMessage(new HttpMethod(req.Method), req.Url); + + foreach (var h in req.Headers) + { + if (!h.Key.Equals("Proxy-Authorization", StringComparison.OrdinalIgnoreCase)) + message.Headers.TryAddWithoutValidation(h.Key, h.Value); + } + + 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); + } + + // ================= 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 + 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); + } + + // ================= AUTH ================= + + static bool CheckAuth(string headers) + { + string expected = Convert.ToBase64String(Encoding.ASCII.GetBytes($"{USER}:{PASS}")); + return headers.Contains($"Proxy-Authorization: Basic {expected}"); + } + + static async Task Write407(NetworkStream stream) + { + string resp = + "HTTP/1.1 407 Proxy Authentication Required\r\n" + + "Proxy-Authenticate: Basic realm=\"proxy\"\r\n\r\n"; + + await stream.WriteAsync(Encoding.ASCII.GetBytes(resp)); + } + + // ================= REQUEST PARSER ================= + + class HttpRequestData + { + public string Method; + public string Url; + public string Target; + public string HeadersRaw; + public Dictionary Headers = new(); + public Stream Body; + } + + static async Task ReadHeaders(NetworkStream 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); + + var lines = text.Split("\r\n"); + var first = lines[0].Split(' '); + + var data = new HttpRequestData + { + Method = first[0], + Target = first[1], + HeadersRaw = text + }; + + foreach (var line in lines.Skip(1)) + { + if (string.IsNullOrWhiteSpace(line)) break; + + int idx = line.IndexOf(':'); + if (idx > 0) + data.Headers[line[..idx]] = line[(idx + 1)..].Trim(); + } + + if (data.Method != "CONNECT") + { + data.Url = data.Target.StartsWith("http") + ? data.Target + : $"http://{data.Headers["Host"]}{data.Target}"; + } + + return data; + } + + static async Task WriteHeader(NetworkStream stream, string key, string value) + { + string line = $"{key}: {value}\r\n"; + await stream.WriteAsync(Encoding.ASCII.GetBytes(line)); + } +} + +// ================= 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; +} + +static class WinHttpHelper +{ + [DllImport("winhttp.dll", SetLastError = true)] + static extern bool WinHttpGetIEProxyConfigForCurrentUser(out WINHTTP_CURRENT_USER_IE_PROXY_CONFIG config); + + public static Uri GetProxyForUrl(Uri url) + { + WinHttpGetIEProxyConfigForCurrentUser(out var cfg); + + if (!string.IsNullOrEmpty(cfg.lpszProxy)) + { + var parts = cfg.lpszProxy.Split(':'); + return new Uri($"http://{parts[0]}:{parts[1]}"); + } + + return url; // direct + } + + struct WINHTTP_CURRENT_USER_IE_PROXY_CONFIG + { + public bool fAutoDetect; + public IntPtr lpszAutoConfigUrl; + public IntPtr lpszProxy; + public IntPtr lpszProxyBypass; + } +} \ No newline at end of file