Commit 1b0bfecd authored by Honfika's avatar Honfika

better uri handling

parent 7325e32a
...@@ -10,15 +10,15 @@ namespace Titanium.Web.Proxy.Compression ...@@ -10,15 +10,15 @@ namespace Titanium.Web.Proxy.Compression
/// </summary> /// </summary>
internal static class CompressionFactory internal static class CompressionFactory
{ {
internal static Stream Create(string type, Stream stream, bool leaveOpen = true) internal static Stream Create(HttpCompression type, Stream stream, bool leaveOpen = true)
{ {
switch (type) switch (type)
{ {
case KnownHeaders.ContentEncodingGzip: case HttpCompression.Gzip:
return new GZipStream(stream, CompressionMode.Compress, leaveOpen); return new GZipStream(stream, CompressionMode.Compress, leaveOpen);
case KnownHeaders.ContentEncodingDeflate: case HttpCompression.Deflate:
return new DeflateStream(stream, CompressionMode.Compress, leaveOpen); return new DeflateStream(stream, CompressionMode.Compress, leaveOpen);
case KnownHeaders.ContentEncodingBrotli: case HttpCompression.Brotli:
return new BrotliSharpLib.BrotliStream(stream, CompressionMode.Compress, leaveOpen); return new BrotliSharpLib.BrotliStream(stream, CompressionMode.Compress, leaveOpen);
default: default:
throw new Exception($"Unsupported compression mode: {type}"); throw new Exception($"Unsupported compression mode: {type}");
......
using System;
using Titanium.Web.Proxy.Http;
namespace Titanium.Web.Proxy.Compression
{
internal static class CompressionUtil
{
public static HttpCompression CompressionNameToEnum(string name)
{
if (KnownHeaders.ContentEncodingGzip.Equals(name.AsSpan()))
return HttpCompression.Gzip;
if (KnownHeaders.ContentEncodingDeflate.Equals(name.AsSpan()))
return HttpCompression.Deflate;
if (KnownHeaders.ContentEncodingBrotli.Equals(name.AsSpan()))
return HttpCompression.Brotli;
return HttpCompression.Unsupported;
}
}
}
...@@ -10,15 +10,15 @@ namespace Titanium.Web.Proxy.Compression ...@@ -10,15 +10,15 @@ namespace Titanium.Web.Proxy.Compression
/// </summary> /// </summary>
internal class DecompressionFactory internal class DecompressionFactory
{ {
internal static Stream Create(string type, Stream stream, bool leaveOpen = true) internal static Stream Create(HttpCompression type, Stream stream, bool leaveOpen = true)
{ {
switch (type) switch (type)
{ {
case KnownHeaders.ContentEncodingGzip: case HttpCompression.Gzip:
return new GZipStream(stream, CompressionMode.Decompress, leaveOpen); return new GZipStream(stream, CompressionMode.Decompress, leaveOpen);
case KnownHeaders.ContentEncodingDeflate: case HttpCompression.Deflate:
return new DeflateStream(stream, CompressionMode.Decompress, leaveOpen); return new DeflateStream(stream, CompressionMode.Decompress, leaveOpen);
case KnownHeaders.ContentEncodingBrotli: case HttpCompression.Brotli:
return new BrotliSharpLib.BrotliStream(stream, CompressionMode.Decompress, leaveOpen); return new BrotliSharpLib.BrotliStream(stream, CompressionMode.Decompress, leaveOpen);
default: default:
throw new Exception($"Unsupported decompression mode: {type}"); throw new Exception($"Unsupported decompression mode: {type}");
......
using System.IO.Compression;
namespace Titanium.Web.Proxy.Compression
{
internal enum HttpCompression
{
Unsupported,
Gzip,
Deflate,
Brotli,
}
}
...@@ -54,7 +54,7 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -54,7 +54,7 @@ namespace Titanium.Web.Proxy.EventArguments
readChunkTrail = true; readChunkTrail = true;
string? chunkHead = baseStream.ReadLineAsync().Result; string? chunkHead = baseStream.ReadLineAsync().Result!;
int idx = chunkHead.IndexOf(";", StringComparison.Ordinal); int idx = chunkHead.IndexOf(";", StringComparison.Ordinal);
if (idx >= 0) if (idx >= 0)
{ {
......
...@@ -38,7 +38,7 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -38,7 +38,7 @@ namespace Titanium.Web.Proxy.EventArguments
/// Constructor to initialize the proxy /// Constructor to initialize the proxy
/// </summary> /// </summary>
internal SessionEventArgs(ProxyServer server, ProxyEndPoint endPoint, ProxyClient proxyClient, ConnectRequest? connectRequest, CancellationTokenSource cancellationTokenSource) internal SessionEventArgs(ProxyServer server, ProxyEndPoint endPoint, ProxyClient proxyClient, ConnectRequest? connectRequest, CancellationTokenSource cancellationTokenSource)
: base(server, endPoint, proxyClient, connectRequest, null, cancellationTokenSource) : base(server, endPoint, proxyClient, connectRequest, new Request(), cancellationTokenSource)
{ {
} }
...@@ -304,7 +304,7 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -304,7 +304,7 @@ namespace Titanium.Web.Proxy.EventArguments
if (transformation == TransformationMode.Uncompress && contentEncoding != null) if (transformation == TransformationMode.Uncompress && contentEncoding != null)
{ {
s = decompressStream = DecompressionFactory.Create(contentEncoding, s); s = decompressStream = DecompressionFactory.Create(CompressionUtil.CompressionNameToEnum(contentEncoding), s);
} }
try try
......
...@@ -41,7 +41,7 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -41,7 +41,7 @@ namespace Titanium.Web.Proxy.EventArguments
/// Initializes a new instance of the <see cref="SessionEventArgsBase" /> class. /// Initializes a new instance of the <see cref="SessionEventArgsBase" /> class.
/// </summary> /// </summary>
private protected SessionEventArgsBase(ProxyServer server, ProxyEndPoint endPoint, private protected SessionEventArgsBase(ProxyServer server, ProxyEndPoint endPoint,
ProxyClient proxyClient, ConnectRequest? connectRequest, Request? request, CancellationTokenSource cancellationTokenSource) ProxyClient proxyClient, ConnectRequest? connectRequest, Request request, CancellationTokenSource cancellationTokenSource)
{ {
BufferPool = server.BufferPool; BufferPool = server.BufferPool;
ExceptionFunc = server.ExceptionFunc; ExceptionFunc = server.ExceptionFunc;
......
...@@ -62,12 +62,15 @@ namespace Titanium.Web.Proxy ...@@ -62,12 +62,15 @@ namespace Titanium.Web.Proxy
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 = httpUrl;
connectHostname = httpRemoteUri.Host; int idx = connectHostname.IndexOf(":");
if (idx >= 0)
{
connectHostname = connectHostname.Substring(0, idx);
}
var connectRequest = new ConnectRequest var connectRequest = new ConnectRequest(connectHostname)
{ {
RequestUri = httpRemoteUri,
OriginalUrlData = HttpHeader.Encoding.GetBytes(httpUrl), OriginalUrlData = HttpHeader.Encoding.GetBytes(httpUrl),
HttpVersion = version HttpVersion = version
}; };
...@@ -127,6 +130,7 @@ namespace Titanium.Web.Proxy ...@@ -127,6 +130,7 @@ namespace Titanium.Web.Proxy
bool isClientHello = clientHelloInfo != null; bool isClientHello = clientHelloInfo != null;
if (clientHelloInfo != null) if (clientHelloInfo != null)
{ {
connectRequest.Scheme = ProxyServer.UriSchemeHttps;
connectRequest.TunnelType = TunnelType.Https; connectRequest.TunnelType = TunnelType.Https;
connectRequest.ClientHelloInfo = clientHelloInfo; connectRequest.ClientHelloInfo = clientHelloInfo;
} }
...@@ -136,7 +140,6 @@ namespace Titanium.Web.Proxy ...@@ -136,7 +140,6 @@ namespace Titanium.Web.Proxy
if (decryptSsl && clientHelloInfo != null) if (decryptSsl && clientHelloInfo != null)
{ {
clientConnection.SslProtocol = clientHelloInfo.SslProtocol; clientConnection.SslProtocol = clientHelloInfo.SslProtocol;
connectRequest.RequestUri = new Uri("https://" + httpUrl);
bool http2Supported = false; bool http2Supported = false;
...@@ -355,7 +358,7 @@ namespace Titanium.Web.Proxy ...@@ -355,7 +358,7 @@ namespace Titanium.Web.Proxy
// Now create the request // Now create the request
await handleHttpSessionRequest(endPoint, clientConnection, clientStream, clientStreamWriter, await handleHttpSessionRequest(endPoint, clientConnection, clientStream, clientStreamWriter,
cancellationTokenSource, connectHostname, connectArgs, prefetchConnectionTask); cancellationTokenSource, connectArgs, prefetchConnectionTask);
} }
catch (ProxyException e) catch (ProxyException e)
{ {
......
...@@ -62,7 +62,7 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -62,7 +62,7 @@ namespace Titanium.Web.Proxy.Extensions
} }
} }
private static async Task<T> withCancellation<T>(this Task<T> task, CancellationToken cancellationToken) private static async Task<T> withCancellation<T>(this Task<T> task, CancellationToken cancellationToken) where T : struct
{ {
var tcs = new TaskCompletionSource<bool>(); var tcs = new TaskCompletionSource<bool>();
using (cancellationToken.Register(s => ((TaskCompletionSource<bool>)s).TrySetResult(true), tcs)) using (cancellationToken.Register(s => ((TaskCompletionSource<bool>)s).TrySetResult(true), tcs))
......
...@@ -78,7 +78,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -78,7 +78,7 @@ namespace Titanium.Web.Proxy.Helpers
{ {
var parameter = p.Span; var parameter = p.Span;
int equalsIndex = parameter.IndexOf('='); int equalsIndex = parameter.IndexOf('=');
if (equalsIndex != -1 && parameter.Slice(0, equalsIndex).TrimStart().EqualsIgnoreCase(KnownHeaders.ContentTypeCharset.AsSpan())) if (equalsIndex != -1 && KnownHeaders.ContentTypeCharset.Equals(parameter.Slice(0, equalsIndex).TrimStart()))
{ {
var value = parameter.Slice(equalsIndex + 1); var value = parameter.Slice(equalsIndex + 1);
if (value.EqualsIgnoreCase("x-user-defined".AsSpan())) if (value.EqualsIgnoreCase("x-user-defined".AsSpan()))
...@@ -113,7 +113,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -113,7 +113,7 @@ namespace Titanium.Web.Proxy.Helpers
foreach (var parameter in new SemicolonSplitEnumerator(contentType)) foreach (var parameter in new SemicolonSplitEnumerator(contentType))
{ {
int equalsIndex = parameter.Span.IndexOf('='); int equalsIndex = parameter.Span.IndexOf('=');
if (equalsIndex != -1 && parameter.Span.Slice(0, equalsIndex).TrimStart().EqualsIgnoreCase(KnownHeaders.ContentTypeBoundary.AsSpan())) if (equalsIndex != -1 && KnownHeaders.ContentTypeBoundary.Equals(parameter.Span.Slice(0, equalsIndex).TrimStart()))
{ {
var value = parameter.Slice(equalsIndex + 1); var value = parameter.Slice(equalsIndex + 1);
if (value.Length > 2 && value.Span[0] == '"' && value.Span[value.Length - 1] == '"') if (value.Length > 2 && value.Span[0] == '"' && value.Span[value.Length - 1] == '"')
......
...@@ -198,7 +198,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -198,7 +198,7 @@ namespace Titanium.Web.Proxy.Helpers
{ {
while (true) while (true)
{ {
string? chunkHead = await reader.ReadLineAsync(cancellationToken); string chunkHead = (await reader.ReadLineAsync(cancellationToken))!;
int idx = chunkHead.IndexOf(";"); int idx = chunkHead.IndexOf(";");
if (idx >= 0) if (idx >= 0)
{ {
......
...@@ -66,9 +66,9 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -66,9 +66,9 @@ namespace Titanium.Web.Proxy.Helpers
internal bool BypassOnLocal { get; } internal bool BypassOnLocal { get; }
internal Dictionary<ProxyProtocolType, HttpSystemProxyValue> Proxies { get; } internal Dictionary<ProxyProtocolType, HttpSystemProxyValue>? Proxies { get; }
internal string[] BypassList { get; } internal string[]? BypassList { get; }
private static string bypassStringEscape(string rawString) private static string bypassStringEscape(string rawString)
{ {
...@@ -171,7 +171,15 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -171,7 +171,15 @@ namespace Titanium.Web.Proxy.Helpers
if (proxyValues.Length > 0) if (proxyValues.Length > 0)
{ {
result.AddRange(proxyValues.Select(parseProxyValue).Where(parsedValue => parsedValue != null)); foreach (string str in proxyValues)
{
var proxyValue = parseProxyValue(str);
if (proxyValue != null)
{
result.Add(proxyValue);
}
}
} }
else else
{ {
......
using Titanium.Web.Proxy.StreamExtended; using System;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.StreamExtended;
namespace Titanium.Web.Proxy.Http namespace Titanium.Web.Proxy.Http
{ {
...@@ -7,13 +9,14 @@ namespace Titanium.Web.Proxy.Http ...@@ -7,13 +9,14 @@ namespace Titanium.Web.Proxy.Http
/// </summary> /// </summary>
public class ConnectRequest : Request public class ConnectRequest : Request
{ {
public ConnectRequest() public ConnectRequest(string hostname)
{ {
Method = "CONNECT"; Method = "CONNECT";
Hostname = hostname;
} }
public TunnelType TunnelType { get; internal set; } public TunnelType TunnelType { get; internal set; }
public ClientHelloInfo ClientHelloInfo { get; set; } public ClientHelloInfo? ClientHelloInfo { get; set; }
} }
} }
...@@ -70,7 +70,7 @@ namespace Titanium.Web.Proxy.Http ...@@ -70,7 +70,7 @@ namespace Titanium.Web.Proxy.Http
var encoding = HttpHeader.Encoding; var encoding = HttpHeader.Encoding;
#if NETSTANDARD2_1 #if NETSTANDARD2_1
var buf = ArrayPool<byte>.Shared.Rent(str.Length * 4); var buf = ArrayPool<byte>.Shared.Rent(encoding.GetMaxByteCount(str.Length));
var span = new Span<byte>(buf); var span = new Span<byte>(buf);
int bytes = encoding.GetBytes(str.AsSpan(), span); int bytes = encoding.GetBytes(str.AsSpan(), span);
......
...@@ -105,6 +105,21 @@ namespace Titanium.Web.Proxy.Http ...@@ -105,6 +105,21 @@ namespace Titanium.Web.Proxy.Http
return null; return null;
} }
internal HttpHeader? GetFirstHeader(KnownHeader name)
{
if (headers.TryGetValue(name.String, out var header))
{
return header;
}
if (nonUniqueHeaders.TryGetValue(name.String, out var h))
{
return h.FirstOrDefault();
}
return null;
}
/// <summary> /// <summary>
/// Returns all headers /// Returns all headers
/// </summary> /// </summary>
...@@ -129,6 +144,16 @@ namespace Titanium.Web.Proxy.Http ...@@ -129,6 +144,16 @@ namespace Titanium.Web.Proxy.Http
AddHeader(new HttpHeader(name, value)); AddHeader(new HttpHeader(name, value));
} }
internal void AddHeader(KnownHeader name, string value)
{
AddHeader(new HttpHeader(name, value));
}
internal void AddHeader(KnownHeader name, KnownHeader value)
{
AddHeader(new HttpHeader(name, value));
}
/// <summary> /// <summary>
/// Adds the given header object to Request /// Adds the given header object to Request
/// </summary> /// </summary>
...@@ -239,6 +264,27 @@ namespace Titanium.Web.Proxy.Http ...@@ -239,6 +264,27 @@ namespace Titanium.Web.Proxy.Http
return result; return result;
} }
/// <summary>
/// removes all headers with given name
/// </summary>
/// <param name="headerName"></param>
/// <returns>
/// True if header was removed
/// False if no header exists with given name
/// </returns>
public bool RemoveHeader(KnownHeader headerName)
{
bool result = headers.Remove(headerName.String);
// do not convert to '||' expression to avoid lazy evaluation
if (nonUniqueHeaders.Remove(headerName.String))
{
result = true;
}
return result;
}
/// <summary> /// <summary>
/// Removes given header object if it exist /// Removes given header object if it exist
/// </summary> /// </summary>
...@@ -273,9 +319,9 @@ namespace Titanium.Web.Proxy.Http ...@@ -273,9 +319,9 @@ namespace Titanium.Web.Proxy.Http
nonUniqueHeaders.Clear(); nonUniqueHeaders.Clear();
} }
internal string? GetHeaderValueOrNull(string headerName) internal string? GetHeaderValueOrNull(KnownHeader headerName)
{ {
if (headers.TryGetValue(headerName, out var header)) if (headers.TryGetValue(headerName.String, out var header))
{ {
return header.Value; return header.Value;
} }
...@@ -283,7 +329,7 @@ namespace Titanium.Web.Proxy.Http ...@@ -283,7 +329,7 @@ namespace Titanium.Web.Proxy.Http
return null; return null;
} }
internal void SetOrAddHeaderValue(string headerName, string? value) internal void SetOrAddHeaderValue(KnownHeader headerName, string? value)
{ {
if (value == null) if (value == null)
{ {
...@@ -291,13 +337,25 @@ namespace Titanium.Web.Proxy.Http ...@@ -291,13 +337,25 @@ namespace Titanium.Web.Proxy.Http
return; return;
} }
if (headers.TryGetValue(headerName, out var header)) if (headers.TryGetValue(headerName.String, out var header))
{
header.SetValue(value);
}
else
{
headers.Add(headerName.String, new HttpHeader(headerName, value));
}
}
internal void SetOrAddHeaderValue(KnownHeader headerName, KnownHeader value)
{
if (headers.TryGetValue(headerName.String, out var header))
{ {
header.ValueData = value.GetByteString(); header.SetValue(value);
} }
else else
{ {
headers.Add(headerName, new HttpHeader(headerName, value)); headers.Add(headerName.String, new HttpHeader(headerName, value));
} }
} }
......
...@@ -18,10 +18,10 @@ namespace Titanium.Web.Proxy.Http ...@@ -18,10 +18,10 @@ namespace Titanium.Web.Proxy.Http
{ {
private TcpServerConnection? connection; private TcpServerConnection? connection;
internal HttpWebClient(ConnectRequest? connectRequest, Request? request, Lazy<int> processIdFunc) internal HttpWebClient(ConnectRequest? connectRequest, Request request, Lazy<int> processIdFunc)
{ {
ConnectRequest = connectRequest; ConnectRequest = connectRequest;
Request = request ?? new Request(); Request = request;
Response = new Response(); Response = new Response();
ProcessId = processIdFunc; ProcessId = processIdFunc;
} }
...@@ -141,7 +141,7 @@ namespace Titanium.Web.Proxy.Http ...@@ -141,7 +141,7 @@ namespace Titanium.Web.Proxy.Http
// write request headers // write request headers
foreach (var header in Request.Headers) foreach (var header in Request.Headers)
{ {
if (isTransparent || header.Name != KnownHeaders.ProxyAuthorization) if (isTransparent || header.Name != KnownHeaders.ProxyAuthorization.String)
{ {
headerBuilder.WriteHeader(header); headerBuilder.WriteHeader(header);
} }
......
using System;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Models;
namespace Titanium.Web.Proxy.Http
{
public class KnownHeader
{
internal ByteString String8;
public string String;
private KnownHeader(string str)
{
String8 = (ByteString)str;
String = str;
}
public override string ToString()
{
return String;
}
internal bool Equals(ReadOnlySpan<char> value)
{
return String.AsSpan().EqualsIgnoreCase(value);
}
public static implicit operator KnownHeader(string str) => new KnownHeader(str);
}
}
...@@ -6,46 +6,46 @@ ...@@ -6,46 +6,46 @@
public static class KnownHeaders public static class KnownHeaders
{ {
// Both // Both
public const string Connection = "Connection"; public static KnownHeader Connection = "Connection";
public const string ConnectionClose = "close"; public static KnownHeader ConnectionClose = "close";
public const string ConnectionKeepAlive = "keep-alive"; public static KnownHeader ConnectionKeepAlive = "keep-alive";
public const string ContentLength = "Content-Length"; public static KnownHeader ContentLength = "Content-Length";
public const string ContentType = "Content-Type"; public static KnownHeader ContentType = "Content-Type";
public const string ContentTypeCharset = "charset"; public static KnownHeader ContentTypeCharset = "charset";
public const string ContentTypeBoundary = "boundary"; public static KnownHeader ContentTypeBoundary = "boundary";
public const string Upgrade = "Upgrade"; public static KnownHeader Upgrade = "Upgrade";
public const string UpgradeWebsocket = "websocket"; public static KnownHeader UpgradeWebsocket = "websocket";
// Request headers // Request headers
public const string AcceptEncoding = "Accept-Encoding"; public static KnownHeader AcceptEncoding = "Accept-Encoding";
public const string Authorization = "Authorization"; public static KnownHeader Authorization = "Authorization";
public const string Expect = "Expect"; public static KnownHeader Expect = "Expect";
public const string Expect100Continue = "100-continue"; public static KnownHeader Expect100Continue = "100-continue";
public const string Host = "Host"; public static KnownHeader Host = "Host";
public const string ProxyAuthorization = "Proxy-Authorization"; public static KnownHeader ProxyAuthorization = "Proxy-Authorization";
public const string ProxyAuthorizationBasic = "basic"; public static KnownHeader ProxyAuthorizationBasic = "basic";
public const string ProxyConnection = "Proxy-Connection"; public static KnownHeader ProxyConnection = "Proxy-Connection";
public const string ProxyConnectionClose = "close"; public static KnownHeader ProxyConnectionClose = "close";
// Response headers // Response headers
public const string ContentEncoding = "Content-Encoding"; public static KnownHeader ContentEncoding = "Content-Encoding";
public const string ContentEncodingDeflate = "deflate"; public static KnownHeader ContentEncodingDeflate = "deflate";
public const string ContentEncodingGzip = "gzip"; public static KnownHeader ContentEncodingGzip = "gzip";
public const string ContentEncodingBrotli = "br"; public static KnownHeader ContentEncodingBrotli = "br";
public const string Location = "Location"; public static KnownHeader Location = "Location";
public const string ProxyAuthenticate = "Proxy-Authenticate"; public static KnownHeader ProxyAuthenticate = "Proxy-Authenticate";
public const string TransferEncoding = "Transfer-Encoding"; public static KnownHeader TransferEncoding = "Transfer-Encoding";
public const string TransferEncodingChunked = "chunked"; public static KnownHeader TransferEncodingChunked = "chunked";
} }
} }
\ No newline at end of file
...@@ -25,7 +25,7 @@ namespace Titanium.Web.Proxy.Http ...@@ -25,7 +25,7 @@ namespace Titanium.Web.Proxy.Http
public bool IsHttps => RequestUri.Scheme == ProxyServer.UriSchemeHttps; public bool IsHttps => RequestUri.Scheme == ProxyServer.UriSchemeHttps;
private ByteString originalUrlData; private ByteString originalUrlData;
private ByteString urlData; private protected ByteString UrlData;
internal ByteString OriginalUrlData internal ByteString OriginalUrlData
{ {
...@@ -33,10 +33,14 @@ namespace Titanium.Web.Proxy.Http ...@@ -33,10 +33,14 @@ namespace Titanium.Web.Proxy.Http
set set
{ {
originalUrlData = value; originalUrlData = value;
urlData = value; UrlData = value;
} }
} }
internal string Scheme { get; set; } = ProxyServer.UriSchemeHttp;
internal string? Hostname { get; set; }
/// <summary> /// <summary>
/// The original request Url. /// The original request Url.
/// </summary> /// </summary>
...@@ -45,15 +49,45 @@ namespace Titanium.Web.Proxy.Http ...@@ -45,15 +49,45 @@ namespace Titanium.Web.Proxy.Http
/// <summary> /// <summary>
/// Request HTTP Uri. /// Request HTTP Uri.
/// </summary> /// </summary>
public Uri RequestUri { get; set; } public Uri RequestUri
{
get
{
string url;
if (startsWithUriScheme(UrlData))
{
url = UrlData.GetString();
}
else
{
string? host = Host ?? Hostname;
string? hostAndPath = host;
if (UrlData.Length > 0 && UrlData[0] == '/')
{
hostAndPath += UrlData.GetString();
}
url = string.Concat(Scheme == ProxyServer.UriSchemeHttps ? "https://" : "http://", hostAndPath);
}
try
{
return new Uri(url);
}
catch (Exception ex)
{
throw new Exception($"Invalid URI: '{url}'", ex);
}
}
}
/// <summary> /// <summary>
/// The request url as it is in the HTTP header /// The request url as it is in the HTTP header
/// </summary> /// </summary>
public string Url public string Url
{ {
get => urlData.GetString(); get => UrlData.GetString();
set => urlData = value.GetByteString(); set => UrlData = value.GetByteString();
} }
[Obsolete("This property is obsolete. Use Url property instead")] [Obsolete("This property is obsolete. Use Url property instead")]
...@@ -142,7 +176,7 @@ namespace Titanium.Web.Proxy.Http ...@@ -142,7 +176,7 @@ namespace Titanium.Web.Proxy.Http
return false; return false;
} }
return headerValue.EqualsIgnoreCase(KnownHeaders.UpgradeWebsocket); return headerValue.EqualsIgnoreCase(KnownHeaders.UpgradeWebsocket.String);
} }
} }
...@@ -256,5 +290,46 @@ namespace Titanium.Web.Proxy.Http ...@@ -256,5 +290,46 @@ namespace Titanium.Web.Proxy.Http
return true; return true;
} }
private bool startsWithUriScheme(ByteString str)
{
if (str.Length < 3)
{
return false;
}
// regex: "^[a-z]*://"
int i;
for (i = 0; i < str.Length - 3; i++)
{
byte ch = str[i];
if (ch == ':')
{
break;
}
if (ch < 'A' || ch > 'z' || (ch > 'Z' && ch < 'a')) // ASCII letter
{
return false;
}
}
if (str[i++] != ':')
{
return false;
}
if (str[i++] != '/')
{
return false;
}
if (str[i] != '/')
{
return false;
}
return true;
}
} }
} }
...@@ -142,7 +142,7 @@ namespace Titanium.Web.Proxy.Http ...@@ -142,7 +142,7 @@ namespace Titanium.Web.Proxy.Http
get get
{ {
string? headerValue = Headers.GetHeaderValueOrNull(KnownHeaders.TransferEncoding); string? headerValue = Headers.GetHeaderValueOrNull(KnownHeaders.TransferEncoding);
return headerValue != null && headerValue.ContainsIgnoreCase(KnownHeaders.TransferEncodingChunked); return headerValue != null && headerValue.ContainsIgnoreCase(KnownHeaders.TransferEncodingChunked.String);
} }
set set
...@@ -219,7 +219,7 @@ namespace Titanium.Web.Proxy.Http ...@@ -219,7 +219,7 @@ namespace Titanium.Web.Proxy.Http
/// <param name="encodingType"></param> /// <param name="encodingType"></param>
/// <param name="body"></param> /// <param name="body"></param>
/// <returns></returns> /// <returns></returns>
internal byte[] GetCompressedBody(string encodingType, byte[] body) internal byte[] GetCompressedBody(HttpCompression encodingType, byte[] body)
{ {
using (var ms = new MemoryStream()) using (var ms = new MemoryStream())
{ {
...@@ -247,7 +247,7 @@ namespace Titanium.Web.Proxy.Http ...@@ -247,7 +247,7 @@ namespace Titanium.Web.Proxy.Http
var body = Body; var body = Body;
if (contentEncoding != null && body != null) if (contentEncoding != null && body != null)
{ {
body = GetCompressedBody(contentEncoding, body); body = GetCompressedBody(CompressionUtil.CompressionNameToEnum(contentEncoding), body);
if (isChunked == false) if (isChunked == false)
{ {
......
...@@ -82,7 +82,7 @@ namespace Titanium.Web.Proxy.Http ...@@ -82,7 +82,7 @@ namespace Titanium.Web.Proxy.Http
if (headerValue != null) if (headerValue != null)
{ {
if (headerValue.EqualsIgnoreCase(KnownHeaders.ConnectionClose)) if (headerValue.EqualsIgnoreCase(KnownHeaders.ConnectionClose.String))
{ {
return false; return false;
} }
......
...@@ -23,7 +23,7 @@ namespace Titanium.Web.Proxy.Http2.Hpack ...@@ -23,7 +23,7 @@ namespace Titanium.Web.Proxy.Http2.Hpack
public class DynamicTable public class DynamicTable
{ {
// a circular queue of header fields // a circular queue of header fields
HttpHeader?[] headerFields; HttpHeader[] headerFields = Array.Empty<HttpHeader>();
int head; int head;
int tail; int tail;
......
...@@ -30,7 +30,7 @@ namespace Titanium.Web.Proxy.Http2.Hpack ...@@ -30,7 +30,7 @@ namespace Titanium.Web.Proxy.Http2.Hpack
private const int bucketSize = 17; private const int bucketSize = 17;
// a linked hash map of header fields // a linked hash map of header fields
private readonly HeaderEntry?[] headerFields = new HeaderEntry[bucketSize]; private readonly HeaderEntry[] headerFields = new HeaderEntry[bucketSize];
private readonly HeaderEntry head = new HeaderEntry(-1, ByteString.Empty, ByteString.Empty, int.MaxValue, null); private readonly HeaderEntry head = new HeaderEntry(-1, ByteString.Empty, ByteString.Empty, int.MaxValue, null);
private int size; private int size;
...@@ -337,7 +337,7 @@ namespace Titanium.Web.Proxy.Http2.Hpack ...@@ -337,7 +337,7 @@ namespace Titanium.Web.Proxy.Http2.Hpack
int h = hash(name); int h = hash(name);
int i = Encoder.index(h); int i = Encoder.index(h);
int index = -1; int index = -1;
for (var e = headerFields[i]; e != null; e = e.Next) for (HeaderEntry? e = headerFields[i]; e != null; e = e.Next)
{ {
if (e.Hash == h && name.Equals(e.NameData)) if (e.Hash == h && name.Equals(e.NameData))
{ {
...@@ -521,6 +521,8 @@ namespace Titanium.Web.Proxy.Http2.Hpack ...@@ -521,6 +521,8 @@ namespace Titanium.Web.Proxy.Http2.Hpack
Index = index; Index = index;
Hash = hash; Hash = hash;
Next = next; Next = next;
Before = this;
After = this;
} }
/// <summary> /// <summary>
......
...@@ -10,12 +10,9 @@ ...@@ -10,12 +10,9 @@
public int StreamId; public int StreamId;
public byte[] Buffer; public void CopyToBuffer(byte[] buf)
public byte[] CopyToBuffer()
{ {
int length = Length; int length = Length;
var buf = /*new byte[9];*/Buffer;
buf[0] = (byte)((length >> 16) & 0xff); buf[0] = (byte)((length >> 16) & 0xff);
buf[1] = (byte)((length >> 8) & 0xff); buf[1] = (byte)((length >> 8) & 0xff);
buf[2] = (byte)(length & 0xff); buf[2] = (byte)(length & 0xff);
...@@ -26,7 +23,6 @@ ...@@ -26,7 +23,6 @@
//buf[6] = (byte)((streamId >> 16) & 0xff); //buf[6] = (byte)((streamId >> 16) & 0xff);
//buf[7] = (byte)((streamId >> 8) & 0xff); //buf[7] = (byte)((streamId >> 8) & 0xff);
//buf[8] = (byte)(streamId & 0xff); //buf[8] = (byte)(streamId & 0xff);
return buf;
} }
} }
} }
...@@ -70,11 +70,10 @@ namespace Titanium.Web.Proxy.Http2 ...@@ -70,11 +70,10 @@ namespace Titanium.Web.Proxy.Http2
Decoder? decoder = null; Decoder? decoder = null;
var frameHeader = new Http2FrameHeader(); var frameHeader = new Http2FrameHeader();
frameHeader.Buffer = new byte[9]; var frameHeaderBuffer = new byte[9];
byte[]? buffer = null; byte[]? buffer = null;
while (true) while (true)
{ {
var frameHeaderBuffer = frameHeader.Buffer;
int read = await forceRead(input, frameHeaderBuffer, 0, 9, cancellationToken); int read = await forceRead(input, frameHeaderBuffer, 0, 9, cancellationToken);
if (read != 9) if (read != 9)
{ {
...@@ -265,8 +264,10 @@ namespace Titanium.Web.Proxy.Http2 ...@@ -265,8 +264,10 @@ namespace Titanium.Web.Proxy.Http2
request.HttpVersion = HttpVersion.Version20; request.HttpVersion = HttpVersion.Version20;
request.Method = method.GetString(); request.Method = method.GetString();
request.OriginalUrlData = path; request.OriginalUrlData = path;
request.Scheme = headerListener.Scheme;
request.Hostname = headerListener.Authority.GetString();
request.RequestUri = headerListener.GetUri(); //request.RequestUri = headerListener.GetUri();
} }
else else
{ {
...@@ -302,7 +303,7 @@ namespace Titanium.Web.Proxy.Http2 ...@@ -302,7 +303,7 @@ namespace Titanium.Web.Proxy.Http2
{ {
rr.ReadHttp2BeforeHandlerTaskCompletionSource = null; rr.ReadHttp2BeforeHandlerTaskCompletionSource = null;
tcs.SetResult(true); tcs.SetResult(true);
await sendHeader(remoteSettings, frameHeader, rr, endStream, output, args.IsPromise); await sendHeader(remoteSettings, frameHeader, frameHeaderBuffer, rr, endStream, output, args.IsPromise);
} }
else else
{ {
...@@ -379,7 +380,7 @@ namespace Titanium.Web.Proxy.Http2 ...@@ -379,7 +380,7 @@ namespace Titanium.Web.Proxy.Http2
using (var ms = new MemoryStream()) using (var ms = new MemoryStream())
{ {
using (var zip = using (var zip =
DecompressionFactory.Create(rr.ContentEncoding, new MemoryStream(body))) DecompressionFactory.Create(CompressionUtil.CompressionNameToEnum(rr.ContentEncoding), new MemoryStream(body)))
{ {
zip.CopyTo(ms); zip.CopyTo(ms);
} }
...@@ -413,7 +414,7 @@ namespace Titanium.Web.Proxy.Http2 ...@@ -413,7 +414,7 @@ namespace Titanium.Web.Proxy.Http2
breakpoint(); breakpoint();
} }
await sendBody(remoteSettings, rr, frameHeader, buffer, output); await sendBody(remoteSettings, rr, frameHeader, frameHeaderBuffer, buffer, output);
} }
if (!isClient && endStream) if (!isClient && endStream)
...@@ -425,8 +426,8 @@ namespace Titanium.Web.Proxy.Http2 ...@@ -425,8 +426,8 @@ namespace Titanium.Web.Proxy.Http2
if (sendPacket) if (sendPacket)
{ {
// do not cancel the write operation // do not cancel the write operation
var buf = frameHeader.CopyToBuffer(); frameHeader.CopyToBuffer(frameHeaderBuffer);
await output.WriteAsync(buf, 0, buf.Length/*, cancellationToken*/); await output.WriteAsync(frameHeaderBuffer, 0, frameHeaderBuffer.Length/*, cancellationToken*/);
await output.WriteAsync(buffer, 0, length /*, cancellationToken*/); await output.WriteAsync(buffer, 0, length /*, cancellationToken*/);
} }
...@@ -450,7 +451,7 @@ namespace Titanium.Web.Proxy.Http2 ...@@ -450,7 +451,7 @@ namespace Titanium.Web.Proxy.Http2
; ;
} }
private static async Task sendHeader(Http2Settings settings, Http2FrameHeader frameHeader, RequestResponseBase rr, bool endStream, Stream output, bool pushPromise) private static async Task sendHeader(Http2Settings settings, Http2FrameHeader frameHeader, byte[] frameHeaderBuffer, RequestResponseBase rr, bool endStream, Stream output, bool pushPromise)
{ {
var encoder = new Encoder(settings.HeaderTableSize); var encoder = new Encoder(settings.HeaderTableSize);
var ms = new MemoryStream(); var ms = new MemoryStream();
...@@ -507,15 +508,15 @@ namespace Titanium.Web.Proxy.Http2 ...@@ -507,15 +508,15 @@ namespace Titanium.Web.Proxy.Http2
//headerBuffer[4] = (byte)(flags & ~((int)Http2FrameFlag.Padded)); //headerBuffer[4] = (byte)(flags & ~((int)Http2FrameFlag.Padded));
// send the header // send the header
var buf = frameHeader.CopyToBuffer(); frameHeader.CopyToBuffer(frameHeaderBuffer);
await output.WriteAsync(buf, 0, buf.Length/*, cancellationToken*/); await output.WriteAsync(frameHeaderBuffer, 0, frameHeaderBuffer.Length/*, cancellationToken*/);
await output.WriteAsync(data, 0, data.Length /*, cancellationToken*/); await output.WriteAsync(data, 0, data.Length /*, cancellationToken*/);
} }
private static async Task sendBody(Http2Settings settings, RequestResponseBase rr, Http2FrameHeader frameHeader, byte[] buffer, Stream output) private static async Task sendBody(Http2Settings settings, RequestResponseBase rr, Http2FrameHeader frameHeader, byte[] frameHeaderBuffer, byte[] buffer, Stream output)
{ {
var body = rr.CompressBodyAndUpdateContentLength(); var body = rr.CompressBodyAndUpdateContentLength();
await sendHeader(settings, frameHeader, rr, !(rr.HasBody && rr.IsBodyRead), output, false); await sendHeader(settings, frameHeader, frameHeaderBuffer, rr, !(rr.HasBody && rr.IsBodyRead), output, false);
if (rr.HasBody && rr.IsBodyRead) if (rr.HasBody && rr.IsBodyRead)
{ {
...@@ -530,8 +531,8 @@ namespace Titanium.Web.Proxy.Http2 ...@@ -530,8 +531,8 @@ namespace Titanium.Web.Proxy.Http2
frameHeader.Type = Http2FrameType.Data; frameHeader.Type = Http2FrameType.Data;
frameHeader.Flags = pos < body.Length ? (Http2FrameFlag)0 : Http2FrameFlag.EndStream; frameHeader.Flags = pos < body.Length ? (Http2FrameFlag)0 : Http2FrameFlag.EndStream;
var buf = frameHeader.CopyToBuffer(); frameHeader.CopyToBuffer(frameHeaderBuffer);
await output.WriteAsync(buf, 0, buf.Length/*, cancellationToken*/); await output.WriteAsync(frameHeaderBuffer, 0, frameHeaderBuffer.Length/*, cancellationToken*/);
await output.WriteAsync(buffer, 0, bodyFrameLength /*, cancellationToken*/); await output.WriteAsync(buffer, 0, bodyFrameLength /*, cancellationToken*/);
} }
} }
...@@ -571,18 +572,40 @@ namespace Titanium.Web.Proxy.Http2 ...@@ -571,18 +572,40 @@ namespace Titanium.Web.Proxy.Http2
class MyHeaderListener : IHeaderListener class MyHeaderListener : IHeaderListener
{ {
private static ByteString SchemeHttp = (ByteString)ProxyServer.UriSchemeHttp;
private static ByteString SchemeHttps = (ByteString)ProxyServer.UriSchemeHttps;
private readonly Action<ByteString, ByteString> addHeaderFunc; private readonly Action<ByteString, ByteString> addHeaderFunc;
public ByteString Method { get; private set; } public ByteString Method { get; private set; }
public ByteString Status { get; private set; } public ByteString Status { get; private set; }
private ByteString authority; public ByteString Authority { get; private set; }
private ByteString scheme; private ByteString scheme;
public ByteString Path { get; private set; } public ByteString Path { get; private set; }
public string Scheme
{
get
{
if (scheme.Equals(SchemeHttp))
{
return ProxyServer.UriSchemeHttp;
}
if (scheme.Equals(SchemeHttps))
{
return ProxyServer.UriSchemeHttps;
}
return string.Empty;
}
}
public MyHeaderListener(Action<ByteString, ByteString> addHeaderFunc) public MyHeaderListener(Action<ByteString, ByteString> addHeaderFunc)
{ {
this.addHeaderFunc = addHeaderFunc; this.addHeaderFunc = addHeaderFunc;
...@@ -599,7 +622,7 @@ namespace Titanium.Web.Proxy.Http2 ...@@ -599,7 +622,7 @@ namespace Titanium.Web.Proxy.Http2
Method = value; Method = value;
return; return;
case ":authority": case ":authority":
authority = value; Authority = value;
return; return;
case ":scheme": case ":scheme":
scheme = value; scheme = value;
...@@ -618,20 +641,20 @@ namespace Titanium.Web.Proxy.Http2 ...@@ -618,20 +641,20 @@ namespace Titanium.Web.Proxy.Http2
public Uri GetUri() public Uri GetUri()
{ {
if (authority.Length == 0) if (Authority.Length == 0)
{ {
// todo // todo
authority = HttpHeader.Encoding.GetBytes("abc.abc"); Authority = HttpHeader.Encoding.GetBytes("abc.abc");
} }
var bytes = new byte[scheme.Length + 3 + authority.Length + Path.Length]; var bytes = new byte[scheme.Length + 3 + Authority.Length + Path.Length];
scheme.Span.CopyTo(bytes); scheme.Span.CopyTo(bytes);
int idx = scheme.Length; int idx = scheme.Length;
bytes[idx++] = (byte)':'; bytes[idx++] = (byte)':';
bytes[idx++] = (byte)'/'; bytes[idx++] = (byte)'/';
bytes[idx++] = (byte)'/'; bytes[idx++] = (byte)'/';
authority.Span.CopyTo(bytes.AsSpan(idx, authority.Length)); Authority.Span.CopyTo(bytes.AsSpan(idx, Authority.Length));
idx += authority.Length; idx += Authority.Length;
Path.Span.CopyTo(bytes.AsSpan(idx, Path.Length)); Path.Span.CopyTo(bytes.AsSpan(idx, Path.Length));
return new Uri(HttpHeader.Encoding.GetString(bytes)); return new Uri(HttpHeader.Encoding.GetString(bytes));
......
...@@ -38,5 +38,10 @@ namespace Titanium.Web.Proxy.Models ...@@ -38,5 +38,10 @@ namespace Titanium.Web.Proxy.Models
public static implicit operator ByteString(byte[] data) => new ByteString(data); public static implicit operator ByteString(byte[] data) => new ByteString(data);
public static implicit operator ByteString(ReadOnlyMemory<byte> data) => new ByteString(data); public static implicit operator ByteString(ReadOnlyMemory<byte> data) => new ByteString(data);
public byte this[int i]
{
get => Span[i];
}
} }
} }
...@@ -41,6 +41,10 @@ namespace Titanium.Web.Proxy.Models ...@@ -41,6 +41,10 @@ namespace Titanium.Web.Proxy.Models
internal static readonly HttpHeader ProxyConnectionKeepAlive = new HttpHeader("Proxy-Connection", "keep-alive"); internal static readonly HttpHeader ProxyConnectionKeepAlive = new HttpHeader("Proxy-Connection", "keep-alive");
private string? nameString;
private string? valueString;
/// <summary> /// <summary>
/// Initialize a new instance. /// Initialize a new instance.
/// </summary> /// </summary>
...@@ -53,8 +57,29 @@ namespace Titanium.Web.Proxy.Models ...@@ -53,8 +57,29 @@ namespace Titanium.Web.Proxy.Models
throw new Exception("Name cannot be null or empty"); throw new Exception("Name cannot be null or empty");
} }
NameData = name.Trim().GetByteString(); nameString = name.Trim();
ValueData = value.Trim().GetByteString(); NameData = nameString.GetByteString();
valueString = value.Trim();
ValueData = valueString.GetByteString();
}
internal HttpHeader(KnownHeader name, string value)
{
nameString = name.String;
NameData = name.String8;
valueString = value.Trim();
ValueData = valueString.GetByteString();
}
internal HttpHeader(KnownHeader name, KnownHeader value)
{
nameString = name.String;
NameData = name.String8;
valueString = value.String;
ValueData = value.String8;
} }
internal HttpHeader(ByteString name, ByteString value) internal HttpHeader(ByteString name, ByteString value)
...@@ -78,16 +103,16 @@ namespace Titanium.Web.Proxy.Models ...@@ -78,16 +103,16 @@ namespace Titanium.Web.Proxy.Models
/// <summary> /// <summary>
/// Header Name. /// Header Name.
/// </summary> /// </summary>
public string Name => NameData.GetString(); public string Name => nameString ??= NameData.GetString();
internal ByteString NameData { get; } internal ByteString NameData { get; }
/// <summary> /// <summary>
/// Header Value. /// Header Value.
/// </summary> /// </summary>
public string Value => ValueData.GetString(); public string Value => valueString ??= ValueData.GetString();
internal ByteString ValueData { get; set; } internal ByteString ValueData { get; private set; }
public int Size => Name.Length + Value.Length + HttpHeaderOverhead; public int Size => Name.Length + Value.Length + HttpHeaderOverhead;
...@@ -96,6 +121,18 @@ namespace Titanium.Web.Proxy.Models ...@@ -96,6 +121,18 @@ namespace Titanium.Web.Proxy.Models
return name.Length + value.Length + HttpHeaderOverhead; return name.Length + value.Length + HttpHeaderOverhead;
} }
internal void SetValue(string value)
{
valueString = value;
ValueData = value.GetByteString();
}
internal void SetValue(KnownHeader value)
{
valueString = value.String;
ValueData = value.String8;
}
/// <summary> /// <summary>
/// Returns header as a valid header string. /// Returns header as a valid header string.
/// </summary> /// </summary>
......
...@@ -372,7 +372,7 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -372,7 +372,7 @@ namespace Titanium.Web.Proxy.Network.Tcp
if (useUpstreamProxy && (isConnect || isHttps)) if (useUpstreamProxy && (isConnect || isHttps))
{ {
var writer = new HttpRequestWriter(stream, proxyServer.BufferPool); var writer = new HttpRequestWriter(stream, proxyServer.BufferPool);
var connectRequest = new ConnectRequest var connectRequest = new ConnectRequest(remoteHostName)
{ {
OriginalUrlData = HttpHeader.Encoding.GetBytes($"{remoteHostName}:{remotePort}"), OriginalUrlData = HttpHeader.Encoding.GetBytes($"{remoteHostName}:{remotePort}"),
HttpVersion = httpVersion HttpVersion = httpVersion
......
...@@ -52,7 +52,7 @@ namespace Titanium.Web.Proxy ...@@ -52,7 +52,7 @@ namespace Titanium.Web.Proxy
if (ProxyBasicAuthenticateFunc != null) if (ProxyBasicAuthenticateFunc != null)
{ {
return await authenticateUserBasic(session, authenticationType, credentials); return await authenticateUserBasic(session, authenticationType, credentials, ProxyBasicAuthenticateFunc);
} }
if (ProxySchemeAuthenticateFunc != null) if (ProxySchemeAuthenticateFunc != null)
...@@ -82,9 +82,11 @@ namespace Titanium.Web.Proxy ...@@ -82,9 +82,11 @@ namespace Titanium.Web.Proxy
} }
} }
private async Task<bool> authenticateUserBasic(SessionEventArgsBase session, ReadOnlyMemory<char> authenticationType, ReadOnlyMemory<char> credentials) private async Task<bool> authenticateUserBasic(SessionEventArgsBase session,
ReadOnlyMemory<char> authenticationType, ReadOnlyMemory<char> credentials,
Func<SessionEventArgsBase, string, string, Task<bool>> proxyBasicAuthenticateFunc)
{ {
if (!authenticationType.Span.EqualsIgnoreCase(KnownHeaders.ProxyAuthorizationBasic.AsSpan())) if (!KnownHeaders.ProxyAuthorizationBasic.Equals(authenticationType.Span))
{ {
// Return not authorized // Return not authorized
session.HttpClient.Response = createAuthentication407Response("Proxy Authentication Invalid"); session.HttpClient.Response = createAuthentication407Response("Proxy Authentication Invalid");
...@@ -102,7 +104,7 @@ namespace Titanium.Web.Proxy ...@@ -102,7 +104,7 @@ namespace Titanium.Web.Proxy
string username = decoded.Substring(0, colonIndex); string username = decoded.Substring(0, colonIndex);
string password = decoded.Substring(colonIndex + 1); string password = decoded.Substring(colonIndex + 1);
bool authenticated = await ProxyBasicAuthenticateFunc(session, username, password); bool authenticated = await proxyBasicAuthenticateFunc(session, username, password);
if (!authenticated) if (!authenticated)
{ {
session.HttpClient.Response = createAuthentication407Response("Proxy Authentication Invalid"); session.HttpClient.Response = createAuthentication407Response("Proxy Authentication Invalid");
......
...@@ -45,7 +45,7 @@ namespace Titanium.Web.Proxy ...@@ -45,7 +45,7 @@ namespace Titanium.Web.Proxy
/// <summary> /// <summary>
/// Backing field for exposed public property. /// Backing field for exposed public property.
/// </summary> /// </summary>
private ExceptionHandler exceptionFunc; private ExceptionHandler? exceptionFunc;
/// <summary> /// <summary>
/// Backing field for exposed public property. /// Backing field for exposed public property.
...@@ -291,14 +291,14 @@ namespace Titanium.Web.Proxy ...@@ -291,14 +291,14 @@ namespace Titanium.Web.Proxy
/// Parameters are username and password as provided by client. /// Parameters are username and password as provided by client.
/// Should return true for successful authentication. /// Should return true for successful authentication.
/// </summary> /// </summary>
public Func<SessionEventArgsBase, string, string, Task<bool>> ProxyBasicAuthenticateFunc { get; set; } public Func<SessionEventArgsBase, string, string, Task<bool>>? ProxyBasicAuthenticateFunc { get; set; }
/// <summary> /// <summary>
/// A pluggable callback to authenticate clients by scheme instead of requiring basic authentication through ProxyBasicAuthenticateFunc. /// A pluggable callback to authenticate clients by scheme instead of requiring basic authentication through ProxyBasicAuthenticateFunc.
/// Parameters are current working session, schemeType, and token as provided by a calling client. /// Parameters are current working session, schemeType, and token as provided by a calling client.
/// Should return success for successful authentication, continuation if the package requests, or failure. /// Should return success for successful authentication, continuation if the package requests, or failure.
/// </summary> /// </summary>
public Func<SessionEventArgsBase, string, string, Task<ProxyAuthenticationContext>> ProxySchemeAuthenticateFunc { get; set; } public Func<SessionEventArgsBase, string, string, Task<ProxyAuthenticationContext>>? ProxySchemeAuthenticateFunc { get; set; }
/// <summary> /// <summary>
/// A collection of scheme types, e.g. basic, NTLM, Kerberos, Negotiate, to return if scheme authentication is required. /// A collection of scheme types, e.g. basic, NTLM, Kerberos, Negotiate, to return if scheme authentication is required.
...@@ -707,7 +707,7 @@ namespace Titanium.Web.Proxy ...@@ -707,7 +707,7 @@ namespace Titanium.Web.Proxy
/// <returns>The external proxy as task result.</returns> /// <returns>The external proxy as task result.</returns>
private Task<ExternalProxy?> getSystemUpStreamProxy(SessionEventArgsBase sessionEventArgs) private Task<ExternalProxy?> getSystemUpStreamProxy(SessionEventArgsBase sessionEventArgs)
{ {
var proxy = systemProxyResolver.GetProxy(sessionEventArgs.HttpClient.Request.RequestUri); var proxy = systemProxyResolver!.GetProxy(sessionEventArgs.HttpClient.Request.RequestUri);
return Task.FromResult(proxy); return Task.FromResult(proxy);
} }
......
...@@ -33,15 +33,11 @@ namespace Titanium.Web.Proxy ...@@ -33,15 +33,11 @@ namespace Titanium.Web.Proxy
/// <param name="clientStream">The client stream.</param> /// <param name="clientStream">The client stream.</param>
/// <param name="clientStreamWriter">The client stream writer.</param> /// <param name="clientStreamWriter">The client stream writer.</param>
/// <param name="cancellationTokenSource">The cancellation token source for this async task.</param> /// <param name="cancellationTokenSource">The cancellation token source for this async task.</param>
/// <param name="httpsConnectHostname">
/// The https hostname as appeared in CONNECT request if this is a HTTPS request from
/// explicit endpoint.
/// </param>
/// <param name="connectArgs">The Connect request if this is a HTTPS request from explicit endpoint.</param> /// <param name="connectArgs">The Connect request if this is a HTTPS request from explicit endpoint.</param>
/// <param name="prefetchConnectionTask">Prefetched server connection for current client using Connect/SNI headers.</param> /// <param name="prefetchConnectionTask">Prefetched server connection for current client using Connect/SNI headers.</param>
private async Task handleHttpSessionRequest(ProxyEndPoint endPoint, TcpClientConnection clientConnection, private async Task handleHttpSessionRequest(ProxyEndPoint endPoint, TcpClientConnection clientConnection,
CustomBufferedStream clientStream, HttpResponseWriter clientStreamWriter, CustomBufferedStream clientStream, HttpResponseWriter clientStreamWriter,
CancellationTokenSource cancellationTokenSource, string? httpsConnectHostname, TunnelConnectSessionEventArgs? connectArgs, CancellationTokenSource cancellationTokenSource, TunnelConnectSessionEventArgs? connectArgs = null,
Task<TcpServerConnection>? prefetchConnectionTask = null) Task<TcpServerConnection>? prefetchConnectionTask = null)
{ {
var connectRequest = connectArgs?.HttpClient.ConnectRequest; var connectRequest = connectArgs?.HttpClient.ConnectRequest;
...@@ -85,41 +81,13 @@ namespace Titanium.Web.Proxy ...@@ -85,41 +81,13 @@ namespace Titanium.Web.Proxy
await HeaderParser.ReadHeaders(clientStream, args.HttpClient.Request.Headers, await HeaderParser.ReadHeaders(clientStream, args.HttpClient.Request.Headers,
cancellationToken); cancellationToken);
Uri httpRemoteUri; var request = args.HttpClient.Request;
if (ProxyConstants.UriSchemeRegex.IsMatch(httpUrl)) if (connectRequest != null)
{ {
try request.Scheme = connectRequest.Scheme;
{ request.Hostname = connectRequest.Hostname;
httpRemoteUri = new Uri(httpUrl);
}
catch (Exception ex)
{
throw new Exception($"Invalid URI: '{httpUrl}'", ex);
}
} }
else
{
string? host = args.HttpClient.Request.Host ?? httpsConnectHostname;
string? hostAndPath = host;
if (httpUrl.StartsWith("/"))
{
hostAndPath += httpUrl;
}
string url = string.Concat(httpsConnectHostname == null ? "http://" : "https://",
hostAndPath);
try
{
httpRemoteUri = new Uri(url);
}
catch (Exception ex)
{
throw new Exception($"Invalid URI: '{url}'", ex);
}
}
var request = args.HttpClient.Request;
request.RequestUri = httpRemoteUri;
request.OriginalUrlData = HttpHeader.Encoding.GetBytes(httpUrl); request.OriginalUrlData = HttpHeader.Encoding.GetBytes(httpUrl);
request.Method = httpMethod; request.Method = httpMethod;
...@@ -128,7 +96,7 @@ namespace Titanium.Web.Proxy ...@@ -128,7 +96,7 @@ namespace Titanium.Web.Proxy
if (!args.IsTransparent) if (!args.IsTransparent)
{ {
// proxy authorization check // proxy authorization check
if (httpsConnectHostname == null && await checkAuthorization(args) == false) if (connectRequest == null && await checkAuthorization(args) == false)
{ {
await onBeforeResponse(args); await onBeforeResponse(args);
......
...@@ -15,15 +15,12 @@ namespace Titanium.Web.Proxy.Shared ...@@ -15,15 +15,12 @@ namespace Titanium.Web.Proxy.Shared
internal static readonly string NewLine = "\r\n"; internal static readonly string NewLine = "\r\n";
internal static readonly byte[] NewLineBytes = { (byte)'\r', (byte)'\n' }; internal static readonly byte[] NewLineBytes = { (byte)'\r', (byte)'\n' };
internal static readonly Regex UriSchemeRegex =
new Regex("^[a-z]*://", RegexOptions.IgnoreCase | RegexOptions.Compiled);
internal static readonly HashSet<string> ProxySupportedCompressions = internal static readonly HashSet<string> ProxySupportedCompressions =
new HashSet<string>(StringComparer.OrdinalIgnoreCase) new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{ {
KnownHeaders.ContentEncodingGzip, KnownHeaders.ContentEncodingGzip.String,
KnownHeaders.ContentEncodingDeflate, KnownHeaders.ContentEncodingDeflate.String,
KnownHeaders.ContentEncodingBrotli KnownHeaders.ContentEncodingBrotli.String
}; };
internal static readonly Regex CNRemoverRegex = internal static readonly Regex CNRemoverRegex =
......
...@@ -133,8 +133,7 @@ namespace Titanium.Web.Proxy ...@@ -133,8 +133,7 @@ namespace Titanium.Web.Proxy
// HTTPS server created - we can now decrypt the client's traffic // HTTPS server created - we can now decrypt the client's traffic
// Now create the request // Now create the request
await handleHttpSessionRequest(endPoint, clientConnection, clientStream, clientStreamWriter, await handleHttpSessionRequest(endPoint, clientConnection, clientStream, clientStreamWriter, cancellationTokenSource);
cancellationTokenSource, httpsHostName, null, null);
} }
catch (ProxyException e) catch (ProxyException e)
{ {
......
...@@ -33,7 +33,7 @@ namespace Titanium.Web.Proxy.IntegrationTests ...@@ -33,7 +33,7 @@ namespace Titanium.Web.Proxy.IntegrationTests
proxy.Enable100ContinueBehaviour = true; proxy.Enable100ContinueBehaviour = true;
proxy.BeforeRequest += (sender, e) => proxy.BeforeRequest += (sender, e) =>
{ {
e.HttpClient.Request.RequestUri = new Uri(server.ListeningTcpUrl); e.HttpClient.Request.Url = server.ListeningTcpUrl;
return Task.CompletedTask; return Task.CompletedTask;
}; };
...@@ -57,7 +57,7 @@ namespace Titanium.Web.Proxy.IntegrationTests ...@@ -57,7 +57,7 @@ namespace Titanium.Web.Proxy.IntegrationTests
proxy.Enable100ContinueBehaviour = true; proxy.Enable100ContinueBehaviour = true;
proxy.BeforeRequest += (sender, e) => proxy.BeforeRequest += (sender, e) =>
{ {
e.HttpClient.Request.RequestUri = new Uri(server.ListeningTcpUrl); e.HttpClient.Request.Url = server.ListeningTcpUrl;
return Task.CompletedTask; return Task.CompletedTask;
}; };
...@@ -80,7 +80,7 @@ namespace Titanium.Web.Proxy.IntegrationTests ...@@ -80,7 +80,7 @@ namespace Titanium.Web.Proxy.IntegrationTests
proxy.Enable100ContinueBehaviour = true; proxy.Enable100ContinueBehaviour = true;
proxy.BeforeRequest += (sender, e) => proxy.BeforeRequest += (sender, e) =>
{ {
e.HttpClient.Request.RequestUri = new Uri(server.ListeningTcpUrl); e.HttpClient.Request.Url = server.ListeningTcpUrl;
return Task.CompletedTask; return Task.CompletedTask;
}; };
...@@ -108,7 +108,7 @@ namespace Titanium.Web.Proxy.IntegrationTests ...@@ -108,7 +108,7 @@ namespace Titanium.Web.Proxy.IntegrationTests
{ {
try try
{ {
e.HttpClient.Request.RequestUri = new Uri(server.ListeningTcpUrl); e.HttpClient.Request.Url = server.ListeningTcpUrl;
throw dbzEx; throw dbzEx;
} }
catch catch
......
...@@ -47,7 +47,7 @@ namespace Titanium.Web.Proxy.IntegrationTests ...@@ -47,7 +47,7 @@ namespace Titanium.Web.Proxy.IntegrationTests
var proxy = testSuite.GetProxy(); var proxy = testSuite.GetProxy();
proxy.BeforeRequest += async (sender, e) => proxy.BeforeRequest += async (sender, e) =>
{ {
e.HttpClient.Request.RequestUri = new Uri(server.ListeningHttpUrl); e.HttpClient.Request.Url = server.ListeningHttpUrl;
await Task.FromResult(0); await Task.FromResult(0);
}; };
......
...@@ -24,7 +24,7 @@ namespace Titanium.Web.Proxy.IntegrationTests ...@@ -24,7 +24,7 @@ namespace Titanium.Web.Proxy.IntegrationTests
var proxy = testSuite.GetReverseProxy(); var proxy = testSuite.GetReverseProxy();
proxy.BeforeRequest += async (sender, e) => proxy.BeforeRequest += async (sender, e) =>
{ {
e.HttpClient.Request.RequestUri = new Uri(server.ListeningHttpUrl); e.HttpClient.Request.Url = server.ListeningHttpUrl;
await Task.FromResult(0); await Task.FromResult(0);
}; };
...@@ -53,7 +53,7 @@ namespace Titanium.Web.Proxy.IntegrationTests ...@@ -53,7 +53,7 @@ namespace Titanium.Web.Proxy.IntegrationTests
var proxy = testSuite.GetReverseProxy(); var proxy = testSuite.GetReverseProxy();
proxy.BeforeRequest += async (sender, e) => proxy.BeforeRequest += async (sender, e) =>
{ {
e.HttpClient.Request.RequestUri = new Uri(server.ListeningHttpUrl); e.HttpClient.Request.Url = server.ListeningHttpUrl;
await Task.FromResult(0); await Task.FromResult(0);
}; };
...@@ -82,7 +82,7 @@ namespace Titanium.Web.Proxy.IntegrationTests ...@@ -82,7 +82,7 @@ namespace Titanium.Web.Proxy.IntegrationTests
var proxy = testSuite.GetReverseProxy(); var proxy = testSuite.GetReverseProxy();
proxy.BeforeRequest += async (sender, e) => proxy.BeforeRequest += async (sender, e) =>
{ {
e.HttpClient.Request.RequestUri = new Uri(server.ListeningHttpsUrl); e.HttpClient.Request.Url = server.ListeningHttpsUrl;
await Task.FromResult(0); await Task.FromResult(0);
}; };
...@@ -111,7 +111,7 @@ namespace Titanium.Web.Proxy.IntegrationTests ...@@ -111,7 +111,7 @@ namespace Titanium.Web.Proxy.IntegrationTests
var proxy = testSuite.GetReverseProxy(); var proxy = testSuite.GetReverseProxy();
proxy.BeforeRequest += async (sender, e) => proxy.BeforeRequest += async (sender, e) =>
{ {
e.HttpClient.Request.RequestUri = new Uri(server.ListeningHttpsUrl); e.HttpClient.Request.Url = server.ListeningHttpsUrl;
await Task.FromResult(0); await Task.FromResult(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