Unverified Commit 183f9601 authored by honfika's avatar honfika Committed by GitHub

Merge pull request #655 from justcoding121/master

beta
parents c6150bf9 7325e32a
......@@ -278,7 +278,6 @@ namespace Titanium.Web.Proxy.Examples.Basic
}
@lock.Release();
}
///// <summary>
......
......@@ -22,15 +22,10 @@ namespace Titanium.Web.Proxy
// 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 @@ namespace Titanium.Web.Proxy
};
// why is the sender null?
ClientCertificateSelectionCallback.InvokeAsync(this, args, exceptionFunc).Wait();
ClientCertificateSelectionCallback.InvokeAsync(this, args, ExceptionFunc).Wait();
return args.ClientCertificate;
}
......
......@@ -10,20 +10,27 @@ namespace Titanium.Web.Proxy.EventArguments
/// </summary>
public class CertificateValidationEventArgs : EventArgs
{
public CertificateValidationEventArgs(X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
Certificate = certificate;
Chain = chain;
SslPolicyErrors = sslPolicyErrors;
}
/// <summary>
/// Server certificate.
/// </summary>
public X509Certificate Certificate { get; internal set; }
public X509Certificate Certificate { get; }
/// <summary>
/// Certificate chain.
/// </summary>
public X509Chain Chain { get; internal set; }
public X509Chain Chain { get; }
/// <summary>
/// SSL policy errors.
/// </summary>
public SslPolicyErrors SslPolicyErrors { get; internal set; }
public SslPolicyErrors SslPolicyErrors { get; }
/// <summary>
/// Is the given server certificate valid?
......
......@@ -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;
......@@ -49,12 +49,12 @@ namespace Titanium.Web.Proxy.EventArguments
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 @@ namespace Titanium.Web.Proxy.EventArguments
bytesRemaining = -1;
// chunk trail
baseStream.ReadLineAsync().Wait();
var task = baseStream.ReadLineAsync();
if (!task.IsCompleted)
task.AsTask().Wait();
}
}
......
......@@ -9,6 +9,7 @@ using Titanium.Web.Proxy.Helpers;
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 @@ namespace Titanium.Web.Proxy.EventArguments
/// <summary>
/// Constructor to initialize the proxy
/// </summary>
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 @@ namespace Titanium.Web.Proxy.EventArguments
/// </summary>
public event EventHandler<MultipartRequestPartSentEventArgs>? MultipartRequestPartSent;
private ICustomStreamReader getStreamReader(bool isRequest)
private CustomBufferedStream getStreamReader(bool isRequest)
{
return isRequest ? ProxyClient.ClientStream : HttpClient.Connection.Stream;
}
......@@ -333,7 +327,7 @@ namespace Titanium.Web.Proxy.EventArguments
/// Read a line from the byte stream
/// </summary>
/// <returns></returns>
private async Task<long> readUntilBoundaryAsync(ICustomStreamReader reader, long totalBytesToRead, ReadOnlyMemory<char> boundary, CancellationToken cancellationToken)
private async Task<long> readUntilBoundaryAsync(ILineStream reader, long totalBytesToRead, ReadOnlyMemory<char> boundary, CancellationToken cancellationToken)
{
int bufferDataLength = 0;
......
......@@ -22,7 +22,7 @@ namespace Titanium.Web.Proxy.EventArguments
{
private static bool isWindowsAuthenticationSupported => RunTime.IsWindows;
internal readonly CancellationTokenSource? CancellationTokenSource;
internal readonly CancellationTokenSource CancellationTokenSource;
internal TcpServerConnection ServerConnection => HttpClient.Connection;
......@@ -40,25 +40,19 @@ namespace Titanium.Web.Proxy.EventArguments
/// <summary>
/// Initializes a new instance of the <see cref="SessionEventArgsBase" /> class.
/// </summary>
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<int>(() => ProxyClient.Connection.GetProcessId(endPoint)));
LocalEndPoint = endPoint;
EnableWinAuth = server.EnableWinAuth && isWindowsAuthenticationSupported;
HttpClient.ProcessId = new Lazy<int>(() => ProxyClient.Connection.GetProcessId(endPoint));
}
/// <summary>
......@@ -84,7 +78,7 @@ namespace Titanium.Web.Proxy.EventArguments
get => enableWinAuth;
set
{
if (!isWindowsAuthenticationSupported)
if (value && !isWindowsAuthenticationSupported)
throw new Exception("Windows Authentication is not supported");
enableWinAuth = value;
......
......@@ -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 @@ namespace Titanium.Web.Proxy.EventArguments
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;
}
/// <summary>
......
......@@ -14,6 +14,7 @@ using Titanium.Web.Proxy.Helpers;
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;
......@@ -53,13 +54,13 @@ namespace Titanium.Web.Proxy
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;
......@@ -67,18 +68,16 @@ namespace Titanium.Web.Proxy
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);
......@@ -303,7 +302,7 @@ namespace Titanium.Web.Proxy
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;
......@@ -336,10 +335,8 @@ namespace Titanium.Web.Proxy
var connectionPreface = new ReadOnlyMemory<byte>(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); },
......
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<byte> 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);
}
}
}
......@@ -94,19 +94,19 @@ namespace System.Net.Security
{
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<SslApplicationProtocol> ApplicationProtocols { get; set; }
internal List<SslApplicationProtocol>? ApplicationProtocols { get; set; }
internal RemoteCertificateValidationCallback RemoteCertificateValidationCallback { get; set; }
internal RemoteCertificateValidationCallback? RemoteCertificateValidationCallback { get; set; }
internal EncryptionPolicy EncryptionPolicy { get; set; }
}
......
using System;
using System.Globalization;
using Titanium.Web.Proxy.Models;
namespace Titanium.Web.Proxy.Extensions
{
......
using System;
using System.Text;
namespace Titanium.Web.Proxy.Extensions
{
......@@ -12,5 +13,5 @@ namespace Titanium.Web.Proxy.Extensions
return uri.IsWellFormedOriginalString() ? uri.PathAndQuery : uri.GetComponents(UriComponents.PathAndQuery, UriFormat.Unescaped);
}
}
}
}
......@@ -5,6 +5,7 @@ using System.Threading;
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<char> data;
......@@ -73,7 +70,7 @@ namespace Titanium.Web.Proxy.Helpers
// return default if not specified
if (contentType == null)
{
return defaultEncoding;
return HttpHeader.DefaultEncoding;
}
// extract the encoding by finding the charset
......@@ -105,7 +102,7 @@ namespace Titanium.Web.Proxy.Helpers
}
// return default if not specified
return defaultEncoding;
return HttpHeader.DefaultEncoding;
}
internal static ReadOnlyMemory<char> GetBoundaryFromContentType(string? contentType)
......@@ -173,7 +170,7 @@ 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(ICustomStreamReader clientStreamReader, IBufferPool bufferPool, CancellationToken cancellationToken = default)
internal static Task<int> IsConnectMethod(CustomBufferedStream clientStreamReader, IBufferPool bufferPool, CancellationToken cancellationToken = default)
{
return startsWith(clientStreamReader, bufferPool, "CONNECT", cancellationToken);
}
......@@ -182,7 +179,7 @@ namespace Titanium.Web.Proxy.Helpers
/// 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(ICustomStreamReader clientStreamReader, IBufferPool bufferPool, CancellationToken cancellationToken = default)
internal static Task<int> IsPriMethod(CustomBufferedStream clientStreamReader, IBufferPool bufferPool, CancellationToken cancellationToken = default)
{
return startsWith(clientStreamReader, bufferPool, "PRI", cancellationToken);
}
......@@ -193,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(ICustomStreamReader clientStreamReader, IBufferPool bufferPool, string expectedStart, CancellationToken cancellationToken = default)
private static async Task<int> startsWith(CustomBufferedStream clientStreamReader, IBufferPool bufferPool, string expectedStart, CancellationToken cancellationToken = default)
{
const int lengthToCheck = 10;
if (bufferPool.BufferSize < lengthToCheck)
......
......@@ -22,7 +22,7 @@ namespace Titanium.Web.Proxy.Helpers
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);
}
}
......
......@@ -6,6 +6,7 @@ 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;
......@@ -19,7 +20,7 @@ namespace Titanium.Web.Proxy.Helpers
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)
{
......@@ -87,12 +88,13 @@ namespace Titanium.Web.Proxy.Helpers
/// <summary>
/// Write the headers to client
/// </summary>
/// <param name="header"></param>
/// <param name="headerBuilder"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
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);
}
/// <summary>
......@@ -147,7 +149,7 @@ namespace Titanium.Web.Proxy.Helpers
/// <param name="onCopy"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
internal Task CopyBodyAsync(ICustomStreamReader streamReader, bool isChunked, long contentLength,
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
......@@ -192,11 +194,11 @@ namespace Titanium.Web.Proxy.Helpers
/// <param name="onCopy"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
private async Task copyBodyChunkedAsync(ICustomStreamReader reader, Action<byte[], int, int>? onCopy, CancellationToken cancellationToken)
private async Task copyBodyChunkedAsync(CustomBufferedStream reader, Action<byte[], int, int>? onCopy, CancellationToken cancellationToken)
{
while (true)
{
string chunkHead = await reader.ReadLineAsync(cancellationToken);
string? chunkHead = await reader.ReadLineAsync(cancellationToken);
int idx = chunkHead.IndexOf(";");
if (idx >= 0)
{
......@@ -232,7 +234,7 @@ namespace Titanium.Web.Proxy.Helpers
/// <param name="onCopy"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
private async Task copyBytesFromStream(ICustomStreamReader reader, long count, Action<byte[], int, int>? onCopy,
private async Task copyBytesFromStream(CustomBufferedStream reader, long count, Action<byte[], int, int>? onCopy,
CancellationToken cancellationToken)
{
var buffer = bufferPool.GetBuffer();
......@@ -279,7 +281,7 @@ namespace Titanium.Web.Proxy.Helpers
{
var body = requestResponse.CompressBodyAndUpdateContentLength();
headerBuilder.WriteHeaders(requestResponse.Headers);
await WriteAsync(headerBuilder.GetBytes(), true, cancellationToken);
await WriteHeadersAsync(headerBuilder, cancellationToken);
if (body != null)
{
......
......@@ -34,9 +34,9 @@ namespace Titanium.Web.Proxy.Helpers.WinHttp
}
}
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 @@ namespace Titanium.Web.Proxy.Helpers.WinHttp
public bool AutomaticallyDetectSettings { get; internal set; }
private WebProxy proxy { get; set; }
private WebProxy? proxy { get; set; }
public void Dispose()
{
......
......@@ -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)
{
......@@ -67,7 +67,7 @@ namespace Titanium.Web.Proxy.Http
public void Write(string str)
{
var encoding = HttpHelper.HeaderEncoding;
var encoding = HttpHeader.Encoding;
#if NETSTANDARD2_1
var buf = ArrayPool<byte>.Shared.Rent(str.Length * 4);
......@@ -83,9 +83,16 @@ namespace Titanium.Web.Proxy.Http
#endif
}
public byte[] GetBytes()
public ArraySegment<byte> 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);
}
}
}
......@@ -4,6 +4,7 @@ using System.Collections.Generic;
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 @@ namespace Titanium.Web.Proxy.Http
if (headers.TryGetValue(headerName, out var header))
{
header.Value = value;
header.ValueData = value.GetByteString();
}
else
{
......
......@@ -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 @@ namespace Titanium.Web.Proxy.Http
}
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);
}
}
......
......@@ -18,10 +18,12 @@ namespace Titanium.Web.Proxy.Http
{
private TcpServerConnection? connection;
internal HttpWebClient(Request? request)
internal HttpWebClient(ConnectRequest? connectRequest, Request? request, Lazy<int> processIdFunc)
{
ConnectRequest = connectRequest;
Request = request ?? new Request();
Response = new Response();
ProcessId = processIdFunc;
}
/// <summary>
......@@ -65,7 +67,7 @@ namespace Titanium.Web.Proxy.Http
/// <summary>
/// Headers passed with Connect.
/// </summary>
public ConnectRequest? ConnectRequest { get; internal set; }
public ConnectRequest? ConnectRequest { get; }
/// <summary>
/// Web Request.
......@@ -114,7 +116,7 @@ namespace Titanium.Web.Proxy.Http
string url;
if (useUpstreamProxy || isTransparent)
{
url = Request.RequestUriString;
url = Request.Url;
}
else
{
......@@ -147,9 +149,7 @@ namespace Titanium.Web.Proxy.Http
headerBuilder.WriteLine();
var data = headerBuilder.GetBytes();
await writer.WriteAsync(data, 0, data.Length, cancellationToken);
await writer.WriteHeadersAsync(headerBuilder, cancellationToken);
if (enable100ContinueBehaviour && Request.ExpectContinue)
{
......
......@@ -14,40 +14,54 @@ namespace Titanium.Web.Proxy.Http
[TypeConverter(typeof(ExpandableObjectConverter))]
public class Request : RequestResponseBase
{
private string originalUrl;
/// <summary>
/// Request Method.
/// </summary>
public string Method { get; set; }
/// <summary>
/// Request HTTP Uri.
/// </summary>
public Uri RequestUri { get; set; }
/// <summary>
/// Is Https?
/// </summary>
public bool IsHttps => RequestUri.Scheme == ProxyServer.UriSchemeHttps;
/// <summary>
/// The original request Url.
/// </summary>
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;
}
}
/// <summary>
/// The request uri as it is in the HTTP header
/// The original request Url.
/// </summary>
public string RequestUriString { get; set; }
public string OriginalUrl => originalUrlData.GetString();
/// <summary>
/// Request HTTP Uri.
/// </summary>
public Uri RequestUri { get; set; }
/// <summary>
/// The request url as it is in the HTTP header
/// </summary>
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;
}
/// <summary>
/// Has request body?
......@@ -108,11 +122,6 @@ namespace Titanium.Web.Proxy.Http
/// </summary>
public bool IsMultipartFormData => ContentType?.StartsWith("multipart/form-data") == true;
/// <summary>
/// Request Url.
/// </summary>
public string Url => RequestUri.OriginalString;
/// <summary>
/// Cancels the client HTTP request without sending to server.
/// This should be set when API user responds with custom response.
......@@ -155,9 +164,9 @@ namespace Titanium.Web.Proxy.Http
get
{
var headerBuilder = new HeaderBuilder();
headerBuilder.WriteRequestLine(Method, RequestUriString, HttpVersion);
headerBuilder.WriteRequestLine(Method, Url, HttpVersion);
headerBuilder.WriteHeaders(Headers);
return HttpHelper.HeaderEncoding.GetString(headerBuilder.GetBytes());
return headerBuilder.GetString(HttpHeader.Encoding);
}
}
......
......@@ -57,7 +57,7 @@ namespace Titanium.Web.Proxy.Http
internal bool Http2IgnoreBodyFrames;
internal Task Http2BeforeHandlerTask;
internal Task? Http2BeforeHandlerTask;
/// <summary>
/// Priority used only in HTTP/2
......
......@@ -102,7 +102,7 @@ namespace Titanium.Web.Proxy.Http
var headerBuilder = new HeaderBuilder();
headerBuilder.WriteResponseLine(HttpVersion, StatusCode, StatusDescription);
headerBuilder.WriteHeaders(Headers);
return HttpHelper.HeaderEncoding.GetString(headerBuilder.GetBytes());
return headerBuilder.GetString(HttpHeader.Encoding);
}
}
......@@ -121,8 +121,7 @@ namespace Titanium.Web.Proxy.Http
}
}
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)
......
......@@ -22,7 +22,7 @@ using Titanium.Web.Proxy.Models;
namespace Titanium.Web.Proxy.Http2.Hpack
{
public class Decoder
internal class Decoder
{
private readonly DynamicTable dynamicTable;
......@@ -39,7 +39,7 @@ namespace Titanium.Web.Proxy.Http2.Hpack
private int skipLength;
private int nameLength;
private int valueLength;
private string name;
private ByteString name;
private enum State
{
......@@ -248,7 +248,7 @@ namespace Titanium.Web.Proxy.Http2.Hpack
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 @@ namespace Titanium.Web.Proxy.Http2.Hpack
if (nameLength + HttpHeader.HttpHeaderOverhead > dynamicTable.Capacity)
{
dynamicTable.Clear();
name = string.Empty;
name = Array.Empty<byte>();
skipLength = nameLength;
state = State.SkipLiteralHeaderName;
break;
......@@ -292,7 +292,7 @@ namespace Titanium.Web.Proxy.Http2.Hpack
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 @@ namespace Titanium.Web.Proxy.Http2.Hpack
if (nameLength + HttpHeader.HttpHeaderOverhead > dynamicTable.Capacity)
{
dynamicTable.Clear();
name = string.Empty;
name = ByteString.Empty;
skipLength = nameLength;
state = State.SkipLiteralHeaderName;
break;
......@@ -375,7 +375,7 @@ namespace Titanium.Web.Proxy.Http2.Hpack
if (valueLength == 0)
{
InsertHeader(headerListener, name, string.Empty, indexType);
InsertHeader(headerListener, name, Array.Empty<byte>(), indexType);
state = State.ReadHeaderRepresentation;
}
else
......@@ -531,16 +531,16 @@ namespace Titanium.Web.Proxy.Http2.Hpack
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 @@ namespace Titanium.Web.Proxy.Http2.Hpack
}
}
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 @@ namespace Titanium.Web.Proxy.Http2.Hpack
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 @@ namespace Titanium.Web.Proxy.Http2.Hpack
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
......
/*

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 @@ using Titanium.Web.Proxy.Models;
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;
/// <summary>
......@@ -63,7 +66,7 @@ namespace Titanium.Web.Proxy.Http2.Hpack
/// <param name="sensitive">If set to <c>true</c> sensitive.</param>
/// <param name="indexType">Index type.</param>
/// <param name="useStaticName">Use static name.</param>
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 @@ namespace Titanium.Web.Proxy.Http2.Hpack
/// Encode string literal according to Section 5.2.
/// </summary>
/// <param name="output">Output.</param>
/// <param name="stringLiteral">String literal.</param>
private void encodeStringLiteral(BinaryWriter output, string stringLiteral)
/// <param name="stringData">String data.</param>
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 @@ namespace Titanium.Web.Proxy.Http2.Hpack
else
{
encodeInteger(output, 0x00, 7, stringData.Length);
output.Write(stringData, 0, stringData.Length);
output.Write(stringData.Span);
}
}
......@@ -215,7 +217,7 @@ namespace Titanium.Web.Proxy.Http2.Hpack
/// <param name="value">Value.</param>
/// <param name="indexType">Index type.</param>
/// <param name="nameIndex">Name index.</param>
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 @@ namespace Titanium.Web.Proxy.Http2.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 @@ namespace Titanium.Web.Proxy.Http2.Hpack
/// <returns>The entry.</returns>
/// <param name="name">Name.</param>
/// <param name="value">Value.</param>
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 @@ namespace Titanium.Web.Proxy.Http2.Hpack
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 @@ namespace Titanium.Web.Proxy.Http2.Hpack
/// </summary>
/// <returns>The index.</returns>
/// <param name="name">Name.</param>
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 @@ namespace Titanium.Web.Proxy.Http2.Hpack
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 @@ namespace Titanium.Web.Proxy.Http2.Hpack
/// </summary>
/// <param name="name">Name.</param>
/// <param name="value">Value.</param>
private void add(string name, string value)
private void add(ByteString name, ByteString value)
{
int headerSize = HttpHeader.SizeOf(name, value);
......@@ -457,12 +459,12 @@ namespace Titanium.Web.Proxy.Http2.Hpack
/// </summary>
/// <returns><c>true</c> if hash name; otherwise, <c>false</c>.</returns>
/// <param name="name">Name.</param>
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 @@ namespace Titanium.Web.Proxy.Http2.Hpack
/// <param name="value">Value.</param>
/// <param name="index">Index.</param>
/// <param name="next">Next.</param>
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 @@ namespace Titanium.Web.Proxy.Http2.Hpack
}
}
}
#endif
......@@ -54,7 +54,7 @@ namespace Titanium.Web.Proxy.Http2.Hpack
/// <param name="buf">the string literal to be decoded</param>
/// <returns>the output stream for the compressed data</returns>
/// <exception cref="IOException">throws IOException if an I/O error occurs. In particular, an <code>IOException</code> may be thrown if the output stream has been closed.</exception>
public string Decode(byte[] buf)
public ReadOnlyMemory<byte> Decode(byte[] buf)
{
var resultBuf = new byte[buf.Length * 2];
int resultSize = 0;
......@@ -109,7 +109,7 @@ namespace Titanium.Web.Proxy.Http2.Hpack
throw new IOException("Invalid Padding");
}
return Encoding.UTF8.GetString(resultBuf, 0, resultSize);
return resultBuf.AsMemory(0, resultSize);
}
private class Node
......
......@@ -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
{
/// <summary>
/// Huffman Encoder
......@@ -42,39 +43,15 @@ namespace Titanium.Web.Proxy.Http2.Hpack
/// </summary>
/// <param name="output">the output stream for the compressed data</param>
/// <param name="data">the string literal to be Huffman encoded</param>
/// <exception cref="IOException">if an I/O error occurs.</exception>
/// <see cref="Encode(BinaryWriter,byte[],int,int)"/>
public void Encode(BinaryWriter output, byte[] data)
{
Encode(output, data, 0, data.Length);
}
/// <summary>
/// Compresses the input string literal using the Huffman coding.
/// </summary>
/// <param name="output">the output stream for the compressed data</param>
/// <param name="data">the string literal to be Huffman encoded</param>
/// <param name="off">the start offset in the data</param>
/// <param name="len">the number of bytes to encode</param>
/// <exception cref="IOException">if an I/O error occurs. In particular, an <code>IOException</code> may be thrown if the output stream has been closed.</exception>
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 @@ namespace Titanium.Web.Proxy.Http2.Hpack
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 @@ namespace Titanium.Web.Proxy.Http2.Hpack
/// </summary>
/// <returns>the number of bytes required to Huffman encode <code>data</code></returns>
/// <param name="data">the string literal to be Huffman encoded</param>
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];
}
......
......@@ -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
{
/// <summary>
/// EmitHeader is called by the decoder during header field emission.
......@@ -25,6 +29,6 @@ namespace Titanium.Web.Proxy.Http2.Hpack
/// <param name="name">Name.</param>
/// <param name="value">Value.</param>
/// <param name="sensitive">If set to <c>true</c> sensitive.</param>
void AddHeader(string name, string value, bool sensitive);
void AddHeader(ByteString name, ByteString value, bool sensitive);
}
}
#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.EventArguments;
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 @@ namespace Titanium.Web.Proxy.Http2
(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 @@ namespace Titanium.Web.Proxy.Http2
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 @@ namespace Titanium.Web.Proxy.Http2
{
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 @@ namespace Titanium.Web.Proxy.Http2
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 @@ namespace Titanium.Web.Proxy.Http2
class MyHeaderListener : IHeaderListener
{
private readonly Action<string, string> addHeaderFunc;
private readonly Action<ByteString, ByteString> 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<string, string> addHeaderFunc)
public MyHeaderListener(Action<ByteString, ByteString> 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 @@ namespace Titanium.Web.Proxy.Http2
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));
}
}
}
......
using System;
using System.Text;
namespace Titanium.Web.Proxy.Models
{
internal struct ByteString : IEquatable<ByteString>
{
public static ByteString Empty = new ByteString(ReadOnlyMemory<byte>.Empty);
public ReadOnlyMemory<byte> Data { get; }
public ReadOnlySpan<byte> Span => Data.Span;
public int Length => Data.Length;
public ByteString(ReadOnlyMemory<byte> 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<byte> data) => new ByteString(data);
}
}
......@@ -11,9 +11,9 @@ namespace Titanium.Web.Proxy.Models
private static readonly Lazy<NetworkCredential> defaultCredentials =
new Lazy<NetworkCredential>(() => CredentialCache.DefaultNetworkCredentials);
private string password;
private string? password;
private string userName;
private string? userName;
/// <summary>
/// Use default windows credentials?
......@@ -28,7 +28,7 @@ namespace Titanium.Web.Proxy.Models
/// <summary>
/// Username.
/// </summary>
public string UserName
public string? UserName
{
get => UseDefaultCredentials ? defaultCredentials.Value.UserName : userName;
set
......@@ -45,7 +45,7 @@ namespace Titanium.Web.Proxy.Models
/// <summary>
/// Password.
/// </summary>
public string Password
public string? Password
{
get => UseDefaultCredentials ? defaultCredentials.Value.Password : password;
set
......@@ -62,22 +62,13 @@ namespace Titanium.Web.Proxy.Models
/// <summary>
/// Host name.
/// </summary>
public string HostName { get; set; }
public string HostName { get; set; } = string.Empty;
/// <summary>
/// Port.
/// </summary>
public int Port { get; set; }
/// <summary>
/// Get cache key for Tcp connection cache.
/// </summary>
/// <returns></returns>
internal string GetCacheKey()
{
return $"{HostName}-{Port}" + (UseDefaultCredentials ? $"-{UserName}-{Password}" : string.Empty);
}
/// <summary>
/// returns data in Hostname:port format.
/// </summary>
......
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 @@ namespace Titanium.Web.Proxy.Models
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");
/// <summary>
......@@ -47,30 +53,45 @@ namespace Titanium.Web.Proxy.Models
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;
}
/// <summary>
/// Header Name.
/// </summary>
public string Name { get; }
public string Name => NameData.GetString();
internal ByteString NameData { get; }
/// <summary>
/// Header Value.
/// </summary>
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 @@ namespace Titanium.Web.Proxy.Models
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}")));
......
......@@ -25,7 +25,7 @@ namespace Titanium.Web.Proxy.Models
/// <summary>
/// underlying TCP Listener object
/// </summary>
internal TcpListener Listener { get; set; }
internal TcpListener? Listener { get; set; }
/// <summary>
/// Ip Address we are listening.
......@@ -45,6 +45,6 @@ namespace Titanium.Web.Proxy.Models
/// <summary>
/// Generic certificate to use for SSL decryption.
/// </summary>
public X509Certificate2 GenericCertificate { get; set; }
public X509Certificate2? GenericCertificate { get; set; }
}
}
......@@ -8,7 +8,12 @@ namespace Titanium.Web.Proxy.Network
/// </summary>
internal sealed class CachedCertificate
{
internal X509Certificate2 Certificate { get; set; }
public CachedCertificate(X509Certificate2 certificate)
{
Certificate = certificate;
}
internal X509Certificate2 Certificate { get; }
/// <summary>
/// Last time this certificate was used.
......
......@@ -44,7 +44,7 @@ namespace Titanium.Web.Proxy.Network.Certificate
private readonly Type typeX509PrivateKey;
private object sharedPrivateKey;
private object? sharedPrivateKey;
/// <summary>
/// Constructor.
......
......@@ -60,15 +60,30 @@ namespace Titanium.Web.Proxy.Network
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 @@ namespace Titanium.Web.Proxy.Network
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 @@ namespace Titanium.Web.Proxy.Network
var result = CreateCertificate(certificateName, false);
if (result != null)
{
cachedCertificates.TryAdd(certificateName, new CachedCertificate
{
Certificate = result
});
cachedCertificates.TryAdd(certificateName, new CachedCertificate(result));
}
return result;
......
......@@ -9,19 +9,26 @@ namespace Titanium.Web.Proxy.Network
/// </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; set; }
internal TcpClientConnection Connection { get; }
/// <summary>
/// Holds the stream to client
/// </summary>
internal CustomBufferedStream ClientStream { get; set; }
internal CustomBufferedStream ClientStream { get; }
/// <summary>
/// Used to write line by line to client
/// </summary>
internal HttpResponseWriter ClientStreamWriter { get; set; }
internal HttpResponseWriter ClientStreamWriter { get; }
}
}
......@@ -11,6 +11,7 @@ using System.Text;
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;
......@@ -48,27 +49,52 @@ namespace Titanium.Web.Proxy.Network.Tcp
internal string GetConnectionCacheKey(string remoteHostName, int remotePort,
bool isHttps, List<SslApplicationProtocol>? 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();
}
......@@ -180,7 +206,7 @@ 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, ExternalProxy? externalProxy,
bool noCache, CancellationToken cancellationToken)
{
var sslProtocol = session?.ProxyClient.Connection.SslProtocol ?? SslProtocols.None;
......@@ -210,9 +236,7 @@ namespace Titanium.Web.Proxy.Network.Tcp
}
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;
}
......@@ -231,11 +255,12 @@ namespace Titanium.Web.Proxy.Network.Tcp
/// <param name="session">The http session.</param>
/// <param name="upStreamEndPoint">The local upstream endpoint to make request via.</param>
/// <param name="externalProxy">The external proxy to make request via.</param>
/// <param name="cacheKey">The connection cache key</param>
/// <param name="cancellationToken">The cancellation token for this async task.</param>
/// <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,
ProxyServer proxyServer, SessionEventArgsBase? session, IPEndPoint? upStreamEndPoint, ExternalProxy? externalProxy, string cacheKey,
CancellationToken cancellationToken)
{
// deny connection to proxy end points to avoid infinite connection loop.
......@@ -280,8 +305,8 @@ namespace Titanium.Web.Proxy.Network.Tcp
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)
......@@ -349,7 +374,7 @@ namespace Titanium.Web.Proxy.Network.Tcp
var writer = new HttpRequestWriter(stream, proxyServer.BufferPool);
var connectRequest = new ConnectRequest
{
OriginalUrl = $"{remoteHostName}:{remotePort}",
OriginalUrlData = HttpHeader.Encoding.GetBytes($"{remoteHostName}:{remotePort}"),
HttpVersion = httpVersion
};
......@@ -364,7 +389,8 @@ namespace Titanium.Web.Proxy.Network.Tcp
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);
......@@ -416,17 +442,8 @@ namespace Titanium.Web.Proxy.Network.Tcp
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);
}
......
......@@ -15,7 +15,9 @@ 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, 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 @@ namespace Titanium.Web.Proxy.Network.Tcp
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; }
......
......@@ -47,24 +47,7 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security
internal Message(byte[] message)
{
type = 3;
Decode(message);
}
/// <summary>
/// Domain name
/// </summary>
internal string Domain { get; private set; }
/// <summary>
/// Username
/// </summary>
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 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security
Username = DecodeString(message, userOff, userLen);
}
/// <summary>
/// Domain name
/// </summary>
internal string Domain { get; private set; }
/// <summary>
/// Username
/// </summary>
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)
......
......@@ -64,17 +64,14 @@ namespace Titanium.Web.Proxy
}
// read the request line
string httpCmd = await clientStream.ReadLineAsync(cancellationToken);
string? httpCmd = await clientStream.ReadLineAsync(cancellationToken);
if (string.IsNullOrEmpty(httpCmd))
{
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
};
......@@ -82,7 +79,7 @@ namespace Titanium.Web.Proxy
{
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,
......@@ -123,12 +120,10 @@ namespace Titanium.Web.Proxy
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)
{
......
......@@ -93,7 +93,7 @@ namespace Titanium.Web.Proxy
// 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;
......
......@@ -17,19 +17,19 @@ namespace Titanium.Web.Proxy.StreamExtended
"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 @@ namespace Titanium.Web.Proxy.StreamExtended
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<string, SslExtension>? Extensions { get; set; }
......@@ -79,9 +79,15 @@ namespace Titanium.Web.Proxy.StreamExtended
}
}
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)
......
......@@ -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.
/// </summary>
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 @@ namespace Titanium.Web.Proxy.StreamExtended.Network
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 @@ namespace Titanium.Web.Proxy.StreamExtended.Network
return await reader.FillBufferAsync(cancellationToken);
}
public byte PeekByteFromBuffer(int index)
{
return reader.PeekByteFromBuffer(index);
}
public Task<int> PeekByteAsync(int index, CancellationToken cancellationToken = default)
{
return reader.PeekByteAsync(index, cancellationToken);
}
public Task<int> 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,45 +59,7 @@ namespace Titanium.Web.Proxy.StreamExtended.Network
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<int> 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 Task<string?> ReadLineAsync(CancellationToken cancellationToken = default)
public ValueTask<string?> ReadLineAsync(CancellationToken cancellationToken = default)
{
return CustomBufferedStream.ReadLineInternalAsync(this, bufferPool, cancellationToken);
}
......
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;
}
/// <summary>
/// Gets a value indicating whether data is available.
/// </summary>
bool ICustomStreamReader.DataAvailable => Available > 0;
/// <summary>
/// Gets the available data size.
/// </summary>
public int Available => baseStream.Available - Position;
internal async Task<bool> 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;
}
/// <summary>
/// Fills the buffer asynchronous.
/// </summary>
/// <returns></returns>
ValueTask<bool> ICustomStreamReader.FillBufferAsync(CancellationToken cancellationToken)
{
return baseStream.FillBufferAsync(cancellationToken);
}
/// <summary>
/// Peeks a byte from buffer.
/// </summary>
/// <param name="index">The index.</param>
/// <returns></returns>
byte ICustomStreamReader.PeekByteFromBuffer(int index)
{
return baseStream.PeekByteFromBuffer(index);
}
/// <summary>
/// Peeks bytes asynchronous.
/// </summary>
/// <param name="buffer">The buffer to copy.</param>
/// <param name="offset">The offset where copying.</param>
/// <param name="index">The index.</param>
/// <param name="count">The count.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns></returns>
Task<int> ICustomStreamReader.PeekBytesAsync(byte[] buffer, int offset, int index, int count, CancellationToken cancellationToken)
{
return baseStream.PeekBytesAsync(buffer, offset, index, count, cancellationToken);
}
/// <summary>
/// Peeks a byte asynchronous.
/// </summary>
/// <param name="index">The index.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns></returns>
Task<int> ICustomStreamReader.PeekByteAsync(int index, CancellationToken cancellationToken)
{
return baseStream.PeekByteAsync(index, cancellationToken);
}
/// <summary>
/// Reads a byte from buffer.
/// </summary>
/// <returns></returns>
byte ICustomStreamReader.ReadByteFromBuffer()
{
return ReadByte();
}
int ICustomStreamReader.Read(byte[] buffer, int offset, int count)
{
return baseStream.Read(buffer, offset, count);
}
/// <summary>
/// Reads the asynchronous.
/// </summary>
/// <param name="buffer">The buffer.</param>
/// <param name="offset">The offset.</param>
/// <param name="count">The count.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns></returns>
Task<int> ICustomStreamReader.ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
return baseStream.ReadAsync(buffer, offset, count, cancellationToken);
}
/// <summary>
/// Read a line from the byte stream
/// </summary>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<string?> ICustomStreamReader.ReadLineAsync(CancellationToken cancellationToken)
{
return CustomBufferedStream.ReadLineInternalAsync(this, bufferPool, cancellationToken);
}
}
}
......@@ -6,6 +6,7 @@ using System.Text;
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.
/// </summary>
/// <seealso cref="System.IO.Stream" />
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;
......@@ -157,7 +157,7 @@ namespace Titanium.Web.Proxy.StreamExtended.Network
/// <returns>
/// A task that represents the asynchronous copy operation.
/// </returns>
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 @@ namespace Titanium.Web.Proxy.StreamExtended.Network
/// <returns>
/// A task that represents the asynchronous flush operation.
/// </returns>
public override Task FlushAsync(CancellationToken cancellationToken = default)
public override Task FlushAsync(CancellationToken cancellationToken)
{
return BaseStream.FlushAsync(cancellationToken);
}
......@@ -201,7 +201,7 @@ namespace Titanium.Web.Proxy.StreamExtended.Network
/// less than the requested number, or it can be 0 (zero)
/// if the end of the stream has been reached.
/// </returns>
public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken = default)
public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
if (bufferLength == 0)
{
......@@ -219,6 +219,45 @@ namespace Titanium.Web.Proxy.StreamExtended.Network
return available;
}
/// <summary>
/// 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.
/// </summary>
/// <param name="buffer">The buffer to write the data into.</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 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.
/// </returns>
#if NETSTANDARD2_1
public override async ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
#else
public async ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
#endif
{
if (bufferLength == 0)
{
await FillBufferAsync(cancellationToken);
}
int available = Math.Min(bufferLength, buffer.Length);
if (available > 0)
{
new Span<byte>(streamBuffer, bufferPos, available).CopyTo(buffer.Span);
bufferPos += available;
bufferLength -= available;
}
return available;
}
/// <summary>
/// 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.
/// </summary>
......@@ -247,7 +286,7 @@ namespace Titanium.Web.Proxy.StreamExtended.Network
/// <param name="index">The index.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns></returns>
public async Task<int> PeekByteAsync(int index, CancellationToken cancellationToken = default)
public async ValueTask<int> PeekByteAsync(int index, CancellationToken cancellationToken = default)
{
// When index is greater than the buffer size
if (streamBuffer.Length <= index)
......@@ -277,7 +316,7 @@ namespace Titanium.Web.Proxy.StreamExtended.Network
/// <param name="count">The count.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns></returns>
public async Task<int> PeekBytesAsync(byte[] buffer, int offset, int index, int count, CancellationToken cancellationToken = default)
public async ValueTask<int> 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)
......@@ -346,7 +385,7 @@ namespace Titanium.Web.Proxy.StreamExtended.Network
/// A task that represents the asynchronous write operation.
/// </returns>
[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 +597,7 @@ namespace Titanium.Web.Proxy.StreamExtended.Network
/// Read a line from the byte stream
/// </summary>
/// <returns></returns>
public Task<string?> ReadLineAsync(CancellationToken cancellationToken = default)
public ValueTask<string?> ReadLineAsync(CancellationToken cancellationToken = default)
{
return ReadLineInternalAsync(this, bufferPool, cancellationToken);
}
......@@ -567,7 +606,7 @@ namespace Titanium.Web.Proxy.StreamExtended.Network
/// Read a line from the byte stream
/// </summary>
/// <returns></returns>
internal static async Task<string?> ReadLineInternalAsync(ICustomStreamReader reader, IBufferPool bufferPool, CancellationToken cancellationToken = default)
internal static async ValueTask<string?> ReadLineInternalAsync(ILineStream reader, IBufferPool bufferPool, CancellationToken cancellationToken = default)
{
byte lastChar = default;
......
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.StreamExtended.Network
{
/// <summary>
/// This concrete implemetation of interface acts as the source stream for CopyStream class.
/// </summary>
public interface ICustomStreamReader
{
int Available { get; }
bool DataAvailable { get; }
/// <summary>
/// Fills the buffer asynchronous.
/// </summary>
/// <returns></returns>
ValueTask<bool> FillBufferAsync(CancellationToken cancellationToken = default);
/// <summary>
/// Peeks a byte from buffer.
/// </summary>
/// <param name="index">The index.</param>
/// <returns></returns>
/// <exception cref="Exception">Index is out of buffer size</exception>
byte PeekByteFromBuffer(int index);
/// <summary>
/// Peeks a byte asynchronous.
/// </summary>
/// <param name="index">The index.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns></returns>
Task<int> PeekByteAsync(int index, CancellationToken cancellationToken = default);
/// <summary>
/// Peeks bytes asynchronous.
/// </summary>
/// <param name="buffer">The buffer to copy.</param>
/// <param name="offset">The offset where copying.</param>
/// <param name="index">The index.</param>
/// <param name="count">The count.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns></returns>
Task<int> PeekBytesAsync(byte[] buffer, int offset, int index, int count, CancellationToken cancellationToken = default);
byte ReadByteFromBuffer();
/// <summary>
/// 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.
/// </summary>
/// <param name="buffer">An array of bytes. When this method returns, the buffer contains the specified byte array with the values between <paramref name="offset" /> and (<paramref name="offset" /> + <paramref name="count" /> - 1) replaced by the bytes read from the current source.</param>
/// <param name="offset">The zero-based byte offset in <paramref name="buffer" /> at which to begin storing the data read from the current stream.</param>
/// <param name="count">The maximum number of bytes to be read from the current stream.</param>
/// <returns>
/// 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.
/// </returns>
int Read(byte[] buffer, int offset, int count);
/// <summary>
/// Read the specified number (or less) of raw bytes from the base stream to the given buffer to the specified offset
/// </summary>
/// <param name="buffer"></param>
/// <param name="offset"></param>
/// <param name="bytesToRead"></param>
/// <param name="cancellationToken"></param>
/// <returns>The number of bytes read</returns>
Task<int> ReadAsync(byte[] buffer, int offset, int bytesToRead,
CancellationToken cancellationToken = default);
/// <summary>
/// Read a line from the byte stream
/// </summary>
/// <returns></returns>
Task<string?> ReadLineAsync(CancellationToken cancellationToken = default);
}
}
using System.Threading;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.StreamExtended.Network
{
public interface ILineStream
{
bool DataAvailable { get; }
/// <summary>
/// Fills the buffer asynchronous.
/// </summary>
/// <returns></returns>
ValueTask<bool> FillBufferAsync(CancellationToken cancellationToken = default);
byte ReadByteFromBuffer();
/// <summary>
/// Read a line from the byte stream
/// </summary>
/// <returns></returns>
ValueTask<string?> ReadLineAsync(CancellationToken cancellationToken = default);
}
}
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.StreamExtended.Network
{
public interface IPeekStream
{
/// <summary>
/// Peeks a byte from buffer.
/// </summary>
/// <param name="index">The index.</param>
/// <returns></returns>
/// <exception cref="Exception">Index is out of buffer size</exception>
byte PeekByteFromBuffer(int index);
/// <summary>
/// Peeks a byte asynchronous.
/// </summary>
/// <param name="index">The index.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns></returns>
ValueTask<int> PeekByteAsync(int index, CancellationToken cancellationToken = default);
/// <summary>
/// Peeks bytes asynchronous.
/// </summary>
/// <param name="buffer">The buffer to copy.</param>
/// <param name="offset">The offset where copying.</param>
/// <param name="index">The index.</param>
/// <param name="count">The count.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns></returns>
ValueTask<int> PeekBytesAsync(byte[] buffer, int offset, int index, int count, CancellationToken cancellationToken = default);
}
}
\ No newline at end of file
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<bool> 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;
}
}
}
......@@ -16,13 +16,25 @@ namespace Titanium.Web.Proxy.StreamExtended
"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 @@ namespace Titanium.Web.Proxy.StreamExtended
}
}
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; }
......
......@@ -12,19 +12,6 @@ namespace Titanium.Web.Proxy.StreamExtended
/// </summary>
internal class SslTools
{
/// <summary>
/// Is the given stream starts with an SSL client hello?
/// </summary>
/// <param name="stream"></param>
/// <param name="bufferPool"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public static async Task<bool> IsClientHello(CustomBufferedStream stream, IBufferPool bufferPool, CancellationToken cancellationToken)
{
var clientHello = await PeekClientHello(stream, bufferPool, cancellationToken);
return clientHello != null;
}
/// <summary>
/// Peek the SSL client hello information.
/// </summary>
......@@ -46,7 +33,7 @@ namespace Titanium.Web.Proxy.StreamExtended
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 @@ namespace Titanium.Web.Proxy.StreamExtended
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 @@ namespace Titanium.Web.Proxy.StreamExtended
byte[] compressionData = peekStream.ReadBytes(length);
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);
}
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 @@ namespace Titanium.Web.Proxy.StreamExtended
{
// 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 @@ namespace Titanium.Web.Proxy.StreamExtended
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 @@ namespace Titanium.Web.Proxy.StreamExtended
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 @@ namespace Titanium.Web.Proxy.StreamExtended
return null;
}
private static async Task<Dictionary<string, SslExtension>?> ReadExtensions(int majorVersion, int minorVersion, CustomBufferedPeekStream peekStream, IBufferPool bufferPool, CancellationToken cancellationToken)
private static async Task<Dictionary<string, SslExtension>?> ReadExtensions(int majorVersion, int minorVersion, PeekStreamReader peekStreamReader, IBufferPool bufferPool, CancellationToken cancellationToken)
{
Dictionary<string, SslExtension>? 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<string, SslExtension>();
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;
......
......@@ -12,6 +12,7 @@ using Titanium.Web.Proxy.Exceptions;
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 @@ namespace Titanium.Web.Proxy
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);
}
......
......@@ -31,11 +31,8 @@ namespace Titanium.Web.Proxy
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))
{
......
......@@ -132,7 +132,7 @@ namespace Titanium.Web.Proxy
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);
......
......@@ -22,7 +22,7 @@ namespace Titanium.Web.Proxy.IntegrationTests.Helpers
var request = new Request
{
Method = "POST",
RequestUriString = "/",
Url = "/",
HttpVersion = new Version(1, 1)
};
request.Headers.AddHeader(KnownHeaders.Host, server);
......
......@@ -26,14 +26,14 @@ namespace Titanium.Web.Proxy.IntegrationTests.Helpers
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 @@ namespace Titanium.Web.Proxy.IntegrationTests.Helpers
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 @@ namespace Titanium.Web.Proxy.IntegrationTests.Helpers
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 @@ namespace Titanium.Web.Proxy.IntegrationTests.Helpers
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 @@ namespace Titanium.Web.Proxy.IntegrationTests.Helpers
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)
......
......@@ -13,7 +13,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.App" />
<FrameworkReference Include="Microsoft.AspNetCore.App" />
<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" />
......
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