diff --git a/Program.cs b/Program.cs index 809f5f6..e8470d0 100644 --- a/Program.cs +++ b/Program.cs @@ -14,6 +14,9 @@ class Program { static string USER = "user"; static string PASS = "pass"; + static readonly string UPSTREAM_USER = Environment.GetEnvironmentVariable("UPSTREAM_USER"); + static readonly string UPSTREAM_PASS = Environment.GetEnvironmentVariable("UPSTREAM_PASS"); + static readonly string UPSTREAM_PROXY_AUTH_HEADER = BuildUpstreamProxyAuthHeader(); static long _connectionSeq; static async Task Main() @@ -117,7 +120,8 @@ class Program var upstreamStream = upstream.GetStream(); - await upstreamStream.WriteAsync(req.RawBytes); + var outbound = BuildUpstreamHttpRequest(req); + await upstreamStream.WriteAsync(outbound); await Pump(upstreamStream, clientStream, $"conn:{connectionId} HTTP upstream->client"); } @@ -152,7 +156,7 @@ class Program var upstreamStream = upstream.GetStream(); string connectReq = - $"CONNECT {host}:{port} HTTP/1.1\r\nHost: {host}:{port}\r\n\r\n"; + $"CONNECT {host}:{port} HTTP/1.1\r\nHost: {host}:{port}\r\n{UPSTREAM_PROXY_AUTH_HEADER}\r\n"; await upstreamStream.WriteAsync(Encoding.ASCII.GetBytes(connectReq)); @@ -206,6 +210,7 @@ class Program public string Url; public string Raw; public byte[] RawBytes; + public int HeaderLength; } static async Task ReadHeaders(Stream stream, string context) @@ -232,7 +237,8 @@ class Program Method = first[0], Target = first[1], Raw = raw, - RawBytes = buffer.Take(read).ToArray() + RawBytes = buffer.Take(read).ToArray(), + HeaderLength = GetHeaderLength(raw) }; if (req.Method != "CONNECT") @@ -309,6 +315,57 @@ class Program return string.Empty; return message.Replace("\r", " ").Replace("\n", " ").Trim(); } + + static int GetHeaderLength(string raw) + { + int idx = raw.IndexOf("\r\n\r\n", StringComparison.Ordinal); + return idx >= 0 ? idx + 4 : raw.Length; + } + + static byte[] BuildUpstreamHttpRequest(HttpRequest req) + { + var lines = req.Raw.Split("\r\n", StringSplitOptions.None); + var sb = new StringBuilder(); + sb.Append(lines[0]).Append("\r\n"); + + for (int i = 1; i < lines.Length; i++) + { + var line = lines[i]; + if (line.Length == 0) + break; + + if (line.StartsWith("Proxy-Authorization:", StringComparison.OrdinalIgnoreCase)) + continue; // local proxy auth, must not go upstream + if (line.StartsWith("Proxy-Connection:", StringComparison.OrdinalIgnoreCase)) + continue; + + sb.Append(line).Append("\r\n"); + } + + if (!string.IsNullOrEmpty(UPSTREAM_PROXY_AUTH_HEADER)) + sb.Append(UPSTREAM_PROXY_AUTH_HEADER); + + sb.Append("\r\n"); + var headerBytes = Encoding.ASCII.GetBytes(sb.ToString()); + + if (req.HeaderLength >= req.RawBytes.Length) + return headerBytes; + + int bodyLen = req.RawBytes.Length - req.HeaderLength; + var result = new byte[headerBytes.Length + bodyLen]; + Buffer.BlockCopy(headerBytes, 0, result, 0, headerBytes.Length); + Buffer.BlockCopy(req.RawBytes, req.HeaderLength, result, headerBytes.Length, bodyLen); + return result; + } + + static string BuildUpstreamProxyAuthHeader() + { + if (string.IsNullOrWhiteSpace(UPSTREAM_USER) || string.IsNullOrWhiteSpace(UPSTREAM_PASS)) + return string.Empty; + + string encoded = Convert.ToBase64String(Encoding.ASCII.GetBytes($"{UPSTREAM_USER}:{UPSTREAM_PASS}")); + return $"Proxy-Authorization: Basic {encoded}\r\n"; + } } // ================= WINHTTP =================