Commit 1b0bfecd authored by Honfika's avatar Honfika

better uri handling

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