From ec6a8de6bccac64193300ac5c26d3002d09fa316 Mon Sep 17 00:00:00 2001 From: Honfika Date: Wed, 16 Oct 2019 22:28:40 +0200 Subject: [PATCH 1/6] Headerbulder improvement --- src/Titanium.Web.Proxy/Helpers/HttpWriter.cs | 9 +++++---- src/Titanium.Web.Proxy/Http/HeaderBuilder.cs | 13 ++++++++++--- src/Titanium.Web.Proxy/Http/HttpWebClient.cs | 4 +--- src/Titanium.Web.Proxy/Http/Request.cs | 2 +- src/Titanium.Web.Proxy/Http/Response.cs | 2 +- 5 files changed, 18 insertions(+), 12 deletions(-) diff --git a/src/Titanium.Web.Proxy/Helpers/HttpWriter.cs b/src/Titanium.Web.Proxy/Helpers/HttpWriter.cs index 3522d5839..5b7c87e25 100644 --- a/src/Titanium.Web.Proxy/Helpers/HttpWriter.cs +++ b/src/Titanium.Web.Proxy/Helpers/HttpWriter.cs @@ -87,12 +87,13 @@ internal Task WriteLineAsync(string value, CancellationToken cancellationToken = /// /// Write the headers to client /// - /// + /// /// /// - internal async Task WriteHeadersAsync(HeaderBuilder header, CancellationToken cancellationToken = default) + internal async Task WriteHeadersAsync(HeaderBuilder headerBuilder, CancellationToken cancellationToken = default) { - await WriteAsync(header.GetBytes(), true, cancellationToken); + var buffer = headerBuilder.GetBuffer(); + await WriteAsync(buffer.Array, buffer.Offset, buffer.Count, true, cancellationToken); } /// @@ -279,7 +280,7 @@ protected async Task WriteAsync(RequestResponseBase requestResponse, HeaderBuild { var body = requestResponse.CompressBodyAndUpdateContentLength(); headerBuilder.WriteHeaders(requestResponse.Headers); - await WriteAsync(headerBuilder.GetBytes(), true, cancellationToken); + await WriteHeadersAsync(headerBuilder, cancellationToken); if (body != null) { diff --git a/src/Titanium.Web.Proxy/Http/HeaderBuilder.cs b/src/Titanium.Web.Proxy/Http/HeaderBuilder.cs index f72beee04..dfd6606be 100644 --- a/src/Titanium.Web.Proxy/Http/HeaderBuilder.cs +++ b/src/Titanium.Web.Proxy/Http/HeaderBuilder.cs @@ -10,7 +10,7 @@ namespace Titanium.Web.Proxy.Http { internal class HeaderBuilder { - private MemoryStream stream = new MemoryStream(); + private readonly MemoryStream stream = new MemoryStream(); public void WriteRequestLine(string httpMethod, string httpUrl, Version version) { @@ -83,9 +83,16 @@ public void Write(string str) #endif } - public byte[] GetBytes() + public ArraySegment GetBuffer() { - return stream.ToArray(); + stream.TryGetBuffer(out var buffer); + return buffer; + } + + public string GetString(Encoding encoding) + { + stream.TryGetBuffer(out var buffer); + return encoding.GetString(buffer.Array, buffer.Offset, buffer.Count); } } } diff --git a/src/Titanium.Web.Proxy/Http/HttpWebClient.cs b/src/Titanium.Web.Proxy/Http/HttpWebClient.cs index 69649fcbc..7086ddbc8 100644 --- a/src/Titanium.Web.Proxy/Http/HttpWebClient.cs +++ b/src/Titanium.Web.Proxy/Http/HttpWebClient.cs @@ -147,9 +147,7 @@ internal async Task SendRequest(bool enable100ContinueBehaviour, bool isTranspar headerBuilder.WriteLine(); - var data = headerBuilder.GetBytes(); - - await writer.WriteAsync(data, 0, data.Length, cancellationToken); + await writer.WriteHeadersAsync(headerBuilder, cancellationToken); if (enable100ContinueBehaviour && Request.ExpectContinue) { diff --git a/src/Titanium.Web.Proxy/Http/Request.cs b/src/Titanium.Web.Proxy/Http/Request.cs index c204a5f15..5338212eb 100644 --- a/src/Titanium.Web.Proxy/Http/Request.cs +++ b/src/Titanium.Web.Proxy/Http/Request.cs @@ -157,7 +157,7 @@ public override string HeaderText var headerBuilder = new HeaderBuilder(); headerBuilder.WriteRequestLine(Method, RequestUriString, HttpVersion); headerBuilder.WriteHeaders(Headers); - return HttpHelper.HeaderEncoding.GetString(headerBuilder.GetBytes()); + return headerBuilder.GetString(HttpHelper.HeaderEncoding); } } diff --git a/src/Titanium.Web.Proxy/Http/Response.cs b/src/Titanium.Web.Proxy/Http/Response.cs index ecf53039a..585fb5c08 100644 --- a/src/Titanium.Web.Proxy/Http/Response.cs +++ b/src/Titanium.Web.Proxy/Http/Response.cs @@ -102,7 +102,7 @@ public override string HeaderText var headerBuilder = new HeaderBuilder(); headerBuilder.WriteResponseLine(HttpVersion, StatusCode, StatusDescription); headerBuilder.WriteHeaders(Headers); - return HttpHelper.HeaderEncoding.GetString(headerBuilder.GetBytes()); + return headerBuilder.GetString(HttpHelper.HeaderEncoding); } } From b25d572ad39ec00b86492bc2ec60cd96771255cf Mon Sep 17 00:00:00 2001 From: Honfika Date: Thu, 17 Oct 2019 08:24:11 +0200 Subject: [PATCH 2/6] Small fixes --- .../EventArguments/LimitedStream.cs | 8 +++++--- src/Titanium.Web.Proxy/ExplicitClientHandler.cs | 6 +++--- src/Titanium.Web.Proxy/Helpers/HttpWriter.cs | 2 +- src/Titanium.Web.Proxy/Http/Response.cs | 3 +-- .../Network/Tcp/TcpConnectionFactory.cs | 4 +++- src/Titanium.Web.Proxy/RequestHandler.cs | 5 ++--- .../StreamExtended/Network/CopyStream.cs | 2 +- .../Network/CustomBufferedPeekStream.cs | 2 +- .../StreamExtended/Network/CustomBufferedStream.cs | 12 ++++++------ .../StreamExtended/Network/ICustomStreamReader.cs | 2 +- src/Titanium.Web.Proxy/WebSocketHandler.cs | 7 ++----- 11 files changed, 26 insertions(+), 27 deletions(-) diff --git a/src/Titanium.Web.Proxy/EventArguments/LimitedStream.cs b/src/Titanium.Web.Proxy/EventArguments/LimitedStream.cs index 18dfcfe7d..f3b9f15fb 100644 --- a/src/Titanium.Web.Proxy/EventArguments/LimitedStream.cs +++ b/src/Titanium.Web.Proxy/EventArguments/LimitedStream.cs @@ -49,12 +49,12 @@ private void getNextChunk() if (readChunkTrail) { // read the chunk trail of the previous chunk - string s = baseStream.ReadLineAsync().Result; + string? s = baseStream.ReadLineAsync().Result; } readChunkTrail = true; - string chunkHead = baseStream.ReadLineAsync().Result; + string? chunkHead = baseStream.ReadLineAsync().Result; int idx = chunkHead.IndexOf(";", StringComparison.Ordinal); if (idx >= 0) { @@ -73,7 +73,9 @@ private void getNextChunk() bytesRemaining = -1; // chunk trail - baseStream.ReadLineAsync().Wait(); + var task = baseStream.ReadLineAsync(); + if (!task.IsCompleted) + task.AsTask().Wait(); } } diff --git a/src/Titanium.Web.Proxy/ExplicitClientHandler.cs b/src/Titanium.Web.Proxy/ExplicitClientHandler.cs index 15d009fe9..5ed1e180b 100644 --- a/src/Titanium.Web.Proxy/ExplicitClientHandler.cs +++ b/src/Titanium.Web.Proxy/ExplicitClientHandler.cs @@ -53,13 +53,13 @@ private async Task handleClient(ExplicitProxyEndPoint endPoint, TcpClientConnect if (await HttpHelper.IsConnectMethod(clientStream, BufferPool, cancellationToken) == 1) { // read the first line HTTP command - string httpCmd = await clientStream.ReadLineAsync(cancellationToken); + string? httpCmd = await clientStream.ReadLineAsync(cancellationToken); if (string.IsNullOrEmpty(httpCmd)) { return; } - Request.ParseRequestLine(httpCmd, out string _, out string httpUrl, out var version); + Request.ParseRequestLine(httpCmd!, out string _, out string httpUrl, out var version); var httpRemoteUri = new Uri("http://" + httpUrl); connectHostname = httpRemoteUri.Host; @@ -303,7 +303,7 @@ await TcpHelper.SendRaw(clientStream, connection.Stream, BufferPool, if (connectArgs != null && await HttpHelper.IsPriMethod(clientStream, BufferPool, cancellationToken) == 1) { // todo - string httpCmd = await clientStream.ReadLineAsync(cancellationToken); + string? httpCmd = await clientStream.ReadLineAsync(cancellationToken); if (httpCmd == "PRI * HTTP/2.0") { connectArgs.HttpClient.ConnectRequest!.TunnelType = TunnelType.Http2; diff --git a/src/Titanium.Web.Proxy/Helpers/HttpWriter.cs b/src/Titanium.Web.Proxy/Helpers/HttpWriter.cs index 5b7c87e25..a857ba44d 100644 --- a/src/Titanium.Web.Proxy/Helpers/HttpWriter.cs +++ b/src/Titanium.Web.Proxy/Helpers/HttpWriter.cs @@ -197,7 +197,7 @@ private async Task copyBodyChunkedAsync(ICustomStreamReader reader, Action= 0) { diff --git a/src/Titanium.Web.Proxy/Http/Response.cs b/src/Titanium.Web.Proxy/Http/Response.cs index 585fb5c08..0cef5d005 100644 --- a/src/Titanium.Web.Proxy/Http/Response.cs +++ b/src/Titanium.Web.Proxy/Http/Response.cs @@ -121,8 +121,7 @@ internal override void EnsureBodyAvailable(bool throwWhenNotReadYet = true) } } - internal static void ParseResponseLine(string httpStatus, out Version version, out int statusCode, - out string statusDescription) + internal static void ParseResponseLine(string httpStatus, out Version version, out int statusCode, out string statusDescription) { int firstSpace = httpStatus.IndexOf(' '); if (firstSpace == -1) diff --git a/src/Titanium.Web.Proxy/Network/Tcp/TcpConnectionFactory.cs b/src/Titanium.Web.Proxy/Network/Tcp/TcpConnectionFactory.cs index 8f45b0608..f7b0d652c 100644 --- a/src/Titanium.Web.Proxy/Network/Tcp/TcpConnectionFactory.cs +++ b/src/Titanium.Web.Proxy/Network/Tcp/TcpConnectionFactory.cs @@ -11,6 +11,7 @@ using System.Threading; using System.Threading.Tasks; using Titanium.Web.Proxy.EventArguments; +using Titanium.Web.Proxy.Exceptions; using Titanium.Web.Proxy.Extensions; using Titanium.Web.Proxy.Helpers; using Titanium.Web.Proxy.Http; @@ -364,7 +365,8 @@ private async Task createServerConnection(string remoteHost await writer.WriteRequestAsync(connectRequest, cancellationToken: cancellationToken); - string httpStatus = await stream.ReadLineAsync(cancellationToken); + string httpStatus = await stream.ReadLineAsync(cancellationToken) + ?? throw new ServerConnectionException("Server connection was closed."); Response.ParseResponseLine(httpStatus, out _, out int statusCode, out string statusDescription); diff --git a/src/Titanium.Web.Proxy/RequestHandler.cs b/src/Titanium.Web.Proxy/RequestHandler.cs index 25f308b01..98af08881 100644 --- a/src/Titanium.Web.Proxy/RequestHandler.cs +++ b/src/Titanium.Web.Proxy/RequestHandler.cs @@ -64,8 +64,7 @@ private async Task handleHttpSessionRequest(ProxyEndPoint endPoint, TcpClientCon } // read the request line - string httpCmd = await clientStream.ReadLineAsync(cancellationToken); - + string? httpCmd = await clientStream.ReadLineAsync(cancellationToken); if (string.IsNullOrEmpty(httpCmd)) { return; @@ -82,7 +81,7 @@ private async Task handleHttpSessionRequest(ProxyEndPoint endPoint, TcpClientCon { try { - Request.ParseRequestLine(httpCmd, out string httpMethod, out string httpUrl, out var version); + Request.ParseRequestLine(httpCmd!, out string httpMethod, out string httpUrl, out var version); // Read the request headers in to unique and non-unique header collections await HeaderParser.ReadHeaders(clientStream, args.HttpClient.Request.Headers, diff --git a/src/Titanium.Web.Proxy/StreamExtended/Network/CopyStream.cs b/src/Titanium.Web.Proxy/StreamExtended/Network/CopyStream.cs index 2e11a6db5..3edaf1e36 100644 --- a/src/Titanium.Web.Proxy/StreamExtended/Network/CopyStream.cs +++ b/src/Titanium.Web.Proxy/StreamExtended/Network/CopyStream.cs @@ -124,7 +124,7 @@ public async Task ReadAsync(byte[] buffer, int offset, int count, Cancellat return result; } - public Task ReadLineAsync(CancellationToken cancellationToken = default) + public ValueTask ReadLineAsync(CancellationToken cancellationToken = default) { return CustomBufferedStream.ReadLineInternalAsync(this, bufferPool, cancellationToken); } diff --git a/src/Titanium.Web.Proxy/StreamExtended/Network/CustomBufferedPeekStream.cs b/src/Titanium.Web.Proxy/StreamExtended/Network/CustomBufferedPeekStream.cs index d557596ca..fbdbd82a8 100644 --- a/src/Titanium.Web.Proxy/StreamExtended/Network/CustomBufferedPeekStream.cs +++ b/src/Titanium.Web.Proxy/StreamExtended/Network/CustomBufferedPeekStream.cs @@ -141,7 +141,7 @@ Task ICustomStreamReader.ReadAsync(byte[] buffer, int offset, int count, Ca /// /// /// - Task ICustomStreamReader.ReadLineAsync(CancellationToken cancellationToken) + ValueTask ICustomStreamReader.ReadLineAsync(CancellationToken cancellationToken) { return CustomBufferedStream.ReadLineInternalAsync(this, bufferPool, cancellationToken); } diff --git a/src/Titanium.Web.Proxy/StreamExtended/Network/CustomBufferedStream.cs b/src/Titanium.Web.Proxy/StreamExtended/Network/CustomBufferedStream.cs index 6e7c57346..36d66b521 100644 --- a/src/Titanium.Web.Proxy/StreamExtended/Network/CustomBufferedStream.cs +++ b/src/Titanium.Web.Proxy/StreamExtended/Network/CustomBufferedStream.cs @@ -157,7 +157,7 @@ public override void Write(byte[] buffer, int offset, int count) /// /// A task that represents the asynchronous copy operation. /// - public override async Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken = default) + public override async Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) { if (bufferLength > 0) { @@ -176,7 +176,7 @@ public override async Task CopyToAsync(Stream destination, int bufferSize, Cance /// /// A task that represents the asynchronous flush operation. /// - public override Task FlushAsync(CancellationToken cancellationToken = default) + public override Task FlushAsync(CancellationToken cancellationToken) { return BaseStream.FlushAsync(cancellationToken); } @@ -201,7 +201,7 @@ public override Task FlushAsync(CancellationToken cancellationToken = default) /// less than the requested number, or it can be 0 (zero) /// if the end of the stream has been reached. /// - public override async Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken = default) + public override async Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) { if (bufferLength == 0) { @@ -346,7 +346,7 @@ public byte ReadByteFromBuffer() /// A task that represents the asynchronous write operation. /// [DebuggerStepThrough] - public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken = default) + public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) { OnDataWrite(buffer, offset, count); @@ -558,7 +558,7 @@ public async ValueTask FillBufferAsync(CancellationToken cancellationToken /// Read a line from the byte stream /// /// - public Task ReadLineAsync(CancellationToken cancellationToken = default) + public ValueTask ReadLineAsync(CancellationToken cancellationToken = default) { return ReadLineInternalAsync(this, bufferPool, cancellationToken); } @@ -567,7 +567,7 @@ public async ValueTask FillBufferAsync(CancellationToken cancellationToken /// Read a line from the byte stream /// /// - internal static async Task ReadLineInternalAsync(ICustomStreamReader reader, IBufferPool bufferPool, CancellationToken cancellationToken = default) + internal static async ValueTask ReadLineInternalAsync(ICustomStreamReader reader, IBufferPool bufferPool, CancellationToken cancellationToken = default) { byte lastChar = default; diff --git a/src/Titanium.Web.Proxy/StreamExtended/Network/ICustomStreamReader.cs b/src/Titanium.Web.Proxy/StreamExtended/Network/ICustomStreamReader.cs index 69f6c6496..5dff9ff5e 100644 --- a/src/Titanium.Web.Proxy/StreamExtended/Network/ICustomStreamReader.cs +++ b/src/Titanium.Web.Proxy/StreamExtended/Network/ICustomStreamReader.cs @@ -74,6 +74,6 @@ Task ReadAsync(byte[] buffer, int offset, int bytesToRead, /// Read a line from the byte stream /// /// - Task ReadLineAsync(CancellationToken cancellationToken = default); + ValueTask ReadLineAsync(CancellationToken cancellationToken = default); } } diff --git a/src/Titanium.Web.Proxy/WebSocketHandler.cs b/src/Titanium.Web.Proxy/WebSocketHandler.cs index b2702899a..10ebe2a7b 100644 --- a/src/Titanium.Web.Proxy/WebSocketHandler.cs +++ b/src/Titanium.Web.Proxy/WebSocketHandler.cs @@ -31,11 +31,8 @@ private async Task handleWebSocketUpgrade(string requestHttpMethod, string reque string httpStatus; try { - httpStatus = await serverConnection.Stream.ReadLineAsync(cancellationToken); - if (httpStatus == null) - { - throw new ServerConnectionException("Server connection was closed."); - } + httpStatus = await serverConnection.Stream.ReadLineAsync(cancellationToken) + ?? throw new ServerConnectionException("Server connection was closed."); } catch (Exception e) when (!(e is ServerConnectionException)) { From a4c7727e824256e2957154529e15b385e0191633 Mon Sep 17 00:00:00 2001 From: Honfika Date: Fri, 18 Oct 2019 08:22:02 +0200 Subject: [PATCH 3/6] ValueTasks --- .../StreamExtended/Network/CopyStream.cs | 23 +++++++++- .../Network/CustomBufferedPeekStream.cs | 18 ++++++-- .../Network/CustomBufferedStream.cs | 43 ++++++++++++++++++- .../Network/ICustomStreamReader.cs | 15 +++++-- 4 files changed, 88 insertions(+), 11 deletions(-) diff --git a/src/Titanium.Web.Proxy/StreamExtended/Network/CopyStream.cs b/src/Titanium.Web.Proxy/StreamExtended/Network/CopyStream.cs index 3edaf1e36..e86eed49e 100644 --- a/src/Titanium.Web.Proxy/StreamExtended/Network/CopyStream.cs +++ b/src/Titanium.Web.Proxy/StreamExtended/Network/CopyStream.cs @@ -48,12 +48,12 @@ public byte PeekByteFromBuffer(int index) return reader.PeekByteFromBuffer(index); } - public Task PeekByteAsync(int index, CancellationToken cancellationToken = default) + public ValueTask PeekByteAsync(int index, CancellationToken cancellationToken = default) { return reader.PeekByteAsync(index, cancellationToken); } - public Task PeekBytesAsync(byte[] buffer, int offset, int index, int size, CancellationToken cancellationToken = default) + public ValueTask PeekBytesAsync(byte[] buffer, int offset, int index, int size, CancellationToken cancellationToken = default) { return reader.PeekBytesAsync(buffer, offset, index, size, cancellationToken); } @@ -124,6 +124,25 @@ public async Task ReadAsync(byte[] buffer, int offset, int count, Cancellat return result; } + public async ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken = default) + { + int result = await reader.ReadAsync(buffer, cancellationToken); + if (result > 0) + { + if (bufferLength + result > bufferPool.BufferSize) + { + await FlushAsync(cancellationToken); + } + + buffer.Span.Slice(0, result).CopyTo(new Span(this.buffer, bufferLength, result)); + bufferLength += result; + ReadBytes += result; + await FlushAsync(cancellationToken); + } + + return result; + } + public ValueTask ReadLineAsync(CancellationToken cancellationToken = default) { return CustomBufferedStream.ReadLineInternalAsync(this, bufferPool, cancellationToken); diff --git a/src/Titanium.Web.Proxy/StreamExtended/Network/CustomBufferedPeekStream.cs b/src/Titanium.Web.Proxy/StreamExtended/Network/CustomBufferedPeekStream.cs index fbdbd82a8..42acd087c 100644 --- a/src/Titanium.Web.Proxy/StreamExtended/Network/CustomBufferedPeekStream.cs +++ b/src/Titanium.Web.Proxy/StreamExtended/Network/CustomBufferedPeekStream.cs @@ -1,4 +1,5 @@ -using System.Threading; +using System; +using System.Threading; using System.Threading.Tasks; using Titanium.Web.Proxy.StreamExtended.BufferPool; @@ -93,7 +94,7 @@ byte ICustomStreamReader.PeekByteFromBuffer(int index) /// The count. /// The cancellation token. /// - Task ICustomStreamReader.PeekBytesAsync(byte[] buffer, int offset, int index, int count, CancellationToken cancellationToken) + ValueTask ICustomStreamReader.PeekBytesAsync(byte[] buffer, int offset, int index, int count, CancellationToken cancellationToken) { return baseStream.PeekBytesAsync(buffer, offset, index, count, cancellationToken); } @@ -104,7 +105,7 @@ Task ICustomStreamReader.PeekBytesAsync(byte[] buffer, int offset, int inde /// The index. /// The cancellation token. /// - Task ICustomStreamReader.PeekByteAsync(int index, CancellationToken cancellationToken) + ValueTask ICustomStreamReader.PeekByteAsync(int index, CancellationToken cancellationToken) { return baseStream.PeekByteAsync(index, cancellationToken); } @@ -136,6 +137,17 @@ Task ICustomStreamReader.ReadAsync(byte[] buffer, int offset, int count, Ca return baseStream.ReadAsync(buffer, offset, count, cancellationToken); } + /// + /// Reads the asynchronous. + /// + /// The buffer. + /// The cancellation token. + /// + public ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken = default) + { + return baseStream.ReadAsync(buffer, cancellationToken); + } + /// /// Read a line from the byte stream /// diff --git a/src/Titanium.Web.Proxy/StreamExtended/Network/CustomBufferedStream.cs b/src/Titanium.Web.Proxy/StreamExtended/Network/CustomBufferedStream.cs index 36d66b521..c7976d683 100644 --- a/src/Titanium.Web.Proxy/StreamExtended/Network/CustomBufferedStream.cs +++ b/src/Titanium.Web.Proxy/StreamExtended/Network/CustomBufferedStream.cs @@ -219,6 +219,45 @@ public override async Task ReadAsync(byte[] buffer, int offset, int count, return available; } + /// + /// Asynchronously reads a sequence of bytes from the current stream, + /// advances the position within the stream by the number of bytes read, + /// and monitors cancellation requests. + /// + /// The buffer to write the data into. + /// The token to monitor for cancellation requests. + /// The default value is . + /// + /// A task that represents the asynchronous read operation. + /// The value of the parameter contains the total + /// number of bytes read into the buffer. + /// The result value can be less than the number of bytes + /// requested if the number of bytes currently available is + /// less than the requested number, or it can be 0 (zero) + /// if the end of the stream has been reached. + /// +#if NETSTANDARD2_1 + public override async ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken = default) +#else + public async ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken = default) +#endif + { + if (bufferLength == 0) + { + await FillBufferAsync(cancellationToken); + } + + int available = Math.Min(bufferLength, buffer.Length); + if (available > 0) + { + new Span(streamBuffer, bufferPos, available).CopyTo(buffer.Span); + bufferPos += available; + bufferLength -= available; + } + + return available; + } + /// /// Reads a byte from the stream and advances the position within the stream by one byte, or returns -1 if at the end of the stream. /// @@ -247,7 +286,7 @@ public override int ReadByte() /// The index. /// The cancellation token. /// - public async Task PeekByteAsync(int index, CancellationToken cancellationToken = default) + public async ValueTask PeekByteAsync(int index, CancellationToken cancellationToken = default) { // When index is greater than the buffer size if (streamBuffer.Length <= index) @@ -277,7 +316,7 @@ public async Task PeekByteAsync(int index, CancellationToken cancellationTo /// The count. /// The cancellation token. /// - public async Task PeekBytesAsync(byte[] buffer, int offset, int index, int count, CancellationToken cancellationToken = default) + public async ValueTask PeekBytesAsync(byte[] buffer, int offset, int index, int count, CancellationToken cancellationToken = default) { // When index is greater than the buffer size if (streamBuffer.Length <= index + count) diff --git a/src/Titanium.Web.Proxy/StreamExtended/Network/ICustomStreamReader.cs b/src/Titanium.Web.Proxy/StreamExtended/Network/ICustomStreamReader.cs index 5dff9ff5e..d448adcf6 100644 --- a/src/Titanium.Web.Proxy/StreamExtended/Network/ICustomStreamReader.cs +++ b/src/Titanium.Web.Proxy/StreamExtended/Network/ICustomStreamReader.cs @@ -33,7 +33,7 @@ public interface ICustomStreamReader /// The index. /// The cancellation token. /// - Task PeekByteAsync(int index, CancellationToken cancellationToken = default); + ValueTask PeekByteAsync(int index, CancellationToken cancellationToken = default); /// /// Peeks bytes asynchronous. @@ -44,7 +44,7 @@ public interface ICustomStreamReader /// The count. /// The cancellation token. /// - Task PeekBytesAsync(byte[] buffer, int offset, int index, int count, CancellationToken cancellationToken = default); + ValueTask PeekBytesAsync(byte[] buffer, int offset, int index, int count, CancellationToken cancellationToken = default); byte ReadByteFromBuffer(); @@ -67,8 +67,15 @@ public interface ICustomStreamReader /// /// /// The number of bytes read - Task ReadAsync(byte[] buffer, int offset, int bytesToRead, - CancellationToken cancellationToken = default); + Task ReadAsync(byte[] buffer, int offset, int bytesToRead, CancellationToken cancellationToken = default); + + /// + /// Read the specified number (or less) of raw bytes from the base stream to the given buffer to the specified offset + /// + /// + /// + /// The number of bytes read + ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken = default); /// /// Read a line from the byte stream From fc2bbb8034b95abd4442a543ce655764e06c3d4e Mon Sep 17 00:00:00 2001 From: Honfika Date: Fri, 18 Oct 2019 08:22:50 +0200 Subject: [PATCH 4/6] remove empty line --- .../Titanium.Web.Proxy.Examples.Basic/ProxyTestController.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/Titanium.Web.Proxy.Examples.Basic/ProxyTestController.cs b/examples/Titanium.Web.Proxy.Examples.Basic/ProxyTestController.cs index dbd2dbd2d..4eec71133 100644 --- a/examples/Titanium.Web.Proxy.Examples.Basic/ProxyTestController.cs +++ b/examples/Titanium.Web.Proxy.Examples.Basic/ProxyTestController.cs @@ -278,7 +278,6 @@ private async Task writeToConsole(string message, bool useRedColor = false) } @lock.Release(); - } ///// From 1e87fcc5e438aa27379a048ba0969a1ba364762e Mon Sep 17 00:00:00 2001 From: Den Pakizh Date: Thu, 24 Oct 2019 11:26:38 +0300 Subject: [PATCH 5/6] fixed "Windows Authentication is not supported" exceptions on non-Windows systems --- src/Titanium.Web.Proxy/EventArguments/SessionEventArgsBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Titanium.Web.Proxy/EventArguments/SessionEventArgsBase.cs b/src/Titanium.Web.Proxy/EventArguments/SessionEventArgsBase.cs index 07fc38536..f9e3ecc2b 100644 --- a/src/Titanium.Web.Proxy/EventArguments/SessionEventArgsBase.cs +++ b/src/Titanium.Web.Proxy/EventArguments/SessionEventArgsBase.cs @@ -84,7 +84,7 @@ public bool EnableWinAuth get => enableWinAuth; set { - if (!isWindowsAuthenticationSupported) + if (value && !isWindowsAuthenticationSupported) throw new Exception("Windows Authentication is not supported"); enableWinAuth = value; From 66671d106cee5c7d394effd870ac829f7b1a811c Mon Sep 17 00:00:00 2001 From: Honfika Date: Sat, 16 Nov 2019 09:42:51 +0100 Subject: [PATCH 6/6] Store the headers as byte arrays --- src/Titanium.Web.Proxy/CertificateHandler.cs | 11 +- .../CertificateValidationEventArgs.cs | 13 +- .../EventArguments/LimitedStream.cs | 4 +- .../EventArguments/SessionEventArgs.cs | 16 +- .../EventArguments/SessionEventArgsBase.cs | 16 +- .../EventArguments/TunnelConnectEventArgs.cs | 6 +- .../ExplicitClientHandler.cs | 11 +- .../Extensions/HttpHeaderExtensions.cs | 27 ++ .../Extensions/SslExtensions.cs | 10 +- .../Extensions/StringExtensions.cs | 1 + .../Extensions/UriExtensions.cs | 3 +- src/Titanium.Web.Proxy/Helpers/HttpHelper.cs | 15 +- .../Helpers/HttpRequestWriter.cs | 2 +- src/Titanium.Web.Proxy/Helpers/HttpWriter.cs | 9 +- .../Helpers/WinHttp/WinHttpWebProxyFinder.cs | 6 +- src/Titanium.Web.Proxy/Http/HeaderBuilder.cs | 2 +- .../Http/HeaderCollection.cs | 3 +- src/Titanium.Web.Proxy/Http/HeaderParser.cs | 4 +- src/Titanium.Web.Proxy/Http/HttpWebClient.cs | 8 +- src/Titanium.Web.Proxy/Http/Request.cs | 57 ++-- .../Http/RequestResponseBase.cs | 2 +- src/Titanium.Web.Proxy/Http/Response.cs | 2 +- src/Titanium.Web.Proxy/Http2/Hpack/Decoder.cs | 26 +- src/Titanium.Web.Proxy/Http2/Hpack/Encoder.cs | 47 ++-- .../Http2/Hpack/HuffmanDecoder.cs | 4 +- .../Http2/Hpack/HuffmanEncoder.cs | 44 +-- .../Http2/Hpack/IHeaderListener.cs | 8 +- .../Http2/Hpack/StaticTable.cs | 251 +++++++----------- src/Titanium.Web.Proxy/Http2/Http2Helper.cs | 71 +++-- src/Titanium.Web.Proxy/Models/ByteString.cs | 42 +++ .../Models/ExternalProxy.cs | 19 +- src/Titanium.Web.Proxy/Models/HttpHeader.cs | 39 ++- .../Models/ProxyEndPoint.cs | 4 +- .../Network/CachedCertificate.cs | 7 +- .../Certificate/WinCertificateMaker.cs | 2 +- .../Network/CertificateManager.cs | 35 +-- src/Titanium.Web.Proxy/Network/ProxyClient.cs | 13 +- .../Network/Tcp/TcpConnectionFactory.cs | 71 +++-- .../Network/Tcp/TcpServerConnection.cs | 14 +- .../Network/WinAuth/Security/Message.cs | 29 +- src/Titanium.Web.Proxy/RequestHandler.cs | 8 +- src/Titanium.Web.Proxy/ResponseHandler.cs | 2 +- .../StreamExtended/ClientHelloInfo.cs | 26 +- .../StreamExtended/Network/CopyStream.cs | 90 +------ .../Network/CustomBufferedPeekStream.cs | 162 ----------- .../Network/CustomBufferedStream.cs | 8 +- .../Network/ICustomStreamReader.cs | 86 ------ .../StreamExtended/Network/ILineStream.cs | 24 ++ .../StreamExtended/Network/IPeekStream.cs | 36 +++ .../Network/PeekStreamReader.cs | 57 ++++ .../StreamExtended/ServerHelloInfo.cs | 26 +- .../StreamExtended/SslTools.cs | 81 ++---- .../TransparentClientHandler.cs | 8 +- src/Titanium.Web.Proxy/WinAuthHandler.cs | 2 +- .../Helpers/HttpContinueClient.cs | 2 +- .../Helpers/HttpMessageParsing.cs | 20 +- ...Titanium.Web.Proxy.IntegrationTests.csproj | 2 +- 57 files changed, 705 insertions(+), 889 deletions(-) create mode 100644 src/Titanium.Web.Proxy/Extensions/HttpHeaderExtensions.cs create mode 100644 src/Titanium.Web.Proxy/Models/ByteString.cs delete mode 100644 src/Titanium.Web.Proxy/StreamExtended/Network/CustomBufferedPeekStream.cs delete mode 100644 src/Titanium.Web.Proxy/StreamExtended/Network/ICustomStreamReader.cs create mode 100644 src/Titanium.Web.Proxy/StreamExtended/Network/ILineStream.cs create mode 100644 src/Titanium.Web.Proxy/StreamExtended/Network/IPeekStream.cs create mode 100644 src/Titanium.Web.Proxy/StreamExtended/Network/PeekStreamReader.cs diff --git a/src/Titanium.Web.Proxy/CertificateHandler.cs b/src/Titanium.Web.Proxy/CertificateHandler.cs index e0f713d93..b7efcfbe5 100644 --- a/src/Titanium.Web.Proxy/CertificateHandler.cs +++ b/src/Titanium.Web.Proxy/CertificateHandler.cs @@ -22,15 +22,10 @@ internal bool ValidateServerCertificate(object sender, X509Certificate certifica // if user callback is registered then do it if (ServerCertificateValidationCallback != null) { - var args = new CertificateValidationEventArgs - { - Certificate = certificate, - Chain = chain, - SslPolicyErrors = sslPolicyErrors - }; + var args = new CertificateValidationEventArgs(certificate, chain, sslPolicyErrors); // why is the sender null? - ServerCertificateValidationCallback.InvokeAsync(this, args, exceptionFunc).Wait(); + ServerCertificateValidationCallback.InvokeAsync(this, args, ExceptionFunc).Wait(); return args.IsValid; } @@ -90,7 +85,7 @@ internal bool ValidateServerCertificate(object sender, X509Certificate certifica }; // why is the sender null? - ClientCertificateSelectionCallback.InvokeAsync(this, args, exceptionFunc).Wait(); + ClientCertificateSelectionCallback.InvokeAsync(this, args, ExceptionFunc).Wait(); return args.ClientCertificate; } diff --git a/src/Titanium.Web.Proxy/EventArguments/CertificateValidationEventArgs.cs b/src/Titanium.Web.Proxy/EventArguments/CertificateValidationEventArgs.cs index 5f3411f06..22e9bf381 100644 --- a/src/Titanium.Web.Proxy/EventArguments/CertificateValidationEventArgs.cs +++ b/src/Titanium.Web.Proxy/EventArguments/CertificateValidationEventArgs.cs @@ -10,20 +10,27 @@ namespace Titanium.Web.Proxy.EventArguments /// public class CertificateValidationEventArgs : EventArgs { + public CertificateValidationEventArgs(X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) + { + Certificate = certificate; + Chain = chain; + SslPolicyErrors = sslPolicyErrors; + } + /// /// Server certificate. /// - public X509Certificate Certificate { get; internal set; } + public X509Certificate Certificate { get; } /// /// Certificate chain. /// - public X509Chain Chain { get; internal set; } + public X509Chain Chain { get; } /// /// SSL policy errors. /// - public SslPolicyErrors SslPolicyErrors { get; internal set; } + public SslPolicyErrors SslPolicyErrors { get; } /// /// Is the given server certificate valid? diff --git a/src/Titanium.Web.Proxy/EventArguments/LimitedStream.cs b/src/Titanium.Web.Proxy/EventArguments/LimitedStream.cs index f3b9f15fb..ac8763e60 100644 --- a/src/Titanium.Web.Proxy/EventArguments/LimitedStream.cs +++ b/src/Titanium.Web.Proxy/EventArguments/LimitedStream.cs @@ -11,13 +11,13 @@ namespace Titanium.Web.Proxy.EventArguments internal class LimitedStream : Stream { private readonly IBufferPool bufferPool; - private readonly ICustomStreamReader baseStream; + private readonly CustomBufferedStream baseStream; private readonly bool isChunked; private long bytesRemaining; private bool readChunkTrail; - internal LimitedStream(ICustomStreamReader baseStream, IBufferPool bufferPool, bool isChunked, + internal LimitedStream(CustomBufferedStream baseStream, IBufferPool bufferPool, bool isChunked, long contentLength) { this.baseStream = baseStream; diff --git a/src/Titanium.Web.Proxy/EventArguments/SessionEventArgs.cs b/src/Titanium.Web.Proxy/EventArguments/SessionEventArgs.cs index 6008cc701..13e2ff5be 100644 --- a/src/Titanium.Web.Proxy/EventArguments/SessionEventArgs.cs +++ b/src/Titanium.Web.Proxy/EventArguments/SessionEventArgs.cs @@ -9,6 +9,7 @@ using Titanium.Web.Proxy.Http; using Titanium.Web.Proxy.Http.Responses; using Titanium.Web.Proxy.Models; +using Titanium.Web.Proxy.Network; using Titanium.Web.Proxy.StreamExtended.Network; namespace Titanium.Web.Proxy.EventArguments @@ -36,15 +37,8 @@ public class SessionEventArgs : SessionEventArgsBase /// /// Constructor to initialize the proxy /// - internal SessionEventArgs(ProxyServer server, ProxyEndPoint endPoint, - CancellationTokenSource cancellationTokenSource) - : this(server, endPoint, null, cancellationTokenSource) - { - } - - protected SessionEventArgs(ProxyServer server, ProxyEndPoint endPoint, - Request? request, CancellationTokenSource cancellationTokenSource) - : base(server, endPoint, cancellationTokenSource, request) + internal SessionEventArgs(ProxyServer server, ProxyEndPoint endPoint, ProxyClient proxyClient, ConnectRequest? connectRequest, CancellationTokenSource cancellationTokenSource) + : base(server, endPoint, proxyClient, connectRequest, null, cancellationTokenSource) { } @@ -72,7 +66,7 @@ public bool ReRequest /// public event EventHandler? MultipartRequestPartSent; - private ICustomStreamReader getStreamReader(bool isRequest) + private CustomBufferedStream getStreamReader(bool isRequest) { return isRequest ? ProxyClient.ClientStream : HttpClient.Connection.Stream; } @@ -333,7 +327,7 @@ private async Task copyBodyAsync(bool isRequest, bool useOriginalHeaderValues, H /// Read a line from the byte stream /// /// - private async Task readUntilBoundaryAsync(ICustomStreamReader reader, long totalBytesToRead, ReadOnlyMemory boundary, CancellationToken cancellationToken) + private async Task readUntilBoundaryAsync(ILineStream reader, long totalBytesToRead, ReadOnlyMemory boundary, CancellationToken cancellationToken) { int bufferDataLength = 0; diff --git a/src/Titanium.Web.Proxy/EventArguments/SessionEventArgsBase.cs b/src/Titanium.Web.Proxy/EventArguments/SessionEventArgsBase.cs index 07fc38536..bc27a7fc6 100644 --- a/src/Titanium.Web.Proxy/EventArguments/SessionEventArgsBase.cs +++ b/src/Titanium.Web.Proxy/EventArguments/SessionEventArgsBase.cs @@ -22,7 +22,7 @@ public abstract class SessionEventArgsBase : EventArgs, IDisposable { private static bool isWindowsAuthenticationSupported => RunTime.IsWindows; - internal readonly CancellationTokenSource? CancellationTokenSource; + internal readonly CancellationTokenSource CancellationTokenSource; internal TcpServerConnection ServerConnection => HttpClient.Connection; @@ -40,25 +40,19 @@ public abstract class SessionEventArgsBase : EventArgs, IDisposable /// /// Initializes a new instance of the class. /// - private SessionEventArgsBase(ProxyServer server) + private protected SessionEventArgsBase(ProxyServer server, ProxyEndPoint endPoint, + ProxyClient proxyClient, ConnectRequest? connectRequest, Request? request, CancellationTokenSource cancellationTokenSource) { BufferPool = server.BufferPool; ExceptionFunc = server.ExceptionFunc; TimeLine["Session Created"] = DateTime.Now; - } - protected SessionEventArgsBase(ProxyServer server, ProxyEndPoint endPoint, - CancellationTokenSource cancellationTokenSource, - Request? request) : this(server) - { CancellationTokenSource = cancellationTokenSource; - ProxyClient = new ProxyClient(); - HttpClient = new HttpWebClient(request); + ProxyClient = proxyClient; + HttpClient = new HttpWebClient(connectRequest, request, new Lazy(() => ProxyClient.Connection.GetProcessId(endPoint))); LocalEndPoint = endPoint; EnableWinAuth = server.EnableWinAuth && isWindowsAuthenticationSupported; - - HttpClient.ProcessId = new Lazy(() => ProxyClient.Connection.GetProcessId(endPoint)); } /// diff --git a/src/Titanium.Web.Proxy/EventArguments/TunnelConnectEventArgs.cs b/src/Titanium.Web.Proxy/EventArguments/TunnelConnectEventArgs.cs index 215def025..b02ab94d8 100644 --- a/src/Titanium.Web.Proxy/EventArguments/TunnelConnectEventArgs.cs +++ b/src/Titanium.Web.Proxy/EventArguments/TunnelConnectEventArgs.cs @@ -2,6 +2,7 @@ using System.Threading; using Titanium.Web.Proxy.Http; using Titanium.Web.Proxy.Models; +using Titanium.Web.Proxy.Network; using Titanium.Web.Proxy.StreamExtended.Network; namespace Titanium.Web.Proxy.EventArguments @@ -14,10 +15,9 @@ public class TunnelConnectSessionEventArgs : SessionEventArgsBase private bool? isHttpsConnect; internal TunnelConnectSessionEventArgs(ProxyServer server, ProxyEndPoint endPoint, ConnectRequest connectRequest, - CancellationTokenSource cancellationTokenSource) - : base(server, endPoint, cancellationTokenSource, connectRequest) + ProxyClient proxyClient, CancellationTokenSource cancellationTokenSource) + : base(server, endPoint, proxyClient, connectRequest, connectRequest, cancellationTokenSource) { - HttpClient.ConnectRequest = connectRequest; } /// diff --git a/src/Titanium.Web.Proxy/ExplicitClientHandler.cs b/src/Titanium.Web.Proxy/ExplicitClientHandler.cs index 5ed1e180b..e54c85e99 100644 --- a/src/Titanium.Web.Proxy/ExplicitClientHandler.cs +++ b/src/Titanium.Web.Proxy/ExplicitClientHandler.cs @@ -14,6 +14,7 @@ using Titanium.Web.Proxy.Http; using Titanium.Web.Proxy.Http2; using Titanium.Web.Proxy.Models; +using Titanium.Web.Proxy.Network; using Titanium.Web.Proxy.Network.Tcp; using Titanium.Web.Proxy.StreamExtended; using Titanium.Web.Proxy.StreamExtended.Network; @@ -67,18 +68,16 @@ private async Task handleClient(ExplicitProxyEndPoint endPoint, TcpClientConnect var connectRequest = new ConnectRequest { RequestUri = httpRemoteUri, - OriginalUrl = httpUrl, + OriginalUrlData = HttpHeader.Encoding.GetBytes(httpUrl), HttpVersion = version }; await HeaderParser.ReadHeaders(clientStream, connectRequest.Headers, cancellationToken); connectArgs = new TunnelConnectSessionEventArgs(this, endPoint, connectRequest, - cancellationTokenSource); + new ProxyClient(clientConnection, clientStream, clientStreamWriter), cancellationTokenSource); clientStream.DataRead += (o, args) => connectArgs.OnDataSent(args.Buffer, args.Offset, args.Count); clientStream.DataWrite += (o, args) => connectArgs.OnDataReceived(args.Buffer, args.Offset, args.Count); - connectArgs.ProxyClient.Connection = clientConnection; - connectArgs.ProxyClient.ClientStream = clientStream; await endPoint.InvokeBeforeTunnelConnectRequest(this, connectArgs, ExceptionFunc); @@ -336,10 +335,8 @@ await TcpHelper.SendRaw(clientStream, connection.Stream, BufferPool, var connectionPreface = new ReadOnlyMemory(Http2Helper.ConnectionPreface); await connection.StreamWriter.WriteAsync(connectionPreface, cancellationToken); await Http2Helper.SendHttp2(clientStream, connection.Stream, - () => new SessionEventArgs(this, endPoint, cancellationTokenSource) + () => new SessionEventArgs(this, endPoint, new ProxyClient(clientConnection, clientStream, clientStreamWriter), connectArgs?.HttpClient.ConnectRequest, cancellationTokenSource) { - ProxyClient = { Connection = clientConnection }, - HttpClient = { ConnectRequest = connectArgs?.HttpClient.ConnectRequest }, UserData = connectArgs?.UserData }, async args => { await onBeforeRequest(args); }, diff --git a/src/Titanium.Web.Proxy/Extensions/HttpHeaderExtensions.cs b/src/Titanium.Web.Proxy/Extensions/HttpHeaderExtensions.cs new file mode 100644 index 000000000..f2f56dc51 --- /dev/null +++ b/src/Titanium.Web.Proxy/Extensions/HttpHeaderExtensions.cs @@ -0,0 +1,27 @@ +using System; +using Titanium.Web.Proxy.Models; + +namespace Titanium.Web.Proxy.Extensions +{ + internal static class HttpHeaderExtensions + { + internal static string GetString(this ByteString str) + { + return GetString(str.Span); + } + + internal static string GetString(this ReadOnlySpan bytes) + { +#if NETSTANDARD2_1 + return HttpHeader.Encoding.GetString(bytes); +#else + return HttpHeader.Encoding.GetString(bytes.ToArray()); +#endif + } + + internal static ByteString GetByteString(this string str) + { + return HttpHeader.Encoding.GetBytes(str); + } + } +} diff --git a/src/Titanium.Web.Proxy/Extensions/SslExtensions.cs b/src/Titanium.Web.Proxy/Extensions/SslExtensions.cs index b023286f9..a325ee7a2 100644 --- a/src/Titanium.Web.Proxy/Extensions/SslExtensions.cs +++ b/src/Titanium.Web.Proxy/Extensions/SslExtensions.cs @@ -94,19 +94,19 @@ internal class SslClientAuthenticationOptions { internal bool AllowRenegotiation { get; set; } - internal string TargetHost { get; set; } + internal string? TargetHost { get; set; } - internal X509CertificateCollection ClientCertificates { get; set; } + internal X509CertificateCollection? ClientCertificates { get; set; } - internal LocalCertificateSelectionCallback LocalCertificateSelectionCallback { get; set; } + internal LocalCertificateSelectionCallback? LocalCertificateSelectionCallback { get; set; } internal SslProtocols EnabledSslProtocols { get; set; } internal X509RevocationMode CertificateRevocationCheckMode { get; set; } - internal List ApplicationProtocols { get; set; } + internal List? ApplicationProtocols { get; set; } - internal RemoteCertificateValidationCallback RemoteCertificateValidationCallback { get; set; } + internal RemoteCertificateValidationCallback? RemoteCertificateValidationCallback { get; set; } internal EncryptionPolicy EncryptionPolicy { get; set; } } diff --git a/src/Titanium.Web.Proxy/Extensions/StringExtensions.cs b/src/Titanium.Web.Proxy/Extensions/StringExtensions.cs index f419fd7f6..acfd0d34d 100644 --- a/src/Titanium.Web.Proxy/Extensions/StringExtensions.cs +++ b/src/Titanium.Web.Proxy/Extensions/StringExtensions.cs @@ -1,5 +1,6 @@ using System; using System.Globalization; +using Titanium.Web.Proxy.Models; namespace Titanium.Web.Proxy.Extensions { diff --git a/src/Titanium.Web.Proxy/Extensions/UriExtensions.cs b/src/Titanium.Web.Proxy/Extensions/UriExtensions.cs index d0de6fd1c..c95ff600d 100644 --- a/src/Titanium.Web.Proxy/Extensions/UriExtensions.cs +++ b/src/Titanium.Web.Proxy/Extensions/UriExtensions.cs @@ -1,4 +1,5 @@ using System; +using System.Text; namespace Titanium.Web.Proxy.Extensions { @@ -12,5 +13,5 @@ internal static string GetOriginalPathAndQuery(this Uri uri) return uri.IsWellFormedOriginalString() ? uri.PathAndQuery : uri.GetComponents(UriComponents.PathAndQuery, UriFormat.Unescaped); } - } + } } diff --git a/src/Titanium.Web.Proxy/Helpers/HttpHelper.cs b/src/Titanium.Web.Proxy/Helpers/HttpHelper.cs index 96e39fe9a..e28387089 100644 --- a/src/Titanium.Web.Proxy/Helpers/HttpHelper.cs +++ b/src/Titanium.Web.Proxy/Helpers/HttpHelper.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using Titanium.Web.Proxy.Extensions; using Titanium.Web.Proxy.Http; +using Titanium.Web.Proxy.Models; using Titanium.Web.Proxy.Shared; using Titanium.Web.Proxy.StreamExtended.BufferPool; using Titanium.Web.Proxy.StreamExtended.Network; @@ -13,10 +14,6 @@ namespace Titanium.Web.Proxy.Helpers { internal static class HttpHelper { - private static readonly Encoding defaultEncoding = Encoding.GetEncoding("ISO-8859-1"); - - public static Encoding HeaderEncoding => defaultEncoding; - struct SemicolonSplitEnumerator { private readonly ReadOnlyMemory data; @@ -73,7 +70,7 @@ internal static Encoding GetEncodingFromContentType(string? contentType) // return default if not specified if (contentType == null) { - return defaultEncoding; + return HttpHeader.DefaultEncoding; } // extract the encoding by finding the charset @@ -105,7 +102,7 @@ internal static Encoding GetEncodingFromContentType(string? contentType) } // return default if not specified - return defaultEncoding; + return HttpHeader.DefaultEncoding; } internal static ReadOnlyMemory GetBoundaryFromContentType(string? contentType) @@ -173,7 +170,7 @@ internal static string GetWildCardDomainName(string hostname) /// Determines whether is connect method. /// /// 1: when CONNECT, 0: when valid HTTP method, -1: otherwise - internal static Task IsConnectMethod(ICustomStreamReader clientStreamReader, IBufferPool bufferPool, CancellationToken cancellationToken = default) + internal static Task IsConnectMethod(CustomBufferedStream clientStreamReader, IBufferPool bufferPool, CancellationToken cancellationToken = default) { return startsWith(clientStreamReader, bufferPool, "CONNECT", cancellationToken); } @@ -182,7 +179,7 @@ internal static Task IsConnectMethod(ICustomStreamReader clientStreamReader /// Determines whether is pri method (HTTP/2). /// /// 1: when PRI, 0: when valid HTTP method, -1: otherwise - internal static Task IsPriMethod(ICustomStreamReader clientStreamReader, IBufferPool bufferPool, CancellationToken cancellationToken = default) + internal static Task IsPriMethod(CustomBufferedStream clientStreamReader, IBufferPool bufferPool, CancellationToken cancellationToken = default) { return startsWith(clientStreamReader, bufferPool, "PRI", cancellationToken); } @@ -193,7 +190,7 @@ internal static Task IsPriMethod(ICustomStreamReader clientStreamReader, IB /// /// 1: when starts with the given string, 0: when valid HTTP method, -1: otherwise /// - private static async Task startsWith(ICustomStreamReader clientStreamReader, IBufferPool bufferPool, string expectedStart, CancellationToken cancellationToken = default) + private static async Task startsWith(CustomBufferedStream clientStreamReader, IBufferPool bufferPool, string expectedStart, CancellationToken cancellationToken = default) { const int lengthToCheck = 10; if (bufferPool.BufferSize < lengthToCheck) diff --git a/src/Titanium.Web.Proxy/Helpers/HttpRequestWriter.cs b/src/Titanium.Web.Proxy/Helpers/HttpRequestWriter.cs index 770728331..2800eb5ed 100644 --- a/src/Titanium.Web.Proxy/Helpers/HttpRequestWriter.cs +++ b/src/Titanium.Web.Proxy/Helpers/HttpRequestWriter.cs @@ -22,7 +22,7 @@ internal HttpRequestWriter(Stream stream, IBufferPool bufferPool) internal async Task WriteRequestAsync(Request request, CancellationToken cancellationToken = default) { var headerBuilder = new HeaderBuilder(); - headerBuilder.WriteRequestLine(request.Method, request.RequestUriString, request.HttpVersion); + headerBuilder.WriteRequestLine(request.Method, request.Url, request.HttpVersion); await WriteAsync(request, headerBuilder, cancellationToken); } } diff --git a/src/Titanium.Web.Proxy/Helpers/HttpWriter.cs b/src/Titanium.Web.Proxy/Helpers/HttpWriter.cs index a857ba44d..408dc402a 100644 --- a/src/Titanium.Web.Proxy/Helpers/HttpWriter.cs +++ b/src/Titanium.Web.Proxy/Helpers/HttpWriter.cs @@ -6,6 +6,7 @@ using System.Threading; using System.Threading.Tasks; using Titanium.Web.Proxy.Http; +using Titanium.Web.Proxy.Models; using Titanium.Web.Proxy.Shared; using Titanium.Web.Proxy.StreamExtended.BufferPool; using Titanium.Web.Proxy.StreamExtended.Network; @@ -19,7 +20,7 @@ internal class HttpWriter : ICustomStreamWriter private static readonly byte[] newLine = ProxyConstants.NewLineBytes; - private static Encoding encoding => HttpHelper.HeaderEncoding; + private static Encoding encoding => HttpHeader.Encoding; internal HttpWriter(Stream stream, IBufferPool bufferPool) { @@ -148,7 +149,7 @@ internal Task WriteBodyAsync(byte[] data, bool isChunked, CancellationToken canc /// /// /// - internal Task CopyBodyAsync(ICustomStreamReader streamReader, bool isChunked, long contentLength, + internal Task CopyBodyAsync(CustomBufferedStream streamReader, bool isChunked, long contentLength, Action? onCopy, CancellationToken cancellationToken) { // For chunked request we need to read data as they arrive, until we reach a chunk end symbol @@ -193,7 +194,7 @@ private async Task writeBodyChunkedAsync(byte[] data, CancellationToken cancella /// /// /// - private async Task copyBodyChunkedAsync(ICustomStreamReader reader, Action? onCopy, CancellationToken cancellationToken) + private async Task copyBodyChunkedAsync(CustomBufferedStream reader, Action? onCopy, CancellationToken cancellationToken) { while (true) { @@ -233,7 +234,7 @@ private async Task copyBodyChunkedAsync(ICustomStreamReader reader, Action /// /// - private async Task copyBytesFromStream(ICustomStreamReader reader, long count, Action? onCopy, + private async Task copyBytesFromStream(CustomBufferedStream reader, long count, Action? onCopy, CancellationToken cancellationToken) { var buffer = bufferPool.GetBuffer(); diff --git a/src/Titanium.Web.Proxy/Helpers/WinHttp/WinHttpWebProxyFinder.cs b/src/Titanium.Web.Proxy/Helpers/WinHttp/WinHttpWebProxyFinder.cs index e54543aa0..9ccbf31d9 100644 --- a/src/Titanium.Web.Proxy/Helpers/WinHttp/WinHttpWebProxyFinder.cs +++ b/src/Titanium.Web.Proxy/Helpers/WinHttp/WinHttpWebProxyFinder.cs @@ -34,9 +34,9 @@ public WinHttpWebProxyFinder() } } - public ICredentials Credentials { get; set; } + public ICredentials? Credentials { get; set; } - public ProxyInfo ProxyInfo { get; internal set; } + public ProxyInfo? ProxyInfo { get; internal set; } public bool BypassLoopback { get; internal set; } @@ -46,7 +46,7 @@ public WinHttpWebProxyFinder() public bool AutomaticallyDetectSettings { get; internal set; } - private WebProxy proxy { get; set; } + private WebProxy? proxy { get; set; } public void Dispose() { diff --git a/src/Titanium.Web.Proxy/Http/HeaderBuilder.cs b/src/Titanium.Web.Proxy/Http/HeaderBuilder.cs index dfd6606be..371d72af8 100644 --- a/src/Titanium.Web.Proxy/Http/HeaderBuilder.cs +++ b/src/Titanium.Web.Proxy/Http/HeaderBuilder.cs @@ -67,7 +67,7 @@ public void WriteLine() public void Write(string str) { - var encoding = HttpHelper.HeaderEncoding; + var encoding = HttpHeader.Encoding; #if NETSTANDARD2_1 var buf = ArrayPool.Shared.Rent(str.Length * 4); diff --git a/src/Titanium.Web.Proxy/Http/HeaderCollection.cs b/src/Titanium.Web.Proxy/Http/HeaderCollection.cs index f11cf2abd..8b5225277 100644 --- a/src/Titanium.Web.Proxy/Http/HeaderCollection.cs +++ b/src/Titanium.Web.Proxy/Http/HeaderCollection.cs @@ -4,6 +4,7 @@ using System.Collections.ObjectModel; using System.ComponentModel; using System.Linq; +using Titanium.Web.Proxy.Extensions; using Titanium.Web.Proxy.Models; namespace Titanium.Web.Proxy.Http @@ -292,7 +293,7 @@ internal void SetOrAddHeaderValue(string headerName, string? value) if (headers.TryGetValue(headerName, out var header)) { - header.Value = value; + header.ValueData = value.GetByteString(); } else { diff --git a/src/Titanium.Web.Proxy/Http/HeaderParser.cs b/src/Titanium.Web.Proxy/Http/HeaderParser.cs index 6e19ca781..847b4679e 100644 --- a/src/Titanium.Web.Proxy/Http/HeaderParser.cs +++ b/src/Titanium.Web.Proxy/Http/HeaderParser.cs @@ -7,7 +7,7 @@ namespace Titanium.Web.Proxy.Http { internal static class HeaderParser { - internal static async Task ReadHeaders(ICustomStreamReader reader, HeaderCollection headerCollection, + internal static async Task ReadHeaders(ILineStream reader, HeaderCollection headerCollection, CancellationToken cancellationToken) { string? tmpLine; @@ -20,7 +20,7 @@ internal static async Task ReadHeaders(ICustomStreamReader reader, HeaderCollect } string headerName = tmpLine.AsSpan(0, colonIndex).ToString(); - string headerValue = tmpLine.AsSpan(colonIndex + 1).ToString(); + string headerValue = tmpLine.AsSpan(colonIndex + 1).TrimStart().ToString(); headerCollection.AddHeader(headerName, headerValue); } } diff --git a/src/Titanium.Web.Proxy/Http/HttpWebClient.cs b/src/Titanium.Web.Proxy/Http/HttpWebClient.cs index 7086ddbc8..0794d72af 100644 --- a/src/Titanium.Web.Proxy/Http/HttpWebClient.cs +++ b/src/Titanium.Web.Proxy/Http/HttpWebClient.cs @@ -18,10 +18,12 @@ public class HttpWebClient { private TcpServerConnection? connection; - internal HttpWebClient(Request? request) + internal HttpWebClient(ConnectRequest? connectRequest, Request? request, Lazy processIdFunc) { + ConnectRequest = connectRequest; Request = request ?? new Request(); Response = new Response(); + ProcessId = processIdFunc; } /// @@ -65,7 +67,7 @@ internal TcpServerConnection Connection /// /// Headers passed with Connect. /// - public ConnectRequest? ConnectRequest { get; internal set; } + public ConnectRequest? ConnectRequest { get; } /// /// Web Request. @@ -114,7 +116,7 @@ internal async Task SendRequest(bool enable100ContinueBehaviour, bool isTranspar string url; if (useUpstreamProxy || isTransparent) { - url = Request.RequestUriString; + url = Request.Url; } else { diff --git a/src/Titanium.Web.Proxy/Http/Request.cs b/src/Titanium.Web.Proxy/Http/Request.cs index 5338212eb..519ba4640 100644 --- a/src/Titanium.Web.Proxy/Http/Request.cs +++ b/src/Titanium.Web.Proxy/Http/Request.cs @@ -14,40 +14,54 @@ namespace Titanium.Web.Proxy.Http [TypeConverter(typeof(ExpandableObjectConverter))] public class Request : RequestResponseBase { - private string originalUrl; - /// /// Request Method. /// public string Method { get; set; } - /// - /// Request HTTP Uri. - /// - public Uri RequestUri { get; set; } - /// /// Is Https? /// public bool IsHttps => RequestUri.Scheme == ProxyServer.UriSchemeHttps; - /// - /// The original request Url. - /// - public string OriginalUrl + private ByteString originalUrlData; + private ByteString urlData; + + internal ByteString OriginalUrlData { - get => originalUrl; - internal set + get => originalUrlData; + set { - originalUrl = value; - RequestUriString = value; + originalUrlData = value; + urlData = value; } } /// - /// The request uri as it is in the HTTP header + /// The original request Url. /// - public string RequestUriString { get; set; } + public string OriginalUrl => originalUrlData.GetString(); + + /// + /// Request HTTP Uri. + /// + public Uri RequestUri { get; set; } + + /// + /// The request url as it is in the HTTP header + /// + public string Url + { + get => urlData.GetString(); + set => urlData = value.GetByteString(); + } + + [Obsolete("This property is obsolete. Use Url property instead")] + public string RequestUriString + { + get => Url; + set => Url = value; + } /// /// Has request body? @@ -108,11 +122,6 @@ public bool ExpectContinue /// public bool IsMultipartFormData => ContentType?.StartsWith("multipart/form-data") == true; - /// - /// Request Url. - /// - public string Url => RequestUri.OriginalString; - /// /// Cancels the client HTTP request without sending to server. /// This should be set when API user responds with custom response. @@ -155,9 +164,9 @@ public override string HeaderText get { var headerBuilder = new HeaderBuilder(); - headerBuilder.WriteRequestLine(Method, RequestUriString, HttpVersion); + headerBuilder.WriteRequestLine(Method, Url, HttpVersion); headerBuilder.WriteHeaders(Headers); - return headerBuilder.GetString(HttpHelper.HeaderEncoding); + return headerBuilder.GetString(HttpHeader.Encoding); } } diff --git a/src/Titanium.Web.Proxy/Http/RequestResponseBase.cs b/src/Titanium.Web.Proxy/Http/RequestResponseBase.cs index b190ed86e..b270dcdc0 100644 --- a/src/Titanium.Web.Proxy/Http/RequestResponseBase.cs +++ b/src/Titanium.Web.Proxy/Http/RequestResponseBase.cs @@ -57,7 +57,7 @@ public abstract class RequestResponseBase internal bool Http2IgnoreBodyFrames; - internal Task Http2BeforeHandlerTask; + internal Task? Http2BeforeHandlerTask; /// /// Priority used only in HTTP/2 diff --git a/src/Titanium.Web.Proxy/Http/Response.cs b/src/Titanium.Web.Proxy/Http/Response.cs index 0cef5d005..c82f6f7f5 100644 --- a/src/Titanium.Web.Proxy/Http/Response.cs +++ b/src/Titanium.Web.Proxy/Http/Response.cs @@ -102,7 +102,7 @@ public override string HeaderText var headerBuilder = new HeaderBuilder(); headerBuilder.WriteResponseLine(HttpVersion, StatusCode, StatusDescription); headerBuilder.WriteHeaders(Headers); - return headerBuilder.GetString(HttpHelper.HeaderEncoding); + return headerBuilder.GetString(HttpHeader.Encoding); } } diff --git a/src/Titanium.Web.Proxy/Http2/Hpack/Decoder.cs b/src/Titanium.Web.Proxy/Http2/Hpack/Decoder.cs index fc359f805..1f4a1eac5 100644 --- a/src/Titanium.Web.Proxy/Http2/Hpack/Decoder.cs +++ b/src/Titanium.Web.Proxy/Http2/Hpack/Decoder.cs @@ -22,7 +22,7 @@ namespace Titanium.Web.Proxy.Http2.Hpack { - public class Decoder + internal class Decoder { private readonly DynamicTable dynamicTable; @@ -39,7 +39,7 @@ public class Decoder private int skipLength; private int nameLength; private int valueLength; - private string name; + private ByteString name; private enum State { @@ -248,7 +248,7 @@ public void Decode(BinaryReader input, IHeaderListener headerListener) if (indexType == HpackUtil.IndexType.None) { // Name is unused so skip bytes - name = string.Empty; + name = ByteString.Empty; skipLength = nameLength; state = State.SkipLiteralHeaderName; break; @@ -258,7 +258,7 @@ public void Decode(BinaryReader input, IHeaderListener headerListener) if (nameLength + HttpHeader.HttpHeaderOverhead > dynamicTable.Capacity) { dynamicTable.Clear(); - name = string.Empty; + name = Array.Empty(); skipLength = nameLength; state = State.SkipLiteralHeaderName; break; @@ -292,7 +292,7 @@ public void Decode(BinaryReader input, IHeaderListener headerListener) if (indexType == HpackUtil.IndexType.None) { // Name is unused so skip bytes - name = string.Empty; + name = ByteString.Empty; skipLength = nameLength; state = State.SkipLiteralHeaderName; break; @@ -302,7 +302,7 @@ public void Decode(BinaryReader input, IHeaderListener headerListener) if (nameLength + HttpHeader.HttpHeaderOverhead > dynamicTable.Capacity) { dynamicTable.Clear(); - name = string.Empty; + name = ByteString.Empty; skipLength = nameLength; state = State.SkipLiteralHeaderName; break; @@ -375,7 +375,7 @@ public void Decode(BinaryReader input, IHeaderListener headerListener) if (valueLength == 0) { - InsertHeader(headerListener, name, string.Empty, indexType); + InsertHeader(headerListener, name, Array.Empty(), indexType); state = State.ReadHeaderRepresentation; } else @@ -531,16 +531,16 @@ private HttpHeader GetHeaderField(int index) private void ReadName(int index) { - name = GetHeaderField(index).Name; + name = GetHeaderField(index).NameData; } private void IndexHeader(int index, IHeaderListener headerListener) { var headerField = GetHeaderField(index); - AddHeader(headerListener, headerField.Name, headerField.Value, false); + AddHeader(headerListener, headerField.NameData, headerField.ValueData, false); } - private void InsertHeader(IHeaderListener headerListener, string name, string value, HpackUtil.IndexType indexType) + private void InsertHeader(IHeaderListener headerListener, ByteString name, ByteString value, HpackUtil.IndexType indexType) { AddHeader(headerListener, name, value, indexType == HpackUtil.IndexType.Never); @@ -559,7 +559,7 @@ private void InsertHeader(IHeaderListener headerListener, string name, string va } } - private void AddHeader(IHeaderListener headerListener, string name, string value, bool sensitive) + private void AddHeader(IHeaderListener headerListener, ByteString name, ByteString value, bool sensitive) { if (name.Length == 0) { @@ -592,7 +592,7 @@ private bool ExceedsMaxHeaderSize(long size) return true; } - private string ReadStringLiteral(BinaryReader input, int length) + private ByteString ReadStringLiteral(BinaryReader input, int length) { var buf = new byte[length]; int lengthToRead = length; @@ -607,7 +607,7 @@ private string ReadStringLiteral(BinaryReader input, int length) throw new IOException("decompression failure"); } - return huffmanEncoded ? HuffmanDecoder.Instance.Decode(buf) : Encoding.UTF8.GetString(buf); + return new ByteString(huffmanEncoded ? HuffmanDecoder.Instance.Decode(buf) : buf); } // Unsigned Little Endian Base 128 Variable-Length Integer Encoding diff --git a/src/Titanium.Web.Proxy/Http2/Hpack/Encoder.cs b/src/Titanium.Web.Proxy/Http2/Hpack/Encoder.cs index d4fac909f..b12858c2c 100644 --- a/src/Titanium.Web.Proxy/Http2/Hpack/Encoder.cs +++ b/src/Titanium.Web.Proxy/Http2/Hpack/Encoder.cs @@ -1,4 +1,8 @@ -/* + + +using Titanium.Web.Proxy.Extensions; +#if NETSTANDARD2_1 +/* * Copyright 2014 Twitter, Inc * This file is a derivative work modified by Ringo Leese * @@ -14,7 +18,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - using System; using System.IO; using System.Text; @@ -22,13 +25,13 @@ namespace Titanium.Web.Proxy.Http2.Hpack { - public class Encoder + internal class Encoder { private const int bucketSize = 17; // a linked hash map of header fields private readonly HeaderEntry?[] headerFields = new HeaderEntry[bucketSize]; - private readonly HeaderEntry head = new HeaderEntry(-1, string.Empty, string.Empty, int.MaxValue, null); + private readonly HeaderEntry head = new HeaderEntry(-1, ByteString.Empty, ByteString.Empty, int.MaxValue, null); private int size; /// @@ -63,7 +66,7 @@ public Encoder(int maxHeaderTableSize) /// If set to true sensitive. /// Index type. /// Use static name. - public void EncodeHeader(BinaryWriter output, string name, string value, bool sensitive = false, HpackUtil.IndexType indexType = HpackUtil.IndexType.Incremental, bool useStaticName = true) + public void EncodeHeader(BinaryWriter output, ByteString name, ByteString value, bool sensitive = false, HpackUtil.IndexType indexType = HpackUtil.IndexType.Incremental, bool useStaticName = true) { // If the header value is sensitive then it must never be indexed if (sensitive) @@ -190,12 +193,11 @@ private static void encodeInteger(BinaryWriter output, int mask, int n, int i) /// Encode string literal according to Section 5.2. /// /// Output. - /// String literal. - private void encodeStringLiteral(BinaryWriter output, string stringLiteral) + /// String data. + private void encodeStringLiteral(BinaryWriter output, ByteString stringData) { - var stringData = Encoding.UTF8.GetBytes(stringLiteral); int huffmanLength = HuffmanEncoder.Instance.GetEncodedLength(stringData); - if (huffmanLength < stringLiteral.Length) + if (huffmanLength < stringData.Length) { encodeInteger(output, 0x80, 7, huffmanLength); HuffmanEncoder.Instance.Encode(output, stringData); @@ -203,7 +205,7 @@ private void encodeStringLiteral(BinaryWriter output, string stringLiteral) else { encodeInteger(output, 0x00, 7, stringData.Length); - output.Write(stringData, 0, stringData.Length); + output.Write(stringData.Span); } } @@ -215,7 +217,7 @@ private void encodeStringLiteral(BinaryWriter output, string stringLiteral) /// Value. /// Index type. /// Name index. - private void encodeLiteral(BinaryWriter output, string name, string value, HpackUtil.IndexType indexType, + private void encodeLiteral(BinaryWriter output, ByteString name, ByteString value, HpackUtil.IndexType indexType, int nameIndex) { int mask; @@ -250,7 +252,7 @@ private void encodeLiteral(BinaryWriter output, string name, string value, Hpack encodeStringLiteral(output, value); } - private int getNameIndex(string name) + private int getNameIndex(ByteString name) { int index = StaticTable.GetIndex(name); if (index == -1) @@ -299,9 +301,9 @@ private int length() /// The entry. /// Name. /// Value. - private HeaderEntry? getEntry(string name, string value) + private HeaderEntry? getEntry(ByteString name, ByteString value) { - if (length() == 0 || name == null || value == null) + if (length() == 0 || name.Length == 0 || value.Length == 0) { return null; } @@ -310,7 +312,7 @@ private int length() int i = index(h); for (var e = headerFields[i]; e != null; e = e.Next) { - if (e.Hash == h && name.Equals(e.Name, StringComparison.OrdinalIgnoreCase) && Equals(value, e.Value)) + if (e.Hash == h && name.Equals(e.NameData) && Equals(value, e.ValueData)) { return e; } @@ -325,9 +327,9 @@ private int length() /// /// The index. /// Name. - private int getIndex(string name) + private int getIndex(ByteString name) { - if (length() == 0 || name == null) + if (length() == 0 || name.Length == 0) { return -1; } @@ -337,7 +339,7 @@ private int getIndex(string name) int index = -1; for (var e = headerFields[i]; e != null; e = e.Next) { - if (e.Hash == h && name.Equals(e.Name, StringComparison.OrdinalIgnoreCase)) + if (e.Hash == h && name.Equals(e.NameData)) { index = e.Index; break; @@ -371,7 +373,7 @@ private int getIndex(int index) /// /// Name. /// Value. - private void add(string name, string value) + private void add(ByteString name, ByteString value) { int headerSize = HttpHeader.SizeOf(name, value); @@ -457,12 +459,12 @@ private void clear() /// /// true if hash name; otherwise, false. /// Name. - private static int hash(string name) + private static int hash(ByteString name) { int h = 0; for (int i = 0; i < name.Length; i++) { - h = 31 * h + name[i]; + h = 31 * h + name.Span[i]; } if (h > 0) @@ -514,7 +516,7 @@ private class HeaderEntry : HttpHeader /// Value. /// Index. /// Next. - public HeaderEntry(int hash, string name, string value, int index, HeaderEntry? next) : base(name, value, true) + public HeaderEntry(int hash, ByteString name, ByteString value, int index, HeaderEntry? next) : base(name, value, true) { Index = index; Hash = hash; @@ -544,3 +546,4 @@ public void AddBefore(HeaderEntry existingEntry) } } } +#endif diff --git a/src/Titanium.Web.Proxy/Http2/Hpack/HuffmanDecoder.cs b/src/Titanium.Web.Proxy/Http2/Hpack/HuffmanDecoder.cs index f422069ad..168833370 100644 --- a/src/Titanium.Web.Proxy/Http2/Hpack/HuffmanDecoder.cs +++ b/src/Titanium.Web.Proxy/Http2/Hpack/HuffmanDecoder.cs @@ -54,7 +54,7 @@ private HuffmanDecoder() /// the string literal to be decoded /// the output stream for the compressed data /// throws IOException if an I/O error occurs. In particular, an IOException may be thrown if the output stream has been closed. - public string Decode(byte[] buf) + public ReadOnlyMemory Decode(byte[] buf) { var resultBuf = new byte[buf.Length * 2]; int resultSize = 0; @@ -109,7 +109,7 @@ public string Decode(byte[] buf) throw new IOException("Invalid Padding"); } - return Encoding.UTF8.GetString(resultBuf, 0, resultSize); + return resultBuf.AsMemory(0, resultSize); } private class Node diff --git a/src/Titanium.Web.Proxy/Http2/Hpack/HuffmanEncoder.cs b/src/Titanium.Web.Proxy/Http2/Hpack/HuffmanEncoder.cs index 6ad95c579..9644b2e68 100644 --- a/src/Titanium.Web.Proxy/Http2/Hpack/HuffmanEncoder.cs +++ b/src/Titanium.Web.Proxy/Http2/Hpack/HuffmanEncoder.cs @@ -17,10 +17,11 @@ using System; using System.IO; +using Titanium.Web.Proxy.Models; namespace Titanium.Web.Proxy.Http2.Hpack { - public class HuffmanEncoder + internal class HuffmanEncoder { /// /// Huffman Encoder @@ -42,39 +43,15 @@ public class HuffmanEncoder /// /// the output stream for the compressed data /// the string literal to be Huffman encoded - /// if an I/O error occurs. - /// - public void Encode(BinaryWriter output, byte[] data) - { - Encode(output, data, 0, data.Length); - } - - /// - /// Compresses the input string literal using the Huffman coding. - /// - /// the output stream for the compressed data - /// the string literal to be Huffman encoded - /// the start offset in the data - /// the number of bytes to encode /// if an I/O error occurs. In particular, an IOException may be thrown if the output stream has been closed. - public void Encode(BinaryWriter output, byte[] data, int off, int len) + public void Encode(BinaryWriter output, ByteString data) { if (output == null) { throw new ArgumentNullException(nameof(output)); } - if (data == null) - { - throw new ArgumentNullException(nameof(data)); - } - - if (off < 0 || len < 0 || (off + len) < 0 || off > data.Length || (off + len) > data.Length) - { - throw new IndexOutOfRangeException(); - } - - if (len == 0) + if (data.Length == 0) { return; } @@ -82,9 +59,9 @@ public void Encode(BinaryWriter output, byte[] data, int off, int len) long current = 0L; int n = 0; - for (int i = 0; i < len; i++) + for (int i = 0; i < data.Length; i++) { - int b = data[off + i] & 0xFF; + int b = data.Span[i] & 0xFF; uint code = (uint)codes[b]; int nbits = lengths[b]; @@ -112,15 +89,10 @@ public void Encode(BinaryWriter output, byte[] data, int off, int len) /// /// the number of bytes required to Huffman encode data /// the string literal to be Huffman encoded - public int GetEncodedLength(byte[] data) + public int GetEncodedLength(ByteString data) { - if (data == null) - { - throw new ArgumentNullException(nameof(data)); - } - long len = 0L; - foreach (byte b in data) + foreach (byte b in data.Span) { len += lengths[b]; } diff --git a/src/Titanium.Web.Proxy/Http2/Hpack/IHeaderListener.cs b/src/Titanium.Web.Proxy/Http2/Hpack/IHeaderListener.cs index bce72d71d..bdf26b6ab 100644 --- a/src/Titanium.Web.Proxy/Http2/Hpack/IHeaderListener.cs +++ b/src/Titanium.Web.Proxy/Http2/Hpack/IHeaderListener.cs @@ -14,9 +14,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + +using System; +using Titanium.Web.Proxy.Models; + namespace Titanium.Web.Proxy.Http2.Hpack { - public interface IHeaderListener + internal interface IHeaderListener { /// /// EmitHeader is called by the decoder during header field emission. @@ -25,6 +29,6 @@ public interface IHeaderListener /// Name. /// Value. /// If set to true sensitive. - void AddHeader(string name, string value, bool sensitive); + void AddHeader(ByteString name, ByteString value, bool sensitive); } } diff --git a/src/Titanium.Web.Proxy/Http2/Hpack/StaticTable.cs b/src/Titanium.Web.Proxy/Http2/Hpack/StaticTable.cs index 99e96ef6f..163525d4a 100644 --- a/src/Titanium.Web.Proxy/Http2/Hpack/StaticTable.cs +++ b/src/Titanium.Web.Proxy/Http2/Hpack/StaticTable.cs @@ -18,149 +18,104 @@ using System; using System.Collections.Generic; using System.Text; +using Titanium.Web.Proxy.Extensions; using Titanium.Web.Proxy.Models; namespace Titanium.Web.Proxy.Http2.Hpack { - public static class StaticTable + internal static class StaticTable { /// /// Appendix A: Static Table Definition /// /// - private static readonly List staticTable = new List + private static readonly List staticTable; + + private static readonly Dictionary staticIndexByName; + + public static ByteString KnownHeaderAuhtority = (ByteString)":authority"; + + public static ByteString KnownHeaderMethod = (ByteString)":method"; + + public static ByteString KnownHeaderPath = (ByteString)":path"; + + public static ByteString KnownHeaderScheme = (ByteString)":scheme"; + + public static ByteString KnownHeaderStatus = (ByteString)":status"; + + static StaticTable() { - /* 1 */ - new HttpHeader(":authority", string.Empty), - /* 2 */ - new HttpHeader(":method", "GET"), - /* 3 */ - new HttpHeader(":method", "POST"), - /* 4 */ - new HttpHeader(":path", "/"), - /* 5 */ - new HttpHeader(":path", "/index.html"), - /* 6 */ - new HttpHeader(":scheme", "http"), - /* 7 */ - new HttpHeader(":scheme", "https"), - /* 8 */ - new HttpHeader(":status", "200"), - /* 9 */ - new HttpHeader(":status", "204"), - /* 10 */ - new HttpHeader(":status", "206"), - /* 11 */ - new HttpHeader(":status", "304"), - /* 12 */ - new HttpHeader(":status", "400"), - /* 13 */ - new HttpHeader(":status", "404"), - /* 14 */ - new HttpHeader(":status", "500"), - /* 15 */ - new HttpHeader("Accept-Charset", string.Empty), - /* 16 */ - new HttpHeader("Accept-Encoding", "gzip, deflate"), - /* 17 */ - new HttpHeader("Accept-Language", string.Empty), - /* 18 */ - new HttpHeader("Accept-Ranges", string.Empty), - /* 19 */ - new HttpHeader("Accept", string.Empty), - /* 20 */ - new HttpHeader("Access-Control-Allow-Origin", string.Empty), - /* 21 */ - new HttpHeader("Age", string.Empty), - /* 22 */ - new HttpHeader("Allow", string.Empty), - /* 23 */ - new HttpHeader("Authorization", string.Empty), - /* 24 */ - new HttpHeader("Cache-Control", string.Empty), - /* 25 */ - new HttpHeader("Content-Disposition", string.Empty), - /* 26 */ - new HttpHeader("Content-Encoding", string.Empty), - /* 27 */ - new HttpHeader("Content-Language", string.Empty), - /* 28 */ - new HttpHeader("Content-Length", string.Empty), - /* 29 */ - new HttpHeader("Content-Location", string.Empty), - /* 30 */ - new HttpHeader("Content-Range", string.Empty), - /* 31 */ - new HttpHeader("Content-Type", string.Empty), - /* 32 */ - new HttpHeader("Cookie", string.Empty), - /* 33 */ - new HttpHeader("Date", string.Empty), - /* 34 */ - new HttpHeader("ETag", string.Empty), - /* 35 */ - new HttpHeader("Expect", string.Empty), - /* 36 */ - new HttpHeader("Expires", string.Empty), - /* 37 */ - new HttpHeader("From", string.Empty), - /* 38 */ - new HttpHeader("Host", string.Empty), - /* 39 */ - new HttpHeader("If-Match", string.Empty), - /* 40 */ - new HttpHeader("If-Modified-Since", string.Empty), - /* 41 */ - new HttpHeader("If-None-Match", string.Empty), - /* 42 */ - new HttpHeader("If-Range", string.Empty), - /* 43 */ - new HttpHeader("If-Unmodified-Since", string.Empty), - /* 44 */ - new HttpHeader("Last-Modified", string.Empty), - /* 45 */ - new HttpHeader("Link", string.Empty), - /* 46 */ - new HttpHeader("Location", string.Empty), - /* 47 */ - new HttpHeader("Max-Forwards", string.Empty), - /* 48 */ - new HttpHeader("Proxy-Authenticate", string.Empty), - /* 49 */ - new HttpHeader("Proxy-Authorization", string.Empty), - /* 50 */ - new HttpHeader("Range", string.Empty), - /* 51 */ - new HttpHeader("Referer", string.Empty), - /* 52 */ - new HttpHeader("Refresh", string.Empty), - /* 53 */ - new HttpHeader("Retry-After", string.Empty), - /* 54 */ - new HttpHeader("Server", string.Empty), - /* 55 */ - new HttpHeader("Set-Cookie", string.Empty), - /* 56 */ - new HttpHeader("Strict-Transport-Security", string.Empty), - /* 57 */ - new HttpHeader("Transfer-Encoding", string.Empty), - /* 58 */ - new HttpHeader("User-Agent", string.Empty), - /* 59 */ - new HttpHeader("Vary", string.Empty), - /* 60 */ - new HttpHeader("Via", string.Empty), - /* 61 */ - new HttpHeader("WWW-Authenticate", string.Empty) - }; - - private static readonly Dictionary staticIndexByName = createMap(); + const int entryCount = 61; + staticTable = new List(entryCount); + staticIndexByName = new Dictionary(entryCount); + create(KnownHeaderAuhtority, string.Empty); // 1 + create(KnownHeaderMethod, "GET"); // 2 + create(KnownHeaderMethod, "POST"); // 3 + create(KnownHeaderPath, "/"); // 4 + create(KnownHeaderPath, "/index.html"); // 5 + create(KnownHeaderScheme, "http"); // 6 + create(KnownHeaderScheme, "https"); // 7 + create(KnownHeaderStatus, "200"); // 8 + create(KnownHeaderStatus, "204"); // 9 + create(KnownHeaderStatus, "206"); // 10 + create(KnownHeaderStatus, "304"); // 11 + create(KnownHeaderStatus, "400"); // 12 + create(KnownHeaderStatus, "404"); // 13 + create(KnownHeaderStatus, "500"); // 14 + create("Accept-Charset", string.Empty); // 15 + create("Accept-Encoding", "gzip, deflate"); // 16 + create("Accept-Language", string.Empty); // 17 + create("Accept-Ranges", string.Empty); // 18 + create("Accept", string.Empty); // 19 + create("Access-Control-Allow-Origin", string.Empty); // 20 + create("Age", string.Empty); // 21 + create("Allow", string.Empty); // 22 + create("Authorization", string.Empty); // 23 + create("Cache-Control", string.Empty); // 24 + create("Content-Disposition", string.Empty); // 25 + create("Content-Encoding", string.Empty); // 26 + create("Content-Language", string.Empty); // 27 + create("Content-Length", string.Empty); // 28 + create("Content-Location", string.Empty); // 29 + create("Content-Range", string.Empty); // 30 + create("Content-Type", string.Empty); // 31 + create("Cookie", string.Empty); // 32 + create("Date", string.Empty); // 33 + create("ETag", string.Empty); // 34 + create("Expect", string.Empty); // 35 + create("Expires", string.Empty); // 36 + create("From", string.Empty); // 37 + create("Host", string.Empty); // 38 + create("If-Match", string.Empty); // 39 + create("If-Modified-Since", string.Empty); // 40 + create("If-None-Match", string.Empty); // 41 + create("If-Range", string.Empty); // 42 + create("If-Unmodified-Since", string.Empty); // 43 + create("Last-Modified", string.Empty); // 44 + create("Link", string.Empty); // 45 + create("Location", string.Empty); // 46 + create("Max-Forwards", string.Empty); // 47 + create("Proxy-Authenticate", string.Empty); // 48 + create("Proxy-Authorization", string.Empty); // 49 + create("Range", string.Empty); // 50 + create("Referer", string.Empty); // 51 + create("Refresh", string.Empty); // 52 + create("Retry-After", string.Empty); // 53 + create("Server", string.Empty); // 54 + create("Set-Cookie", string.Empty); // 55 + create("Strict-Transport-Security", string.Empty); // 56 + create("Transfer-Encoding", string.Empty); // 57 + create("User-Agent", string.Empty); // 58 + create("Vary", string.Empty); // 59 + create("Via", string.Empty); // 60 + create("WWW-Authenticate", string.Empty); // 61 + } - /// - /// The number of header fields in the static table. - /// - /// The length. - public static int Length => staticTable.Count; + /// + /// The number of header fields in the static table. + /// + /// The length. + public static int Length => staticTable.Count; /// /// Return the http header field at the given index value. @@ -178,7 +133,7 @@ public static HttpHeader Get(int index) /// /// The index. /// Name. - public static int GetIndex(string name) + public static int GetIndex(ByteString name) { if (!staticIndexByName.TryGetValue(name, out int index)) { @@ -195,7 +150,7 @@ public static int GetIndex(string name) /// The index. /// Name. /// Value. - public static int GetIndex(string name, string value) + public static int GetIndex(ByteString name, ByteString value) { int index = GetIndex(name); if (index == -1) @@ -207,12 +162,12 @@ public static int GetIndex(string name, string value) while (index <= Length) { var entry = Get(index); - if (!name.Equals(entry.Name, StringComparison.OrdinalIgnoreCase)) + if (!name.Equals(entry.NameData)) { break; } - if (HpackUtil.Equals(value, entry.Value)) + if (Equals(value, entry.Value)) { return index; } @@ -223,25 +178,15 @@ public static int GetIndex(string name, string value) return -1; } - /// - /// create a map of header name to index value to allow quick lookup - /// - /// The map. - private static Dictionary createMap() + private static void create(string name, string value) { - int length = staticTable.Count; - var ret = new Dictionary(length); - - // Iterate through the static table in reverse order to - // save the smallest index for a given name in the map. - for (int index = length; index > 0; index--) - { - var entry = Get(index); - string name = entry.Name.ToLower(); - ret[name] = index; - } + create((ByteString)name.ToLower(), value); + } - return ret; + private static void create(ByteString name, string value) + { + staticTable.Add(new HttpHeader(name, (ByteString)value)); + staticIndexByName[name] = staticTable.Count; } } } diff --git a/src/Titanium.Web.Proxy/Http2/Http2Helper.cs b/src/Titanium.Web.Proxy/Http2/Http2Helper.cs index f3a421bb2..e0c142390 100644 --- a/src/Titanium.Web.Proxy/Http2/Http2Helper.cs +++ b/src/Titanium.Web.Proxy/Http2/Http2Helper.cs @@ -1,4 +1,6 @@ -#if NETSTANDARD2_1 + +using Titanium.Web.Proxy.Extensions; +#if NETSTANDARD2_1 using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -13,6 +15,7 @@ using Titanium.Web.Proxy.Exceptions; using Titanium.Web.Proxy.Http; using Titanium.Web.Proxy.Http2.Hpack; +using Titanium.Web.Proxy.Models; using Decoder = Titanium.Web.Proxy.Http2.Hpack.Decoder; using Encoder = Titanium.Web.Proxy.Http2.Hpack.Encoder; @@ -234,7 +237,7 @@ private static async Task copyHttp2FrameAsync(Stream input, Stream output, (name, value) => { var headers = isClient ? args.HttpClient.Request.Headers : args.HttpClient.Response.Headers; - headers.AddHeader(name, value); + headers.AddHeader(new HttpHeader(name, value)); }); try { @@ -252,16 +255,16 @@ private static async Task copyHttp2FrameAsync(Stream input, Stream output, if (rr is Request request) { - string? method = headerListener.Method; - string? path = headerListener.Path; - if (method == null || path == null) + var method = headerListener.Method; + var path = headerListener.Path; + if (method.Length == 0 || path.Length == 0) { throw new Exception("HTTP/2 Missing method or path"); } request.HttpVersion = HttpVersion.Version20; - request.Method = method; - request.OriginalUrl = path; + request.Method = method.GetString(); + request.OriginalUrlData = path; request.RequestUri = headerListener.GetUri(); } @@ -269,7 +272,10 @@ private static async Task copyHttp2FrameAsync(Stream input, Stream output, { var response = (Response)rr; response.HttpVersion = HttpVersion.Version20; - int.TryParse(headerListener.Status, out int statusCode); + + // todo: avoid string conversion + string statusHack = HttpHeader.Encoding.GetString(headerListener.Status.Span); + int.TryParse(statusHack, out int statusCode); response.StatusCode = statusCode; response.StatusDescription = string.Empty; } @@ -461,21 +467,21 @@ private static async Task sendHeader(Http2Settings settings, Http2FrameHeader fr if (rr is Request request) { - encoder.EncodeHeader(writer, ":method", request.Method); - encoder.EncodeHeader(writer, ":authority", request.RequestUri.Host); - encoder.EncodeHeader(writer, ":scheme", request.RequestUri.Scheme); - encoder.EncodeHeader(writer, ":path", request.RequestUriString, false, + encoder.EncodeHeader(writer, StaticTable.KnownHeaderMethod, request.Method.GetByteString()); + encoder.EncodeHeader(writer, StaticTable.KnownHeaderAuhtority, request.RequestUri.Host.GetByteString()); + encoder.EncodeHeader(writer, StaticTable.KnownHeaderScheme, request.RequestUri.Scheme.GetByteString()); + encoder.EncodeHeader(writer, StaticTable.KnownHeaderPath, request.Url.GetByteString(), false, HpackUtil.IndexType.None, false); } else { var response = (Response)rr; - encoder.EncodeHeader(writer, ":status", response.StatusCode.ToString()); + encoder.EncodeHeader(writer, StaticTable.KnownHeaderStatus, response.StatusCode.ToString().GetByteString()); } foreach (var header in rr.Headers) { - encoder.EncodeHeader(writer, header.Name.ToLower(), header.Value); + encoder.EncodeHeader(writer, header.NameData, header.ValueData); } var data = ms.ToArray(); @@ -565,28 +571,29 @@ class Http2Settings class MyHeaderListener : IHeaderListener { - private readonly Action addHeaderFunc; + private readonly Action addHeaderFunc; - public string? Method { get; private set; } + public ByteString Method { get; private set; } - public string? Status { get; private set; } + public ByteString Status { get; private set; } - private string? authority; + private ByteString authority; - private string? scheme; + private ByteString scheme; - public string? Path { get; private set; } + public ByteString Path { get; private set; } - public MyHeaderListener(Action addHeaderFunc) + public MyHeaderListener(Action addHeaderFunc) { this.addHeaderFunc = addHeaderFunc; } - public void AddHeader(string name, string value, bool sensitive) + public void AddHeader(ByteString name, ByteString value, bool sensitive) { - if (name[0] == ':') + if (name.Span[0] == ':') { - switch (name) + string nameStr = Encoding.ASCII.GetString(name.Span); + switch (nameStr) { case ":method": Method = value; @@ -611,13 +618,23 @@ public void AddHeader(string name, string value, bool sensitive) public Uri GetUri() { - if (authority == null) + if (authority.Length == 0) { // todo - authority = "abc.abc"; + authority = HttpHeader.Encoding.GetBytes("abc.abc"); } - return new Uri(scheme + "://" + authority + Path); + var bytes = new byte[scheme.Length + 3 + authority.Length + Path.Length]; + scheme.Span.CopyTo(bytes); + int idx = scheme.Length; + bytes[idx++] = (byte)':'; + bytes[idx++] = (byte)'/'; + bytes[idx++] = (byte)'/'; + authority.Span.CopyTo(bytes.AsSpan(idx, authority.Length)); + idx += authority.Length; + Path.Span.CopyTo(bytes.AsSpan(idx, Path.Length)); + + return new Uri(HttpHeader.Encoding.GetString(bytes)); } } } diff --git a/src/Titanium.Web.Proxy/Models/ByteString.cs b/src/Titanium.Web.Proxy/Models/ByteString.cs new file mode 100644 index 000000000..b37dcdd17 --- /dev/null +++ b/src/Titanium.Web.Proxy/Models/ByteString.cs @@ -0,0 +1,42 @@ +using System; +using System.Text; + +namespace Titanium.Web.Proxy.Models +{ + internal struct ByteString : IEquatable + { + public static ByteString Empty = new ByteString(ReadOnlyMemory.Empty); + + public ReadOnlyMemory Data { get; } + + public ReadOnlySpan Span => Data.Span; + + public int Length => Data.Length; + + public ByteString(ReadOnlyMemory data) + { + Data = data; + } + + public override bool Equals(object? obj) + { + return obj is ByteString other && Equals(other); + } + + public bool Equals(ByteString other) + { + return Data.Span.SequenceEqual(other.Data.Span); + } + + public override int GetHashCode() + { + return Data.GetHashCode(); + } + + public static explicit operator ByteString(string str) => new ByteString(Encoding.ASCII.GetBytes(str)); + + public static implicit operator ByteString(byte[] data) => new ByteString(data); + + public static implicit operator ByteString(ReadOnlyMemory data) => new ByteString(data); + } +} diff --git a/src/Titanium.Web.Proxy/Models/ExternalProxy.cs b/src/Titanium.Web.Proxy/Models/ExternalProxy.cs index d84fd88ce..4c1deca60 100644 --- a/src/Titanium.Web.Proxy/Models/ExternalProxy.cs +++ b/src/Titanium.Web.Proxy/Models/ExternalProxy.cs @@ -11,9 +11,9 @@ public class ExternalProxy private static readonly Lazy defaultCredentials = new Lazy(() => CredentialCache.DefaultNetworkCredentials); - private string password; + private string? password; - private string userName; + private string? userName; /// /// Use default windows credentials? @@ -28,7 +28,7 @@ public class ExternalProxy /// /// Username. /// - public string UserName + public string? UserName { get => UseDefaultCredentials ? defaultCredentials.Value.UserName : userName; set @@ -45,7 +45,7 @@ public string UserName /// /// Password. /// - public string Password + public string? Password { get => UseDefaultCredentials ? defaultCredentials.Value.Password : password; set @@ -62,22 +62,13 @@ public string Password /// /// Host name. /// - public string HostName { get; set; } + public string HostName { get; set; } = string.Empty; /// /// Port. /// public int Port { get; set; } - /// - /// Get cache key for Tcp connection cache. - /// - /// - internal string GetCacheKey() - { - return $"{HostName}-{Port}" + (UseDefaultCredentials ? $"-{UserName}-{Password}" : string.Empty); - } - /// /// returns data in Hostname:port format. /// diff --git a/src/Titanium.Web.Proxy/Models/HttpHeader.cs b/src/Titanium.Web.Proxy/Models/HttpHeader.cs index d432b37c6..a73b96478 100644 --- a/src/Titanium.Web.Proxy/Models/HttpHeader.cs +++ b/src/Titanium.Web.Proxy/Models/HttpHeader.cs @@ -1,6 +1,8 @@ using System; using System.Net; using System.Text; +using Titanium.Web.Proxy.Extensions; +using Titanium.Web.Proxy.Helpers; using Titanium.Web.Proxy.Http; namespace Titanium.Web.Proxy.Models @@ -33,6 +35,10 @@ public class HttpHeader internal static Version Version20 { get; } = new Version(2, 0); #endif + internal static readonly Encoding DefaultEncoding = Encoding.GetEncoding("ISO-8859-1"); + + public static Encoding Encoding => DefaultEncoding; + internal static readonly HttpHeader ProxyConnectionKeepAlive = new HttpHeader("Proxy-Connection", "keep-alive"); /// @@ -47,30 +53,45 @@ public HttpHeader(string name, string value) throw new Exception("Name cannot be null or empty"); } - Name = name.Trim(); - Value = value.Trim(); + NameData = name.Trim().GetByteString(); + ValueData = value.Trim().GetByteString(); + } + + internal HttpHeader(ByteString name, ByteString value) + { + if (name.Length == 0) + { + throw new Exception("Name cannot be empty"); + } + + NameData = name; + ValueData = value; } - protected HttpHeader(string name, string value, bool headerEntry) + private protected HttpHeader(ByteString name, ByteString value, bool headerEntry) { // special header entry created in inherited class with empty name - Name = name.Trim(); - Value = value.Trim(); + NameData = name; + ValueData = value; } /// /// Header Name. /// - public string Name { get; } + public string Name => NameData.GetString(); + + internal ByteString NameData { get; } /// /// Header Value. /// - public string Value { get; set; } + public string Value => ValueData.GetString(); + + internal ByteString ValueData { get; set; } public int Size => Name.Length + Value.Length + HttpHeaderOverhead; - public static int SizeOf(string name, string value) + internal static int SizeOf(ByteString name, ByteString value) { return name.Length + value.Length + HttpHeaderOverhead; } @@ -84,7 +105,7 @@ public override string ToString() return $"{Name}: {Value}"; } - internal static HttpHeader GetProxyAuthorizationHeader(string userName, string password) + internal static HttpHeader GetProxyAuthorizationHeader(string? userName, string? password) { var result = new HttpHeader(KnownHeaders.ProxyAuthorization, "Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes($"{userName}:{password}"))); diff --git a/src/Titanium.Web.Proxy/Models/ProxyEndPoint.cs b/src/Titanium.Web.Proxy/Models/ProxyEndPoint.cs index 6905e296c..d4218e8eb 100644 --- a/src/Titanium.Web.Proxy/Models/ProxyEndPoint.cs +++ b/src/Titanium.Web.Proxy/Models/ProxyEndPoint.cs @@ -25,7 +25,7 @@ protected ProxyEndPoint(IPAddress ipAddress, int port, bool decryptSsl) /// /// underlying TCP Listener object /// - internal TcpListener Listener { get; set; } + internal TcpListener? Listener { get; set; } /// /// Ip Address we are listening. @@ -45,6 +45,6 @@ protected ProxyEndPoint(IPAddress ipAddress, int port, bool decryptSsl) /// /// Generic certificate to use for SSL decryption. /// - public X509Certificate2 GenericCertificate { get; set; } + public X509Certificate2? GenericCertificate { get; set; } } } diff --git a/src/Titanium.Web.Proxy/Network/CachedCertificate.cs b/src/Titanium.Web.Proxy/Network/CachedCertificate.cs index 5f5154c13..112b17558 100644 --- a/src/Titanium.Web.Proxy/Network/CachedCertificate.cs +++ b/src/Titanium.Web.Proxy/Network/CachedCertificate.cs @@ -8,7 +8,12 @@ namespace Titanium.Web.Proxy.Network /// internal sealed class CachedCertificate { - internal X509Certificate2 Certificate { get; set; } + public CachedCertificate(X509Certificate2 certificate) + { + Certificate = certificate; + } + + internal X509Certificate2 Certificate { get; } /// /// Last time this certificate was used. diff --git a/src/Titanium.Web.Proxy/Network/Certificate/WinCertificateMaker.cs b/src/Titanium.Web.Proxy/Network/Certificate/WinCertificateMaker.cs index a04b68b20..15d81a152 100644 --- a/src/Titanium.Web.Proxy/Network/Certificate/WinCertificateMaker.cs +++ b/src/Titanium.Web.Proxy/Network/Certificate/WinCertificateMaker.cs @@ -44,7 +44,7 @@ internal class WinCertificateMaker : ICertificateMaker private readonly Type typeX509PrivateKey; - private object sharedPrivateKey; + private object? sharedPrivateKey; /// /// Constructor. diff --git a/src/Titanium.Web.Proxy/Network/CertificateManager.cs b/src/Titanium.Web.Proxy/Network/CertificateManager.cs index 58358ac09..a193c7e5c 100644 --- a/src/Titanium.Web.Proxy/Network/CertificateManager.cs +++ b/src/Titanium.Web.Proxy/Network/CertificateManager.cs @@ -60,15 +60,30 @@ private readonly CancellationTokenSource clearCertificatesTokenSource private readonly object rootCertCreationLock = new object(); - private ICertificateMaker certEngine; + private ICertificateMaker? certEngineValue; + + private ICertificateMaker certEngine + { + get + { + if (certEngineValue == null) + { + certEngineValue = engine == CertificateEngine.BouncyCastle + ? (ICertificateMaker)new BCCertificateMaker(ExceptionFunc) + : new WinCertificateMaker(ExceptionFunc); + } + + return certEngineValue; + } + } private CertificateEngine engine; - private string issuer; + private string? issuer; private X509Certificate2? rootCertificate; - private string rootCertificateName; + private string? rootCertificateName; private ICertificateCache certificateCache = new DefaultCertificateDiskCache(); @@ -156,16 +171,9 @@ public CertificateEngine CertificateEngine if (value != engine) { - certEngine = null!; + certEngineValue = null!; engine = value; } - - if (certEngine == null) - { - certEngine = engine == CertificateEngine.BouncyCastle - ? (ICertificateMaker)new BCCertificateMaker(ExceptionFunc) - : new WinCertificateMaker(ExceptionFunc); - } } } @@ -468,10 +476,7 @@ private X509Certificate2 makeCertificate(string certificateName, bool isRootCert var result = CreateCertificate(certificateName, false); if (result != null) { - cachedCertificates.TryAdd(certificateName, new CachedCertificate - { - Certificate = result - }); + cachedCertificates.TryAdd(certificateName, new CachedCertificate(result)); } return result; diff --git a/src/Titanium.Web.Proxy/Network/ProxyClient.cs b/src/Titanium.Web.Proxy/Network/ProxyClient.cs index 792d00d0f..1969cb1db 100644 --- a/src/Titanium.Web.Proxy/Network/ProxyClient.cs +++ b/src/Titanium.Web.Proxy/Network/ProxyClient.cs @@ -9,19 +9,26 @@ namespace Titanium.Web.Proxy.Network /// internal class ProxyClient { + public ProxyClient(TcpClientConnection connection, CustomBufferedStream clientStream, HttpResponseWriter clientStreamWriter) + { + Connection = connection; + ClientStream = clientStream; + ClientStreamWriter = clientStreamWriter; + } + /// /// TcpClient connection used to communicate with client /// - internal TcpClientConnection Connection { get; set; } + internal TcpClientConnection Connection { get; } /// /// Holds the stream to client /// - internal CustomBufferedStream ClientStream { get; set; } + internal CustomBufferedStream ClientStream { get; } /// /// Used to write line by line to client /// - internal HttpResponseWriter ClientStreamWriter { get; set; } + internal HttpResponseWriter ClientStreamWriter { get; } } } diff --git a/src/Titanium.Web.Proxy/Network/Tcp/TcpConnectionFactory.cs b/src/Titanium.Web.Proxy/Network/Tcp/TcpConnectionFactory.cs index f7b0d652c..01f19edc6 100644 --- a/src/Titanium.Web.Proxy/Network/Tcp/TcpConnectionFactory.cs +++ b/src/Titanium.Web.Proxy/Network/Tcp/TcpConnectionFactory.cs @@ -49,27 +49,52 @@ internal TcpConnectionFactory(ProxyServer server) internal string GetConnectionCacheKey(string remoteHostName, int remotePort, bool isHttps, List? applicationProtocols, - IPEndPoint upStreamEndPoint, ExternalProxy? externalProxy) + IPEndPoint? upStreamEndPoint, ExternalProxy? externalProxy) { // http version is ignored since its an application level decision b/w HTTP 1.0/1.1 // also when doing connect request MS Edge browser sends http 1.0 but uses 1.1 after server sends 1.1 its response. // That can create cache miss for same server connection unnecessarily especially when prefetching with Connect. // http version 2 is separated using applicationProtocols below. - var cacheKeyBuilder = new StringBuilder($"{remoteHostName}-{remotePort}-" + - // when creating Tcp client isConnect won't matter - $"{isHttps}-"); + var cacheKeyBuilder = new StringBuilder(); + cacheKeyBuilder.Append(remoteHostName); + cacheKeyBuilder.Append("-"); + cacheKeyBuilder.Append(remotePort); + cacheKeyBuilder.Append("-"); + // when creating Tcp client isConnect won't matter + cacheKeyBuilder.Append(isHttps); + if (applicationProtocols != null) { foreach (var protocol in applicationProtocols.OrderBy(x => x)) { - cacheKeyBuilder.Append($"{protocol}-"); + cacheKeyBuilder.Append("-"); + cacheKeyBuilder.Append(protocol); } } - cacheKeyBuilder.Append(upStreamEndPoint != null - ? $"{upStreamEndPoint.Address}-{upStreamEndPoint.Port}-" - : string.Empty); - cacheKeyBuilder.Append(externalProxy != null ? $"{externalProxy.GetCacheKey()}-" : string.Empty); + if (upStreamEndPoint != null) + { + cacheKeyBuilder.Append("-"); + cacheKeyBuilder.Append(upStreamEndPoint.Address); + cacheKeyBuilder.Append("-"); + cacheKeyBuilder.Append(upStreamEndPoint.Port); + } + + if (externalProxy != null) + { + cacheKeyBuilder.Append("-"); + cacheKeyBuilder.Append(externalProxy.HostName); + cacheKeyBuilder.Append("-"); + cacheKeyBuilder.Append(externalProxy.Port); + + if (externalProxy.UseDefaultCredentials) + { + cacheKeyBuilder.Append("-"); + cacheKeyBuilder.Append(externalProxy.UserName); + cacheKeyBuilder.Append("-"); + cacheKeyBuilder.Append(externalProxy.Password); + } + } return cacheKeyBuilder.ToString(); } @@ -181,7 +206,7 @@ internal async Task GetServerConnection(ProxyServer server, /// internal async Task GetServerConnection(string remoteHostName, int remotePort, Version httpVersion, bool isHttps, List? applicationProtocols, bool isConnect, - ProxyServer proxyServer, SessionEventArgsBase? session, IPEndPoint upStreamEndPoint, ExternalProxy? externalProxy, + ProxyServer proxyServer, SessionEventArgsBase? session, IPEndPoint? upStreamEndPoint, ExternalProxy? externalProxy, bool noCache, CancellationToken cancellationToken) { var sslProtocol = session?.ProxyClient.Connection.SslProtocol ?? SslProtocols.None; @@ -211,9 +236,7 @@ internal async Task GetServerConnection(string remoteHostNa } var connection = await createServerConnection(remoteHostName, remotePort, httpVersion, isHttps, sslProtocol, - applicationProtocols, isConnect, proxyServer, session, upStreamEndPoint, externalProxy, cancellationToken); - - connection.CacheKey = cacheKey; + applicationProtocols, isConnect, proxyServer, session, upStreamEndPoint, externalProxy, cacheKey, cancellationToken); return connection; } @@ -232,11 +255,12 @@ internal async Task GetServerConnection(string remoteHostNa /// The http session. /// The local upstream endpoint to make request via. /// The external proxy to make request via. + /// The connection cache key /// The cancellation token for this async task. /// private async Task createServerConnection(string remoteHostName, int remotePort, Version httpVersion, bool isHttps, SslProtocols sslProtocol, List? applicationProtocols, bool isConnect, - ProxyServer proxyServer, SessionEventArgsBase? session, IPEndPoint upStreamEndPoint, ExternalProxy? externalProxy, + ProxyServer proxyServer, SessionEventArgsBase? session, IPEndPoint? upStreamEndPoint, ExternalProxy? externalProxy, string cacheKey, CancellationToken cancellationToken) { // deny connection to proxy end points to avoid infinite connection loop. @@ -281,8 +305,8 @@ private async Task createServerConnection(string remoteHost retry: try { - var hostname = useUpstreamProxy ? externalProxy!.HostName : remoteHostName; - var port = useUpstreamProxy ? externalProxy!.Port : remotePort; + string hostname = useUpstreamProxy ? externalProxy!.HostName : remoteHostName; + int port = useUpstreamProxy ? externalProxy!.Port : remotePort; var ipAddresses = await Dns.GetHostAddressesAsync(hostname); if (ipAddresses == null || ipAddresses.Length == 0) @@ -350,7 +374,7 @@ private async Task createServerConnection(string remoteHost var writer = new HttpRequestWriter(stream, proxyServer.BufferPool); var connectRequest = new ConnectRequest { - OriginalUrl = $"{remoteHostName}:{remotePort}", + OriginalUrlData = HttpHeader.Encoding.GetBytes($"{remoteHostName}:{remotePort}"), HttpVersion = httpVersion }; @@ -418,17 +442,8 @@ private async Task createServerConnection(string remoteHost throw; } - return new TcpServerConnection(proxyServer, tcpClient, stream) - { - UpStreamProxy = externalProxy, - UpStreamEndPoint = upStreamEndPoint, - HostName = remoteHostName, - Port = remotePort, - IsHttps = isHttps, - NegotiatedApplicationProtocol = negotiatedApplicationProtocol, - UseUpstreamProxy = useUpstreamProxy, - Version = httpVersion - }; + return new TcpServerConnection(proxyServer, tcpClient, stream, remoteHostName, remotePort, isHttps, + negotiatedApplicationProtocol, httpVersion, useUpstreamProxy, externalProxy, upStreamEndPoint, cacheKey); } diff --git a/src/Titanium.Web.Proxy/Network/Tcp/TcpServerConnection.cs b/src/Titanium.Web.Proxy/Network/Tcp/TcpServerConnection.cs index d6a0e7e81..ef40391bb 100644 --- a/src/Titanium.Web.Proxy/Network/Tcp/TcpServerConnection.cs +++ b/src/Titanium.Web.Proxy/Network/Tcp/TcpServerConnection.cs @@ -15,7 +15,9 @@ namespace Titanium.Web.Proxy.Network.Tcp /// internal class TcpServerConnection : IDisposable { - internal TcpServerConnection(ProxyServer proxyServer, TcpClient tcpClient, CustomBufferedStream stream) + internal TcpServerConnection(ProxyServer proxyServer, TcpClient tcpClient, CustomBufferedStream stream, + string hostName, int port, bool isHttps, SslApplicationProtocol negotiatedApplicationProtocol, + Version version, bool useUpstreamProxy, ExternalProxy? upStreamProxy, IPEndPoint? upStreamEndPoint, string cacheKey) { this.tcpClient = tcpClient; LastAccess = DateTime.Now; @@ -23,6 +25,16 @@ internal TcpServerConnection(ProxyServer proxyServer, TcpClient tcpClient, Custo this.proxyServer.UpdateServerConnectionCount(true); StreamWriter = new HttpRequestWriter(stream, proxyServer.BufferPool); Stream = stream; + HostName = hostName; + Port = port; + IsHttps = isHttps; + NegotiatedApplicationProtocol = negotiatedApplicationProtocol; + Version = version; + UseUpstreamProxy = useUpstreamProxy; + UpStreamProxy = upStreamProxy; + UpStreamEndPoint = upStreamEndPoint; + + CacheKey = cacheKey; } private ProxyServer proxyServer { get; } diff --git a/src/Titanium.Web.Proxy/Network/WinAuth/Security/Message.cs b/src/Titanium.Web.Proxy/Network/WinAuth/Security/Message.cs index cc8884efa..2c537f11e 100644 --- a/src/Titanium.Web.Proxy/Network/WinAuth/Security/Message.cs +++ b/src/Titanium.Web.Proxy/Network/WinAuth/Security/Message.cs @@ -47,24 +47,7 @@ internal class Message internal Message(byte[] message) { type = 3; - Decode(message); - } - - /// - /// Domain name - /// - internal string Domain { get; private set; } - - /// - /// Username - /// - internal string? Username { get; private set; } - - internal Common.NtlmFlags Flags { get; set; } - // methods - private void Decode(byte[] message) - { if (message == null) { throw new ArgumentNullException(nameof(message)); @@ -108,6 +91,18 @@ private void Decode(byte[] message) Username = DecodeString(message, userOff, userLen); } + /// + /// Domain name + /// + internal string Domain { get; private set; } + + /// + /// Username + /// + internal string Username { get; private set; } + + internal Common.NtlmFlags Flags { get; set; } + private string DecodeString(byte[] buffer, int offset, int len) { if ((Flags & Common.NtlmFlags.NegotiateUnicode) != 0) diff --git a/src/Titanium.Web.Proxy/RequestHandler.cs b/src/Titanium.Web.Proxy/RequestHandler.cs index 98af08881..91033b5de 100644 --- a/src/Titanium.Web.Proxy/RequestHandler.cs +++ b/src/Titanium.Web.Proxy/RequestHandler.cs @@ -70,10 +70,8 @@ private async Task handleHttpSessionRequest(ProxyEndPoint endPoint, TcpClientCon return; } - var args = new SessionEventArgs(this, endPoint, cancellationTokenSource) + var args = new SessionEventArgs(this, endPoint, new ProxyClient(clientConnection, clientStream, clientStreamWriter), connectRequest, cancellationTokenSource) { - ProxyClient = { Connection = clientConnection }, - HttpClient = { ConnectRequest = connectRequest }, UserData = connectArgs?.UserData }; @@ -122,12 +120,10 @@ await HeaderParser.ReadHeaders(clientStream, args.HttpClient.Request.Headers, var request = args.HttpClient.Request; request.RequestUri = httpRemoteUri; - request.OriginalUrl = httpUrl; + request.OriginalUrlData = HttpHeader.Encoding.GetBytes(httpUrl); request.Method = httpMethod; request.HttpVersion = version; - args.ProxyClient.ClientStream = clientStream; - args.ProxyClient.ClientStreamWriter = clientStreamWriter; if (!args.IsTransparent) { diff --git a/src/Titanium.Web.Proxy/ResponseHandler.cs b/src/Titanium.Web.Proxy/ResponseHandler.cs index 2b8a4dc3d..18ff4aa97 100644 --- a/src/Titanium.Web.Proxy/ResponseHandler.cs +++ b/src/Titanium.Web.Proxy/ResponseHandler.cs @@ -93,7 +93,7 @@ private async Task handleHttpSessionResponse(SessionEventArgs args) // clear current response await args.ClearResponse(cancellationToken); - await handleHttpSessionRequest(args.HttpClient.Request.Method, args.HttpClient.Request.RequestUriString, args.HttpClient.Request.HttpVersion, + await handleHttpSessionRequest(args.HttpClient.Request.Method, args.HttpClient.Request.Url, args.HttpClient.Request.HttpVersion, args, null, args.ClientConnection.NegotiatedApplicationProtocol, cancellationToken, args.CancellationTokenSource); return; diff --git a/src/Titanium.Web.Proxy/StreamExtended/ClientHelloInfo.cs b/src/Titanium.Web.Proxy/StreamExtended/ClientHelloInfo.cs index e4e9e3f4b..9dc0b56ba 100644 --- a/src/Titanium.Web.Proxy/StreamExtended/ClientHelloInfo.cs +++ b/src/Titanium.Web.Proxy/StreamExtended/ClientHelloInfo.cs @@ -17,19 +17,19 @@ public class ClientHelloInfo "DEFLATE" }; - public int HandshakeVersion { get; set; } + public int HandshakeVersion { get; } - public int MajorVersion { get; set; } + public int MajorVersion { get; } - public int MinorVersion { get; set; } + public int MinorVersion { get; } - public byte[] Random { get; set; } + public byte[] Random { get; } public DateTime Time { get { - DateTime time = DateTime.MinValue; + var time = DateTime.MinValue; if (Random.Length > 3) { time = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc) @@ -42,13 +42,13 @@ public DateTime Time public byte[] SessionId { get; } - public int[] Ciphers { get; set; } + public int[] Ciphers { get; } - public byte[] CompressionData { get; set; } + public byte[]? CompressionData { get; internal set; } - internal int ClientHelloLength { get; set; } + internal int ClientHelloLength { get; } - internal int EntensionsStartPosition { get; set; } + internal int ExtensionsStartPosition { get; set; } public Dictionary? Extensions { get; set; } @@ -79,9 +79,15 @@ public SslProtocols SslProtocol } } - public ClientHelloInfo(byte[] sessionId) + internal ClientHelloInfo(int handshakeVersion, int majorVersion, int minorVersion, byte[] random, byte[] sessionId, int[] ciphers, int clientHelloLength) { + HandshakeVersion = handshakeVersion; + MajorVersion = majorVersion; + MinorVersion = minorVersion; + Random = random; SessionId = sessionId; + Ciphers = ciphers; + ClientHelloLength = clientHelloLength; } private static string SslVersionToString(int major, int minor) diff --git a/src/Titanium.Web.Proxy/StreamExtended/Network/CopyStream.cs b/src/Titanium.Web.Proxy/StreamExtended/Network/CopyStream.cs index e86eed49e..2e6b35ee8 100644 --- a/src/Titanium.Web.Proxy/StreamExtended/Network/CopyStream.cs +++ b/src/Titanium.Web.Proxy/StreamExtended/Network/CopyStream.cs @@ -9,9 +9,9 @@ namespace Titanium.Web.Proxy.StreamExtended.Network /// Copies the source stream to destination stream. /// But this let users to peek and read the copying process. /// - public class CopyStream : ICustomStreamReader, IDisposable + internal class CopyStream : ILineStream, IDisposable { - private readonly ICustomStreamReader reader; + private readonly CustomBufferedStream reader; private readonly ICustomStreamWriter writer; @@ -23,13 +23,11 @@ public class CopyStream : ICustomStreamReader, IDisposable private bool disposed; - public int Available => reader.Available; - public bool DataAvailable => reader.DataAvailable; public long ReadBytes { get; private set; } - public CopyStream(ICustomStreamReader reader, ICustomStreamWriter writer, IBufferPool bufferPool) + public CopyStream(CustomBufferedStream reader, ICustomStreamWriter writer, IBufferPool bufferPool) { this.reader = reader; this.writer = writer; @@ -43,31 +41,6 @@ public async ValueTask FillBufferAsync(CancellationToken cancellationToken return await reader.FillBufferAsync(cancellationToken); } - public byte PeekByteFromBuffer(int index) - { - return reader.PeekByteFromBuffer(index); - } - - public ValueTask PeekByteAsync(int index, CancellationToken cancellationToken = default) - { - return reader.PeekByteAsync(index, cancellationToken); - } - - public ValueTask PeekBytesAsync(byte[] buffer, int offset, int index, int size, CancellationToken cancellationToken = default) - { - return reader.PeekBytesAsync(buffer, offset, index, size, cancellationToken); - } - - public void Flush() - { - // send out the current data from from the buffer - if (bufferLength > 0) - { - writer.Write(buffer, 0, bufferLength); - bufferLength = 0; - } - } - public async Task FlushAsync(CancellationToken cancellationToken = default) { // send out the current data from from the buffer @@ -86,63 +59,6 @@ public byte ReadByteFromBuffer() return b; } - public int Read(byte[] buffer, int offset, int count) - { - int result = reader.Read(buffer, offset, count); - if (result > 0) - { - if (bufferLength + result > bufferPool.BufferSize) - { - Flush(); - } - - Buffer.BlockCopy(buffer, offset, this.buffer, bufferLength, result); - bufferLength += result; - ReadBytes += result; - Flush(); - } - - return result; - } - - public async Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken = default) - { - int result = await reader.ReadAsync(buffer, offset, count, cancellationToken); - if (result > 0) - { - if (bufferLength + result > bufferPool.BufferSize) - { - await FlushAsync(cancellationToken); - } - - Buffer.BlockCopy(buffer, offset, this.buffer, bufferLength, result); - bufferLength += result; - ReadBytes += result; - await FlushAsync(cancellationToken); - } - - return result; - } - - public async ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken = default) - { - int result = await reader.ReadAsync(buffer, cancellationToken); - if (result > 0) - { - if (bufferLength + result > bufferPool.BufferSize) - { - await FlushAsync(cancellationToken); - } - - buffer.Span.Slice(0, result).CopyTo(new Span(this.buffer, bufferLength, result)); - bufferLength += result; - ReadBytes += result; - await FlushAsync(cancellationToken); - } - - return result; - } - public ValueTask ReadLineAsync(CancellationToken cancellationToken = default) { return CustomBufferedStream.ReadLineInternalAsync(this, bufferPool, cancellationToken); diff --git a/src/Titanium.Web.Proxy/StreamExtended/Network/CustomBufferedPeekStream.cs b/src/Titanium.Web.Proxy/StreamExtended/Network/CustomBufferedPeekStream.cs deleted file mode 100644 index 42acd087c..000000000 --- a/src/Titanium.Web.Proxy/StreamExtended/Network/CustomBufferedPeekStream.cs +++ /dev/null @@ -1,162 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using Titanium.Web.Proxy.StreamExtended.BufferPool; - -namespace Titanium.Web.Proxy.StreamExtended.Network -{ - internal class CustomBufferedPeekStream : ICustomStreamReader - { - private readonly IBufferPool bufferPool; - private readonly ICustomStreamReader baseStream; - - internal int Position { get; private set; } - - internal CustomBufferedPeekStream(ICustomStreamReader baseStream, IBufferPool bufferPool, int startPosition = 0) - { - this.bufferPool = bufferPool; - this.baseStream = baseStream; - Position = startPosition; - } - - /// - /// Gets a value indicating whether data is available. - /// - bool ICustomStreamReader.DataAvailable => Available > 0; - - /// - /// Gets the available data size. - /// - public int Available => baseStream.Available - Position; - - internal async Task EnsureBufferLength(int length, CancellationToken cancellationToken) - { - var val = await baseStream.PeekByteAsync(Position + length - 1, cancellationToken); - return val != -1; - } - - internal byte ReadByte() - { - return baseStream.PeekByteFromBuffer(Position++); - } - - internal int ReadInt16() - { - int i1 = ReadByte(); - int i2 = ReadByte(); - return (i1 << 8) + i2; - } - - internal int ReadInt24() - { - int i1 = ReadByte(); - int i2 = ReadByte(); - int i3 = ReadByte(); - return (i1 << 16) + (i2 << 8) + i3; - } - - internal byte[] ReadBytes(int length) - { - var buffer = new byte[length]; - for (int i = 0; i < buffer.Length; i++) - { - buffer[i] = ReadByte(); - } - - return buffer; - } - - /// - /// Fills the buffer asynchronous. - /// - /// - ValueTask ICustomStreamReader.FillBufferAsync(CancellationToken cancellationToken) - { - return baseStream.FillBufferAsync(cancellationToken); - } - - /// - /// Peeks a byte from buffer. - /// - /// The index. - /// - byte ICustomStreamReader.PeekByteFromBuffer(int index) - { - return baseStream.PeekByteFromBuffer(index); - } - - /// - /// Peeks bytes asynchronous. - /// - /// The buffer to copy. - /// The offset where copying. - /// The index. - /// The count. - /// The cancellation token. - /// - ValueTask ICustomStreamReader.PeekBytesAsync(byte[] buffer, int offset, int index, int count, CancellationToken cancellationToken) - { - return baseStream.PeekBytesAsync(buffer, offset, index, count, cancellationToken); - } - - /// - /// Peeks a byte asynchronous. - /// - /// The index. - /// The cancellation token. - /// - ValueTask ICustomStreamReader.PeekByteAsync(int index, CancellationToken cancellationToken) - { - return baseStream.PeekByteAsync(index, cancellationToken); - } - - /// - /// Reads a byte from buffer. - /// - /// - byte ICustomStreamReader.ReadByteFromBuffer() - { - return ReadByte(); - } - - int ICustomStreamReader.Read(byte[] buffer, int offset, int count) - { - return baseStream.Read(buffer, offset, count); - } - - /// - /// Reads the asynchronous. - /// - /// The buffer. - /// The offset. - /// The count. - /// The cancellation token. - /// - Task ICustomStreamReader.ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) - { - return baseStream.ReadAsync(buffer, offset, count, cancellationToken); - } - - /// - /// Reads the asynchronous. - /// - /// The buffer. - /// The cancellation token. - /// - public ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken = default) - { - return baseStream.ReadAsync(buffer, cancellationToken); - } - - /// - /// Read a line from the byte stream - /// - /// - /// - ValueTask ICustomStreamReader.ReadLineAsync(CancellationToken cancellationToken) - { - return CustomBufferedStream.ReadLineInternalAsync(this, bufferPool, cancellationToken); - } - - } -} diff --git a/src/Titanium.Web.Proxy/StreamExtended/Network/CustomBufferedStream.cs b/src/Titanium.Web.Proxy/StreamExtended/Network/CustomBufferedStream.cs index c7976d683..7055202f6 100644 --- a/src/Titanium.Web.Proxy/StreamExtended/Network/CustomBufferedStream.cs +++ b/src/Titanium.Web.Proxy/StreamExtended/Network/CustomBufferedStream.cs @@ -6,6 +6,7 @@ using System.Threading; using System.Threading.Tasks; using Titanium.Web.Proxy.Helpers; +using Titanium.Web.Proxy.Models; using Titanium.Web.Proxy.StreamExtended.BufferPool; namespace Titanium.Web.Proxy.StreamExtended.Network @@ -16,13 +17,12 @@ namespace Titanium.Web.Proxy.StreamExtended.Network /// of UTF-8 encoded string or raw bytes asynchronously from last read position. /// /// - internal class CustomBufferedStream : Stream, ICustomStreamReader + internal class CustomBufferedStream : Stream, IPeekStream, ILineStream { private readonly bool leaveOpen; private readonly byte[] streamBuffer; - // default to UTF-8 - private static Encoding encoding => HttpHelper.HeaderEncoding; + private static Encoding encoding => HttpHeader.Encoding; private static readonly bool networkStreamHack = true; @@ -606,7 +606,7 @@ public async ValueTask FillBufferAsync(CancellationToken cancellationToken /// Read a line from the byte stream /// /// - internal static async ValueTask ReadLineInternalAsync(ICustomStreamReader reader, IBufferPool bufferPool, CancellationToken cancellationToken = default) + internal static async ValueTask ReadLineInternalAsync(ILineStream reader, IBufferPool bufferPool, CancellationToken cancellationToken = default) { byte lastChar = default; diff --git a/src/Titanium.Web.Proxy/StreamExtended/Network/ICustomStreamReader.cs b/src/Titanium.Web.Proxy/StreamExtended/Network/ICustomStreamReader.cs deleted file mode 100644 index d448adcf6..000000000 --- a/src/Titanium.Web.Proxy/StreamExtended/Network/ICustomStreamReader.cs +++ /dev/null @@ -1,86 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace Titanium.Web.Proxy.StreamExtended.Network -{ - /// - /// This concrete implemetation of interface acts as the source stream for CopyStream class. - /// - public interface ICustomStreamReader - { - int Available { get; } - - bool DataAvailable { get; } - - /// - /// Fills the buffer asynchronous. - /// - /// - ValueTask FillBufferAsync(CancellationToken cancellationToken = default); - - /// - /// Peeks a byte from buffer. - /// - /// The index. - /// - /// Index is out of buffer size - byte PeekByteFromBuffer(int index); - - /// - /// Peeks a byte asynchronous. - /// - /// The index. - /// The cancellation token. - /// - ValueTask PeekByteAsync(int index, CancellationToken cancellationToken = default); - - /// - /// Peeks bytes asynchronous. - /// - /// The buffer to copy. - /// The offset where copying. - /// The index. - /// The count. - /// The cancellation token. - /// - ValueTask PeekBytesAsync(byte[] buffer, int offset, int index, int count, CancellationToken cancellationToken = default); - - byte ReadByteFromBuffer(); - - /// - /// When overridden in a derived class, reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read. - /// - /// An array of bytes. When this method returns, the buffer contains the specified byte array with the values between and ( + - 1) replaced by the bytes read from the current source. - /// The zero-based byte offset in at which to begin storing the data read from the current stream. - /// The maximum number of bytes to be read from the current stream. - /// - /// The total number of bytes read into the buffer. This can be less than the number of bytes requested if that many bytes are not currently available, or zero (0) if the end of the stream has been reached. - /// - int Read(byte[] buffer, int offset, int count); - - /// - /// Read the specified number (or less) of raw bytes from the base stream to the given buffer to the specified offset - /// - /// - /// - /// - /// - /// The number of bytes read - Task ReadAsync(byte[] buffer, int offset, int bytesToRead, CancellationToken cancellationToken = default); - - /// - /// Read the specified number (or less) of raw bytes from the base stream to the given buffer to the specified offset - /// - /// - /// - /// The number of bytes read - ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken = default); - - /// - /// Read a line from the byte stream - /// - /// - ValueTask ReadLineAsync(CancellationToken cancellationToken = default); - } -} diff --git a/src/Titanium.Web.Proxy/StreamExtended/Network/ILineStream.cs b/src/Titanium.Web.Proxy/StreamExtended/Network/ILineStream.cs new file mode 100644 index 000000000..ac8e41abd --- /dev/null +++ b/src/Titanium.Web.Proxy/StreamExtended/Network/ILineStream.cs @@ -0,0 +1,24 @@ +using System.Threading; +using System.Threading.Tasks; + +namespace Titanium.Web.Proxy.StreamExtended.Network +{ + public interface ILineStream + { + bool DataAvailable { get; } + + /// + /// Fills the buffer asynchronous. + /// + /// + ValueTask FillBufferAsync(CancellationToken cancellationToken = default); + + byte ReadByteFromBuffer(); + + /// + /// Read a line from the byte stream + /// + /// + ValueTask ReadLineAsync(CancellationToken cancellationToken = default); + } +} diff --git a/src/Titanium.Web.Proxy/StreamExtended/Network/IPeekStream.cs b/src/Titanium.Web.Proxy/StreamExtended/Network/IPeekStream.cs new file mode 100644 index 000000000..bb42377e2 --- /dev/null +++ b/src/Titanium.Web.Proxy/StreamExtended/Network/IPeekStream.cs @@ -0,0 +1,36 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Titanium.Web.Proxy.StreamExtended.Network +{ + public interface IPeekStream + { + /// + /// Peeks a byte from buffer. + /// + /// The index. + /// + /// Index is out of buffer size + byte PeekByteFromBuffer(int index); + + /// + /// Peeks a byte asynchronous. + /// + /// The index. + /// The cancellation token. + /// + ValueTask PeekByteAsync(int index, CancellationToken cancellationToken = default); + + /// + /// Peeks bytes asynchronous. + /// + /// The buffer to copy. + /// The offset where copying. + /// The index. + /// The count. + /// The cancellation token. + /// + ValueTask PeekBytesAsync(byte[] buffer, int offset, int index, int count, CancellationToken cancellationToken = default); + } +} \ No newline at end of file diff --git a/src/Titanium.Web.Proxy/StreamExtended/Network/PeekStreamReader.cs b/src/Titanium.Web.Proxy/StreamExtended/Network/PeekStreamReader.cs new file mode 100644 index 000000000..df5ba9a1b --- /dev/null +++ b/src/Titanium.Web.Proxy/StreamExtended/Network/PeekStreamReader.cs @@ -0,0 +1,57 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Titanium.Web.Proxy.StreamExtended.BufferPool; + +namespace Titanium.Web.Proxy.StreamExtended.Network +{ + internal class PeekStreamReader + { + private readonly IPeekStream baseStream; + + public int Position { get; private set; } + + public PeekStreamReader(IPeekStream baseStream, int startPosition = 0) + { + this.baseStream = baseStream; + Position = startPosition; + } + + public async ValueTask EnsureBufferLength(int length, CancellationToken cancellationToken) + { + var val = await baseStream.PeekByteAsync(Position + length - 1, cancellationToken); + return val != -1; + } + + public byte ReadByte() + { + return baseStream.PeekByteFromBuffer(Position++); + } + + public int ReadInt16() + { + int i1 = ReadByte(); + int i2 = ReadByte(); + return (i1 << 8) + i2; + } + + public int ReadInt24() + { + int i1 = ReadByte(); + int i2 = ReadByte(); + int i3 = ReadByte(); + return (i1 << 16) + (i2 << 8) + i3; + } + + public byte[] ReadBytes(int length) + { + var buffer = new byte[length]; + for (int i = 0; i < buffer.Length; i++) + { + buffer[i] = ReadByte(); + } + + return buffer; + } + } +} diff --git a/src/Titanium.Web.Proxy/StreamExtended/ServerHelloInfo.cs b/src/Titanium.Web.Proxy/StreamExtended/ServerHelloInfo.cs index 009a6d05c..bbcccda3f 100644 --- a/src/Titanium.Web.Proxy/StreamExtended/ServerHelloInfo.cs +++ b/src/Titanium.Web.Proxy/StreamExtended/ServerHelloInfo.cs @@ -16,13 +16,25 @@ public class ServerHelloInfo "DEFLATE" }; - public int HandshakeVersion { get; set; } + public ServerHelloInfo(int handshakeVersion, int majorVersion, int minorVersion, byte[] random, + byte[] sessionId, int cipherSuite, int serverHelloLength) + { + HandshakeVersion = handshakeVersion; + MajorVersion = majorVersion; + MinorVersion = minorVersion; + Random = random; + SessionId = sessionId; + CipherSuite = cipherSuite; + ServerHelloLength = serverHelloLength; + } + + public int HandshakeVersion { get; } - public int MajorVersion { get; set; } + public int MajorVersion { get; } - public int MinorVersion { get; set; } + public int MinorVersion { get; } - public byte[] Random { get; set; } + public byte[] Random { get; } public DateTime Time { @@ -39,13 +51,13 @@ public DateTime Time } } - public byte[] SessionId { get; set; } + public byte[] SessionId { get; } - public int CipherSuite { get; set; } + public int CipherSuite { get; } public byte CompressionMethod { get; set; } - internal int ServerHelloLength { get; set; } + internal int ServerHelloLength { get; } internal int EntensionsStartPosition { get; set; } diff --git a/src/Titanium.Web.Proxy/StreamExtended/SslTools.cs b/src/Titanium.Web.Proxy/StreamExtended/SslTools.cs index d3d5cc98d..19a508dcb 100644 --- a/src/Titanium.Web.Proxy/StreamExtended/SslTools.cs +++ b/src/Titanium.Web.Proxy/StreamExtended/SslTools.cs @@ -12,19 +12,6 @@ namespace Titanium.Web.Proxy.StreamExtended /// internal class SslTools { - /// - /// Is the given stream starts with an SSL client hello? - /// - /// - /// - /// - /// - public static async Task IsClientHello(CustomBufferedStream stream, IBufferPool bufferPool, CancellationToken cancellationToken) - { - var clientHello = await PeekClientHello(stream, bufferPool, cancellationToken); - return clientHello != null; - } - /// /// Peek the SSL client hello information. /// @@ -46,7 +33,7 @@ public static async Task IsClientHello(CustomBufferedStream stream, IBuffe if ((recordType & 0x80) == 0x80) { // SSL 2 - var peekStream = new CustomBufferedPeekStream(clientStream, bufferPool, 1); + var peekStream = new PeekStreamReader(clientStream, 1); // length value + minimum length if (!await peekStream.EnsureBufferLength(10, cancellationToken)) @@ -88,21 +75,14 @@ public static async Task IsClientHello(CustomBufferedStream stream, IBuffe byte[] sessionId = peekStream.ReadBytes(sessionIdLength); byte[] random = peekStream.ReadBytes(randomLength); - var clientHelloInfo = new ClientHelloInfo(sessionId) - { - HandshakeVersion = 2, - MajorVersion = majorVersion, - MinorVersion = minorVersion, - Random = random, - Ciphers = ciphers, - ClientHelloLength = peekStream.Position, - }; + var clientHelloInfo = new ClientHelloInfo(2, majorVersion, minorVersion, random, sessionId, ciphers, + peekStream.Position); return clientHelloInfo; } else if (recordType == 0x16) { - var peekStream = new CustomBufferedPeekStream(clientStream, bufferPool, 1); + var peekStream = new PeekStreamReader(clientStream, 1); // should contain at least 43 bytes // 2 version + 2 length + 1 type + 3 length(?) + 2 version + 32 random + 1 sessionid length @@ -168,25 +148,19 @@ public static async Task IsClientHello(CustomBufferedStream stream, IBuffe byte[] compressionData = peekStream.ReadBytes(length); - int extenstionsStartPosition = peekStream.Position; + int extensionsStartPosition = peekStream.Position; Dictionary? extensions = null; - if(extenstionsStartPosition < recordLength + 5) + if(extensionsStartPosition < recordLength + 5) { extensions = await ReadExtensions(majorVersion, minorVersion, peekStream, bufferPool, cancellationToken); } - var clientHelloInfo = new ClientHelloInfo(sessionId) + var clientHelloInfo = new ClientHelloInfo(3, majorVersion, minorVersion, random, sessionId, ciphers, peekStream.Position) { - HandshakeVersion = 3, - MajorVersion = majorVersion, - MinorVersion = minorVersion, - Random = random, - Ciphers = ciphers, + ExtensionsStartPosition = extensionsStartPosition, CompressionData = compressionData, - ClientHelloLength = peekStream.Position, - EntensionsStartPosition = extenstionsStartPosition, Extensions = extensions, }; @@ -231,7 +205,7 @@ public static async Task IsServerHello(CustomBufferedStream stream, IBuffe { // SSL 2 // not tested. SSL2 is deprecated - var peekStream = new CustomBufferedPeekStream(serverStream, bufferPool, 1); + var peekStream = new PeekStreamReader(serverStream, 1); // length value + minimum length if (!await peekStream.EnsureBufferLength(39, cancellationToken)) @@ -265,22 +239,14 @@ public static async Task IsServerHello(CustomBufferedStream stream, IBuffe byte[] sessionId = peekStream.ReadBytes(1); int cipherSuite = peekStream.ReadInt16(); - var serverHelloInfo = new ServerHelloInfo - { - HandshakeVersion = 2, - MajorVersion = majorVersion, - MinorVersion = minorVersion, - Random = random, - SessionId = sessionId, - CipherSuite = cipherSuite, - ServerHelloLength = peekStream.Position, - }; + var serverHelloInfo = new ServerHelloInfo(2, majorVersion, minorVersion, random, sessionId, cipherSuite, + peekStream.Position); return serverHelloInfo; } else if (recordType == 0x16) { - var peekStream = new CustomBufferedPeekStream(serverStream, bufferPool, 1); + var peekStream = new PeekStreamReader(serverStream, 1); // should contain at least 43 bytes // 2 version + 2 length + 1 type + 3 length(?) + 2 version + 32 random + 1 sessionid length @@ -329,16 +295,9 @@ public static async Task IsServerHello(CustomBufferedStream stream, IBuffe extensions = await ReadExtensions(majorVersion, minorVersion, peekStream, bufferPool, cancellationToken); } - var serverHelloInfo = new ServerHelloInfo + var serverHelloInfo = new ServerHelloInfo(3, majorVersion, minorVersion, random, sessionId, cipherSuite, peekStream.Position) { - HandshakeVersion = 3, - MajorVersion = majorVersion, - MinorVersion = minorVersion, - Random = random, - SessionId = sessionId, - CipherSuite = cipherSuite, CompressionMethod = compressionMethod, - ServerHelloLength = peekStream.Position, EntensionsStartPosition = extenstionsStartPosition, Extensions = extensions, }; @@ -349,24 +308,24 @@ public static async Task IsServerHello(CustomBufferedStream stream, IBuffe return null; } - private static async Task?> ReadExtensions(int majorVersion, int minorVersion, CustomBufferedPeekStream peekStream, IBufferPool bufferPool, CancellationToken cancellationToken) + private static async Task?> ReadExtensions(int majorVersion, int minorVersion, PeekStreamReader peekStreamReader, IBufferPool bufferPool, CancellationToken cancellationToken) { Dictionary? extensions = null; if (majorVersion > 3 || majorVersion == 3 && minorVersion >= 1) { - if (await peekStream.EnsureBufferLength(2, cancellationToken)) + if (await peekStreamReader.EnsureBufferLength(2, cancellationToken)) { - int extensionsLength = peekStream.ReadInt16(); + int extensionsLength = peekStreamReader.ReadInt16(); - if (await peekStream.EnsureBufferLength(extensionsLength, cancellationToken)) + if (await peekStreamReader.EnsureBufferLength(extensionsLength, cancellationToken)) { extensions = new Dictionary(); int idx = 0; while (extensionsLength > 3) { - int id = peekStream.ReadInt16(); - int length = peekStream.ReadInt16(); - byte[] data = peekStream.ReadBytes(length); + int id = peekStreamReader.ReadInt16(); + int length = peekStreamReader.ReadInt16(); + byte[] data = peekStreamReader.ReadBytes(length); var extension = SslExtensions.GetExtension(id, data, idx++); extensions[extension.Name] = extension; extensionsLength -= 4 + length; diff --git a/src/Titanium.Web.Proxy/TransparentClientHandler.cs b/src/Titanium.Web.Proxy/TransparentClientHandler.cs index 0c86b82ab..83aee5690 100644 --- a/src/Titanium.Web.Proxy/TransparentClientHandler.cs +++ b/src/Titanium.Web.Proxy/TransparentClientHandler.cs @@ -12,6 +12,7 @@ using Titanium.Web.Proxy.Extensions; using Titanium.Web.Proxy.Helpers; using Titanium.Web.Proxy.Models; +using Titanium.Web.Proxy.Network; using Titanium.Web.Proxy.Network.Tcp; using Titanium.Web.Proxy.StreamExtended; using Titanium.Web.Proxy.StreamExtended.Network; @@ -81,11 +82,8 @@ private async Task handleClient(TransparentProxyEndPoint endPoint, TcpClientConn catch (Exception e) { var certname = certificate?.GetNameInfo(X509NameType.SimpleName, false); - var session = new SessionEventArgs(this, endPoint, cancellationTokenSource) - { - ProxyClient = { Connection = clientConnection }, - HttpClient = { ConnectRequest = null } - }; + var session = new SessionEventArgs(this, endPoint, new ProxyClient(clientConnection, clientStream, clientStreamWriter), null, + cancellationTokenSource); throw new ProxyConnectException( $"Couldn't authenticate host '{httpsHostName}' with certificate '{certname}'.", e, session); } diff --git a/src/Titanium.Web.Proxy/WinAuthHandler.cs b/src/Titanium.Web.Proxy/WinAuthHandler.cs index b9d9b7a27..72f13a592 100644 --- a/src/Titanium.Web.Proxy/WinAuthHandler.cs +++ b/src/Titanium.Web.Proxy/WinAuthHandler.cs @@ -132,7 +132,7 @@ private async Task handle401UnAuthorized(SessionEventArgs args) authHeader.Value.Length > x.Length + 1); string serverToken = authHeader.Value.Substring(scheme.Length + 1); - string clientToken = WinAuthHandler.GetFinalAuthToken(request.Host, serverToken, args.HttpClient.Data); + string clientToken = WinAuthHandler.GetFinalAuthToken(request.Host!, serverToken, args.HttpClient.Data); string auth = string.Concat(scheme, clientToken); diff --git a/tests/Titanium.Web.Proxy.IntegrationTests/Helpers/HttpContinueClient.cs b/tests/Titanium.Web.Proxy.IntegrationTests/Helpers/HttpContinueClient.cs index f008bb4c4..feca67d23 100644 --- a/tests/Titanium.Web.Proxy.IntegrationTests/Helpers/HttpContinueClient.cs +++ b/tests/Titanium.Web.Proxy.IntegrationTests/Helpers/HttpContinueClient.cs @@ -22,7 +22,7 @@ public async Task Post(string server, int port, string content) var request = new Request { Method = "POST", - RequestUriString = "/", + Url = "/", HttpVersion = new Version(1, 1) }; request.Headers.AddHeader(KnownHeaders.Host, server); diff --git a/tests/Titanium.Web.Proxy.IntegrationTests/Helpers/HttpMessageParsing.cs b/tests/Titanium.Web.Proxy.IntegrationTests/Helpers/HttpMessageParsing.cs index d7b1b7b7a..933fbfab7 100644 --- a/tests/Titanium.Web.Proxy.IntegrationTests/Helpers/HttpMessageParsing.cs +++ b/tests/Titanium.Web.Proxy.IntegrationTests/Helpers/HttpMessageParsing.cs @@ -26,14 +26,14 @@ internal static Request ParseRequest(string messageText, bool requireBody) try { Request.ParseRequestLine(line, out var method, out var url, out var version); - RequestResponseBase request = new Request + var request = new Request { - Method = method, RequestUriString = url, HttpVersion = version + Method = method, Url = url, HttpVersion = version }; while (!string.IsNullOrEmpty(line = reader.ReadLine())) { var header = line.Split(colonSplit, 2); - request.Headers.AddHeader(header[0], header[1]); + request.Headers.AddHeader(header[0].Trim(), header[1].Trim()); } // First zero-length line denotes end of headers. If we @@ -42,10 +42,10 @@ internal static Request ParseRequest(string messageText, bool requireBody) return null; if (!requireBody) - return request as Request; + return request; - if (parseBody(reader, ref request)) - return request as Request; + if (parseBody(reader, request)) + return request; } catch { @@ -71,7 +71,7 @@ internal static Response ParseResponse(string messageText) try { Response.ParseResponseLine(line, out var version, out var status, out var desc); - RequestResponseBase response = new Response + var response = new Response { HttpVersion = version, StatusCode = status, StatusDescription = desc }; @@ -87,8 +87,8 @@ internal static Response ParseResponse(string messageText) if (line?.Length != 0) return null; - if (parseBody(reader, ref response)) - return response as Response; + if (parseBody(reader, response)) + return response; } catch { @@ -98,7 +98,7 @@ internal static Response ParseResponse(string messageText) return null; } - private static bool parseBody(StringReader reader, ref RequestResponseBase obj) + private static bool parseBody(StringReader reader, RequestResponseBase obj) { obj.OriginalContentLength = obj.ContentLength; if (obj.ContentLength <= 0) diff --git a/tests/Titanium.Web.Proxy.IntegrationTests/Titanium.Web.Proxy.IntegrationTests.csproj b/tests/Titanium.Web.Proxy.IntegrationTests/Titanium.Web.Proxy.IntegrationTests.csproj index 42c450b0b..279dcb57d 100644 --- a/tests/Titanium.Web.Proxy.IntegrationTests/Titanium.Web.Proxy.IntegrationTests.csproj +++ b/tests/Titanium.Web.Proxy.IntegrationTests/Titanium.Web.Proxy.IntegrationTests.csproj @@ -13,7 +13,7 @@ - +