265 lines
7.1 KiB
C#
265 lines
7.1 KiB
C#
using System;
|
|
using System.Buffers;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Net;
|
|
using System.Net.Sockets;
|
|
using System.Runtime.InteropServices;
|
|
using System.Text;
|
|
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.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, HttpRequest req)
|
|
{
|
|
var proxyUri = WinHttpHelper.GetProxyForUrl(new Uri(req.Url));
|
|
|
|
using var upstream = new TcpClient();
|
|
await upstream.ConnectAsync(proxyUri.Host, proxyUri.Port);
|
|
|
|
var upstreamStream = upstream.GetStream();
|
|
|
|
await upstreamStream.WriteAsync(req.RawBytes);
|
|
|
|
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 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);
|
|
}
|
|
|
|
// ================= AUTH =================
|
|
|
|
static bool CheckAuth(string raw)
|
|
{
|
|
string expected = Convert.ToBase64String(Encoding.ASCII.GetBytes($"{USER}:{PASS}"));
|
|
return raw.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));
|
|
}
|
|
|
|
// ================= PARSER =================
|
|
|
|
class HttpRequest
|
|
{
|
|
public string Method;
|
|
public string Target;
|
|
public string Url;
|
|
public string Raw;
|
|
public byte[] RawBytes;
|
|
}
|
|
|
|
static async Task<HttpRequest> ReadHeaders(Stream stream)
|
|
{
|
|
var buffer = new byte[8192];
|
|
int read = await stream.ReadAsync(buffer);
|
|
|
|
if (read <= 0) return null;
|
|
|
|
string raw = Encoding.ASCII.GetString(buffer, 0, read);
|
|
|
|
var lines = raw.Split("\r\n");
|
|
var first = lines[0].Split(' ');
|
|
|
|
var req = new HttpRequest
|
|
{
|
|
Method = first[0],
|
|
Target = first[1],
|
|
Raw = raw,
|
|
RawBytes = buffer.Take(read).ToArray()
|
|
};
|
|
|
|
if (req.Method != "CONNECT")
|
|
{
|
|
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}";
|
|
}
|
|
|
|
return req;
|
|
}
|
|
|
|
// ================= STREAM =================
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
// ================= WINHTTP =================
|
|
|
|
static class WinHttpHelper
|
|
{
|
|
[DllImport("winhttp.dll", SetLastError = true)]
|
|
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)
|
|
{
|
|
IntPtr session = WinHttpOpen("proxy", 0, null, null, 0);
|
|
|
|
var options = new WINHTTP_AUTOPROXY_OPTIONS
|
|
{
|
|
dwFlags = 0x00000001 | 0x00000002,
|
|
dwAutoDetectFlags = 0x00000001 | 0x00000002
|
|
};
|
|
|
|
if (WinHttpGetProxyForUrl(session, url.ToString(), ref options, out var info))
|
|
{
|
|
string proxy = PtrToString(info.lpszProxy);
|
|
|
|
if (!string.IsNullOrEmpty(proxy))
|
|
{
|
|
var first = proxy.Split(';')[0];
|
|
WinHttpCloseHandle(session);
|
|
return new Uri(Normalize(first));
|
|
}
|
|
}
|
|
|
|
WinHttpCloseHandle(session);
|
|
return url;
|
|
}
|
|
|
|
static string PtrToString(IntPtr ptr)
|
|
{
|
|
if (ptr == IntPtr.Zero) return null;
|
|
return Marshal.PtrToStringUni(ptr);
|
|
}
|
|
|
|
static string Normalize(string proxy)
|
|
{
|
|
if (!proxy.StartsWith("http"))
|
|
return "http://" + proxy;
|
|
return proxy;
|
|
}
|
|
|
|
struct WINHTTP_AUTOPROXY_OPTIONS
|
|
{
|
|
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;
|
|
}
|
|
} |