Unverified Commit 80dd44e5 authored by honfika's avatar honfika Committed by GitHub

Merge pull request #353 from justcoding121/develop

beta|develop
parents 7a44e456 5fe25998
...@@ -143,7 +143,7 @@ namespace Titanium.Web.Proxy.Examples.Basic ...@@ -143,7 +143,7 @@ namespace Titanium.Web.Proxy.Examples.Basic
} }
//intecept & cancel redirect or update requests //intecept & cancel redirect or update requests
public async Task OnRequest(object sender, SessionEventArgs e) private async Task OnRequest(object sender, SessionEventArgs e)
{ {
Console.WriteLine("Active Client Connections:" + ((ProxyServer)sender).ClientConnectionCount); Console.WriteLine("Active Client Connections:" + ((ProxyServer)sender).ClientConnectionCount);
Console.WriteLine(e.WebSession.Request.Url); Console.WriteLine(e.WebSession.Request.Url);
...@@ -151,6 +151,12 @@ namespace Titanium.Web.Proxy.Examples.Basic ...@@ -151,6 +151,12 @@ namespace Titanium.Web.Proxy.Examples.Basic
//read request headers //read request headers
requestHeaderHistory[e.Id] = e.WebSession.Request.Headers; requestHeaderHistory[e.Id] = e.WebSession.Request.Headers;
////This sample shows how to get the multipart form data headers
//if (e.WebSession.Request.Host == "mail.yahoo.com" && e.WebSession.Request.IsMultipartFormData)
//{
// e.MultipartRequestPartSent += MultipartRequestPartSent;
//}
if (e.WebSession.Request.HasBody) if (e.WebSession.Request.HasBody)
{ {
//Get/Set request body bytes //Get/Set request body bytes
...@@ -185,7 +191,17 @@ namespace Titanium.Web.Proxy.Examples.Basic ...@@ -185,7 +191,17 @@ namespace Titanium.Web.Proxy.Examples.Basic
} }
//Modify response //Modify response
public async Task OnResponse(object sender, SessionEventArgs e) private void MultipartRequestPartSent(object sender, MultipartRequestPartSentEventArgs e)
{
var session = (SessionEventArgs)sender;
Console.WriteLine("Multipart form data headers:");
foreach (var header in e.Headers)
{
Console.WriteLine(header);
}
}
private async Task OnResponse(object sender, SessionEventArgs e)
{ {
Console.WriteLine("Active Server Connections:" + ((ProxyServer)sender).ServerConnectionCount); Console.WriteLine("Active Server Connections:" + ((ProxyServer)sender).ServerConnectionCount);
......
...@@ -11,7 +11,6 @@ using Titanium.Web.Proxy.EventArguments; ...@@ -11,7 +11,6 @@ using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Helpers; using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Http; using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Models; using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Network;
namespace Titanium.Web.Proxy.Examples.Wpf namespace Titanium.Web.Proxy.Examples.Wpf
{ {
...@@ -63,13 +62,14 @@ namespace Titanium.Web.Proxy.Examples.Wpf ...@@ -63,13 +62,14 @@ namespace Titanium.Web.Proxy.Examples.Wpf
public MainWindow() public MainWindow()
{ {
proxyServer = new ProxyServer(); proxyServer = new ProxyServer();
//proxyServer.CertificateEngine = CertificateEngine.BouncyCastle; //proxyServer.CertificateEngine = CertificateEngine.DefaultWindows;
proxyServer.TrustRootCertificate = true; proxyServer.TrustRootCertificate = true;
proxyServer.CertificateManager.TrustRootCertificateAsAdministrator(); proxyServer.CertificateManager.TrustRootCertificateAsAdministrator();
proxyServer.ForwardToUpstreamGateway = true; proxyServer.ForwardToUpstreamGateway = true;
var explicitEndPoint = new ExplicitProxyEndPoint(IPAddress.Any, 8000, true) var explicitEndPoint = new ExplicitProxyEndPoint(IPAddress.Any, 8000, true)
{ {
ExcludedHttpsHostNameRegex = new[] { "ssllabs.com" },
//IncludedHttpsHostNameRegex = new string[0], //IncludedHttpsHostNameRegex = new string[0],
}; };
...@@ -134,10 +134,9 @@ namespace Titanium.Web.Proxy.Examples.Wpf ...@@ -134,10 +134,9 @@ namespace Titanium.Web.Proxy.Examples.Wpf
SessionListItem item = null; SessionListItem item = null;
await Dispatcher.InvokeAsync(() => await Dispatcher.InvokeAsync(() =>
{ {
if (sessionDictionary.TryGetValue(e.WebSession, out var item2)) if (sessionDictionary.TryGetValue(e.WebSession, out item))
{ {
item2.Update(); item.Update();
item = item2;
} }
}); });
...@@ -147,6 +146,11 @@ namespace Titanium.Web.Proxy.Examples.Wpf ...@@ -147,6 +146,11 @@ namespace Titanium.Web.Proxy.Examples.Wpf
{ {
e.WebSession.Response.KeepBody = true; e.WebSession.Response.KeepBody = true;
await e.GetResponseBody(); await e.GetResponseBody();
await Dispatcher.InvokeAsync(() =>
{
item.Update();
});
} }
} }
} }
......
...@@ -13,7 +13,7 @@ namespace Titanium.Web.Proxy.Examples.Wpf ...@@ -13,7 +13,7 @@ namespace Titanium.Web.Proxy.Examples.Wpf
private string protocol; private string protocol;
private string host; private string host;
private string url; private string url;
private long bodySize; private long? bodySize;
private string process; private string process;
private long receivedDataCount; private long receivedDataCount;
private long sentDataCount; private long sentDataCount;
...@@ -48,7 +48,7 @@ namespace Titanium.Web.Proxy.Examples.Wpf ...@@ -48,7 +48,7 @@ namespace Titanium.Web.Proxy.Examples.Wpf
set => SetField(ref url, value); set => SetField(ref url, value);
} }
public long BodySize public long? BodySize
{ {
get => bodySize; get => bodySize;
set => SetField(ref bodySize, value); set => SetField(ref bodySize, value);
...@@ -76,8 +76,11 @@ namespace Titanium.Web.Proxy.Examples.Wpf ...@@ -76,8 +76,11 @@ namespace Titanium.Web.Proxy.Examples.Wpf
protected void SetField<T>(ref T field, T value,[CallerMemberName] string propertyName = null) protected void SetField<T>(ref T field, T value,[CallerMemberName] string propertyName = null)
{ {
field = value; if (!Equals(field, value))
OnPropertyChanged(propertyName); {
field = value;
OnPropertyChanged(propertyName);
}
} }
[NotifyPropertyChangedInvocator] [NotifyPropertyChangedInvocator]
...@@ -105,7 +108,24 @@ namespace Titanium.Web.Proxy.Examples.Wpf ...@@ -105,7 +108,24 @@ namespace Titanium.Web.Proxy.Examples.Wpf
Url = request.RequestUri.AbsolutePath; Url = request.RequestUri.AbsolutePath;
} }
BodySize = response?.ContentLength ?? -1; if (!IsTunnelConnect)
{
long responseSize = -1;
if (response != null)
{
if (response.ContentLength != -1)
{
responseSize = response.ContentLength;
}
else if (response.IsBodyRead && response.Body != null)
{
responseSize = response.Body.Length;
}
}
BodySize = responseSize;
}
Process = GetProcessDescription(WebSession.ProcessId.Value); Process = GetProcessDescription(WebSession.ProcessId.Value);
} }
......
...@@ -51,8 +51,8 @@ ...@@ -51,8 +51,8 @@
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="StreamExtended, Version=1.0.110.0, Culture=neutral, PublicKeyToken=bbfa0f1d54f50043, processorArchitecture=MSIL"> <Reference Include="StreamExtended, Version=1.0.132.0, Culture=neutral, PublicKeyToken=bbfa0f1d54f50043, processorArchitecture=MSIL">
<HintPath>..\..\packages\StreamExtended.1.0.110-beta\lib\net45\StreamExtended.dll</HintPath> <HintPath>..\..\packages\StreamExtended.1.0.132-beta\lib\net45\StreamExtended.dll</HintPath>
</Reference> </Reference>
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="System.Data" /> <Reference Include="System.Data" />
......
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<packages> <packages>
<package id="StreamExtended" version="1.0.110-beta" targetFramework="net45" /> <package id="StreamExtended" version="1.0.132-beta" targetFramework="net45" />
</packages> </packages>
\ No newline at end of file
using System;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.EventArguments
{
public delegate Task AsyncEventHandler<TEventArgs>(object sender, TEventArgs e);
}
\ No newline at end of file
using Titanium.Web.Proxy.Http; using System;
using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Models; using Titanium.Web.Proxy.Models;
namespace Titanium.Web.Proxy.EventArguments namespace Titanium.Web.Proxy.EventArguments
...@@ -7,8 +8,8 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -7,8 +8,8 @@ namespace Titanium.Web.Proxy.EventArguments
{ {
public bool IsHttpsConnect { get; set; } public bool IsHttpsConnect { get; set; }
internal TunnelConnectSessionEventArgs(int bufferSize, ProxyEndPoint endPoint, ConnectRequest connectRequest) internal TunnelConnectSessionEventArgs(int bufferSize, ProxyEndPoint endPoint, ConnectRequest connectRequest, Action<Exception> exceptionFunc)
: base(bufferSize, endPoint, null) : base(bufferSize, endPoint, null, exceptionFunc)
{ {
WebSession.Request = connectRequest; WebSession.Request = connectRequest;
} }
......
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Titanium.Web.Proxy.EventArguments;
namespace Titanium.Web.Proxy.Extensions namespace Titanium.Web.Proxy.Extensions
{ {
internal static class FuncExtensions internal static class FuncExtensions
{ {
public static void InvokeParallel<T>(this Func<object, T, Task> callback, object sender, T args) public static void InvokeParallel<T>(this AsyncEventHandler<T> callback, object sender, T args)
{ {
var invocationList = callback.GetInvocationList(); var invocationList = callback.GetInvocationList();
var handlerTasks = new Task[invocationList.Length]; var handlerTasks = new Task[invocationList.Length];
for (int i = 0; i < invocationList.Length; i++) for (int i = 0; i < invocationList.Length; i++)
{ {
handlerTasks[i] = ((Func<object, T, Task>)invocationList[i])(sender, args); handlerTasks[i] = ((AsyncEventHandler<T>)invocationList[i])(sender, args);
} }
Task.WhenAll(handlerTasks).Wait(); Task.WhenAll(handlerTasks).Wait();
} }
public static async Task InvokeParallelAsync<T>(this Func<object, T, Task> callback, object sender, T args, Action<Exception> exceptionFunc) public static async Task InvokeParallelAsync<T>(this AsyncEventHandler<T> callback, object sender, T args, Action<Exception> exceptionFunc)
{ {
var invocationList = callback.GetInvocationList(); var invocationList = callback.GetInvocationList();
var handlerTasks = new Task[invocationList.Length]; var handlerTasks = new Task[invocationList.Length];
for (int i = 0; i < invocationList.Length; i++) for (int i = 0; i < invocationList.Length; i++)
{ {
handlerTasks[i] = InvokeAsync((Func<object, T, Task>)invocationList[i], sender, args, exceptionFunc); handlerTasks[i] = InternalInvokeAsync((AsyncEventHandler<T>)invocationList[i], sender, args, exceptionFunc);
} }
await Task.WhenAll(handlerTasks); await Task.WhenAll(handlerTasks);
} }
private static async Task InvokeAsync<T>(Func<object, T, Task> callback, object sender, T args, Action<Exception> exceptionFunc) public static async Task InvokeAsync<T>(this AsyncEventHandler<T> callback, object sender, T args, Action<Exception> exceptionFunc)
{
var invocationList = callback.GetInvocationList();
for (int i = 0; i < invocationList.Length; i++)
{
await InternalInvokeAsync((AsyncEventHandler<T>)invocationList[i], sender, args, exceptionFunc);
}
}
private static async Task InternalInvokeAsync<T>(AsyncEventHandler<T> callback, object sender, T args, Action<Exception> exceptionFunc)
{ {
try try
{ {
......
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using StreamExtended;
namespace Titanium.Web.Proxy.Extensions
{
static class SslExtensions
{
public static string GetServerName(this ClientHelloInfo clientHelloInfo)
{
if (clientHelloInfo.Extensions != null && clientHelloInfo.Extensions.TryGetValue("server_name", out var serverNameExtension))
{
return serverNameExtension.Data;
}
return null;
}
}
}
using System; using System;
using System.Globalization;
using System.IO; using System.IO;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using StreamExtended.Network; using StreamExtended.Helpers;
namespace Titanium.Web.Proxy.Extensions namespace Titanium.Web.Proxy.Extensions
{ {
...@@ -18,83 +18,59 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -18,83 +18,59 @@ namespace Titanium.Web.Proxy.Extensions
/// <param name="output"></param> /// <param name="output"></param>
/// <param name="onCopy"></param> /// <param name="onCopy"></param>
/// <param name="bufferSize"></param> /// <param name="bufferSize"></param>
internal static async Task CopyToAsync(this Stream input, Stream output, Action<byte[], int, int> onCopy, int bufferSize) internal static Task CopyToAsync(this Stream input, Stream output, Action<byte[], int, int> onCopy, int bufferSize)
{ {
byte[] buffer = new byte[bufferSize]; return CopyToAsync(input, output, onCopy, bufferSize, CancellationToken.None);
while (true)
{
int num = await input.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
int bytesRead;
if ((bytesRead = num) != 0)
{
await output.WriteAsync(buffer, 0, bytesRead).ConfigureAwait(false);
onCopy?.Invoke(buffer, 0, bytesRead);
}
else
{
break;
}
}
} }
/// <summary> /// <summary>
/// copies the specified bytes to the stream from the input stream /// Copy streams asynchronously
/// </summary> /// </summary>
/// <param name="streamReader"></param> /// <param name="input"></param>
/// <param name="stream"></param> /// <param name="output"></param>
/// <param name="totalBytesToRead"></param> /// <param name="onCopy"></param>
/// <returns></returns> /// <param name="bufferSize"></param>
internal static async Task CopyBytesToStream(this CustomBinaryReader streamReader, Stream stream, long totalBytesToRead) /// <param name="cancellationToken"></param>
internal static async Task CopyToAsync(this Stream input, Stream output, Action<byte[], int, int> onCopy, int bufferSize, CancellationToken cancellationToken)
{ {
var buffer = streamReader.Buffer; byte[] buffer = BufferPool.GetBuffer(bufferSize);
long remainingBytes = totalBytesToRead; try
while (remainingBytes > 0)
{ {
int bytesToRead = buffer.Length; while (!cancellationToken.IsCancellationRequested)
if (remainingBytes < bytesToRead)
{
bytesToRead = (int)remainingBytes;
}
int bytesRead = await streamReader.ReadBytesAsync(buffer, bytesToRead);
if (bytesRead == 0)
{ {
break; // cancellation is not working on Socket ReadAsync
// https://github.com/dotnet/corefx/issues/15033
int num = await input.ReadAsync(buffer, 0, buffer.Length, CancellationToken.None).WithCancellation(cancellationToken);
int bytesRead;
if ((bytesRead = num) != 0 && !cancellationToken.IsCancellationRequested)
{
await output.WriteAsync(buffer, 0, bytesRead, CancellationToken.None);
onCopy?.Invoke(buffer, 0, bytesRead);
}
else
{
break;
}
} }
}
remainingBytes -= bytesRead; finally
{
await stream.WriteAsync(buffer, 0, bytesRead); BufferPool.ReturnBuffer(buffer);
} }
} }
/// <summary> private static async Task<T> WithCancellation<T>(this Task<T> task, CancellationToken cancellationToken)
/// Copies the stream chunked
/// </summary>
/// <param name="clientStreamReader"></param>
/// <param name="stream"></param>
/// <returns></returns>
internal static async Task CopyBytesToStreamChunked(this CustomBinaryReader clientStreamReader, Stream stream)
{ {
while (true) var tcs = new TaskCompletionSource<bool>();
using (cancellationToken.Register(s => ((TaskCompletionSource<bool>)s).TrySetResult(true), tcs))
{ {
string chuchkHead = await clientStreamReader.ReadLineAsync(); if (task != await Task.WhenAny(task, tcs.Task))
int chunkSize = int.Parse(chuchkHead, NumberStyles.HexNumber);
if (chunkSize != 0)
{
await CopyBytesToStream(clientStreamReader, stream, chunkSize);
//chunk trail
await clientStreamReader.ReadLineAsync();
}
else
{ {
await clientStreamReader.ReadLineAsync(); return default(T);
break;
} }
} }
return await task;
} }
} }
} }
using Titanium.Web.Proxy.Helpers; using System;
using System.Net.Sockets;
using System.Reflection;
using Titanium.Web.Proxy.Helpers;
namespace Titanium.Web.Proxy.Extensions namespace Titanium.Web.Proxy.Extensions
{ {
internal static class TcpExtensions internal static class TcpExtensions
{ {
private static readonly Func<Socket, bool> socketCleanedUpGetter;
static TcpExtensions()
{
var property = typeof(Socket).GetProperty("CleanedUp", BindingFlags.NonPublic | BindingFlags.Instance);
if (property != null)
{
var method = property.GetMethod;
if (method != null && method.ReturnType == typeof(bool))
{
socketCleanedUpGetter = (Func<Socket, bool>)Delegate.CreateDelegate(typeof(Func<Socket, bool>), method);
}
}
}
/// <summary> /// <summary>
/// Gets the local port from a native TCP row object. /// Gets the local port from a native TCP row object.
...@@ -24,5 +41,30 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -24,5 +41,30 @@ namespace Titanium.Web.Proxy.Extensions
{ {
return (tcpRow.remotePort1 << 8) + tcpRow.remotePort2 + (tcpRow.remotePort3 << 24) + (tcpRow.remotePort4 << 16); return (tcpRow.remotePort1 << 8) + tcpRow.remotePort2 + (tcpRow.remotePort3 << 24) + (tcpRow.remotePort4 << 16);
} }
internal static void CloseSocket(this TcpClient tcpClient)
{
if (tcpClient == null)
{
return;
}
try
{
//This line is important!
//contributors please don't remove it without discussion
//It helps to avoid eventual deterioration of performance due to TCP port exhaustion
//due to default TCP CLOSE_WAIT timeout for 4 minutes
if (socketCleanedUpGetter == null || !socketCleanedUpGetter(tcpClient.Client))
{
tcpClient.LingerState = new LingerOption(true, 0);
}
tcpClient.Close();
}
catch
{
}
}
} }
} }
using System; using System;
using System.Text; using System.Text;
using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Shared; using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Helpers namespace Titanium.Web.Proxy.Helpers
...@@ -28,7 +29,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -28,7 +29,7 @@ namespace Titanium.Web.Proxy.Helpers
foreach (string parameter in parameters) foreach (string parameter in parameters)
{ {
var split = parameter.Split(ProxyConstants.EqualSplit, 2); var split = parameter.Split(ProxyConstants.EqualSplit, 2);
if (split.Length == 2 && split[0].Trim().Equals("charset", StringComparison.CurrentCultureIgnoreCase)) if (split.Length == 2 && split[0].Trim().Equals(KnownHeaders.ContentTypeCharset, StringComparison.CurrentCultureIgnoreCase))
{ {
string value = split[1]; string value = split[1];
if (value.Equals("x-user-defined", StringComparison.OrdinalIgnoreCase)) if (value.Equals("x-user-defined", StringComparison.OrdinalIgnoreCase))
...@@ -65,7 +66,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -65,7 +66,7 @@ namespace Titanium.Web.Proxy.Helpers
foreach (string parameter in parameters) foreach (string parameter in parameters)
{ {
var split = parameter.Split(ProxyConstants.EqualSplit, 2); var split = parameter.Split(ProxyConstants.EqualSplit, 2);
if (split.Length == 2 && split[0].Trim().Equals("boundary", StringComparison.CurrentCultureIgnoreCase)) if (split.Length == 2 && split[0].Trim().Equals(KnownHeaders.ContentTypeBoundary, StringComparison.CurrentCultureIgnoreCase))
{ {
string value = split[1]; string value = split[1];
if (value.Length > 2 && value[0] == '"' && value[value.Length - 1] == '"') if (value.Length > 2 && value[0] == '"' && value[value.Length - 1] == '"')
...@@ -97,6 +98,13 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -97,6 +98,13 @@ namespace Titanium.Web.Proxy.Helpers
if (hostname.Split(ProxyConstants.DotSplit).Length > 2) if (hostname.Split(ProxyConstants.DotSplit).Length > 2)
{ {
int idx = hostname.IndexOf(ProxyConstants.DotSplit); int idx = hostname.IndexOf(ProxyConstants.DotSplit);
//issue #352
if(hostname.Substring(0, idx).Contains("-"))
{
return hostname;
}
string rootDomain = hostname.Substring(idx + 1); string rootDomain = hostname.Substring(idx + 1);
return "*." + rootDomain; return "*." + rootDomain;
} }
......
...@@ -4,8 +4,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -4,8 +4,7 @@ namespace Titanium.Web.Proxy.Helpers
{ {
sealed class HttpRequestWriter : HttpWriter sealed class HttpRequestWriter : HttpWriter
{ {
public HttpRequestWriter(Stream stream, int bufferSize) public HttpRequestWriter(Stream stream, int bufferSize) : base(stream, bufferSize)
: base(stream, bufferSize, true)
{ {
} }
} }
......
using System; using System;
using System.Globalization;
using System.IO; using System.IO;
using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using StreamExtended.Network;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Http; using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Helpers namespace Titanium.Web.Proxy.Helpers
{ {
sealed class HttpResponseWriter : HttpWriter sealed class HttpResponseWriter : HttpWriter
{ {
public HttpResponseWriter(Stream stream, int bufferSize) public HttpResponseWriter(Stream stream, int bufferSize) : base(stream, bufferSize)
: base(stream, bufferSize, true)
{ {
} }
...@@ -39,102 +33,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -39,102 +33,7 @@ namespace Titanium.Web.Proxy.Helpers
/// <returns></returns> /// <returns></returns>
public Task WriteResponseStatusAsync(Version version, int code, string description) public Task WriteResponseStatusAsync(Version version, int code, string description)
{ {
return WriteLineAsync($"HTTP/{version.Major}.{version.Minor} {code} {description}"); return WriteLineAsync(Response.CreateResponseLine(version, code, description));
}
/// <summary>
/// Writes the byte array body to the stream; optionally chunked
/// </summary>
/// <param name="data"></param>
/// <param name="isChunked"></param>
/// <returns></returns>
internal async Task WriteResponseBodyAsync(byte[] data, bool isChunked)
{
if (!isChunked)
{
await BaseStream.WriteAsync(data, 0, data.Length);
}
else
{
await WriteResponseBodyChunkedAsync(data);
}
}
/// <summary>
/// Copies the specified content length number of bytes to the output stream from the given inputs stream
/// optionally chunked
/// </summary>
/// <param name="bufferSize"></param>
/// <param name="inStreamReader"></param>
/// <param name="isChunked"></param>
/// <param name="contentLength"></param>
/// <returns></returns>
internal async Task WriteResponseBodyAsync(int bufferSize, CustomBinaryReader inStreamReader, bool isChunked,
long contentLength)
{
if (!isChunked)
{
//http 1.0
if (contentLength == -1)
{
contentLength = long.MaxValue;
}
await inStreamReader.CopyBytesToStream(BaseStream, contentLength);
}
else
{
await WriteResponseBodyChunkedAsync(inStreamReader);
}
}
/// <summary>
/// Copies the streams chunked
/// </summary>
/// <param name="inStreamReader"></param>
/// <returns></returns>
internal async Task WriteResponseBodyChunkedAsync(CustomBinaryReader inStreamReader)
{
while (true)
{
string chunkHead = await inStreamReader.ReadLineAsync();
int chunkSize = int.Parse(chunkHead, NumberStyles.HexNumber);
if (chunkSize != 0)
{
var chunkHeadBytes = Encoding.ASCII.GetBytes(chunkSize.ToString("x2"));
await BaseStream.WriteAsync(chunkHeadBytes, 0, chunkHeadBytes.Length);
await BaseStream.WriteAsync(ProxyConstants.NewLineBytes, 0, ProxyConstants.NewLineBytes.Length);
await inStreamReader.CopyBytesToStream(BaseStream, chunkSize);
await BaseStream.WriteAsync(ProxyConstants.NewLineBytes, 0, ProxyConstants.NewLineBytes.Length);
await inStreamReader.ReadLineAsync();
}
else
{
await inStreamReader.ReadLineAsync();
await BaseStream.WriteAsync(ProxyConstants.ChunkEnd, 0, ProxyConstants.ChunkEnd.Length);
break;
}
}
}
/// <summary>
/// Copies the given input bytes to output stream chunked
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
internal async Task WriteResponseBodyChunkedAsync(byte[] data)
{
var chunkHead = Encoding.ASCII.GetBytes(data.Length.ToString("x2"));
await BaseStream.WriteAsync(chunkHead, 0, chunkHead.Length);
await BaseStream.WriteAsync(ProxyConstants.NewLineBytes, 0, ProxyConstants.NewLineBytes.Length);
await BaseStream.WriteAsync(data, 0, data.Length);
await BaseStream.WriteAsync(ProxyConstants.NewLineBytes, 0, ProxyConstants.NewLineBytes.Length);
await BaseStream.WriteAsync(ProxyConstants.ChunkEnd, 0, ProxyConstants.ChunkEnd.Length);
} }
} }
} }
using System.IO; using System;
using System.Globalization;
using System.IO;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using StreamExtended.Helpers;
using StreamExtended.Network;
using Titanium.Web.Proxy.Http; using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Shared; using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Helpers namespace Titanium.Web.Proxy.Helpers
{ {
abstract class HttpWriter : StreamWriter internal class HttpWriter : CustomBinaryWriter
{ {
protected HttpWriter(Stream stream, int bufferSize, bool leaveOpen) public int BufferSize { get; }
: base(stream, Encoding.ASCII, bufferSize, leaveOpen)
private readonly char[] charBuffer;
private static readonly byte[] newLine = ProxyConstants.NewLine;
private static readonly Encoder encoder = Encoding.ASCII.GetEncoder();
internal HttpWriter(Stream stream, int bufferSize) : base(stream)
{ {
NewLine = ProxyConstants.NewLine; BufferSize = bufferSize;
// ASCII encoder max byte count is char count + 1
charBuffer = new char[BufferSize - 1];
} }
public void WriteHeaders(HeaderCollection headers, bool flush = true) public Task WriteLineAsync()
{ {
if (headers != null) return WriteAsync(newLine);
}
public async Task WriteAsync(string value)
{
int charCount = value.Length;
value.CopyTo(0, charBuffer, 0, charCount);
if (charCount < BufferSize)
{ {
foreach (var header in headers) var buffer = BufferPool.GetBuffer(BufferSize);
try
{ {
header.WriteToStream(this); int idx = encoder.GetBytes(charBuffer, 0, charCount, buffer, 0, true);
await WriteAsync(buffer, 0, idx);
}
finally
{
BufferPool.ReturnBuffer(buffer);
} }
} }
else
WriteLine();
if (flush)
{ {
Flush(); var buffer = new byte[charCount + 1];
int idx = encoder.GetBytes(charBuffer, 0, charCount, buffer, 0, true);
await WriteAsync(buffer, 0, idx);
} }
} }
public async Task WriteLineAsync(string value)
{
await WriteAsync(value);
await WriteLineAsync();
}
/// <summary> /// <summary>
/// Write the headers to client /// Write the headers to client
/// </summary> /// </summary>
...@@ -50,8 +84,161 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -50,8 +84,161 @@ namespace Titanium.Web.Proxy.Helpers
await WriteLineAsync(); await WriteLineAsync();
if (flush) if (flush)
{ {
// flush the stream
await FlushAsync();
}
}
public async Task WriteAsync(byte[] data, bool flush = false)
{
await WriteAsync(data, 0, data.Length);
if (flush)
{
// flush the stream
await FlushAsync(); await FlushAsync();
} }
} }
public async Task WriteAsync(byte[] data, int offset, int count, bool flush)
{
await WriteAsync(data, offset, count);
if (flush)
{
// flush the stream
await FlushAsync();
}
}
/// <summary>
/// Writes the byte array body to the stream; optionally chunked
/// </summary>
/// <param name="data"></param>
/// <param name="isChunked"></param>
/// <returns></returns>
internal Task WriteBodyAsync(byte[] data, bool isChunked)
{
if (isChunked)
{
return WriteBodyChunkedAsync(data);
}
return WriteAsync(data);
}
/// <summary>
/// Copies the specified content length number of bytes to the output stream from the given inputs stream
/// optionally chunked
/// </summary>
/// <param name="streamReader"></param>
/// <param name="isChunked"></param>
/// <param name="contentLength"></param>
/// <param name="removeChunkedEncoding"></param>
/// <returns></returns>
internal Task CopyBodyAsync(CustomBinaryReader streamReader, bool isChunked, long contentLength, bool removeChunkedEncoding)
{
//For chunked request we need to read data as they arrive, until we reach a chunk end symbol
if (isChunked)
{
//Need to revist, find any potential bugs
//send the body bytes to server in chunks
return CopyBodyChunkedAsync(streamReader, removeChunkedEncoding);
}
//http 1.0
if (contentLength == -1)
{
contentLength = long.MaxValue;
}
//If not chunked then its easy just read the amount of bytes mentioned in content length header
return CopyBytesFromStream(streamReader, contentLength);
}
/// <summary>
/// Copies the given input bytes to output stream chunked
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
private async Task WriteBodyChunkedAsync(byte[] data)
{
var chunkHead = Encoding.ASCII.GetBytes(data.Length.ToString("x2"));
await WriteAsync(chunkHead);
await WriteLineAsync();
await WriteAsync(data);
await WriteLineAsync();
await WriteLineAsync("0");
await WriteLineAsync();
}
/// <summary>
/// Copies the streams chunked
/// </summary>
/// <param name="reader"></param>
/// <param name="removeChunkedEncoding"></param>
/// <returns></returns>
private async Task CopyBodyChunkedAsync(CustomBinaryReader reader, bool removeChunkedEncoding)
{
while (true)
{
string chunkHead = await reader.ReadLineAsync();
int chunkSize = int.Parse(chunkHead, NumberStyles.HexNumber);
if (!removeChunkedEncoding)
{
await WriteLineAsync(chunkHead);
}
if (chunkSize != 0)
{
await CopyBytesFromStream(reader, chunkSize);
}
if (!removeChunkedEncoding)
{
await WriteLineAsync();
}
//chunk trail
await reader.ReadLineAsync();
if (chunkSize == 0)
{
break;
}
}
}
/// <summary>
/// Copies the specified bytes to the stream from the input stream
/// </summary>
/// <param name="reader"></param>
/// <param name="count"></param>
/// <returns></returns>
private async Task CopyBytesFromStream(CustomBinaryReader reader, long count)
{
var buffer = reader.Buffer;
long remainingBytes = count;
while (remainingBytes > 0)
{
int bytesToRead = buffer.Length;
if (remainingBytes < bytesToRead)
{
bytesToRead = (int)remainingBytes;
}
int bytesRead = await reader.ReadBytesAsync(buffer, bytesToRead);
if (bytesRead == 0)
{
break;
}
remainingBytes -= bytesRead;
await WriteAsync(buffer, 0, bytesRead);
}
}
} }
} }
...@@ -2,6 +2,7 @@ using System; ...@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Titanium.Web.Proxy.Extensions; using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Network.Tcp; using Titanium.Web.Proxy.Network.Tcp;
...@@ -111,16 +112,21 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -111,16 +112,21 @@ namespace Titanium.Web.Proxy.Helpers
/// </summary> /// </summary>
/// <param name="clientStream"></param> /// <param name="clientStream"></param>
/// <param name="serverStream"></param> /// <param name="serverStream"></param>
/// <param name="bufferSize"></param>
/// <param name="onDataSend"></param> /// <param name="onDataSend"></param>
/// <param name="onDataReceive"></param> /// <param name="onDataReceive"></param>
/// <returns></returns> /// <returns></returns>
internal static async Task SendRaw(Stream clientStream, Stream serverStream, int bufferSize, internal static async Task SendRaw(Stream clientStream, Stream serverStream, int bufferSize,
Action<byte[], int, int> onDataSend, Action<byte[], int, int> onDataReceive) Action<byte[], int, int> onDataSend, Action<byte[], int, int> onDataReceive)
{ {
var cts = new CancellationTokenSource();
//Now async relay all server=>client & client=>server data //Now async relay all server=>client & client=>server data
var sendRelay = clientStream.CopyToAsync(serverStream, onDataSend, bufferSize); var sendRelay = clientStream.CopyToAsync(serverStream, onDataSend, bufferSize, cts.Token);
var receiveRelay = serverStream.CopyToAsync(clientStream, onDataReceive, bufferSize, cts.Token);
var receiveRelay = serverStream.CopyToAsync(clientStream, onDataReceive, bufferSize); await Task.WhenAny(sendRelay, receiveRelay);
cts.Cancel();
await Task.WhenAll(sendRelay, receiveRelay); await Task.WhenAll(sendRelay, receiveRelay);
} }
......
...@@ -22,7 +22,7 @@ namespace Titanium.Web.Proxy.Http ...@@ -22,7 +22,7 @@ namespace Titanium.Web.Proxy.Http
StatusDescription = "Connection Established" StatusDescription = "Connection Established"
}; };
response.Headers.AddHeader("Timestamp", DateTime.Now.ToString()); response.Headers.AddHeader(KnownHeaders.Timestamp, DateTime.Now.ToString());
return response; return response;
} }
} }
......
...@@ -63,6 +63,21 @@ namespace Titanium.Web.Proxy.Http ...@@ -63,6 +63,21 @@ namespace Titanium.Web.Proxy.Http
return null; return null;
} }
public HttpHeader GetFirstHeader(string name)
{
if (Headers.TryGetValue(name, out var header))
{
return header;
}
if (NonUniqueHeaders.TryGetValue(name, out var headers))
{
return headers.FirstOrDefault();
}
return null;
}
/// <summary> /// <summary>
/// Returns all headers /// Returns all headers
/// </summary> /// </summary>
...@@ -235,7 +250,7 @@ namespace Titanium.Web.Proxy.Http ...@@ -235,7 +250,7 @@ namespace Titanium.Web.Proxy.Http
return null; return null;
} }
internal string SetOrAddHeaderValue(string headerName, string value) internal void SetOrAddHeaderValue(string headerName, string value)
{ {
if (Headers.TryGetValue(headerName, out var header)) if (Headers.TryGetValue(headerName, out var header))
{ {
...@@ -245,8 +260,6 @@ namespace Titanium.Web.Proxy.Http ...@@ -245,8 +260,6 @@ namespace Titanium.Web.Proxy.Http
{ {
Headers.Add(headerName, new HttpHeader(headerName, value)); Headers.Add(headerName, new HttpHeader(headerName, value));
} }
return null;
} }
/// <summary> /// <summary>
...@@ -255,12 +268,12 @@ namespace Titanium.Web.Proxy.Http ...@@ -255,12 +268,12 @@ namespace Titanium.Web.Proxy.Http
internal void FixProxyHeaders() internal void FixProxyHeaders()
{ {
//If proxy-connection close was returned inform to close the connection //If proxy-connection close was returned inform to close the connection
string proxyHeader = GetHeaderValueOrNull("proxy-connection"); string proxyHeader = GetHeaderValueOrNull(KnownHeaders.ProxyConnection);
RemoveHeader("proxy-connection"); RemoveHeader(KnownHeaders.ProxyConnection);
if (proxyHeader != null) if (proxyHeader != null)
{ {
SetOrAddHeaderValue("connection", proxyHeader); SetOrAddHeaderValue(KnownHeaders.Connection, proxyHeader);
} }
} }
......
...@@ -2,7 +2,6 @@ using System; ...@@ -2,7 +2,6 @@ using System;
using System.IO; using System.IO;
using System.Net; using System.Net;
using System.Threading.Tasks; using System.Threading.Tasks;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Models; using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Network.Tcp; using Titanium.Web.Proxy.Network.Tcp;
...@@ -74,61 +73,45 @@ namespace Titanium.Web.Proxy.Http ...@@ -74,61 +73,45 @@ namespace Titanium.Web.Proxy.Http
connection.LastAccess = DateTime.Now; connection.LastAccess = DateTime.Now;
ServerConnection = connection; ServerConnection = connection;
} }
/// <summary> /// <summary>
/// Prepare and send the http(s) request /// Prepare and send the http(s) request
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
internal async Task SendRequest(bool enable100ContinueBehaviour) internal async Task SendRequest(bool enable100ContinueBehaviour)
{ {
var stream = ServerConnection.Stream; var upstreamProxy = ServerConnection.UpStreamProxy;
byte[] requestBytes; bool useUpstreamProxy = upstreamProxy != null && ServerConnection.IsHttps == false;
using (var ms = new MemoryStream())
using (var writer = new HttpRequestWriter(ms, bufferSize))
{
var upstreamProxy = ServerConnection.UpStreamProxy;
bool useUpstreamProxy = upstreamProxy != null && ServerConnection.IsHttps == false; var writer = ServerConnection.StreamWriter;
//prepare the request & headers //prepare the request & headers
if (useUpstreamProxy) await writer.WriteLineAsync(Request.CreateRequestLine(Request.Method,
{ useUpstreamProxy ? Request.OriginalUrl : Request.RequestUri.PathAndQuery,
writer.WriteLine($"{Request.Method} {Request.OriginalUrl} HTTP/{Request.HttpVersion.Major}.{Request.HttpVersion.Minor}"); Request.HttpVersion));
}
else
{
writer.WriteLine($"{Request.Method} {Request.RequestUri.PathAndQuery} HTTP/{Request.HttpVersion.Major}.{Request.HttpVersion.Minor}");
}
//Send Authentication to Upstream proxy if needed //Send Authentication to Upstream proxy if needed
if (upstreamProxy != null if (upstreamProxy != null
&& ServerConnection.IsHttps == false && ServerConnection.IsHttps == false
&& !string.IsNullOrEmpty(upstreamProxy.UserName) && !string.IsNullOrEmpty(upstreamProxy.UserName)
&& upstreamProxy.Password != null) && upstreamProxy.Password != null)
{ {
HttpHeader.ProxyConnectionKeepAlive.WriteToStream(writer); await HttpHeader.ProxyConnectionKeepAlive.WriteToStreamAsync(writer);
HttpHeader.GetProxyAuthorizationHeader(upstreamProxy.UserName, upstreamProxy.Password).WriteToStream(writer); await HttpHeader.GetProxyAuthorizationHeader(upstreamProxy.UserName, upstreamProxy.Password).WriteToStreamAsync(writer);
} }
//write request headers //write request headers
foreach (var header in Request.Headers) foreach (var header in Request.Headers)
{
if (header.Name != KnownHeaders.ProxyAuthorization)
{ {
if (header.Name != "Proxy-Authorization") await header.WriteToStreamAsync(writer);
{
header.WriteToStream(writer);
}
} }
writer.WriteLine();
writer.Flush();
requestBytes = ms.ToArray();
} }
await stream.WriteAsync(requestBytes, 0, requestBytes.Length); await writer.WriteLineAsync();
await stream.FlushAsync();
if (enable100ContinueBehaviour) if (enable100ContinueBehaviour)
{ {
...@@ -191,6 +174,7 @@ namespace Titanium.Web.Proxy.Http ...@@ -191,6 +174,7 @@ namespace Titanium.Web.Proxy.Http
Response.Is100Continue = true; Response.Is100Continue = true;
Response.StatusCode = 0; Response.StatusCode = 0;
await ServerConnection.StreamReader.ReadLineAsync(); await ServerConnection.StreamReader.ReadLineAsync();
//now receive response //now receive response
await ReceiveResponse(); await ReceiveResponse();
return; return;
...@@ -203,6 +187,7 @@ namespace Titanium.Web.Proxy.Http ...@@ -203,6 +187,7 @@ namespace Titanium.Web.Proxy.Http
Response.ExpectationFailed = true; Response.ExpectationFailed = true;
Response.StatusCode = 0; Response.StatusCode = 0;
await ServerConnection.StreamReader.ReadLineAsync(); await ServerConnection.StreamReader.ReadLineAsync();
//now receive response //now receive response
await ReceiveResponse(); await ReceiveResponse();
return; return;
......
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Http
{
public static class KnownHeaders
{
// Both
public const string Connection = "connection";
public const string ConnectionClose = "close";
public const string ConnectionKeepAlive = "keep-alive";
public const string ContentLength = "content-length";
public const string ContentType = "content-type";
public const string ContentTypeCharset = "charset";
public const string ContentTypeBoundary = "boundary";
public const string Upgrade = "upgrade";
public const string UpgradeWebsocket = "websocket";
// Request headers
public const string AcceptEncoding = "accept-encoding";
public const string Authorization = "Authorization";
public const string Expect = "expect";
public const string Expect100Continue = "100-continue";
public const string Host = "host";
public const string ProxyAuthorization = "Proxy-Authorization";
public const string ProxyConnection = "Proxy-Connection";
public const string ProxyConnectionClose = "close";
// Response headers
public const string ContentEncoding = "content-encoding";
public const string Location = "Location";
public const string ProxyAuthenticate = "Proxy-Authenticate";
public const string TransferEncoding = "transfer-encoding";
public const string TransferEncodingChunked = "chunked";
// ???
public const string Timestamp = "Timestamp";
}
}
...@@ -67,14 +67,14 @@ namespace Titanium.Web.Proxy.Http ...@@ -67,14 +67,14 @@ namespace Titanium.Web.Proxy.Http
/// </summary> /// </summary>
public string Host public string Host
{ {
get => Headers.GetHeaderValueOrNull("host"); get => Headers.GetHeaderValueOrNull(KnownHeaders.Host);
set => Headers.SetOrAddHeaderValue("host", value); set => Headers.SetOrAddHeaderValue(KnownHeaders.Host, value);
} }
/// <summary> /// <summary>
/// Content encoding header value /// Content encoding header value
/// </summary> /// </summary>
public string ContentEncoding => Headers.GetHeaderValueOrNull("content-encoding"); public string ContentEncoding => Headers.GetHeaderValueOrNull(KnownHeaders.ContentEncoding);
/// <summary> /// <summary>
/// Request content-length /// Request content-length
...@@ -83,7 +83,7 @@ namespace Titanium.Web.Proxy.Http ...@@ -83,7 +83,7 @@ namespace Titanium.Web.Proxy.Http
{ {
get get
{ {
string headerValue = Headers.GetHeaderValueOrNull("content-length"); string headerValue = Headers.GetHeaderValueOrNull(KnownHeaders.ContentLength);
if (headerValue == null) if (headerValue == null)
{ {
...@@ -102,12 +102,12 @@ namespace Titanium.Web.Proxy.Http ...@@ -102,12 +102,12 @@ namespace Titanium.Web.Proxy.Http
{ {
if (value >= 0) if (value >= 0)
{ {
Headers.SetOrAddHeaderValue("content-length", value.ToString()); Headers.SetOrAddHeaderValue(KnownHeaders.ContentLength, value.ToString());
IsChunked = false; IsChunked = false;
} }
else else
{ {
Headers.RemoveHeader("content-length"); Headers.RemoveHeader(KnownHeaders.ContentLength);
} }
} }
} }
...@@ -117,8 +117,8 @@ namespace Titanium.Web.Proxy.Http ...@@ -117,8 +117,8 @@ namespace Titanium.Web.Proxy.Http
/// </summary> /// </summary>
public string ContentType public string ContentType
{ {
get => Headers.GetHeaderValueOrNull("content-type"); get => Headers.GetHeaderValueOrNull(KnownHeaders.ContentType);
set => Headers.SetOrAddHeaderValue("content-type", value); set => Headers.SetOrAddHeaderValue(KnownHeaders.ContentType, value);
} }
/// <summary> /// <summary>
...@@ -128,19 +128,19 @@ namespace Titanium.Web.Proxy.Http ...@@ -128,19 +128,19 @@ namespace Titanium.Web.Proxy.Http
{ {
get get
{ {
string headerValue = Headers.GetHeaderValueOrNull("transfer-encoding"); string headerValue = Headers.GetHeaderValueOrNull(KnownHeaders.TransferEncoding);
return headerValue != null && headerValue.ContainsIgnoreCase("chunked"); return headerValue != null && headerValue.ContainsIgnoreCase(KnownHeaders.TransferEncodingChunked);
} }
set set
{ {
if (value) if (value)
{ {
Headers.SetOrAddHeaderValue("transfer-encoding", "chunked"); Headers.SetOrAddHeaderValue(KnownHeaders.TransferEncoding, KnownHeaders.TransferEncodingChunked);
ContentLength = -1; ContentLength = -1;
} }
else else
{ {
Headers.RemoveHeader("transfer-encoding"); Headers.RemoveHeader(KnownHeaders.TransferEncoding);
} }
} }
} }
...@@ -152,8 +152,8 @@ namespace Titanium.Web.Proxy.Http ...@@ -152,8 +152,8 @@ namespace Titanium.Web.Proxy.Http
{ {
get get
{ {
string headerValue = Headers.GetHeaderValueOrNull("expect"); string headerValue = Headers.GetHeaderValueOrNull(KnownHeaders.Expect);
return headerValue != null && headerValue.Equals("100-continue"); return headerValue != null && headerValue.Equals(KnownHeaders.Expect100Continue);
} }
} }
...@@ -241,14 +241,14 @@ namespace Titanium.Web.Proxy.Http ...@@ -241,14 +241,14 @@ namespace Titanium.Web.Proxy.Http
{ {
get get
{ {
string headerValue = Headers.GetHeaderValueOrNull("upgrade"); string headerValue = Headers.GetHeaderValueOrNull(KnownHeaders.Upgrade);
if (headerValue == null) if (headerValue == null)
{ {
return false; return false;
} }
return headerValue.Equals("websocket", StringComparison.CurrentCultureIgnoreCase); return headerValue.Equals(KnownHeaders.UpgradeWebsocket, StringComparison.CurrentCultureIgnoreCase);
} }
} }
...@@ -275,7 +275,7 @@ namespace Titanium.Web.Proxy.Http ...@@ -275,7 +275,7 @@ namespace Titanium.Web.Proxy.Http
get get
{ {
var sb = new StringBuilder(); var sb = new StringBuilder();
sb.AppendLine($"{Method} {OriginalUrl} HTTP/{HttpVersion.Major}.{HttpVersion.Minor}"); sb.AppendLine(CreateRequestLine(Method, OriginalUrl, HttpVersion));
foreach (var header in Headers) foreach (var header in Headers)
{ {
sb.AppendLine(header.ToString()); sb.AppendLine(header.ToString());
...@@ -286,6 +286,11 @@ namespace Titanium.Web.Proxy.Http ...@@ -286,6 +286,11 @@ namespace Titanium.Web.Proxy.Http
} }
} }
internal static string CreateRequestLine(string httpMethod, string httpUrl, Version version)
{
return $"{httpMethod} {httpUrl} HTTP/{version.Major}.{version.Minor}";
}
internal static void ParseRequestLine(string httpCmd, out string httpMethod, out string httpUrl, out Version version) internal static void ParseRequestLine(string httpCmd, out string httpMethod, out string httpUrl, out Version version)
{ {
//break up the line into three components (method, remote URL & Http Version) //break up the line into three components (method, remote URL & Http Version)
......
...@@ -42,12 +42,12 @@ namespace Titanium.Web.Proxy.Http ...@@ -42,12 +42,12 @@ namespace Titanium.Web.Proxy.Http
/// <summary> /// <summary>
/// Content encoding for this response /// Content encoding for this response
/// </summary> /// </summary>
public string ContentEncoding => Headers.GetHeaderValueOrNull("content-encoding")?.Trim(); public string ContentEncoding => Headers.GetHeaderValueOrNull(KnownHeaders.ContentEncoding)?.Trim();
/// <summary> /// <summary>
/// Http version /// Http version
/// </summary> /// </summary>
public Version HttpVersion { get; set; } public Version HttpVersion { get; set; } = HttpHeader.VersionUnknown;
/// <summary> /// <summary>
/// Keeps the response body data after the session is finished /// Keeps the response body data after the session is finished
...@@ -61,16 +61,24 @@ namespace Titanium.Web.Proxy.Http ...@@ -61,16 +61,24 @@ namespace Titanium.Web.Proxy.Http
{ {
get get
{ {
long contentLength = ContentLength;
//If content length is set to 0 the response has no body
if (contentLength == 0)
{
return false;
}
//Has body only if response is chunked or content length >0 //Has body only if response is chunked or content length >0
//If none are true then check if connection:close header exist, if so write response until server or client terminates the connection //If none are true then check if connection:close header exist, if so write response until server or client terminates the connection
if (IsChunked || ContentLength > 0 || !KeepAlive) if (IsChunked || contentLength > 0 || !KeepAlive)
{ {
return true; return true;
} }
//has response if connection:keep-alive header exist and when version is http/1.0 //has response if connection:keep-alive header exist and when version is http/1.0
//Because in Http 1.0 server can return a response without content-length (expectation being client would read until end of stream) //Because in Http 1.0 server can return a response without content-length (expectation being client would read until end of stream)
if (KeepAlive && HttpVersion.Minor == 0) if (KeepAlive && HttpVersion == HttpHeader.Version10)
{ {
return true; return true;
} }
...@@ -86,11 +94,11 @@ namespace Titanium.Web.Proxy.Http ...@@ -86,11 +94,11 @@ namespace Titanium.Web.Proxy.Http
{ {
get get
{ {
string headerValue = Headers.GetHeaderValueOrNull("connection"); string headerValue = Headers.GetHeaderValueOrNull(KnownHeaders.Connection);
if (headerValue != null) if (headerValue != null)
{ {
if (headerValue.ContainsIgnoreCase("close")) if (headerValue.ContainsIgnoreCase(KnownHeaders.ConnectionClose))
{ {
return false; return false;
} }
...@@ -103,7 +111,7 @@ namespace Titanium.Web.Proxy.Http ...@@ -103,7 +111,7 @@ namespace Titanium.Web.Proxy.Http
/// <summary> /// <summary>
/// Content type of this response /// Content type of this response
/// </summary> /// </summary>
public string ContentType => Headers.GetHeaderValueOrNull("content-type"); public string ContentType => Headers.GetHeaderValueOrNull(KnownHeaders.ContentType);
/// <summary> /// <summary>
/// Length of response body /// Length of response body
...@@ -112,15 +120,14 @@ namespace Titanium.Web.Proxy.Http ...@@ -112,15 +120,14 @@ namespace Titanium.Web.Proxy.Http
{ {
get get
{ {
string headerValue = Headers.GetHeaderValueOrNull("content-length"); string headerValue = Headers.GetHeaderValueOrNull(KnownHeaders.ContentLength);
if (headerValue == null) if (headerValue == null)
{ {
return -1; return -1;
} }
long.TryParse(headerValue, out long contentLen); if (long.TryParse(headerValue, out long contentLen))
if (contentLen >= 0)
{ {
return contentLen; return contentLen;
} }
...@@ -131,12 +138,12 @@ namespace Titanium.Web.Proxy.Http ...@@ -131,12 +138,12 @@ namespace Titanium.Web.Proxy.Http
{ {
if (value >= 0) if (value >= 0)
{ {
Headers.SetOrAddHeaderValue("content-length", value.ToString()); Headers.SetOrAddHeaderValue(KnownHeaders.ContentLength, value.ToString());
IsChunked = false; IsChunked = false;
} }
else else
{ {
Headers.RemoveHeader("content-length"); Headers.RemoveHeader(KnownHeaders.ContentLength);
} }
} }
} }
...@@ -148,19 +155,19 @@ namespace Titanium.Web.Proxy.Http ...@@ -148,19 +155,19 @@ namespace Titanium.Web.Proxy.Http
{ {
get get
{ {
string headerValue = Headers.GetHeaderValueOrNull("transfer-encoding"); string headerValue = Headers.GetHeaderValueOrNull(KnownHeaders.TransferEncoding);
return headerValue != null && headerValue.ContainsIgnoreCase("chunked"); return headerValue != null && headerValue.ContainsIgnoreCase(KnownHeaders.TransferEncodingChunked);
} }
set set
{ {
if (value) if (value)
{ {
Headers.SetOrAddHeaderValue("transfer-encoding", "chunked"); Headers.SetOrAddHeaderValue(KnownHeaders.TransferEncoding, KnownHeaders.TransferEncodingChunked);
ContentLength = -1; ContentLength = -1;
} }
else else
{ {
Headers.RemoveHeader("transfer-encoding"); Headers.RemoveHeader(KnownHeaders.TransferEncoding);
} }
} }
} }
...@@ -228,7 +235,7 @@ namespace Titanium.Web.Proxy.Http ...@@ -228,7 +235,7 @@ namespace Titanium.Web.Proxy.Http
/// <summary> /// <summary>
/// Gets the resposne status. /// Gets the resposne status.
/// </summary> /// </summary>
public string Status => $"HTTP/{HttpVersion?.Major}.{HttpVersion?.Minor} {StatusCode} {StatusDescription}"; public string Status => CreateResponseLine(HttpVersion, StatusCode, StatusDescription);
/// <summary> /// <summary>
/// Gets the header text. /// Gets the header text.
...@@ -249,6 +256,11 @@ namespace Titanium.Web.Proxy.Http ...@@ -249,6 +256,11 @@ namespace Titanium.Web.Proxy.Http
} }
} }
internal static string CreateResponseLine(Version version, int statusCode, string statusDescription)
{
return $"HTTP/{version.Major}.{version.Minor} {statusCode} {statusDescription}";
}
internal static void ParseResponseLine(string httpStatus, out Version version, out int statusCode, out string statusDescription) internal static void ParseResponseLine(string httpStatus, out Version version, out int statusCode, out string statusDescription)
{ {
var httpResult = httpStatus.Split(ProxyConstants.SpaceSplit, 3); var httpResult = httpStatus.Split(ProxyConstants.SpaceSplit, 3);
......
using System; using System;
using System.IO;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Http;
namespace Titanium.Web.Proxy.Models namespace Titanium.Web.Proxy.Models
{ {
...@@ -10,6 +11,8 @@ namespace Titanium.Web.Proxy.Models ...@@ -10,6 +11,8 @@ namespace Titanium.Web.Proxy.Models
/// </summary> /// </summary>
public class HttpHeader public class HttpHeader
{ {
internal static readonly Version VersionUnknown = new Version(0, 0);
internal static readonly Version Version10 = new Version(1, 0); internal static readonly Version Version10 = new Version(1, 0);
internal static readonly Version Version11 = new Version(1, 1); internal static readonly Version Version11 = new Version(1, 1);
...@@ -54,19 +57,12 @@ namespace Titanium.Web.Proxy.Models ...@@ -54,19 +57,12 @@ namespace Titanium.Web.Proxy.Models
internal static HttpHeader GetProxyAuthorizationHeader(string userName, string password) internal static HttpHeader GetProxyAuthorizationHeader(string userName, string password)
{ {
var result = new HttpHeader("Proxy-Authorization", var result = new HttpHeader(KnownHeaders.ProxyAuthorization,
"Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes($"{userName}:{password}"))); "Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes($"{userName}:{password}")));
return result; return result;
} }
internal void WriteToStream(StreamWriter writer) internal async Task WriteToStreamAsync(HttpWriter writer)
{
writer.Write(Name);
writer.Write(": ");
writer.WriteLine(Value);
}
internal async Task WriteToStreamAsync(StreamWriter writer)
{ {
await writer.WriteAsync(Name); await writer.WriteAsync(Name);
await writer.WriteAsync(": "); await writer.WriteAsync(": ");
......
#if DEBUG
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using StreamExtended.Network;
namespace Titanium.Web.Proxy.Network
{
class DebugCustomBufferedStream : CustomBufferedStream
{
private const string basePath = @".";
private static int counter;
public int Counter { get; }
private readonly FileStream fileStreamSent;
private readonly FileStream fileStreamReceived;
public DebugCustomBufferedStream(Stream baseStream, int bufferSize) : base(baseStream, bufferSize)
{
Counter = Interlocked.Increment(ref counter);
fileStreamSent = new FileStream(Path.Combine(basePath, $"{Counter}_sent.dat"), FileMode.Create);
fileStreamReceived = new FileStream(Path.Combine(basePath, $"{Counter}_received.dat"), FileMode.Create);
}
protected override void OnDataSent(byte[] buffer, int offset, int count)
{
fileStreamSent.Write(buffer, offset, count);
}
protected override void OnDataReceived(byte[] buffer, int offset, int count)
{
fileStreamReceived.Write(buffer, offset, count);
}
public override void Flush()
{
fileStreamSent.Flush(true);
fileStreamReceived.Flush(true);
base.Flush();
}
}
}
#endif
\ No newline at end of file
...@@ -15,7 +15,7 @@ namespace Titanium.Web.Proxy.Network ...@@ -15,7 +15,7 @@ namespace Titanium.Web.Proxy.Network
internal TcpClient TcpClient { get; set; } internal TcpClient TcpClient { get; set; }
/// <summary> /// <summary>
/// holds the stream to client /// Holds the stream to client
/// </summary> /// </summary>
internal CustomBufferedStream ClientStream { get; set; } internal CustomBufferedStream ClientStream { get; set; }
...@@ -25,7 +25,7 @@ namespace Titanium.Web.Proxy.Network ...@@ -25,7 +25,7 @@ namespace Titanium.Web.Proxy.Network
internal CustomBinaryReader ClientStreamReader { get; set; } internal CustomBinaryReader ClientStreamReader { get; set; }
/// <summary> /// <summary>
/// used to write line by line to client /// Used to write line by line to client
/// </summary> /// </summary>
internal HttpResponseWriter ClientStreamWriter { get; set; } internal HttpResponseWriter ClientStreamWriter { get; set; }
} }
......
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
using System; using System;
using System.Net; using System.Net;
using System.Net.Sockets; using System.Net.Sockets;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Models; using Titanium.Web.Proxy.Models;
namespace Titanium.Web.Proxy.Network.Tcp namespace Titanium.Web.Proxy.Network.Tcp
...@@ -11,6 +13,8 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -11,6 +13,8 @@ namespace Titanium.Web.Proxy.Network.Tcp
/// </summary> /// </summary>
internal class TcpConnection : IDisposable internal class TcpConnection : IDisposable
{ {
private ProxyServer proxyServer { get; }
internal ExternalProxy UpStreamProxy { get; set; } internal ExternalProxy UpStreamProxy { get; set; }
internal string HostName { get; set; } internal string HostName { get; set; }
...@@ -34,10 +38,15 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -34,10 +38,15 @@ namespace Titanium.Web.Proxy.Network.Tcp
internal TcpClient TcpClient { private get; set; } internal TcpClient TcpClient { private get; set; }
/// <summary> /// <summary>
/// used to read lines from server /// Used to read lines from server
/// </summary> /// </summary>
internal CustomBinaryReader StreamReader { get; set; } internal CustomBinaryReader StreamReader { get; set; }
/// <summary>
/// Used to write lines to server
/// </summary>
internal HttpRequestWriter StreamWriter { get; set; }
/// <summary> /// <summary>
/// Server stream /// Server stream
/// </summary> /// </summary>
...@@ -48,9 +57,11 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -48,9 +57,11 @@ namespace Titanium.Web.Proxy.Network.Tcp
/// </summary> /// </summary>
internal DateTime LastAccess { get; set; } internal DateTime LastAccess { get; set; }
internal TcpConnection() internal TcpConnection(ProxyServer proxyServer)
{ {
LastAccess = DateTime.Now; LastAccess = DateTime.Now;
this.proxyServer = proxyServer;
this.proxyServer.UpdateServerConnectionCount(true);
} }
/// <summary> /// <summary>
...@@ -58,25 +69,13 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -58,25 +69,13 @@ namespace Titanium.Web.Proxy.Network.Tcp
/// </summary> /// </summary>
public void Dispose() public void Dispose()
{ {
StreamReader?.Dispose();
Stream?.Dispose(); Stream?.Dispose();
StreamReader?.Dispose(); TcpClient.CloseSocket();
try proxyServer.UpdateServerConnectionCount(false);
{
if (TcpClient != null)
{
//This line is important!
//contributors please don't remove it without discussion
//It helps to avoid eventual deterioration of performance due to TCP port exhaustion
//due to default TCP CLOSE_WAIT timeout for 4 minutes
TcpClient.LingerState = new LingerOption(true, 0);
TcpClient.Close();
}
}
catch
{
}
} }
} }
} }
...@@ -8,6 +8,7 @@ using System.Text; ...@@ -8,6 +8,7 @@ using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Titanium.Web.Proxy.Extensions; using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Helpers; using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Models; using Titanium.Web.Proxy.Models;
namespace Titanium.Web.Proxy.Network.Tcp namespace Titanium.Web.Proxy.Network.Tcp
...@@ -68,24 +69,21 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -68,24 +69,21 @@ namespace Titanium.Web.Proxy.Network.Tcp
if (useUpstreamProxy && (isConnect || isHttps)) if (useUpstreamProxy && (isConnect || isHttps))
{ {
using (var writer = new HttpRequestWriter(stream, server.BufferSize)) var writer = new HttpRequestWriter(stream, server.BufferSize);
{ await writer.WriteLineAsync($"CONNECT {remoteHostName}:{remotePort} HTTP/{httpVersion}");
await writer.WriteLineAsync($"CONNECT {remoteHostName}:{remotePort} HTTP/{httpVersion}"); await writer.WriteLineAsync($"Host: {remoteHostName}:{remotePort}");
await writer.WriteLineAsync($"Host: {remoteHostName}:{remotePort}"); await writer.WriteLineAsync($"{KnownHeaders.Connection}: {KnownHeaders.ConnectionKeepAlive}");
await writer.WriteLineAsync("Connection: Keep-Alive");
if (!string.IsNullOrEmpty(externalProxy.UserName) && externalProxy.Password != null)
{
await HttpHeader.ProxyConnectionKeepAlive.WriteToStreamAsync(writer);
await writer.WriteLineAsync("Proxy-Authorization" + ": Basic " +
Convert.ToBase64String(Encoding.UTF8.GetBytes(
externalProxy.UserName + ":" + externalProxy.Password)));
}
await writer.WriteLineAsync(); if (!string.IsNullOrEmpty(externalProxy.UserName) && externalProxy.Password != null)
await writer.FlushAsync(); {
await HttpHeader.ProxyConnectionKeepAlive.WriteToStreamAsync(writer);
await writer.WriteLineAsync(KnownHeaders.ProxyAuthorization + ": Basic " +
Convert.ToBase64String(Encoding.UTF8.GetBytes(
externalProxy.UserName + ":" + externalProxy.Password)));
} }
await writer.WriteLineAsync();
using (var reader = new CustomBinaryReader(stream, server.BufferSize)) using (var reader = new CustomBinaryReader(stream, server.BufferSize))
{ {
string result = await reader.ReadLineAsync(); string result = await reader.ReadLineAsync();
...@@ -118,9 +116,7 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -118,9 +116,7 @@ namespace Titanium.Web.Proxy.Network.Tcp
throw; throw;
} }
server.UpdateServerConnectionCount(true); return new TcpConnection(server)
return new TcpConnection
{ {
UpStreamProxy = externalProxy, UpStreamProxy = externalProxy,
UpStreamEndPoint = upStreamEndPoint, UpStreamEndPoint = upStreamEndPoint,
...@@ -130,6 +126,7 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -130,6 +126,7 @@ namespace Titanium.Web.Proxy.Network.Tcp
UseUpstreamProxy = useUpstreamProxy, UseUpstreamProxy = useUpstreamProxy,
TcpClient = client, TcpClient = client,
StreamReader = new CustomBinaryReader(stream, server.BufferSize), StreamReader = new CustomBinaryReader(stream, server.BufferSize),
StreamWriter = new HttpRequestWriter(stream, server.BufferSize),
Stream = stream, Stream = stream,
Version = httpVersion Version = httpVersion
}; };
......
...@@ -8,6 +8,7 @@ using Titanium.Web.Proxy.Exceptions; ...@@ -8,6 +8,7 @@ using Titanium.Web.Proxy.Exceptions;
using Titanium.Web.Proxy.Helpers; using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Http; using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Models; using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy namespace Titanium.Web.Proxy
{ {
...@@ -20,37 +21,36 @@ namespace Titanium.Web.Proxy ...@@ -20,37 +21,36 @@ namespace Titanium.Web.Proxy
return true; return true;
} }
var httpHeaders = session.WebSession.Request.Headers.ToArray(); var httpHeaders = session.WebSession.Request.Headers;
try try
{ {
var header = httpHeaders.FirstOrDefault(t => t.Name == "Proxy-Authorization"); var header = httpHeaders.GetFirstHeader(KnownHeaders.ProxyAuthorization);
if (header == null) if (header == null)
{ {
session.WebSession.Response = await SendAuthentication407Response(clientStreamWriter, "Proxy Authentication Required"); session.WebSession.Response = await SendAuthentication407Response(clientStreamWriter, "Proxy Authentication Required");
return false; return false;
} }
string headerValue = header.Value.Trim(); var headerValueParts = header.Value.Split(ProxyConstants.SpaceSplit);
if (!headerValue.StartsWith("basic", StringComparison.CurrentCultureIgnoreCase)) if (headerValueParts.Length != 2 || !headerValueParts[0].Equals("basic", StringComparison.CurrentCultureIgnoreCase))
{ {
//Return not authorized //Return not authorized
session.WebSession.Response = await SendAuthentication407Response(clientStreamWriter, "Proxy Authentication Invalid"); session.WebSession.Response = await SendAuthentication407Response(clientStreamWriter, "Proxy Authentication Invalid");
return false; return false;
} }
headerValue = headerValue.Substring(5).Trim(); string decoded = Encoding.UTF8.GetString(Convert.FromBase64String(headerValueParts[1]));
int colonIndex = decoded.IndexOf(':');
string decoded = Encoding.UTF8.GetString(Convert.FromBase64String(headerValue)); if (colonIndex == -1)
if (decoded.Contains(":") == false)
{ {
//Return not authorized //Return not authorized
session.WebSession.Response = await SendAuthentication407Response(clientStreamWriter, "Proxy Authentication Invalid"); session.WebSession.Response = await SendAuthentication407Response(clientStreamWriter, "Proxy Authentication Invalid");
return false; return false;
} }
string username = decoded.Substring(0, decoded.IndexOf(':')); string username = decoded.Substring(0, colonIndex);
string password = decoded.Substring(decoded.IndexOf(':') + 1); string password = decoded.Substring(colonIndex + 1);
return await AuthenticateUserFunc(username, password); return await AuthenticateUserFunc(username, password);
} }
catch (Exception e) catch (Exception e)
...@@ -72,8 +72,8 @@ namespace Titanium.Web.Proxy ...@@ -72,8 +72,8 @@ namespace Titanium.Web.Proxy
StatusDescription = description StatusDescription = description
}; };
response.Headers.AddHeader("Proxy-Authenticate", $"Basic realm=\"{ProxyRealm}\""); response.Headers.AddHeader(KnownHeaders.ProxyAuthenticate, $"Basic realm=\"{ProxyRealm}\"");
response.Headers.AddHeader("Proxy-Connection", "close"); response.Headers.AddHeader(KnownHeaders.ProxyConnection, KnownHeaders.ProxyConnectionClose);
await clientStreamWriter.WriteResponseAsync(response); await clientStreamWriter.WriteResponseAsync(response);
return response; return response;
......
...@@ -9,6 +9,7 @@ using System.Threading; ...@@ -9,6 +9,7 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using StreamExtended.Network; using StreamExtended.Network;
using Titanium.Web.Proxy.EventArguments; using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Helpers; using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Helpers.WinHttp; using Titanium.Web.Proxy.Helpers.WinHttp;
using Titanium.Web.Proxy.Models; using Titanium.Web.Proxy.Models;
...@@ -164,22 +165,22 @@ namespace Titanium.Web.Proxy ...@@ -164,22 +165,22 @@ namespace Titanium.Web.Proxy
/// <summary> /// <summary>
/// Intercept request to server /// Intercept request to server
/// </summary> /// </summary>
public event Func<object, SessionEventArgs, Task> BeforeRequest; public event AsyncEventHandler<SessionEventArgs> BeforeRequest;
/// <summary> /// <summary>
/// Intercept response from server /// Intercept response from server
/// </summary> /// </summary>
public event Func<object, SessionEventArgs, Task> BeforeResponse; public event AsyncEventHandler<SessionEventArgs> BeforeResponse;
/// <summary> /// <summary>
/// Intercept tunnel connect reques /// Intercept tunnel connect reques
/// </summary> /// </summary>
public event Func<object, TunnelConnectSessionEventArgs, Task> TunnelConnectRequest; public event AsyncEventHandler<TunnelConnectSessionEventArgs> TunnelConnectRequest;
/// <summary> /// <summary>
/// Intercept tunnel connect response /// Intercept tunnel connect response
/// </summary> /// </summary>
public event Func<object, TunnelConnectSessionEventArgs, Task> TunnelConnectResponse; public event AsyncEventHandler<TunnelConnectSessionEventArgs> TunnelConnectResponse;
/// <summary> /// <summary>
/// Occurs when client connection count changed. /// Occurs when client connection count changed.
...@@ -229,12 +230,12 @@ namespace Titanium.Web.Proxy ...@@ -229,12 +230,12 @@ namespace Titanium.Web.Proxy
/// <summary> /// <summary>
/// Verifies the remote Secure Sockets Layer (SSL) certificate used for authentication /// Verifies the remote Secure Sockets Layer (SSL) certificate used for authentication
/// </summary> /// </summary>
public event Func<object, CertificateValidationEventArgs, Task> ServerCertificateValidationCallback; public event AsyncEventHandler<CertificateValidationEventArgs> ServerCertificateValidationCallback;
/// <summary> /// <summary>
/// Callback tooverride client certificate during SSL mutual authentication /// Callback tooverride client certificate during SSL mutual authentication
/// </summary> /// </summary>
public event Func<object, CertificateSelectionEventArgs, Task> ClientCertificateSelectionCallback; public event AsyncEventHandler<CertificateSelectionEventArgs> ClientCertificateSelectionCallback;
/// <summary> /// <summary>
/// Callback for error events in proxy /// Callback for error events in proxy
...@@ -612,16 +613,14 @@ namespace Titanium.Web.Proxy ...@@ -612,16 +613,14 @@ namespace Titanium.Web.Proxy
/// <param name="serverConnection"></param> /// <param name="serverConnection"></param>
private void Dispose(CustomBufferedStream clientStream, CustomBinaryReader clientStreamReader, HttpResponseWriter clientStreamWriter, TcpConnection serverConnection) private void Dispose(CustomBufferedStream clientStream, CustomBinaryReader clientStreamReader, HttpResponseWriter clientStreamWriter, TcpConnection serverConnection)
{ {
clientStream?.Dispose();
clientStreamReader?.Dispose(); clientStreamReader?.Dispose();
clientStreamWriter?.Dispose();
clientStream?.Dispose();
if (serverConnection != null) if (serverConnection != null)
{ {
serverConnection.Dispose(); serverConnection.Dispose();
serverConnection = null; serverConnection = null;
UpdateServerConnectionCount(false);
} }
} }
...@@ -766,22 +765,7 @@ namespace Titanium.Web.Proxy ...@@ -766,22 +765,7 @@ namespace Titanium.Web.Proxy
finally finally
{ {
UpdateClientConnectionCount(false); UpdateClientConnectionCount(false);
tcpClient.CloseSocket();
try
{
if (tcpClient != null)
{
//This line is important!
//contributors please don't remove it without discussion
//It helps to avoid eventual deterioration of performance due to TCP port exhaustion
//due to default TCP CLOSE_WAIT timeout for 4 minutes
tcpClient.LingerState = new LingerOption(true, 0);
tcpClient.Close();
}
}
catch
{
}
} }
} }
......
This diff is collapsed.
...@@ -43,7 +43,7 @@ namespace Titanium.Web.Proxy ...@@ -43,7 +43,7 @@ namespace Titanium.Web.Proxy
//If user requested call back then do it //If user requested call back then do it
if (BeforeResponse != null && !response.ResponseLocked) if (BeforeResponse != null && !response.ResponseLocked)
{ {
await BeforeResponse.InvokeParallelAsync(this, args, ExceptionFunc); await BeforeResponse.InvokeAsync(this, args, ExceptionFunc);
} }
//if user requested to send request again //if user requested to send request again
...@@ -96,7 +96,7 @@ namespace Titanium.Web.Proxy ...@@ -96,7 +96,7 @@ namespace Titanium.Web.Proxy
} }
await clientStreamWriter.WriteHeadersAsync(response.Headers); await clientStreamWriter.WriteHeadersAsync(response.Headers);
await clientStreamWriter.WriteResponseBodyAsync(response.Body, isChunked); await clientStreamWriter.WriteBodyAsync(response.Body, isChunked);
} }
else else
{ {
...@@ -105,12 +105,9 @@ namespace Titanium.Web.Proxy ...@@ -105,12 +105,9 @@ namespace Titanium.Web.Proxy
//Write body if exists //Write body if exists
if (response.HasBody) if (response.HasBody)
{ {
await clientStreamWriter.WriteResponseBodyAsync(BufferSize, args.WebSession.ServerConnection.StreamReader, await args.CopyResponseBodyAsync(clientStreamWriter, false);
response.IsChunked, response.ContentLength);
} }
} }
await clientStreamWriter.FlushAsync();
} }
catch (Exception e) catch (Exception e)
{ {
......
using System.Text; namespace Titanium.Web.Proxy.Shared
namespace Titanium.Web.Proxy.Shared
{ {
/// <summary> /// <summary>
/// Literals shared by Proxy Server /// Literals shared by Proxy Server
...@@ -14,10 +12,6 @@ namespace Titanium.Web.Proxy.Shared ...@@ -14,10 +12,6 @@ namespace Titanium.Web.Proxy.Shared
internal static readonly char[] SemiColonSplit = { ';' }; internal static readonly char[] SemiColonSplit = { ';' };
internal static readonly char[] EqualSplit = { '=' }; internal static readonly char[] EqualSplit = { '=' };
internal static readonly byte[] NewLineBytes = Encoding.ASCII.GetBytes(NewLine); internal static readonly byte[] NewLine = {(byte)'\r', (byte)'\n' };
internal static readonly byte[] ChunkEnd = Encoding.ASCII.GetBytes(0.ToString("x2") + NewLine + NewLine);
internal const string NewLine = "\r\n";
} }
} }
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Portable.BouncyCastle" Version="1.8.1.3" /> <PackageReference Include="Portable.BouncyCastle" Version="1.8.1.3" />
<PackageReference Include="StreamExtended" Version="1.0.110-beta" /> <PackageReference Include="StreamExtended" Version="1.0.132-beta" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'"> <ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
...@@ -21,7 +21,7 @@ ...@@ -21,7 +21,7 @@
<Version>4.4.0</Version> <Version>4.4.0</Version>
</PackageReference> </PackageReference>
<PackageReference Include="System.Security.Principal.Windows"> <PackageReference Include="System.Security.Principal.Windows">
<Version>4.4.0</Version> <Version>4.4.1</Version>
</PackageReference> </PackageReference>
</ItemGroup> </ItemGroup>
......
...@@ -3,6 +3,7 @@ using System.Collections.Generic; ...@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Titanium.Web.Proxy.EventArguments; using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Models; using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Network.WinAuth; using Titanium.Web.Proxy.Network.WinAuth;
...@@ -84,9 +85,9 @@ namespace Titanium.Web.Proxy ...@@ -84,9 +85,9 @@ namespace Titanium.Web.Proxy
string scheme = authSchemes.FirstOrDefault(x => authHeader.Value.Equals(x, StringComparison.OrdinalIgnoreCase)); string scheme = authSchemes.FirstOrDefault(x => authHeader.Value.Equals(x, StringComparison.OrdinalIgnoreCase));
//clear any existing headers to avoid confusing bad servers //clear any existing headers to avoid confusing bad servers
if (args.WebSession.Request.Headers.NonUniqueHeaders.ContainsKey("Authorization")) if (args.WebSession.Request.Headers.NonUniqueHeaders.ContainsKey(KnownHeaders.Authorization))
{ {
args.WebSession.Request.Headers.NonUniqueHeaders.Remove("Authorization"); args.WebSession.Request.Headers.NonUniqueHeaders.Remove(KnownHeaders.Authorization);
} }
//initial value will match exactly any of the schemes //initial value will match exactly any of the schemes
...@@ -94,16 +95,16 @@ namespace Titanium.Web.Proxy ...@@ -94,16 +95,16 @@ namespace Titanium.Web.Proxy
{ {
string clientToken = WinAuthHandler.GetInitialAuthToken(args.WebSession.Request.Host, scheme, args.Id); string clientToken = WinAuthHandler.GetInitialAuthToken(args.WebSession.Request.Host, scheme, args.Id);
var auth = new HttpHeader("Authorization", string.Concat(scheme, clientToken)); var auth = new HttpHeader(KnownHeaders.Authorization, string.Concat(scheme, clientToken));
//replace existing authorization header if any //replace existing authorization header if any
if (args.WebSession.Request.Headers.Headers.ContainsKey("Authorization")) if (args.WebSession.Request.Headers.Headers.ContainsKey(KnownHeaders.Authorization))
{ {
args.WebSession.Request.Headers.Headers["Authorization"] = auth; args.WebSession.Request.Headers.Headers[KnownHeaders.Authorization] = auth;
} }
else else
{ {
args.WebSession.Request.Headers.Headers.Add("Authorization", auth); args.WebSession.Request.Headers.Headers.Add(KnownHeaders.Authorization, auth);
} }
//don't need to send body for Authorization request //don't need to send body for Authorization request
...@@ -122,7 +123,7 @@ namespace Titanium.Web.Proxy ...@@ -122,7 +123,7 @@ namespace Titanium.Web.Proxy
string clientToken = WinAuthHandler.GetFinalAuthToken(args.WebSession.Request.Host, serverToken, args.Id); string clientToken = WinAuthHandler.GetFinalAuthToken(args.WebSession.Request.Host, serverToken, args.Id);
//there will be an existing header from initial client request //there will be an existing header from initial client request
args.WebSession.Request.Headers.Headers["Authorization"] = new HttpHeader("Authorization", string.Concat(scheme, clientToken)); args.WebSession.Request.Headers.Headers[KnownHeaders.Authorization] = new HttpHeader(KnownHeaders.Authorization, string.Concat(scheme, clientToken));
//send body for final auth request //send body for final auth request
if (args.WebSession.Request.HasBody) if (args.WebSession.Request.HasBody)
......
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