Обновить Program.cs

This commit is contained in:
2026-04-09 18:34:52 +02:00
parent 2d8f3d07e1
commit dc5f1dddcc

View File

@@ -1,13 +1,13 @@
using System; using System;
using System.Buffers;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net; using System.Net;
using System.Net.Http;
using System.Net.Sockets; using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Runtime.InteropServices;
class Program class Program
{ {
@@ -18,10 +18,10 @@ class Program
{ {
int port = 8888; int port = 8888;
Console.WriteLine($"Local proxy:"); Console.WriteLine("=== LOCAL PROXY ===");
Console.WriteLine($" http://127.0.0.1:{port}"); Console.WriteLine($"http://127.0.0.1:{port}");
Console.WriteLine($" login: {USER}"); Console.WriteLine($"login: {USER}");
Console.WriteLine($" pass : {PASS}"); Console.WriteLine($"pass : {PASS}");
var listener = new TcpListener(IPAddress.Any, port); var listener = new TcpListener(IPAddress.Any, port);
listener.Start(); listener.Start();
@@ -38,127 +38,81 @@ class Program
using (client) using (client)
{ {
var stream = client.GetStream(); var stream = client.GetStream();
var request = await ReadHeaders(stream); var request = await ReadHeaders(stream);
if (request == null) return; if (request == null) return;
if (!CheckAuth(request.HeadersRaw)) if (!CheckAuth(request.Raw))
{ {
await Write407(stream); await Write407(stream);
return; return;
} }
if (request.Method == "CONNECT") if (request.Method == "CONNECT")
{
await HandleConnect(stream, request.Target); await HandleConnect(stream, request.Target);
}
else else
{
await HandleHttp(stream, request); await HandleHttp(stream, request);
}
} }
} }
// ================= HTTP ================= // ================= HTTP =================
static async Task HandleHttp(NetworkStream clientStream, HttpRequestData req) static async Task HandleHttp(NetworkStream clientStream, HttpRequest req)
{ {
DefaultProxyCredentials = CredentialCache.DefaultCredentials var proxyUri = WinHttpHelper.GetProxyForUrl(new Uri(req.Url));
var handler = new SocketsHttpHandler
{
UseProxy = true,
Proxy = new WinHttpProxy(),
DefaultProxyCredentials = CredentialCache.DefaultCredentials,
AllowAutoRedirect = false
};
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) await upstreamStream.WriteAsync(req.RawBytes);
{
if (!h.Key.Equals("Proxy-Authorization", StringComparison.OrdinalIgnoreCase))
message.Headers.TryAddWithoutValidation(h.Key, h.Value);
}
if (req.Body != null) await Pump(upstreamStream, clientStream);
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 ================= // ================= 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"))
{ {
await clientStream.WriteAsync(Encoding.ASCII.GetBytes("HTTP/1.1 502 Bad Gateway\r\n\r\n")); var parts = target.Split(':');
return; 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 ================= // ================= AUTH =================
static bool CheckAuth(string headers) static bool CheckAuth(string raw)
{ {
string expected = Convert.ToBase64String(Encoding.ASCII.GetBytes($"{USER}:{PASS}")); 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) 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)); await stream.WriteAsync(Encoding.ASCII.GetBytes(resp));
} }
// ================= REQUEST PARSER ================= // ================= PARSER =================
class HttpRequestData class HttpRequest
{ {
public string Method; public string Method;
public string Url;
public string Target; public string Target;
public string HeadersRaw; public string Url;
public Dictionary<string, string> Headers = new(); public string Raw;
public Stream Body; public byte[] RawBytes;
} }
static async Task<HttpRequestData> ReadHeaders(NetworkStream stream) static async Task<HttpRequest> ReadHeaders(Stream stream)
{ {
var buffer = new byte[8192]; var buffer = new byte[8192];
int read = await stream.ReadAsync(buffer); int read = await stream.ReadAsync(buffer);
if (read <= 0) return null; 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 first = lines[0].Split(' ');
var data = new HttpRequestData var req = new HttpRequest
{ {
Method = first[0], Method = first[0],
Target = first[1], 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; string host = lines.FirstOrDefault(l => l.StartsWith("Host:", StringComparison.OrdinalIgnoreCase))?.Split(':')[1].Trim();
req.Url = req.Target.StartsWith("http")
int idx = line.IndexOf(':'); ? req.Target
if (idx > 0) : $"http://{host}{req.Target}";
data.Headers[line[..idx]] = line[(idx + 1)..].Trim();
} }
if (data.Method != "CONNECT") return req;
{
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) // ================= STREAM =================
static async Task Pump(Stream from, Stream to)
{ {
string line = $"{key}: {value}\r\n"; var buffer = ArrayPool<byte>.Shared.Rent(8192);
await stream.WriteAsync(Encoding.ASCII.GetBytes(line));
try
{
while (true)
{
int read = await from.ReadAsync(buffer);
if (read <= 0) break;
await to.WriteAsync(buffer.AsMemory(0, read));
}
}
catch { }
finally
{
ArrayPool<byte>.Shared.Return(buffer);
}
} }
} }
// ================= WINHTTP PROXY ================= // ================= WINHTTP =================
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;
static class WinHttpHelper static class WinHttpHelper
{ {
@@ -264,8 +213,8 @@ static class WinHttpHelper
var options = new WINHTTP_AUTOPROXY_OPTIONS var options = new WINHTTP_AUTOPROXY_OPTIONS
{ {
dwFlags = 0x00000001 | 0x00000002, // AUTO_DETECT + CONFIG_URL dwFlags = 0x00000001 | 0x00000002,
dwAutoDetectFlags = 0x00000001 | 0x00000002 // DHCP + DNS_A dwAutoDetectFlags = 0x00000001 | 0x00000002
}; };
if (WinHttpGetProxyForUrl(session, url.ToString(), ref options, out var info)) if (WinHttpGetProxyForUrl(session, url.ToString(), ref options, out var info))
@@ -275,12 +224,13 @@ static class WinHttpHelper
if (!string.IsNullOrEmpty(proxy)) if (!string.IsNullOrEmpty(proxy))
{ {
var first = proxy.Split(';')[0]; var first = proxy.Split(';')[0];
WinHttpCloseHandle(session);
return new Uri(Normalize(first)); return new Uri(Normalize(first));
} }
} }
WinHttpCloseHandle(session); WinHttpCloseHandle(session);
return url; // direct fallback return url;
} }
static string PtrToString(IntPtr ptr) static string PtrToString(IntPtr ptr)
@@ -312,25 +262,4 @@ static class WinHttpHelper
public IntPtr lpszProxy; public IntPtr lpszProxy;
public IntPtr lpszProxyBypass; public IntPtr lpszProxyBypass;
} }
}
static async Task Pump(Stream from, Stream to)
{
var buffer = ArrayPool<byte>.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<byte>.Shared.Return(buffer);
}
} }