Unverified Commit ebed40b1 authored by honfika's avatar honfika Committed by GitHub

Merge pull request #665 from justcoding121/beta

Stable
parents d8df61ba 3b807dad
......@@ -3,6 +3,7 @@ using System.Globalization;
using System.IO;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Exceptions;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.StreamExtended.BufferPool;
using Titanium.Web.Proxy.StreamExtended.Network;
......@@ -11,16 +12,16 @@ namespace Titanium.Web.Proxy.EventArguments
internal class LimitedStream : Stream
{
private readonly IBufferPool bufferPool;
private readonly CustomBufferedStream baseStream;
private readonly IHttpStreamReader baseReader;
private readonly bool isChunked;
private long bytesRemaining;
private bool readChunkTrail;
internal LimitedStream(CustomBufferedStream baseStream, IBufferPool bufferPool, bool isChunked,
internal LimitedStream(IHttpStreamReader baseStream, IBufferPool bufferPool, bool isChunked,
long contentLength)
{
this.baseStream = baseStream;
this.baseReader = baseStream;
this.bufferPool = bufferPool;
this.isChunked = isChunked;
bytesRemaining = isChunked
......@@ -49,12 +50,12 @@ namespace Titanium.Web.Proxy.EventArguments
if (readChunkTrail)
{
// read the chunk trail of the previous chunk
string? s = baseStream.ReadLineAsync().Result;
string? s = baseReader.ReadLineAsync().Result;
}
readChunkTrail = true;
string? chunkHead = baseStream.ReadLineAsync().Result!;
string? chunkHead = baseReader.ReadLineAsync().Result!;
int idx = chunkHead.IndexOf(";", StringComparison.Ordinal);
if (idx >= 0)
{
......@@ -73,7 +74,7 @@ namespace Titanium.Web.Proxy.EventArguments
bytesRemaining = -1;
// chunk trail
var task = baseStream.ReadLineAsync();
var task = baseReader.ReadLineAsync();
if (!task.IsCompleted)
task.AsTask().Wait();
}
......@@ -119,7 +120,7 @@ namespace Titanium.Web.Proxy.EventArguments
}
int toRead = (int)Math.Min(count, bytesRemaining);
int res = baseStream.Read(buffer, offset, toRead);
int res = baseReader.Read(buffer, offset, toRead);
bytesRemaining -= res;
if (res == 0)
......
......@@ -10,6 +10,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.Network.Tcp;
using Titanium.Web.Proxy.StreamExtended.Network;
namespace Titanium.Web.Proxy.EventArguments
......@@ -22,8 +23,6 @@ namespace Titanium.Web.Proxy.EventArguments
/// </summary>
public class SessionEventArgs : SessionEventArgsBase
{
private static readonly byte[] emptyData = new byte[0];
/// <summary>
/// Backing field for corresponding public property
/// </summary>
......@@ -37,8 +36,8 @@ namespace Titanium.Web.Proxy.EventArguments
/// <summary>
/// Constructor to initialize the proxy
/// </summary>
internal SessionEventArgs(ProxyServer server, ProxyEndPoint endPoint, ProxyClient proxyClient, ConnectRequest? connectRequest, CancellationTokenSource cancellationTokenSource)
: base(server, endPoint, proxyClient, connectRequest, new Request(), cancellationTokenSource)
internal SessionEventArgs(ProxyServer server, ProxyEndPoint endPoint, TcpClientConnection clientConnection, HttpClientStream clientStream, ConnectRequest? connectRequest, CancellationTokenSource cancellationTokenSource)
: base(server, endPoint, clientConnection, clientStream, connectRequest, new Request(), cancellationTokenSource)
{
}
......@@ -66,14 +65,9 @@ namespace Titanium.Web.Proxy.EventArguments
/// </summary>
public event EventHandler<MultipartRequestPartSentEventArgs>? MultipartRequestPartSent;
private CustomBufferedStream getStreamReader(bool isRequest)
{
return isRequest ? ProxyClient.ClientStream : HttpClient.Connection.Stream;
}
private HttpWriter getStreamWriter(bool isRequest)
private HttpStream getStream(bool isRequest)
{
return isRequest ? (HttpWriter)ProxyClient.ClientStreamWriter : HttpClient.Connection.StreamWriter;
return isRequest ? (HttpStream)ClientStream : HttpClient.Connection.Stream;
}
/// <summary>
......@@ -110,7 +104,10 @@ namespace Titanium.Web.Proxy.EventArguments
else
{
var body = await readBodyAsync(true, cancellationToken);
request.Body = body;
if (!request.BodyAvailable)
{
request.Body = body;
}
// Now set the flag to true
// So that next time we can deliver body from cache
......@@ -182,7 +179,10 @@ namespace Titanium.Web.Proxy.EventArguments
else
{
var body = await readBodyAsync(false, cancellationToken);
response.Body = body;
if (!response.BodyAvailable)
{
response.Body = body;
}
// Now set the flag to true
// So that next time we can deliver body from cache
......@@ -193,21 +193,19 @@ namespace Titanium.Web.Proxy.EventArguments
private async Task<byte[]> readBodyAsync(bool isRequest, CancellationToken cancellationToken)
{
using (var bodyStream = new MemoryStream())
{
var writer = new HttpWriter(bodyStream, BufferPool);
if (isRequest)
{
await CopyRequestBodyAsync(writer, TransformationMode.Uncompress, cancellationToken);
}
else
{
await CopyResponseBodyAsync(writer, TransformationMode.Uncompress, cancellationToken);
}
using var bodyStream = new MemoryStream();
using var http = new HttpStream(bodyStream, BufferPool);
return bodyStream.ToArray();
if (isRequest)
{
await CopyRequestBodyAsync(http, TransformationMode.Uncompress, cancellationToken);
}
else
{
await CopyResponseBodyAsync(http, TransformationMode.Uncompress, cancellationToken);
}
return bodyStream.ToArray();
}
/// <summary>
......@@ -225,18 +223,16 @@ namespace Titanium.Web.Proxy.EventArguments
return;
}
using (var bodyStream = new MemoryStream())
{
var writer = new HttpWriter(bodyStream, BufferPool);
await copyBodyAsync(isRequest, true, writer, TransformationMode.None, null, cancellationToken);
}
using var bodyStream = new MemoryStream();
using var http = new HttpStream(bodyStream, BufferPool);
await copyBodyAsync(isRequest, true, http, TransformationMode.None, null, cancellationToken);
}
/// <summary>
/// This is called when the request is PUT/POST/PATCH to read the body
/// </summary>
/// <returns></returns>
internal async Task CopyRequestBodyAsync(HttpWriter writer, TransformationMode transformation, CancellationToken cancellationToken)
internal async Task CopyRequestBodyAsync(IHttpStreamWriter writer, TransformationMode transformation, CancellationToken cancellationToken)
{
var request = HttpClient.Request;
......@@ -245,7 +241,7 @@ namespace Titanium.Web.Proxy.EventArguments
// send the request body bytes to server
if (contentLength > 0 && hasMulipartEventSubscribers && request.IsMultipartFormData)
{
var reader = getStreamReader(true);
var reader = getStream(true);
var boundary = HttpHelper.GetBoundaryFromContentType(request.ContentType);
using (var copyStream = new CopyStream(reader, writer, BufferPool))
......@@ -275,14 +271,14 @@ namespace Titanium.Web.Proxy.EventArguments
}
}
internal async Task CopyResponseBodyAsync(HttpWriter writer, TransformationMode transformation, CancellationToken cancellationToken)
internal async Task CopyResponseBodyAsync(IHttpStreamWriter writer, TransformationMode transformation, CancellationToken cancellationToken)
{
await copyBodyAsync(false, false, writer, transformation, OnDataReceived, cancellationToken);
}
private async Task copyBodyAsync(bool isRequest, bool useOriginalHeaderValues, HttpWriter writer, TransformationMode transformation, Action<byte[], int, int>? onCopy, CancellationToken cancellationToken)
private async Task copyBodyAsync(bool isRequest, bool useOriginalHeaderValues, IHttpStreamWriter writer, TransformationMode transformation, Action<byte[], int, int>? onCopy, CancellationToken cancellationToken)
{
var stream = getStreamReader(isRequest);
var stream = getStream(isRequest);
var requestResponse = isRequest ? (RequestResponseBase)HttpClient.Request : HttpClient.Response;
......@@ -309,10 +305,8 @@ namespace Titanium.Web.Proxy.EventArguments
try
{
using (var bufStream = new CustomBufferedStream(s, BufferPool, true))
{
await writer.CopyBodyAsync(bufStream, false, -1, onCopy, cancellationToken);
}
var http = new HttpStream(s, BufferPool, true);
await writer.CopyBodyAsync(http, false, -1, onCopy, cancellationToken);
}
finally
{
......@@ -595,7 +589,7 @@ namespace Titanium.Web.Proxy.EventArguments
var response = new RedirectResponse();
response.HttpVersion = HttpClient.Request.HttpVersion;
response.Headers.AddHeader(KnownHeaders.Location, url);
response.Body = emptyData;
response.Body = Array.Empty<byte>();
Respond(response, closeServerConnection);
}
......
......@@ -26,7 +26,12 @@ namespace Titanium.Web.Proxy.EventArguments
internal TcpServerConnection ServerConnection => HttpClient.Connection;
internal TcpClientConnection ClientConnection => ProxyClient.Connection;
/// <summary>
/// Holds a reference to client
/// </summary>
internal TcpClientConnection ClientConnection { get; }
internal HttpClientStream ClientStream { get; }
protected readonly IBufferPool BufferPool;
protected readonly ExceptionHandler ExceptionFunc;
......@@ -41,7 +46,7 @@ namespace Titanium.Web.Proxy.EventArguments
/// Initializes a new instance of the <see cref="SessionEventArgsBase" /> class.
/// </summary>
private protected SessionEventArgsBase(ProxyServer server, ProxyEndPoint endPoint,
ProxyClient proxyClient, ConnectRequest? connectRequest, Request request, CancellationTokenSource cancellationTokenSource)
TcpClientConnection clientConnection, HttpClientStream clientStream, ConnectRequest? connectRequest, Request request, CancellationTokenSource cancellationTokenSource)
{
BufferPool = server.BufferPool;
ExceptionFunc = server.ExceptionFunc;
......@@ -49,17 +54,13 @@ namespace Titanium.Web.Proxy.EventArguments
CancellationTokenSource = cancellationTokenSource;
ProxyClient = proxyClient;
HttpClient = new HttpWebClient(connectRequest, request, new Lazy<int>(() => ProxyClient.Connection.GetProcessId(endPoint)));
ClientConnection = clientConnection;
ClientStream = clientStream;
HttpClient = new HttpWebClient(connectRequest, request, new Lazy<int>(() => clientConnection.GetProcessId(endPoint)));
LocalEndPoint = endPoint;
EnableWinAuth = server.EnableWinAuth && isWindowsAuthenticationSupported;
}
/// <summary>
/// Holds a reference to client
/// </summary>
internal ProxyClient ProxyClient { get; }
/// <summary>
/// Returns a user data for this request/response session which is
/// same as the user data of HttpClient.
......@@ -93,7 +94,7 @@ namespace Titanium.Web.Proxy.EventArguments
/// <summary>
/// Client End Point.
/// </summary>
public IPEndPoint ClientEndPoint => (IPEndPoint)ProxyClient.Connection.RemoteEndPoint;
public IPEndPoint ClientEndPoint => (IPEndPoint)ClientConnection.RemoteEndPoint;
/// <summary>
/// The web client used to communicate with server for this session.
......@@ -106,7 +107,7 @@ namespace Titanium.Web.Proxy.EventArguments
/// <summary>
/// Are we using a custom upstream HTTP(S) proxy?
/// </summary>
public ExternalProxy? CustomUpStreamProxyUsed { get; internal set; }
public IExternalProxy? CustomUpStreamProxyUsed { get; internal set; }
/// <summary>
/// Local endpoint via which we make the request.
......
using System;
using System.Threading;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Network;
using Titanium.Web.Proxy.Network.Tcp;
using Titanium.Web.Proxy.StreamExtended.Network;
namespace Titanium.Web.Proxy.EventArguments
......@@ -15,8 +17,8 @@ namespace Titanium.Web.Proxy.EventArguments
private bool? isHttpsConnect;
internal TunnelConnectSessionEventArgs(ProxyServer server, ProxyEndPoint endPoint, ConnectRequest connectRequest,
ProxyClient proxyClient, CancellationTokenSource cancellationTokenSource)
: base(server, endPoint, proxyClient, connectRequest, connectRequest, cancellationTokenSource)
TcpClientConnection clientConnection, HttpClientStream clientStream, CancellationTokenSource cancellationTokenSource)
: base(server, endPoint, clientConnection, clientStream, connectRequest, connectRequest, cancellationTokenSource)
{
}
......
......@@ -36,8 +36,7 @@ namespace Titanium.Web.Proxy
var cancellationTokenSource = new CancellationTokenSource();
var cancellationToken = cancellationTokenSource.Token;
var clientStream = new CustomBufferedStream(clientConnection.GetStream(), BufferPool);
var clientStreamWriter = new HttpResponseWriter(clientStream, BufferPool);
var clientStream = new HttpClientStream(clientConnection.GetStream(), BufferPool);
Task<TcpServerConnection>? prefetchConnectionTask = null;
bool closeServerConnection = false;
......@@ -70,7 +69,7 @@ namespace Titanium.Web.Proxy
await HeaderParser.ReadHeaders(clientStream, connectRequest.Headers, cancellationToken);
connectArgs = new TunnelConnectSessionEventArgs(this, endPoint, connectRequest,
new ProxyClient(clientConnection, clientStream, clientStreamWriter), cancellationTokenSource);
clientConnection, clientStream, 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);
......@@ -92,7 +91,7 @@ namespace Titanium.Web.Proxy
}
// send the response
await clientStreamWriter.WriteResponseAsync(connectArgs.HttpClient.Response,
await clientStream.WriteResponseAsync(connectArgs.HttpClient.Response,
cancellationToken: cancellationToken);
return;
}
......@@ -102,7 +101,7 @@ namespace Titanium.Web.Proxy
await endPoint.InvokeBeforeTunnelConnectResponse(this, connectArgs, ExceptionFunc);
// send the response
await clientStreamWriter.WriteResponseAsync(connectArgs.HttpClient.Response,
await clientStream.WriteResponseAsync(connectArgs.HttpClient.Response,
cancellationToken: cancellationToken);
return;
}
......@@ -115,14 +114,13 @@ namespace Titanium.Web.Proxy
response.Headers.FixProxyHeaders();
connectArgs.HttpClient.Response = response;
await clientStreamWriter.WriteResponseAsync(response, cancellationToken: cancellationToken);
await clientStream.WriteResponseAsync(response, cancellationToken: cancellationToken);
var clientHelloInfo = await SslTools.PeekClientHello(clientStream, BufferPool, cancellationToken);
bool isClientHello = clientHelloInfo != null;
if (clientHelloInfo != null)
{
connectRequest.IsHttps = true;
connectRequest.TunnelType = TunnelType.Https;
connectRequest.ClientHelloInfo = clientHelloInfo;
}
......@@ -131,6 +129,7 @@ namespace Titanium.Web.Proxy
if (decryptSsl && clientHelloInfo != null)
{
connectRequest.IsHttps = true; // todo: move this line to the previous "if"
clientConnection.SslProtocol = clientHelloInfo.SslProtocol;
bool http2Supported = false;
......@@ -216,10 +215,9 @@ namespace Titanium.Web.Proxy
#endif
// HTTPS server created - we can now decrypt the client's traffic
clientStream = new CustomBufferedStream(sslStream, BufferPool);
clientStream = new HttpClientStream(sslStream, BufferPool);
clientStream.DataRead += (o, args) => connectArgs.OnDecryptedDataSent(args.Buffer, args.Offset, args.Count);
clientStream.DataWrite += (o, args) => connectArgs.OnDecryptedDataReceived(args.Buffer, args.Offset, args.Count);
clientStreamWriter = new HttpResponseWriter(clientStream, BufferPool);
}
catch (Exception e)
{
......@@ -274,7 +272,7 @@ namespace Titanium.Web.Proxy
{
// clientStream.Available should be at most BufferSize because it is using the same buffer size
await clientStream.ReadAsync(data, 0, available, cancellationToken);
await connection.StreamWriter.WriteAsync(data, 0, available, true, cancellationToken);
await connection.Stream.WriteAsync(data, 0, available, true, cancellationToken);
}
finally
{
......@@ -335,9 +333,9 @@ namespace Titanium.Web.Proxy
{
#if NETSTANDARD2_1
var connectionPreface = new ReadOnlyMemory<byte>(Http2Helper.ConnectionPreface);
await connection.StreamWriter.WriteAsync(connectionPreface, cancellationToken);
await connection.Stream.WriteAsync(connectionPreface, cancellationToken);
await Http2Helper.SendHttp2(clientStream, connection.Stream,
() => new SessionEventArgs(this, endPoint, new ProxyClient(clientConnection, clientStream, clientStreamWriter), connectArgs?.HttpClient.ConnectRequest, cancellationTokenSource)
() => new SessionEventArgs(this, endPoint, clientConnection, clientStream, connectArgs?.HttpClient.ConnectRequest, cancellationTokenSource)
{
UserData = connectArgs?.UserData
},
......@@ -356,8 +354,7 @@ namespace Titanium.Web.Proxy
calledRequestHandler = true;
// Now create the request
await handleHttpSessionRequest(endPoint, clientConnection, clientStream, clientStreamWriter,
cancellationTokenSource, connectArgs, prefetchConnectionTask);
await handleHttpSessionRequest(endPoint, clientConnection, clientStream, cancellationTokenSource, connectArgs, prefetchConnectionTask);
}
catch (ProxyException e)
{
......@@ -388,11 +385,6 @@ namespace Titanium.Web.Proxy
sslStream?.Dispose();
clientStream.Dispose();
if (!cancellationTokenSource.IsCancellationRequested)
{
cancellationTokenSource.Cancel();
}
}
}
}
......
......@@ -7,9 +7,9 @@ using Titanium.Web.Proxy.StreamExtended.BufferPool;
namespace Titanium.Web.Proxy.Helpers
{
internal sealed class HttpResponseWriter : HttpWriter
internal sealed class HttpClientStream : HttpStream
{
internal HttpResponseWriter(Stream stream, IBufferPool bufferPool)
internal HttpClientStream(Stream stream, IBufferPool bufferPool)
: base(stream, bufferPool)
{
}
......
......@@ -170,18 +170,18 @@ namespace Titanium.Web.Proxy.Helpers
/// Determines whether is connect method.
/// </summary>
/// <returns>1: when CONNECT, 0: when valid HTTP method, -1: otherwise</returns>
internal static Task<int> IsConnectMethod(CustomBufferedStream clientStreamReader, IBufferPool bufferPool, CancellationToken cancellationToken = default)
internal static Task<int> IsConnectMethod(IPeekStream httpReader, IBufferPool bufferPool, CancellationToken cancellationToken = default)
{
return startsWith(clientStreamReader, bufferPool, "CONNECT", cancellationToken);
return startsWith(httpReader, bufferPool, "CONNECT", cancellationToken);
}
/// <summary>
/// Determines whether is pri method (HTTP/2).
/// </summary>
/// <returns>1: when PRI, 0: when valid HTTP method, -1: otherwise</returns>
internal static Task<int> IsPriMethod(CustomBufferedStream clientStreamReader, IBufferPool bufferPool, CancellationToken cancellationToken = default)
internal static Task<int> IsPriMethod(IPeekStream httpReader, IBufferPool bufferPool, CancellationToken cancellationToken = default)
{
return startsWith(clientStreamReader, bufferPool, "PRI", cancellationToken);
return startsWith(httpReader, bufferPool, "PRI", cancellationToken);
}
/// <summary>
......@@ -190,7 +190,7 @@ namespace Titanium.Web.Proxy.Helpers
/// <returns>
/// 1: when starts with the given string, 0: when valid HTTP method, -1: otherwise
/// </returns>
private static async Task<int> startsWith(CustomBufferedStream clientStreamReader, IBufferPool bufferPool, string expectedStart, CancellationToken cancellationToken = default)
private static async Task<int> startsWith(IPeekStream httpReader, IBufferPool bufferPool, string expectedStart, CancellationToken cancellationToken = default)
{
const int lengthToCheck = 10;
if (bufferPool.BufferSize < lengthToCheck)
......@@ -205,7 +205,7 @@ namespace Titanium.Web.Proxy.Helpers
int i = 0;
while (i < lengthToCheck)
{
int peeked = await clientStreamReader.PeekBytesAsync(buffer, i, i, lengthToCheck - i, cancellationToken);
int peeked = await httpReader.PeekBytesAsync(buffer, i, i, lengthToCheck - i, cancellationToken);
if (peeked <= 0)
return -1;
......
......@@ -6,9 +6,9 @@ using Titanium.Web.Proxy.StreamExtended.BufferPool;
namespace Titanium.Web.Proxy.Helpers
{
internal sealed class HttpRequestWriter : HttpWriter
internal sealed class HttpServerStream : HttpStream
{
internal HttpRequestWriter(Stream stream, IBufferPool bufferPool)
internal HttpServerStream(Stream stream, IBufferPool bufferPool)
: base(stream, bufferPool)
{
}
......
using System;
using System.Buffers;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Helpers;
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;
namespace Titanium.Web.Proxy.StreamExtended.Network
namespace Titanium.Web.Proxy.Helpers
{
/// <summary>
/// A custom network stream inherited from stream
/// with an underlying read buffer supporting both read/write
/// of UTF-8 encoded string or raw bytes asynchronously from last read position.
/// </summary>
/// <seealso cref="System.IO.Stream" />
internal class CustomBufferedStream : Stream, IPeekStream, ILineStream
internal class HttpStream : Stream, IHttpStreamWriter, IHttpStreamReader, IPeekStream
{
private readonly bool swallowException;
private readonly bool leaveOpen;
private readonly byte[] streamBuffer;
......@@ -40,11 +39,11 @@ namespace Titanium.Web.Proxy.StreamExtended.Network
public event EventHandler<DataEventArgs>? DataWrite;
public Stream BaseStream { get; }
private Stream baseStream { get; }
public bool IsClosed => closed;
static CustomBufferedStream()
static HttpStream()
{
// TODO: remove this hack when removing .NET 4.x support
try
......@@ -62,15 +61,22 @@ namespace Titanium.Web.Proxy.StreamExtended.Network
}
}
private static readonly byte[] newLine = ProxyConstants.NewLineBytes;
/// <summary>
/// Initializes a new instance of the <see cref="CustomBufferedStream"/> class.
/// Initializes a new instance of the <see cref="HttpStream"/> class.
/// </summary>
/// <param name="baseStream">The base stream.</param>
/// <param name="bufferPool">Bufferpool.</param>
/// <param name="leaveOpen"><see langword="true" /> to leave the stream open after disposing the <see cref="T:CustomBufferedStream" /> object; otherwise, <see langword="false" />.</param>
public CustomBufferedStream(Stream baseStream, IBufferPool bufferPool, bool leaveOpen = false)
internal HttpStream(Stream baseStream, IBufferPool bufferPool, bool leaveOpen = false)
{
BaseStream = baseStream;
if (baseStream is NetworkStream)
{
swallowException = true;
}
this.baseStream = baseStream;
this.leaveOpen = leaveOpen;
streamBuffer = bufferPool.GetBuffer();
this.bufferPool = bufferPool;
......@@ -81,7 +87,7 @@ namespace Titanium.Web.Proxy.StreamExtended.Network
/// </summary>
public override void Flush()
{
BaseStream.Flush();
baseStream.Flush();
}
/// <summary>
......@@ -96,7 +102,7 @@ namespace Titanium.Web.Proxy.StreamExtended.Network
{
bufferLength = 0;
bufferPos = 0;
return BaseStream.Seek(offset, origin);
return baseStream.Seek(offset, origin);
}
/// <summary>
......@@ -105,7 +111,7 @@ namespace Titanium.Web.Proxy.StreamExtended.Network
/// <param name="value">The desired length of the current stream in bytes.</param>
public override void SetLength(long value)
{
BaseStream.SetLength(value);
baseStream.SetLength(value);
}
/// <summary>
......@@ -138,14 +144,14 @@ namespace Titanium.Web.Proxy.StreamExtended.Network
/// <summary>
/// When overridden in a derived class, writes a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written.
/// </summary>
/// <param name="buffer">An array of bytes. This method copies <paramref name="count" /> bytes from <paramref name="buffer" /> to the current stream.</param>
/// <param name="offset">The zero-based byte offset in <paramref name="buffer" /> at which to begin copying bytes to the current stream.</param>
/// <param name="buffer">An array of bytes. This method copies count bytes from buffer to the current stream.</param>
/// <param name="offset">The zero-based byte offset in buffer at which to begin copying bytes to the current stream.</param>
/// <param name="count">The number of bytes to be written to the current stream.</param>
[DebuggerStepThrough]
public override void Write(byte[] buffer, int offset, int count)
{
OnDataWrite(buffer, offset, count);
BaseStream.Write(buffer, offset, count);
baseStream.Write(buffer, offset, count);
}
/// <summary>
......@@ -178,7 +184,7 @@ namespace Titanium.Web.Proxy.StreamExtended.Network
/// </returns>
public override Task FlushAsync(CancellationToken cancellationToken)
{
return BaseStream.FlushAsync(cancellationToken);
return baseStream.FlushAsync(cancellationToken);
}
/// <summary>
......@@ -378,18 +384,14 @@ namespace Titanium.Web.Proxy.StreamExtended.Network
/// Asynchronously writes a sequence of bytes to the current stream, advances the current position within this stream by the number of bytes written, and monitors cancellation requests.
/// </summary>
/// <param name="buffer">The buffer to write data from.</param>
/// <param name="offset">The zero-based byte offset in <paramref name="buffer" /> from which to begin copying bytes to the stream.</param>
/// <param name="offset">The zero-based byte offset in buffer from which to begin copying bytes to the stream.</param>
/// <param name="count">The maximum number of bytes to write.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is <see cref="P:System.Threading.CancellationToken.None" />.</param>
/// <returns>
/// A task that represents the asynchronous write operation.
/// </returns>
/// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is <see cref="P:System.Threading.CancellationToken.None"></see>.</param>
[DebuggerStepThrough]
public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
OnDataWrite(buffer, offset, count);
await BaseStream.WriteAsync(buffer, offset, count, cancellationToken);
await baseStream.WriteAsync(buffer, offset, count, cancellationToken);
}
/// <summary>
......@@ -403,7 +405,7 @@ namespace Titanium.Web.Proxy.StreamExtended.Network
{
buffer[0] = value;
OnDataWrite(buffer, 0, 1);
BaseStream.Write(buffer, 0, 1);
baseStream.Write(buffer, 0, 1);
}
finally
{
......@@ -433,7 +435,7 @@ namespace Titanium.Web.Proxy.StreamExtended.Network
closed = true;
if (!leaveOpen)
{
BaseStream.Dispose();
baseStream.Dispose();
}
bufferPool.ReturnBuffer(streamBuffer);
......@@ -443,27 +445,27 @@ namespace Titanium.Web.Proxy.StreamExtended.Network
/// <summary>
/// When overridden in a derived class, gets a value indicating whether the current stream supports reading.
/// </summary>
public override bool CanRead => BaseStream.CanRead;
public override bool CanRead => baseStream.CanRead;
/// <summary>
/// When overridden in a derived class, gets a value indicating whether the current stream supports seeking.
/// </summary>
public override bool CanSeek => BaseStream.CanSeek;
public override bool CanSeek => baseStream.CanSeek;
/// <summary>
/// When overridden in a derived class, gets a value indicating whether the current stream supports writing.
/// </summary>
public override bool CanWrite => BaseStream.CanWrite;
public override bool CanWrite => baseStream.CanWrite;
/// <summary>
/// Gets a value that determines whether the current stream can time out.
/// </summary>
public override bool CanTimeout => BaseStream.CanTimeout;
public override bool CanTimeout => baseStream.CanTimeout;
/// <summary>
/// When overridden in a derived class, gets the length in bytes of the stream.
/// </summary>
public override long Length => BaseStream.Length;
public override long Length => baseStream.Length;
/// <summary>
/// Gets a value indicating whether data is available.
......@@ -480,8 +482,8 @@ namespace Titanium.Web.Proxy.StreamExtended.Network
/// </summary>
public override long Position
{
get => BaseStream.Position;
set => BaseStream.Position = value;
get => baseStream.Position;
set => baseStream.Position = value;
}
/// <summary>
......@@ -489,8 +491,8 @@ namespace Titanium.Web.Proxy.StreamExtended.Network
/// </summary>
public override int ReadTimeout
{
get => BaseStream.ReadTimeout;
set => BaseStream.ReadTimeout = value;
get => baseStream.ReadTimeout;
set => baseStream.ReadTimeout = value;
}
/// <summary>
......@@ -498,8 +500,8 @@ namespace Titanium.Web.Proxy.StreamExtended.Network
/// </summary>
public override int WriteTimeout
{
get => BaseStream.WriteTimeout;
set => BaseStream.WriteTimeout = value;
get => baseStream.WriteTimeout;
set => baseStream.WriteTimeout = value;
}
/// <summary>
......@@ -524,7 +526,7 @@ namespace Titanium.Web.Proxy.StreamExtended.Network
bool result = false;
try
{
int readBytes = BaseStream.Read(streamBuffer, bufferLength, streamBuffer.Length - bufferLength);
int readBytes = baseStream.Read(streamBuffer, bufferLength, streamBuffer.Length - bufferLength);
result = readBytes > 0;
if (result)
{
......@@ -574,7 +576,7 @@ namespace Titanium.Web.Proxy.StreamExtended.Network
bool result = false;
try
{
int readBytes = await BaseStream.ReadAsync(streamBuffer, bufferLength, bytesToRead, cancellationToken);
int readBytes = await baseStream.ReadAsync(streamBuffer, bufferLength, bytesToRead, cancellationToken);
result = readBytes > 0;
if (result)
{
......@@ -582,6 +584,11 @@ namespace Titanium.Web.Proxy.StreamExtended.Network
bufferLength += readBytes;
}
}
catch
{
if (!swallowException)
throw;
}
finally
{
if (!result)
......@@ -721,7 +728,7 @@ namespace Titanium.Web.Proxy.StreamExtended.Network
return ((TaskResult<int>)asyncResult).Result;
}
/// <summary>
/// Fix the .net bug with SslStream slow WriteAsync
/// https://github.com/justcoding121/Titanium-Web-Proxy/issues/495
......@@ -756,5 +763,292 @@ namespace Titanium.Web.Proxy.StreamExtended.Network
((TaskResult)asyncResult).GetResult();
}
/// <summary>
/// Writes a line async
/// </summary>
/// <param name="cancellationToken">Optional cancellation token for this async task.</param>
/// <returns></returns>
internal Task WriteLineAsync(CancellationToken cancellationToken = default)
{
return WriteAsync(newLine, cancellationToken: cancellationToken);
}
internal Task WriteAsync(string value, CancellationToken cancellationToken = default)
{
return writeAsyncInternal(value, false, cancellationToken);
}
private async Task writeAsyncInternal(string value, bool addNewLine, CancellationToken cancellationToken)
{
int newLineChars = addNewLine ? newLine.Length : 0;
int charCount = value.Length;
if (charCount < bufferPool.BufferSize - newLineChars)
{
var buffer = bufferPool.GetBuffer();
try
{
int idx = encoding.GetBytes(value, 0, charCount, buffer, 0);
if (newLineChars > 0)
{
Buffer.BlockCopy(newLine, 0, buffer, idx, newLineChars);
idx += newLineChars;
}
await baseStream.WriteAsync(buffer, 0, idx, cancellationToken);
}
finally
{
bufferPool.ReturnBuffer(buffer);
}
}
else
{
var buffer = new byte[charCount + newLineChars + 1];
int idx = encoding.GetBytes(value, 0, charCount, buffer, 0);
if (newLineChars > 0)
{
Buffer.BlockCopy(newLine, 0, buffer, idx, newLineChars);
idx += newLineChars;
}
await baseStream.WriteAsync(buffer, 0, idx, cancellationToken);
}
}
internal Task WriteLineAsync(string value, CancellationToken cancellationToken = default)
{
return writeAsyncInternal(value, true, cancellationToken);
}
/// <summary>
/// Write the headers to client
/// </summary>
/// <param name="headerBuilder"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
internal async Task WriteHeadersAsync(HeaderBuilder headerBuilder, CancellationToken cancellationToken = default)
{
var buffer = headerBuilder.GetBuffer();
await WriteAsync(buffer.Array, buffer.Offset, buffer.Count, true, cancellationToken);
}
/// <summary>
/// Writes the data to the stream.
/// </summary>
/// <param name="data">The data.</param>
/// <param name="flush">Should we flush after write?</param>
/// <param name="cancellationToken">The cancellation token.</param>
internal async Task WriteAsync(byte[] data, bool flush = false, CancellationToken cancellationToken = default)
{
await baseStream.WriteAsync(data, 0, data.Length, cancellationToken);
if (flush)
{
await baseStream.FlushAsync(cancellationToken);
}
}
internal async Task WriteAsync(byte[] data, int offset, int count, bool flush,
CancellationToken cancellationToken = default)
{
await baseStream.WriteAsync(data, offset, count, cancellationToken);
if (flush)
{
await baseStream.FlushAsync(cancellationToken);
}
}
/// <summary>
/// Writes the byte array body to the stream; optionally chunked
/// </summary>
/// <param name="data"></param>
/// <param name="isChunked"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
internal Task WriteBodyAsync(byte[] data, bool isChunked, CancellationToken cancellationToken)
{
if (isChunked)
{
return writeBodyChunkedAsync(data, cancellationToken);
}
return WriteAsync(data, cancellationToken: cancellationToken);
}
/// <summary>
/// Copies the specified content length number of bytes to the output stream from the given inputs stream
/// optionally chunked
/// </summary>
/// <param name="streamReader"></param>
/// <param name="isChunked"></param>
/// <param name="contentLength"></param>
/// <param name="onCopy"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public Task CopyBodyAsync(IHttpStreamReader streamReader, bool isChunked, long contentLength,
Action<byte[], int, int>? onCopy, CancellationToken cancellationToken)
{
// For chunked request we need to read data as they arrive, until we reach a chunk end symbol
if (isChunked)
{
return copyBodyChunkedAsync(streamReader, onCopy, cancellationToken);
}
// http 1.0 or the stream reader limits the stream
if (contentLength == -1)
{
contentLength = long.MaxValue;
}
// If not chunked then its easy just read the amount of bytes mentioned in content length header
return copyBytesFromStream(streamReader, contentLength, onCopy, cancellationToken);
}
/// <summary>
/// Copies the given input bytes to output stream chunked
/// </summary>
/// <param name="data"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
private async Task writeBodyChunkedAsync(byte[] data, CancellationToken cancellationToken)
{
var chunkHead = Encoding.ASCII.GetBytes(data.Length.ToString("x2"));
await WriteAsync(chunkHead, cancellationToken: cancellationToken);
await WriteLineAsync(cancellationToken);
await WriteAsync(data, cancellationToken: cancellationToken);
await WriteLineAsync(cancellationToken);
await WriteLineAsync("0", cancellationToken);
await WriteLineAsync(cancellationToken);
}
/// <summary>
/// Copies the streams chunked
/// </summary>
/// <param name="reader"></param>
/// <param name="onCopy"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
private async Task copyBodyChunkedAsync(IHttpStreamReader reader, Action<byte[], int, int>? onCopy, CancellationToken cancellationToken)
{
while (true)
{
string chunkHead = (await reader.ReadLineAsync(cancellationToken))!;
int idx = chunkHead.IndexOf(";");
if (idx >= 0)
{
chunkHead = chunkHead.Substring(0, idx);
}
int chunkSize = int.Parse(chunkHead, NumberStyles.HexNumber);
await WriteLineAsync(chunkHead, cancellationToken);
if (chunkSize != 0)
{
await copyBytesFromStream(reader, chunkSize, onCopy, cancellationToken);
}
await WriteLineAsync(cancellationToken);
// chunk trail
await reader.ReadLineAsync(cancellationToken);
if (chunkSize == 0)
{
break;
}
}
}
/// <summary>
/// Copies the specified bytes to the stream from the input stream
/// </summary>
/// <param name="reader"></param>
/// <param name="count"></param>
/// <param name="onCopy"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
private async Task copyBytesFromStream(IHttpStreamReader reader, long count, Action<byte[], int, int>? onCopy,
CancellationToken cancellationToken)
{
var buffer = bufferPool.GetBuffer();
try
{
long remainingBytes = count;
while (remainingBytes > 0)
{
int bytesToRead = buffer.Length;
if (remainingBytes < bytesToRead)
{
bytesToRead = (int)remainingBytes;
}
int bytesRead = await reader.ReadAsync(buffer, 0, bytesToRead, cancellationToken);
if (bytesRead == 0)
{
break;
}
remainingBytes -= bytesRead;
await baseStream.WriteAsync(buffer, 0, bytesRead, cancellationToken);
onCopy?.Invoke(buffer, 0, bytesRead);
}
}
finally
{
bufferPool.ReturnBuffer(buffer);
}
}
/// <summary>
/// Writes the request/response headers and body.
/// </summary>
/// <param name="requestResponse"></param>
/// <param name="headerBuilder"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
protected async Task WriteAsync(RequestResponseBase requestResponse, HeaderBuilder headerBuilder, CancellationToken cancellationToken = default)
{
var body = requestResponse.CompressBodyAndUpdateContentLength();
headerBuilder.WriteHeaders(requestResponse.Headers);
await WriteHeadersAsync(headerBuilder, cancellationToken);
if (body != null)
{
await WriteBodyAsync(body, requestResponse.IsChunked, cancellationToken);
}
}
#if NETSTANDARD2_1
/// <summary>
/// Asynchronously writes a sequence of bytes to the current stream, advances the current position within this stream by the number of bytes written, and monitors cancellation requests.
/// </summary>
/// <param name="buffer">The buffer to write data from.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is <see cref="P:System.Threading.CancellationToken.None" />.</param>
/// <returns>A task that represents the asynchronous write operation.</returns>
public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default)
{
return baseStream.WriteAsync(buffer, cancellationToken);
}
#else
/// <summary>
/// Asynchronously writes a sequence of bytes to the current stream, advances the current position within this stream by the number of bytes written, and monitors cancellation requests.
/// </summary>
/// <param name="buffer">The buffer to write data from.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is <see cref="P:System.Threading.CancellationToken.None" />.</param>
/// <returns>A task that represents the asynchronous write operation.</returns>
public Task WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken)
{
var buf = ArrayPool<byte>.Shared.Rent(buffer.Length);
buffer.CopyTo(buf);
return baseStream.WriteAsync(buf, 0, buf.Length, cancellationToken);
}
#endif
}
}
using System;
using System.Buffers;
using System.Globalization;
using System.IO;
using System.Text;
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;
namespace Titanium.Web.Proxy.Helpers
{
internal class HttpWriter : ICustomStreamWriter
{
private readonly Stream stream;
private readonly IBufferPool bufferPool;
private static readonly byte[] newLine = ProxyConstants.NewLineBytes;
private static Encoding encoding => HttpHeader.Encoding;
internal HttpWriter(Stream stream, IBufferPool bufferPool)
{
this.stream = stream;
this.bufferPool = bufferPool;
}
/// <summary>
/// Writes a line async
/// </summary>
/// <param name="cancellationToken">Optional cancellation token for this async task.</param>
/// <returns></returns>
internal Task WriteLineAsync(CancellationToken cancellationToken = default)
{
return WriteAsync(newLine, cancellationToken: cancellationToken);
}
internal Task WriteAsync(string value, CancellationToken cancellationToken = default)
{
return writeAsyncInternal(value, false, cancellationToken);
}
private async Task writeAsyncInternal(string value, bool addNewLine, CancellationToken cancellationToken)
{
int newLineChars = addNewLine ? newLine.Length : 0;
int charCount = value.Length;
if (charCount < bufferPool.BufferSize - newLineChars)
{
var buffer = bufferPool.GetBuffer();
try
{
int idx = encoding.GetBytes(value, 0, charCount, buffer, 0);
if (newLineChars > 0)
{
Buffer.BlockCopy(newLine, 0, buffer, idx, newLineChars);
idx += newLineChars;
}
await stream.WriteAsync(buffer, 0, idx, cancellationToken);
}
finally
{
bufferPool.ReturnBuffer(buffer);
}
}
else
{
var buffer = new byte[charCount + newLineChars + 1];
int idx = encoding.GetBytes(value, 0, charCount, buffer, 0);
if (newLineChars > 0)
{
Buffer.BlockCopy(newLine, 0, buffer, idx, newLineChars);
idx += newLineChars;
}
await stream.WriteAsync(buffer, 0, idx, cancellationToken);
}
}
internal Task WriteLineAsync(string value, CancellationToken cancellationToken = default)
{
return writeAsyncInternal(value, true, cancellationToken);
}
/// <summary>
/// Write the headers to client
/// </summary>
/// <param name="headerBuilder"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
internal async Task WriteHeadersAsync(HeaderBuilder headerBuilder, CancellationToken cancellationToken = default)
{
var buffer = headerBuilder.GetBuffer();
await WriteAsync(buffer.Array, buffer.Offset, buffer.Count, true, cancellationToken);
}
/// <summary>
/// Writes the data to the stream.
/// </summary>
/// <param name="data">The data.</param>
/// <param name="flush">Should we flush after write?</param>
/// <param name="cancellationToken">The cancellation token.</param>
internal async Task WriteAsync(byte[] data, bool flush = false, CancellationToken cancellationToken = default)
{
await stream.WriteAsync(data, 0, data.Length, cancellationToken);
if (flush)
{
await stream.FlushAsync(cancellationToken);
}
}
internal async Task WriteAsync(byte[] data, int offset, int count, bool flush,
CancellationToken cancellationToken = default)
{
await stream.WriteAsync(data, offset, count, cancellationToken);
if (flush)
{
await stream.FlushAsync(cancellationToken);
}
}
/// <summary>
/// Writes the byte array body to the stream; optionally chunked
/// </summary>
/// <param name="data"></param>
/// <param name="isChunked"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
internal Task WriteBodyAsync(byte[] data, bool isChunked, CancellationToken cancellationToken)
{
if (isChunked)
{
return writeBodyChunkedAsync(data, cancellationToken);
}
return WriteAsync(data, cancellationToken: cancellationToken);
}
/// <summary>
/// Copies the specified content length number of bytes to the output stream from the given inputs stream
/// optionally chunked
/// </summary>
/// <param name="streamReader"></param>
/// <param name="isChunked"></param>
/// <param name="contentLength"></param>
/// <param name="onCopy"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
internal Task CopyBodyAsync(CustomBufferedStream streamReader, bool isChunked, long contentLength,
Action<byte[], int, int>? onCopy, CancellationToken cancellationToken)
{
// For chunked request we need to read data as they arrive, until we reach a chunk end symbol
if (isChunked)
{
return copyBodyChunkedAsync(streamReader, onCopy, cancellationToken);
}
// http 1.0 or the stream reader limits the stream
if (contentLength == -1)
{
contentLength = long.MaxValue;
}
// If not chunked then its easy just read the amount of bytes mentioned in content length header
return copyBytesFromStream(streamReader, contentLength, onCopy, cancellationToken);
}
/// <summary>
/// Copies the given input bytes to output stream chunked
/// </summary>
/// <param name="data"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
private async Task writeBodyChunkedAsync(byte[] data, CancellationToken cancellationToken)
{
var chunkHead = Encoding.ASCII.GetBytes(data.Length.ToString("x2"));
await WriteAsync(chunkHead, cancellationToken: cancellationToken);
await WriteLineAsync(cancellationToken);
await WriteAsync(data, cancellationToken: cancellationToken);
await WriteLineAsync(cancellationToken);
await WriteLineAsync("0", cancellationToken);
await WriteLineAsync(cancellationToken);
}
/// <summary>
/// Copies the streams chunked
/// </summary>
/// <param name="reader"></param>
/// <param name="onCopy"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
private async Task copyBodyChunkedAsync(CustomBufferedStream reader, Action<byte[], int, int>? onCopy, CancellationToken cancellationToken)
{
while (true)
{
string chunkHead = (await reader.ReadLineAsync(cancellationToken))!;
int idx = chunkHead.IndexOf(";");
if (idx >= 0)
{
chunkHead = chunkHead.Substring(0, idx);
}
int chunkSize = int.Parse(chunkHead, NumberStyles.HexNumber);
await WriteLineAsync(chunkHead, cancellationToken);
if (chunkSize != 0)
{
await copyBytesFromStream(reader, chunkSize, onCopy, cancellationToken);
}
await WriteLineAsync(cancellationToken);
// chunk trail
await reader.ReadLineAsync(cancellationToken);
if (chunkSize == 0)
{
break;
}
}
}
/// <summary>
/// Copies the specified bytes to the stream from the input stream
/// </summary>
/// <param name="reader"></param>
/// <param name="count"></param>
/// <param name="onCopy"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
private async Task copyBytesFromStream(CustomBufferedStream reader, long count, Action<byte[], int, int>? onCopy,
CancellationToken cancellationToken)
{
var buffer = bufferPool.GetBuffer();
try
{
long remainingBytes = count;
while (remainingBytes > 0)
{
int bytesToRead = buffer.Length;
if (remainingBytes < bytesToRead)
{
bytesToRead = (int)remainingBytes;
}
int bytesRead = await reader.ReadAsync(buffer, 0, bytesToRead, cancellationToken);
if (bytesRead == 0)
{
break;
}
remainingBytes -= bytesRead;
await stream.WriteAsync(buffer, 0, bytesRead, cancellationToken);
onCopy?.Invoke(buffer, 0, bytesRead);
}
}
finally
{
bufferPool.ReturnBuffer(buffer);
}
}
/// <summary>
/// Writes the request/response headers and body.
/// </summary>
/// <param name="requestResponse"></param>
/// <param name="headerBuilder"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
protected async Task WriteAsync(RequestResponseBase requestResponse, HeaderBuilder headerBuilder, CancellationToken cancellationToken = default)
{
var body = requestResponse.CompressBodyAndUpdateContentLength();
headerBuilder.WriteHeaders(requestResponse.Headers);
await WriteHeadersAsync(headerBuilder, cancellationToken);
if (body != null)
{
await WriteBodyAsync(body, requestResponse.IsChunked, cancellationToken);
}
}
/// <summary>When overridden in a derived class, writes a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written.</summary>
/// <param name="buffer">An array of bytes. This method copies count bytes from buffer to the current stream.</param>
/// <param name="offset">The zero-based byte offset in buffer at which to begin copying bytes to the current stream.</param>
/// <param name="count">The number of bytes to be written to the current stream.</param>
/// <exception cref="T:System.ArgumentException">The sum of offset and count is greater than the buffer length.</exception>
/// <exception cref="T:System.ArgumentNullException">buffer is null.</exception>
/// <exception cref="T:System.ArgumentOutOfRangeException">offset or count is negative.</exception>
/// <exception cref="T:System.IO.IOException">An I/O error occured, such as the specified file cannot be found.</exception>
/// <exception cref="T:System.NotSupportedException">The stream does not support writing.</exception>
/// <exception cref="T:System.ObjectDisposedException"><see cref="M:System.IO.Stream.Write(System.Byte[],System.Int32,System.Int32)"></see> was called after the stream was closed.</exception>
public void Write(byte[] buffer, int offset, int count)
{
stream.Write(buffer, offset, count);
}
/// <summary>
/// Asynchronously writes a sequence of bytes to the current stream, advances the current position within this stream by the number of bytes written, and monitors cancellation requests.
/// </summary>
/// <param name="buffer">The buffer to write data from.</param>
/// <param name="offset">The zero-based byte offset in <paramref name="buffer" /> from which to begin copying bytes to the stream.</param>
/// <param name="count">The maximum number of bytes to write.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is <see cref="P:System.Threading.CancellationToken.None" />.</param>
/// <returns>A task that represents the asynchronous write operation.</returns>
public Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
return stream.WriteAsync(buffer, offset, count, cancellationToken);
}
#if NETSTANDARD2_1
/// <summary>
/// Asynchronously writes a sequence of bytes to the current stream, advances the current position within this stream by the number of bytes written, and monitors cancellation requests.
/// </summary>
/// <param name="buffer">The buffer to write data from.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is <see cref="P:System.Threading.CancellationToken.None" />.</param>
/// <returns>A task that represents the asynchronous write operation.</returns>
public ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken)
{
return stream.WriteAsync(buffer, cancellationToken);
}
#else
/// <summary>
/// Asynchronously writes a sequence of bytes to the current stream, advances the current position within this stream by the number of bytes written, and monitors cancellation requests.
/// </summary>
/// <param name="buffer">The buffer to write data from.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is <see cref="P:System.Threading.CancellationToken.None" />.</param>
/// <returns>A task that represents the asynchronous write operation.</returns>
public Task WriteAsync2(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken)
{
var buf = ArrayPool<byte>.Shared.Rent(buffer.Length);
buffer.CopyTo(buf);
return stream.WriteAsync(buf, 0, buf.Length, cancellationToken);
}
#endif
}
}
......@@ -95,7 +95,7 @@ namespace Titanium.Web.Proxy.Helpers.WinHttp
return true;
}
public ExternalProxy? GetProxy(Uri destination)
public IExternalProxy? GetProxy(Uri destination)
{
if (GetAutoProxies(destination, out var proxies))
{
......
......@@ -111,7 +111,7 @@ namespace Titanium.Web.Proxy.Http
bool useUpstreamProxy = upstreamProxy != null && Connection.IsHttps == false;
var writer = Connection.StreamWriter;
var serverStream = Connection.Stream;
string url;
if (useUpstreamProxy || isTransparent)
......@@ -153,7 +153,7 @@ namespace Titanium.Web.Proxy.Http
headerBuilder.WriteLine();
await writer.WriteHeadersAsync(headerBuilder, cancellationToken);
await serverStream.WriteHeadersAsync(headerBuilder, cancellationToken);
if (enable100ContinueBehaviour && Request.ExpectContinue)
{
......
......@@ -389,7 +389,10 @@ namespace Titanium.Web.Proxy.Http2
}
}
rr.Body = body;
if (!rr.BodyAvailable)
{
rr.Body = body;
}
}
rr.IsBodyRead = true;
......
......@@ -6,7 +6,7 @@ namespace Titanium.Web.Proxy.Models
/// <summary>
/// An upstream proxy this proxy uses if any.
/// </summary>
public class ExternalProxy
public class ExternalProxy : IExternalProxy
{
private static readonly Lazy<NetworkCredential> defaultCredentials =
new Lazy<NetworkCredential>(() => CredentialCache.DefaultNetworkCredentials);
......
namespace Titanium.Web.Proxy.Models
{
public interface IExternalProxy
{
/// <summary>
/// Use default windows credentials?
/// </summary>
bool UseDefaultCredentials { get; set; }
/// <summary>
/// Bypass this proxy for connections to localhost?
/// </summary>
bool BypassLocalhost { get; set; }
/// <summary>
/// Username.
/// </summary>
string? UserName { get; set; }
/// <summary>
/// Password.
/// </summary>
string? Password { get; set; }
/// <summary>
/// Host name.
/// </summary>
string HostName { get; set; }
/// <summary>
/// Port.
/// </summary>
int Port { get; set; }
string ToString();
}
}

using Titanium.Web.Proxy.StreamExtended.BufferPool;
using Titanium.Web.Proxy.StreamExtended.Network;
#if DEBUG
using System;
using System.IO;
using System.Text;
using System.Threading;
namespace Titanium.Web.Proxy.Network
{
internal class DebugCustomBufferedStream : CustomBufferedStream
{
private const string basePath = @".";
private static int counter;
private readonly FileStream fileStreamReceived;
private readonly FileStream fileStreamSent;
public DebugCustomBufferedStream(Guid connectionId, string type, Stream baseStream, IBufferPool bufferPool, bool leaveOpen = false)
: base(baseStream, bufferPool, leaveOpen)
{
Counter = Interlocked.Increment(ref counter);
fileStreamSent = new FileStream(Path.Combine(basePath, $"{connectionId}_{type}_{Counter}_sent.dat"), FileMode.Create);
fileStreamReceived = new FileStream(Path.Combine(basePath, $"{connectionId}_{type}_{Counter}_received.dat"), FileMode.Create);
}
public int Counter { get; }
protected override void OnDataWrite(byte[] buffer, int offset, int count)
{
fileStreamSent.Write(buffer, offset, count);
Flush();
}
protected override void OnDataRead(byte[] buffer, int offset, int count)
{
fileStreamReceived.Write(buffer, offset, count);
Flush();
}
public void LogException(Exception ex)
{
var data = Encoding.UTF8.GetBytes("EXCEPTION: " + ex);
fileStreamReceived.Write(data, 0, data.Length);
fileStreamReceived.Flush();
}
public override void Flush()
{
fileStreamSent.Flush(true);
fileStreamReceived.Flush(true);
if (CanWrite)
{
base.Flush();
}
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
Flush();
fileStreamSent.Dispose();
fileStreamReceived.Dispose();
}
base.Dispose(disposing);
}
}
}
#endif
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Network.Tcp;
using Titanium.Web.Proxy.StreamExtended.Network;
namespace Titanium.Web.Proxy.Network
{
/// <summary>
/// This class wraps Tcp connection to client
/// </summary>
internal class ProxyClient
{
public ProxyClient(TcpClientConnection connection, CustomBufferedStream clientStream, HttpResponseWriter clientStreamWriter)
{
Connection = connection;
ClientStream = clientStream;
ClientStreamWriter = clientStreamWriter;
}
/// <summary>
/// TcpClient connection used to communicate with client
/// </summary>
internal TcpClientConnection Connection { get; }
/// <summary>
/// Holds the stream to client
/// </summary>
internal CustomBufferedStream ClientStream { get; }
/// <summary>
/// Used to write line by line to client
/// </summary>
internal HttpResponseWriter ClientStreamWriter { get; }
}
}
......@@ -86,7 +86,6 @@ namespace Titanium.Web.Proxy.Network.Tcp
proxyServer.UpdateClientConnectionCount(false);
tcpClient.CloseSocket();
});
}
}
}
......@@ -49,7 +49,7 @@ namespace Titanium.Web.Proxy.Network.Tcp
internal string GetConnectionCacheKey(string remoteHostName, int remotePort,
bool isHttps, List<SslApplicationProtocol>? applicationProtocols,
IPEndPoint? upStreamEndPoint, ExternalProxy? externalProxy)
IPEndPoint? upStreamEndPoint, IExternalProxy? 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.
......@@ -115,7 +115,7 @@ namespace Titanium.Web.Proxy.Network.Tcp
applicationProtocols = new List<SslApplicationProtocol> { applicationProtocol };
}
ExternalProxy? customUpStreamProxy = null;
IExternalProxy? customUpStreamProxy = null;
bool isHttps = session.IsHttps;
if (server.GetCustomUpStreamProxyFunc != null)
......@@ -170,7 +170,7 @@ namespace Titanium.Web.Proxy.Network.Tcp
internal async Task<TcpServerConnection> GetServerConnection(ProxyServer server, SessionEventArgsBase session, bool isConnect,
List<SslApplicationProtocol>? applicationProtocols, bool noCache, CancellationToken cancellationToken)
{
ExternalProxy? customUpStreamProxy = null;
IExternalProxy? customUpStreamProxy = null;
bool isHttps = session.IsHttps;
if (server.GetCustomUpStreamProxyFunc != null)
......@@ -208,10 +208,10 @@ namespace Titanium.Web.Proxy.Network.Tcp
/// <returns></returns>
internal async Task<TcpServerConnection> GetServerConnection(string remoteHostName, int remotePort,
Version httpVersion, bool isHttps, List<SslApplicationProtocol>? applicationProtocols, bool isConnect,
ProxyServer proxyServer, SessionEventArgsBase? session, IPEndPoint? upStreamEndPoint, ExternalProxy? externalProxy,
ProxyServer proxyServer, SessionEventArgsBase? session, IPEndPoint? upStreamEndPoint, IExternalProxy? externalProxy,
bool noCache, CancellationToken cancellationToken)
{
var sslProtocol = session?.ProxyClient.Connection.SslProtocol ?? SslProtocols.None;
var sslProtocol = session?.ClientConnection.SslProtocol ?? SslProtocols.None;
var cacheKey = GetConnectionCacheKey(remoteHostName, remotePort,
isHttps, applicationProtocols, upStreamEndPoint, externalProxy);
......@@ -262,7 +262,7 @@ namespace Titanium.Web.Proxy.Network.Tcp
/// <returns></returns>
private async Task<TcpServerConnection> createServerConnection(string remoteHostName, int remotePort,
Version httpVersion, bool isHttps, SslProtocols sslProtocol, List<SslApplicationProtocol>? applicationProtocols, bool isConnect,
ProxyServer proxyServer, SessionEventArgsBase? session, IPEndPoint? upStreamEndPoint, ExternalProxy? externalProxy, string cacheKey,
ProxyServer proxyServer, SessionEventArgsBase? session, IPEndPoint? upStreamEndPoint, IExternalProxy? externalProxy, string cacheKey,
CancellationToken cancellationToken)
{
// deny connection to proxy end points to avoid infinite connection loop.
......@@ -297,14 +297,14 @@ namespace Titanium.Web.Proxy.Network.Tcp
}
TcpClient? tcpClient = null;
CustomBufferedStream? stream = null;
HttpServerStream? stream = null;
SslApplicationProtocol negotiatedApplicationProtocol = default;
bool retry = true;
var enabledSslProtocols = sslProtocol;
retry:
retry:
try
{
string hostname = useUpstreamProxy ? externalProxy!.HostName : remoteHostName;
......@@ -323,6 +323,7 @@ namespace Titanium.Web.Proxy.Network.Tcp
Array.Sort(ipAddresses, (x, y) => x.AddressFamily.CompareTo(y.AddressFamily));
Exception lastException = null;
for (int i = 0; i < ipAddresses.Length; i++)
{
try
......@@ -352,28 +353,29 @@ namespace Titanium.Web.Proxy.Network.Tcp
}
catch (Exception e)
{
if (i == ipAddresses.Length - 1)
{
throw new Exception($"Could not establish connection to {hostname}", e);
}
// dispose the current TcpClient and try the next address
lastException = e;
tcpClient?.Dispose();
tcpClient = null;
}
}
if (tcpClient == null)
{
throw new Exception($"Could not establish connection to {hostname}", lastException);
}
if (session != null)
{
session.TimeLine["Connection Established"] = DateTime.Now;
}
await proxyServer.InvokeConnectionCreateEvent(tcpClient!, false);
await proxyServer.InvokeConnectionCreateEvent(tcpClient, false);
stream = new CustomBufferedStream(tcpClient!.GetStream(), proxyServer.BufferPool);
stream = new HttpServerStream(tcpClient.GetStream(), proxyServer.BufferPool);
if (useUpstreamProxy && (isConnect || isHttps))
{
var writer = new HttpRequestWriter(stream, proxyServer.BufferPool);
string authority = $"{remoteHostName}:{remotePort}";
var connectRequest = new ConnectRequest(authority)
{
......@@ -391,7 +393,7 @@ namespace Titanium.Web.Proxy.Network.Tcp
HttpHeader.GetProxyAuthorizationHeader(externalProxy.UserName, externalProxy.Password));
}
await writer.WriteRequestAsync(connectRequest, cancellationToken: cancellationToken);
await stream.WriteRequestAsync(connectRequest, cancellationToken: cancellationToken);
string httpStatus = await stream.ReadLineAsync(cancellationToken)
?? throw new ServerConnectionException("Server connection was closed.");
......@@ -411,7 +413,7 @@ namespace Titanium.Web.Proxy.Network.Tcp
{
var sslStream = new SslStream(stream, false, proxyServer.ValidateServerCertificate,
proxyServer.SelectClientCertificate);
stream = new CustomBufferedStream(sslStream, proxyServer.BufferPool);
stream = new HttpServerStream(sslStream, proxyServer.BufferPool);
var options = new SslClientAuthenticationOptions
{
......@@ -430,11 +432,13 @@ namespace Titanium.Web.Proxy.Network.Tcp
{
session.TimeLine["HTTPS Established"] = DateTime.Now;
}
}
}
catch (IOException ex) when (ex.HResult == unchecked((int)0x80131620) && retry && enabledSslProtocols >= SslProtocols.Tls11)
{
stream?.Dispose();
tcpClient?.Close();
enabledSslProtocols = SslProtocols.Tls;
retry = false;
goto retry;
......
......@@ -15,15 +15,14 @@ namespace Titanium.Web.Proxy.Network.Tcp
/// </summary>
internal class TcpServerConnection : IDisposable
{
internal TcpServerConnection(ProxyServer proxyServer, TcpClient tcpClient, CustomBufferedStream stream,
internal TcpServerConnection(ProxyServer proxyServer, TcpClient tcpClient, HttpServerStream stream,
string hostName, int port, bool isHttps, SslApplicationProtocol negotiatedApplicationProtocol,
Version version, bool useUpstreamProxy, ExternalProxy? upStreamProxy, IPEndPoint? upStreamEndPoint, string cacheKey)
Version version, bool useUpstreamProxy, IExternalProxy? upStreamProxy, IPEndPoint? upStreamEndPoint, string cacheKey)
{
this.tcpClient = tcpClient;
TcpClient = tcpClient;
LastAccess = DateTime.Now;
this.proxyServer = proxyServer;
this.proxyServer.UpdateServerConnectionCount(true);
StreamWriter = new HttpRequestWriter(stream, proxyServer.BufferPool);
Stream = stream;
HostName = hostName;
Port = port;
......@@ -41,7 +40,7 @@ namespace Titanium.Web.Proxy.Network.Tcp
internal bool IsClosed => Stream.IsClosed;
internal ExternalProxy? UpStreamProxy { get; set; }
internal IExternalProxy? UpStreamProxy { get; set; }
internal string HostName { get; set; }
......@@ -63,22 +62,15 @@ namespace Titanium.Web.Proxy.Network.Tcp
/// </summary>
internal Version Version { get; set; } = HttpHeader.VersionUnknown;
private readonly TcpClient tcpClient;
/// <summary>
/// The TcpClient.
/// </summary>
internal TcpClient TcpClient => tcpClient;
internal TcpClient TcpClient { get; }
/// <summary>
/// Used to write lines to server
/// </summary>
internal HttpRequestWriter StreamWriter { get; }
/// <summary>
/// Server stream
/// </summary>
internal CustomBufferedStream Stream { get; }
internal HttpServerStream Stream { get; }
/// <summary>
/// Last time this connection was used
......@@ -107,8 +99,8 @@ namespace Titanium.Web.Proxy.Network.Tcp
// This way we can push tcp Time_Wait to server side when possible.
await Task.Delay(1000);
proxyServer.UpdateServerConnectionCount(false);
Stream?.Dispose();
tcpClient.CloseSocket();
Stream.Dispose();
TcpClient.CloseSocket();
});
}
......
......@@ -61,7 +61,7 @@ namespace Titanium.Web.Proxy
/// </summary>
private WinHttpWebProxyFinder? systemProxyResolver;
/// <inheritdoc />
/// <summary>
/// Initializes a new instance of ProxyServer class with provided parameters.
......@@ -145,7 +145,7 @@ namespace Titanium.Web.Proxy
/// Defaults to false.
/// </summary>
public bool EnableWinAuth { get; set; }
/// <summary>
/// Enable disable HTTP/2 support.
/// Warning: HTTP/2 support is very limited
......@@ -253,12 +253,12 @@ namespace Titanium.Web.Proxy
/// <summary>
/// External proxy used for Http requests.
/// </summary>
public ExternalProxy? UpStreamHttpProxy { get; set; }
public IExternalProxy? UpStreamHttpProxy { get; set; }
/// <summary>
/// External proxy used for Https requests.
/// </summary>
public ExternalProxy? UpStreamHttpsProxy { get; set; }
public IExternalProxy? UpStreamHttpsProxy { get; set; }
/// <summary>
/// Local adapter/NIC endpoint where proxy makes request via.
......@@ -275,7 +275,7 @@ namespace Titanium.Web.Proxy
/// A callback to provide authentication credentials for up stream proxy this proxy is using for HTTP(S) requests.
/// User should return the ExternalProxy object with valid credentials.
/// </summary>
public Func<SessionEventArgsBase, Task<ExternalProxy?>>? GetCustomUpStreamProxyFunc { get; set; }
public Func<SessionEventArgsBase, Task<IExternalProxy?>>? GetCustomUpStreamProxyFunc { get; set; }
/// <summary>
/// Callback for error events in this proxy instance.
......@@ -709,7 +709,7 @@ namespace Titanium.Web.Proxy
/// </summary>
/// <param name="sessionEventArgs">The session.</param>
/// <returns>The external proxy as task result.</returns>
private Task<ExternalProxy?> getSystemUpStreamProxy(SessionEventArgsBase sessionEventArgs)
private Task<IExternalProxy?> getSystemUpStreamProxy(SessionEventArgsBase sessionEventArgs)
{
var proxy = systemProxyResolver!.GetProxy(sessionEventArgs.HttpClient.Request.RequestUri);
return Task.FromResult(proxy);
......@@ -803,15 +803,8 @@ namespace Titanium.Web.Proxy
/// </summary>
/// <param name="clientStream">The client stream.</param>
/// <param name="exception">The exception.</param>
private void onException(CustomBufferedStream clientStream, Exception exception)
private void onException(HttpClientStream clientStream, Exception exception)
{
#if DEBUG
if (clientStream is DebugCustomBufferedStream debugStream)
{
debugStream.LogException(exception);
}
#endif
ExceptionFunc(exception);
}
......
......@@ -31,13 +31,11 @@ namespace Titanium.Web.Proxy
/// <param name="endPoint">The proxy endpoint.</param>
/// <param name="clientConnection">The client connection.</param>
/// <param name="clientStream">The client stream.</param>
/// <param name="clientStreamWriter">The client stream writer.</param>
/// <param name="cancellationTokenSource">The cancellation token source for this async task.</param>
/// <param name="connectArgs">The Connect request if this is a HTTPS request from explicit endpoint.</param>
/// <param name="prefetchConnectionTask">Prefetched server connection for current client using Connect/SNI headers.</param>
private async Task handleHttpSessionRequest(ProxyEndPoint endPoint, TcpClientConnection clientConnection,
CustomBufferedStream clientStream, HttpResponseWriter clientStreamWriter,
CancellationTokenSource cancellationTokenSource, TunnelConnectSessionEventArgs? connectArgs = null,
HttpClientStream clientStream, CancellationTokenSource cancellationTokenSource, TunnelConnectSessionEventArgs? connectArgs = null,
Task<TcpServerConnection>? prefetchConnectionTask = null)
{
var connectRequest = connectArgs?.HttpClient.ConnectRequest;
......@@ -66,7 +64,7 @@ namespace Titanium.Web.Proxy
return;
}
var args = new SessionEventArgs(this, endPoint, new ProxyClient(clientConnection, clientStream, clientStreamWriter), connectRequest, cancellationTokenSource)
var args = new SessionEventArgs(this, endPoint, clientConnection, clientStream, connectRequest, cancellationTokenSource)
{
UserData = connectArgs?.UserData
};
......@@ -101,7 +99,7 @@ namespace Titanium.Web.Proxy
await onBeforeResponse(args);
// send the response
await clientStreamWriter.WriteResponseAsync(args.HttpClient.Response,
await clientStream.WriteResponseAsync(args.HttpClient.Response,
cancellationToken: cancellationToken);
return;
}
......@@ -276,7 +274,7 @@ namespace Titanium.Web.Proxy
// if upgrading to websocket then relay the request without reading the contents
await handleWebSocketUpgrade(requestHttpMethod, requestHttpUrl, requestVersion, args, args.HttpClient.Request,
args.HttpClient.Response, args.ProxyClient.ClientStream, args.ProxyClient.ClientStreamWriter,
args.HttpClient.Response, args.ClientStream,
connection, cancellationTokenSource, cancellationToken);
return false;
}
......@@ -304,13 +302,13 @@ namespace Titanium.Web.Proxy
// If a successful 100 continue request was made, inform that to the client and reset response
if (request.ExpectationSucceeded)
{
var clientStreamWriter = args.ProxyClient.ClientStreamWriter;
var writer = args.ClientStream;
var response = args.HttpClient.Response;
var headerBuilder = new HeaderBuilder();
headerBuilder.WriteResponseLine(response.HttpVersion, response.StatusCode, response.StatusDescription);
headerBuilder.WriteHeaders(response.Headers);
await clientStreamWriter.WriteHeadersAsync(headerBuilder, cancellationToken);
await writer.WriteHeadersAsync(headerBuilder, cancellationToken);
await args.ClearResponse(cancellationToken);
}
......@@ -320,14 +318,12 @@ namespace Titanium.Web.Proxy
{
if (request.IsBodyRead)
{
var writer = args.HttpClient.Connection.StreamWriter;
await writer.WriteBodyAsync(body!, request.IsChunked, cancellationToken);
await args.HttpClient.Connection.Stream.WriteBodyAsync(body!, request.IsChunked, cancellationToken);
}
else if (!request.ExpectationFailed)
{
// get the request body unless an unsuccessful 100 continue request was made
HttpWriter writer = args.HttpClient.Connection.StreamWriter!;
await args.CopyRequestBodyAsync(writer, TransformationMode.None, cancellationToken);
await args.CopyRequestBodyAsync(args.HttpClient.Connection.Stream, TransformationMode.None, cancellationToken);
}
}
......@@ -357,7 +353,7 @@ namespace Titanium.Web.Proxy
supportedAcceptEncoding.Add("identity");
requestHeaders.SetOrAddHeaderValue(KnownHeaders.AcceptEncoding,
string.Join(",", supportedAcceptEncoding));
string.Join(", ", supportedAcceptEncoding));
}
requestHeaders.FixProxyHeaders();
......
......@@ -64,13 +64,13 @@ namespace Titanium.Web.Proxy
// it may changed in the user event
response = args.HttpClient.Response;
var clientStreamWriter = args.ProxyClient.ClientStreamWriter;
var clientStream = args.ClientStream;
// user set custom response by ignoring original response from server.
if (response.Locked)
{
// write custom user response with body and return.
await clientStreamWriter.WriteResponseAsync(response, cancellationToken: cancellationToken);
await clientStream.WriteResponseAsync(response, cancellationToken: cancellationToken);
if (args.HttpClient.HasConnection && !args.HttpClient.CloseServerConnection)
{
......@@ -108,7 +108,7 @@ namespace Titanium.Web.Proxy
if (response.IsBodyRead)
{
await clientStreamWriter.WriteResponseAsync(response, cancellationToken: cancellationToken);
await clientStream.WriteResponseAsync(response, cancellationToken: cancellationToken);
}
else
{
......@@ -116,12 +116,12 @@ namespace Titanium.Web.Proxy
var headerBuilder = new HeaderBuilder();
headerBuilder.WriteResponseLine(response.HttpVersion, response.StatusCode, response.StatusDescription);
headerBuilder.WriteHeaders(response.Headers);
await clientStreamWriter.WriteHeadersAsync(headerBuilder, cancellationToken);
await clientStream.WriteHeadersAsync(headerBuilder, cancellationToken);
// Write body if exists
if (response.HasBody)
{
await args.CopyResponseBodyAsync(clientStreamWriter, TransformationMode.None,
await args.CopyResponseBodyAsync(clientStream, TransformationMode.None,
cancellationToken);
}
}
......
using System;
using System.Threading;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.StreamExtended.BufferPool;
namespace Titanium.Web.Proxy.StreamExtended.Network
......@@ -11,9 +12,9 @@ namespace Titanium.Web.Proxy.StreamExtended.Network
/// </summary>
internal class CopyStream : ILineStream, IDisposable
{
private readonly CustomBufferedStream reader;
private readonly IHttpStreamReader reader;
private readonly ICustomStreamWriter writer;
private readonly IHttpStreamWriter writer;
private readonly IBufferPool bufferPool;
......@@ -27,7 +28,7 @@ namespace Titanium.Web.Proxy.StreamExtended.Network
public long ReadBytes { get; private set; }
public CopyStream(CustomBufferedStream reader, ICustomStreamWriter writer, IBufferPool bufferPool)
public CopyStream(IHttpStreamReader reader, IHttpStreamWriter writer, IBufferPool bufferPool)
{
this.reader = reader;
this.writer = writer;
......@@ -61,7 +62,7 @@ namespace Titanium.Web.Proxy.StreamExtended.Network
public ValueTask<string?> ReadLineAsync(CancellationToken cancellationToken = default)
{
return CustomBufferedStream.ReadLineInternalAsync(this, bufferPool, cancellationToken);
return HttpStream.ReadLineInternalAsync(this, bufferPool, cancellationToken);
}
public void Dispose()
......
using System.Threading;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.StreamExtended.Network
{
public interface IHttpStreamReader : ILineStream
{
int Read(byte[] buffer, int offset, int count);
Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken);
}
}
using System.Threading;
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.StreamExtended.Network
......@@ -6,10 +7,13 @@ namespace Titanium.Web.Proxy.StreamExtended.Network
/// <summary>
/// A concrete implementation of this interface is required when calling CopyStream.
/// </summary>
public interface ICustomStreamWriter
internal interface IHttpStreamWriter
{
void Write(byte[] buffer, int i, int bufferLength);
Task WriteAsync(byte[] buffer, int i, int bufferLength, CancellationToken cancellationToken);
Task CopyBodyAsync(IHttpStreamReader streamReader, bool isChunked, long contentLength,
Action<byte[], int, int>? onCopy, CancellationToken cancellationToken);
}
}
\ No newline at end of file
}
......@@ -53,5 +53,5 @@ namespace Titanium.Web.Proxy.StreamExtended.Network
return buffer;
}
}
}
}
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.StreamExtended.BufferPool;
using Titanium.Web.Proxy.StreamExtended.Models;
using Titanium.Web.Proxy.StreamExtended.Network;
......@@ -19,7 +20,7 @@ namespace Titanium.Web.Proxy.StreamExtended
/// <param name="bufferPool"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public static async Task<ClientHelloInfo?> PeekClientHello(CustomBufferedStream clientStream, IBufferPool bufferPool, CancellationToken cancellationToken = default)
public static async Task<ClientHelloInfo?> PeekClientHello(IPeekStream clientStream, IBufferPool bufferPool, CancellationToken cancellationToken = default)
{
// detects the HTTPS ClientHello message as it is described in the following url:
// https://stackoverflow.com/questions/3897883/how-to-detect-an-incoming-ssl-https-handshake-ssl-wire-format
......@@ -127,11 +128,10 @@ namespace Titanium.Web.Proxy.StreamExtended
return null;
}
byte[] ciphersData = peekStream.ReadBytes(length);
int[] ciphers = new int[ciphersData.Length / 2];
int[] ciphers = new int[length / 2];
for (int i = 0; i < ciphers.Length; i++)
{
ciphers[i] = (ciphersData[2 * i] << 8) + ciphersData[2 * i + 1];
ciphers[i] = peekStream.ReadInt16();
}
length = peekStream.ReadByte();
......@@ -178,7 +178,7 @@ namespace Titanium.Web.Proxy.StreamExtended
/// <param name="bufferPool"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public static async Task<bool> IsServerHello(CustomBufferedStream stream, IBufferPool bufferPool, CancellationToken cancellationToken)
public static async Task<bool> IsServerHello(IPeekStream stream, IBufferPool bufferPool, CancellationToken cancellationToken)
{
var serverHello = await PeekServerHello(stream, bufferPool, cancellationToken);
return serverHello != null;
......@@ -190,7 +190,7 @@ namespace Titanium.Web.Proxy.StreamExtended
/// <param name="bufferPool"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public static async Task<ServerHelloInfo?> PeekServerHello(CustomBufferedStream serverStream, IBufferPool bufferPool, CancellationToken cancellationToken = default)
public static async Task<ServerHelloInfo?> PeekServerHello(IPeekStream serverStream, IBufferPool bufferPool, CancellationToken cancellationToken = default)
{
// detects the HTTPS ClientHello message as it is described in the following url:
// https://stackoverflow.com/questions/3897883/how-to-detect-an-incoming-ssl-https-handshake-ssl-wire-format
......@@ -286,11 +286,11 @@ namespace Titanium.Web.Proxy.StreamExtended
int cipherSuite = peekStream.ReadInt16();
byte compressionMethod = peekStream.ReadByte();
int extenstionsStartPosition = peekStream.Position;
int extensionsStartPosition = peekStream.Position;
Dictionary<string, SslExtension>? extensions = null;
if (extenstionsStartPosition < recordLength + 5)
if (extensionsStartPosition < recordLength + 5)
{
extensions = await ReadExtensions(majorVersion, minorVersion, peekStream, bufferPool, cancellationToken);
}
......@@ -298,7 +298,7 @@ namespace Titanium.Web.Proxy.StreamExtended
var serverHelloInfo = new ServerHelloInfo(3, majorVersion, minorVersion, random, sessionId, cipherSuite, peekStream.Position)
{
CompressionMethod = compressionMethod,
EntensionsStartPosition = extenstionsStartPosition,
EntensionsStartPosition = extensionsStartPosition,
Extensions = extensions,
};
......
......@@ -13,7 +13,7 @@
<ItemGroup>
<PackageReference Include="BrotliSharpLib" Version="0.3.3" />
<PackageReference Include="Portable.BouncyCastle" Version="1.8.5" />
<PackageReference Include="Portable.BouncyCastle" Version="1.8.5.2" />
<PackageReference Include="System.Buffers" Version="4.5.0" />
</ItemGroup>
......
......@@ -13,7 +13,7 @@
<ItemGroup>
<PackageReference Include="BrotliSharpLib" Version="0.3.3" />
<PackageReference Include="Portable.BouncyCastle" Version="1.8.5" />
<PackageReference Include="Portable.BouncyCastle" Version="1.8.5.2" />
<PackageReference Include="System.Buffers" Version="4.5.0" />
</ItemGroup>
......
......@@ -14,7 +14,7 @@
<ItemGroup>
<PackageReference Include="BrotliSharpLib" Version="0.3.3" />
<PackageReference Include="Portable.BouncyCastle" Version="1.8.5" />
<PackageReference Include="Portable.BouncyCastle" Version="1.8.5.2" />
<PackageReference Include="System.Buffers" Version="4.5.0" />
<PackageReference Include="System.Memory" Version="4.5.3" />
<PackageReference Include="System.Threading.Tasks.Extensions" Version="4.5.3" />
......
......@@ -15,7 +15,7 @@
<tags></tags>
<dependencies>
<group targetFramework="netstandard2.0">
<dependency id="Portable.BouncyCastle" version="1.8.5" />
<dependency id="Portable.BouncyCastle" version="1.8.5.2" />
<dependency id="BrotliSharpLib" version="0.3.3" />
<dependency id="Microsoft.Win32.Registry" version="4.6.0" />
<dependency id="System.Buffers" version="4.5.0" />
......@@ -24,7 +24,7 @@
<dependency id="System.Security.Principal.Windows" version="4.6.0" />
</group>
<group targetFramework="netstandard2.1">
<dependency id="Portable.BouncyCastle" version="1.8.5" />
<dependency id="Portable.BouncyCastle" version="1.8.5.2" />
<dependency id="BrotliSharpLib" version="0.3.3" />
<dependency id="Microsoft.Win32.Registry" version="4.6.0" />
<dependency id="System.Buffers" version="4.5.0" />
......
......@@ -33,8 +33,7 @@ namespace Titanium.Web.Proxy
var cancellationTokenSource = new CancellationTokenSource();
var cancellationToken = cancellationTokenSource.Token;
var clientStream = new CustomBufferedStream(clientConnection.GetStream(), BufferPool);
var clientStreamWriter = new HttpResponseWriter(clientStream, BufferPool);
var clientStream = new HttpClientStream(clientConnection.GetStream(), BufferPool);
SslStream? sslStream = null;
......@@ -75,14 +74,12 @@ namespace Titanium.Web.Proxy
await sslStream.AuthenticateAsServerAsync(certificate, false, SslProtocols.Tls, false);
// HTTPS server created - we can now decrypt the client's traffic
clientStream = new CustomBufferedStream(sslStream, BufferPool);
clientStreamWriter = new HttpResponseWriter(clientStream, BufferPool);
clientStream = new HttpClientStream(sslStream, BufferPool);
}
catch (Exception e)
{
var certname = certificate?.GetNameInfo(X509NameType.SimpleName, false);
var session = new SessionEventArgs(this, endPoint, new ProxyClient(clientConnection, clientStream, clientStreamWriter), null,
var session = new SessionEventArgs(this, endPoint, clientConnection, clientStream, null,
cancellationTokenSource);
throw new ProxyConnectException(
$"Couldn't authenticate host '{httpsHostName}' with certificate '{certname}'.", e, session);
......@@ -108,7 +105,7 @@ namespace Titanium.Web.Proxy
{
// clientStream.Available should be at most BufferSize because it is using the same buffer size
await clientStream.ReadAsync(data, 0, available, cancellationToken);
await connection.StreamWriter.WriteAsync(data, 0, available, true, cancellationToken);
await connection.Stream.WriteAsync(data, 0, available, true, cancellationToken);
}
finally
{
......@@ -133,7 +130,7 @@ namespace Titanium.Web.Proxy
// HTTPS server created - we can now decrypt the client's traffic
// Now create the request
await handleHttpSessionRequest(endPoint, clientConnection, clientStream, clientStreamWriter, cancellationTokenSource);
await handleHttpSessionRequest(endPoint, clientConnection, clientStream, cancellationTokenSource);
}
catch (ProxyException e)
{
......@@ -155,11 +152,6 @@ namespace Titanium.Web.Proxy
{
sslStream?.Dispose();
clientStream.Dispose();
if (!cancellationTokenSource.IsCancellationRequested)
{
cancellationTokenSource.Cancel();
}
}
}
}
......
......@@ -18,15 +18,14 @@ namespace Titanium.Web.Proxy
/// </summary>
private async Task handleWebSocketUpgrade(string requestHttpMethod, string requestHttpUrl, Version requestVersion,
SessionEventArgs args, Request request, Response response,
CustomBufferedStream clientStream, HttpResponseWriter clientStreamWriter,
TcpServerConnection serverConnection,
HttpClientStream clientStream, TcpServerConnection serverConnection,
CancellationTokenSource cancellationTokenSource, CancellationToken cancellationToken)
{
// prepare the prefix content
var headerBuilder = new HeaderBuilder();
headerBuilder.WriteRequestLine(requestHttpMethod, requestHttpUrl, requestVersion);
headerBuilder.WriteHeaders(request.Headers);
await serverConnection.StreamWriter.WriteHeadersAsync(headerBuilder, cancellationToken);
await serverConnection.Stream.WriteHeadersAsync(headerBuilder, cancellationToken);
string httpStatus;
try
......@@ -51,7 +50,7 @@ namespace Titanium.Web.Proxy
if (!args.IsTransparent)
{
await clientStreamWriter.WriteResponseAsync(response,
await clientStream.WriteResponseAsync(response,
cancellationToken: cancellationToken);
}
......
......@@ -175,7 +175,7 @@ namespace Titanium.Web.Proxy
// Add custom div to body to clarify that the proxy (not the client browser) failed authentication
string authErrorMessage =
"<div class=\"inserted-by-proxy\"><h2>NTLM authentication through Titanium.Web.Proxy (" +
args.ProxyClient.Connection.LocalEndPoint +
args.ClientConnection.LocalEndPoint +
") failed. Please check credentials.</h2></div>";
string originalErrorMessage =
"<div class=\"inserted-by-proxy\"><h3>Response from remote web server below.</h3></div><br/>";
......
......@@ -69,7 +69,7 @@ namespace Titanium.Web.Proxy.IntegrationTests
};
var client = testSuite.GetClient(proxy1, true);
var response = await client.PostAsync(new Uri(server.ListeningHttpsUrl),
new StringContent("hello server. I am a client."));
......
......@@ -17,7 +17,7 @@
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel.Https" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="3.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.3.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" />
<PackageReference Include="MSTest.TestAdapter" Version="2.0.0" />
<PackageReference Include="MSTest.TestFramework" Version="2.0.0" />
</ItemGroup>
......
......@@ -7,7 +7,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.3.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" />
<PackageReference Include="MSTest.TestAdapter" Version="2.0.0" />
<PackageReference Include="MSTest.TestFramework" Version="2.0.0" />
</ItemGroup>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment