diff --git a/HolyClient.sln b/HolyClient.sln index 2c29d89f..a327fa8b 100644 --- a/HolyClient.sln +++ b/HolyClient.sln @@ -75,6 +75,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SandBoxMcProtoNet", "src\Sa EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SandBoxStressTest", "src\Sandbox\SandBoxStressTest\SandBoxStressTest.csproj", "{C5D74EC8-B4DC-4E48-92DD-F124E0B05052}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SandboxProxy", "src\Sandbox\SandboxProxy\SandboxProxy.csproj", "{C26D4BA0-EB02-4E4F-AB81-D6C054C2922F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -179,6 +181,10 @@ Global {C5D74EC8-B4DC-4E48-92DD-F124E0B05052}.Debug|Any CPU.Build.0 = Debug|Any CPU {C5D74EC8-B4DC-4E48-92DD-F124E0B05052}.Release|Any CPU.ActiveCfg = Release|Any CPU {C5D74EC8-B4DC-4E48-92DD-F124E0B05052}.Release|Any CPU.Build.0 = Release|Any CPU + {C26D4BA0-EB02-4E4F-AB81-D6C054C2922F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C26D4BA0-EB02-4E4F-AB81-D6C054C2922F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C26D4BA0-EB02-4E4F-AB81-D6C054C2922F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C26D4BA0-EB02-4E4F-AB81-D6C054C2922F}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -208,6 +214,7 @@ Global {EDA5EB9B-676E-47D4-9714-367C9BD13C69} = {E8DD8BFD-216D-4435-B9F8-E34C03D29FF9} {A865E72C-3D76-4D1F-8F0A-EEA3204B992F} = {BF4F609C-E739-47C1-B6E9-0FEC82E88ACA} {C5D74EC8-B4DC-4E48-92DD-F124E0B05052} = {BF4F609C-E739-47C1-B6E9-0FEC82E88ACA} + {C26D4BA0-EB02-4E4F-AB81-D6C054C2922F} = {BF4F609C-E739-47C1-B6E9-0FEC82E88ACA} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {41AEE66A-568B-4B2B-A50B-5AC57D067A07} diff --git a/src/QuickProxyNet/Internal/HttpHelper.cs b/src/QuickProxyNet/Internal/HttpHelper.cs index 0ddcd1e8..73185824 100644 --- a/src/QuickProxyNet/Internal/HttpHelper.cs +++ b/src/QuickProxyNet/Internal/HttpHelper.cs @@ -1,8 +1,10 @@ -using System.Globalization; +using System.Buffers; +using System.Globalization; using System.Net; using System.Runtime.CompilerServices; using System.Text; using Cysharp.Text; +using DotNext; using DotNext.Buffers; namespace QuickProxyNet; @@ -22,95 +24,156 @@ private static void Test(string host, int port) private static async ValueTask WriteConnectionCommand(Stream stream, string host, int port, NetworkCredential proxyCredentials, CancellationToken cancellationToken) { - using var builder = ZString.CreateUtf8StringBuilder(); + var builder = ZString.CreateUtf8StringBuilder(); + try + { + builder.AppendFormat("CONNECT {0}:{1} HTTP/1.1\r\n", host, port); + builder.AppendFormat("Host: {0}:{1}\r\n", host, port); + if (proxyCredentials != null) + { + var token = Encoding.UTF8.GetBytes(string.Format(CultureInfo.InvariantCulture, "{0}:{1}", + proxyCredentials.UserName, proxyCredentials.Password)); + var base64 = Convert.ToBase64String(token); + builder.AppendFormat("Proxy-Authorization: Basic {0}\r\n", base64); + } + + builder.Append("\r\n"); - builder.AppendFormat("CONNECT {0}:{1} HTTP/1.1\r\n", host, port); - builder.AppendFormat("Host: {0}:{1}\r\n", host, port); - if (proxyCredentials != null) + await stream.WriteAsync(builder.AsMemory(), cancellationToken); + } + finally { - var token = Encoding.UTF8.GetBytes(string.Format(CultureInfo.InvariantCulture, "{0}:{1}", - proxyCredentials.UserName, proxyCredentials.Password)); - var base64 = Convert.ToBase64String(token); - builder.AppendFormat("Proxy-Authorization: Basic {0}\r\n", base64); + builder.Dispose(); } + } + + - builder.Append("\r\n"); + internal static async ValueTask EstablishHttpTunnelAsync(Stream stream, Uri proxyUri, string host, int port, + NetworkCredential? credentials, CancellationToken cancellationToken) + { + await WriteConnectionCommand(stream, host, port, credentials, cancellationToken); - await stream.WriteAsync(builder.AsMemory(), cancellationToken); + var parser = new HttpResponseParser(); + try + { + bool find; + do + { + var memory = parser.GetMemory(); + var nread = await stream.ReadAsync(memory, cancellationToken); + if (nread <= 0) + throw new EndOfStreamException(); + find = parser.Parse(nread); + } while (find == false); + + bool isValid= parser.Validate(); + //string response = parser.ToString(); + + if (!isValid) + { + throw new ProxyProtocolException($"Failed to connect http://{host}:{port}"); + } + + return stream; + } + finally + { + parser.Dispose(); + } } +} - [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - private static bool TryConsumeHeaders(ref Utf16ValueStringBuilder builder) +public struct HttpResponseParser +{ + private static int BufferSize = 1024; + private MemoryOwner _memory; + private static readonly MemoryAllocator _allocator = ArrayPool.Shared.ToAllocator(); + private static readonly byte[] http200 = "HTTP/1.1 200".Select(x => (byte)x).ToArray(); + private int _writtenCount = 0; + private int _indexEnd=-1; + public ReadOnlySpan Span => _memory.Span.Slice(0, _writtenCount); + + public HttpResponseParser() { - return false; + _memory = _allocator.AllocateExactly(BufferSize); } - [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - internal static void ValidateHttpResponse(string response, string host, int port) + public Memory GetMemory() { - if (response.Length >= 15 && response.StartsWith("HTTP/1.", StringComparison.OrdinalIgnoreCase) && - (response[7] == '1' || response[7] == '0') && response[8] == ' ' && - response[9] == '2' && response[10] == '0' && response[11] == '0' && - response[12] == ' ') - return; - - throw new ProxyProtocolException(string.Format(CultureInfo.InvariantCulture, - "Failed to connect to {0}:{1}: {2}", host, port, response)); + if (_writtenCount < _memory.Length) + { + return _memory.Memory.Slice(_writtenCount); + } + + _memory.Resize(_writtenCount + BufferSize); + return _memory.Memory.Slice(_writtenCount); } - internal static async ValueTask EstablishHttpTunnelAsync(Stream stream, Uri proxyUri, string host, int port, - NetworkCredential? credentials, CancellationToken cancellationToken) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Parse(int count) { - await WriteConnectionCommand(stream, host, port, credentials, cancellationToken); + _writtenCount += count; + return ParseHttpEnd(count); + } + private static readonly byte[] NewLine = "\r\n\r\n".Select(x => (byte)x).ToArray(); - using var builder = ZString.CreateUtf8StringBuilder(); - //builder.ToString - do - { - var memory = builder.GetMemory(BufferSize); - var nread = await stream.ReadAsync(memory, cancellationToken); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool ParseHttpEnd(int count) + { + int start = _writtenCount - count; + int length = count; - if (nread <= 0) - break; + int offset = Math.Min(4, start); + start -= offset; + length += offset; - builder.Advance(nread); + ReadOnlySpan bytes = _memory.Span.Slice(start, length); + int index = bytes.IndexOf(NewLine); + if (index < 0) + { + return false; + } - return null; - } while (true); + _indexEnd = index + start; - return null; + return true; } -} -public static class StringEscape -{ - private static readonly char[] toEscape = - "\0\x1\x2\x3\x4\x5\x6\a\b\t\n\v\f\r\xe\xf\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\"\\" - .ToCharArray(); + public bool Validate() + { + if (_indexEnd == -1) + { + throw new InvalidOperationException("No find http response"); + } + + ReadOnlySpan span = Span; + + + if (span.Length > (uint)_indexEnd + 4) + { + return false; + } + if (span.Length >= 15 && span.StartsWith(http200)) + { + return true; + } - private static readonly string[] literals = - @"\0,\x0001,\x0002,\x0003,\x0004,\x0005,\x0006,\a,\b,\t,\n,\v,\f,\r,\x000e,\x000f,\x0010,\x0011,\x0012,\x0013,\x0014,\x0015,\x0016,\x0017,\x0018,\x0019,\x001a,\x001b,\x001c,\x001d,\x001e,\x001f" - .Split(new[] { ',' }); + return false; + } - public static string Escape(this string input) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override string ToString() { - var i = input.IndexOfAny(toEscape); - if (i < 0) return input; - - var sb = new StringBuilder(input.Length + 5); - var j = 0; - do - { - sb.Append(input, j, i - j); - var c = input[i]; - if (c < 0x20) sb.Append(literals[c]); - else sb.Append(@"\").Append(c); - } while ((i = input.IndexOfAny(toEscape, j = ++i)) > 0); + return Encoding.UTF8.GetString(_memory.Span.Slice(0, _indexEnd+4)); + } - return sb.Append(input, j, input.Length - j).ToString(); + public void Dispose() + { + _memory.Dispose(); } } \ No newline at end of file diff --git a/src/Sandbox/SandBoxMcProtoNet/Program.cs b/src/Sandbox/SandBoxMcProtoNet/Program.cs index 5875afc1..c167acbb 100644 --- a/src/Sandbox/SandBoxMcProtoNet/Program.cs +++ b/src/Sandbox/SandBoxMcProtoNet/Program.cs @@ -9,16 +9,27 @@ using McProtoNet.Client; using McProtoNet.MultiVersionProtocol; using McProtoNet.Serialization; +using QuickProxyNet; internal class Program { public static async Task Main(string[] args) { - + byte[] bytes = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + + Console.WriteLine(bytes.AsSpan().IndexOf(new byte[]{3,4,5,6})); + //return; + + + HttpProxyClient proxyClient = new HttpProxyClient("84.252.75.136", 4444); + + await proxyClient.ConnectAsync("google.com", 443); + + return; + Console.WriteLine("Start"); try { - var list = new List(); var listProtocols = new List(); for (int i = 0; i < 200; i++) diff --git a/src/Sandbox/SandboxProxy/Program.cs b/src/Sandbox/SandboxProxy/Program.cs new file mode 100644 index 00000000..f4a3d863 --- /dev/null +++ b/src/Sandbox/SandboxProxy/Program.cs @@ -0,0 +1 @@ +Console.WriteLine("Hi"); \ No newline at end of file diff --git a/src/Sandbox/SandboxProxy/SandboxProxy.csproj b/src/Sandbox/SandboxProxy/SandboxProxy.csproj new file mode 100644 index 00000000..c48f76ec --- /dev/null +++ b/src/Sandbox/SandboxProxy/SandboxProxy.csproj @@ -0,0 +1,10 @@ + + + + Exe + net9.0 + enable + enable + + +