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);
...@@ -75,10 +75,13 @@ namespace Titanium.Web.Proxy.Examples.Wpf ...@@ -75,10 +75,13 @@ namespace Titanium.Web.Proxy.Examples.Wpf
public event PropertyChangedEventHandler PropertyChanged; public event PropertyChangedEventHandler PropertyChanged;
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)
{
if (!Equals(field, value))
{ {
field = value; field = value;
OnPropertyChanged(propertyName); OnPropertyChanged(propertyName);
} }
}
[NotifyPropertyChangedInvocator] [NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
...@@ -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
...@@ -3,8 +3,9 @@ using System.Collections.Generic; ...@@ -3,8 +3,9 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Net; using System.Net;
using System.Threading.Tasks; using System.Threading.Tasks;
using StreamExtended.Helpers;
using StreamExtended.Network;
using Titanium.Web.Proxy.Decompression; using Titanium.Web.Proxy.Decompression;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Helpers; using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Http; using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Http.Responses; using Titanium.Web.Proxy.Http.Responses;
...@@ -21,6 +22,8 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -21,6 +22,8 @@ namespace Titanium.Web.Proxy.EventArguments
/// </summary> /// </summary>
public class SessionEventArgs : EventArgs, IDisposable public class SessionEventArgs : EventArgs, IDisposable
{ {
private static readonly byte[] emptyData = new byte[0];
/// <summary> /// <summary>
/// Size of Buffers used by this object /// Size of Buffers used by this object
/// </summary> /// </summary>
...@@ -31,6 +34,8 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -31,6 +34,8 @@ namespace Titanium.Web.Proxy.EventArguments
/// </summary> /// </summary>
private Func<SessionEventArgs, Task> httpResponseHandler; private Func<SessionEventArgs, Task> httpResponseHandler;
private readonly Action<Exception> exceptionFunc;
/// <summary> /// <summary>
/// Backing field for corresponding public property /// Backing field for corresponding public property
/// </summary> /// </summary>
...@@ -39,9 +44,9 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -39,9 +44,9 @@ namespace Titanium.Web.Proxy.EventArguments
/// <summary> /// <summary>
/// Holds a reference to client /// Holds a reference to client
/// </summary> /// </summary>
internal ProxyClient ProxyClient { get; set; } internal ProxyClient ProxyClient { get; }
internal bool HasMulipartEventSubscribers => MultipartRequestPartSent != null; private bool hasMulipartEventSubscribers => MultipartRequestPartSent != null;
/// <summary> /// <summary>
/// Returns a unique Id for this request/response session /// Returns a unique Id for this request/response session
...@@ -103,10 +108,12 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -103,10 +108,12 @@ namespace Titanium.Web.Proxy.EventArguments
/// </summary> /// </summary>
internal SessionEventArgs(int bufferSize, internal SessionEventArgs(int bufferSize,
ProxyEndPoint endPoint, ProxyEndPoint endPoint,
Func<SessionEventArgs, Task> httpResponseHandler) Func<SessionEventArgs, Task> httpResponseHandler,
Action<Exception> exceptionFunc)
{ {
this.bufferSize = bufferSize; this.bufferSize = bufferSize;
this.httpResponseHandler = httpResponseHandler; this.httpResponseHandler = httpResponseHandler;
this.exceptionFunc = exceptionFunc;
ProxyClient = new ProxyClient(); ProxyClient = new ProxyClient();
WebSession = new HttpWebClient(bufferSize); WebSession = new HttpWebClient(bufferSize);
...@@ -135,42 +142,21 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -135,42 +142,21 @@ namespace Titanium.Web.Proxy.EventArguments
/// <summary> /// <summary>
/// Read request body content as bytes[] for current session /// Read request body content as bytes[] for current session
/// </summary> /// </summary>
private async Task ReadRequestBody() private async Task ReadRequestBodyAsync()
{ {
WebSession.Request.EnsureBodyAvailable(false); WebSession.Request.EnsureBodyAvailable(false);
//Caching check var request = WebSession.Request;
if (!WebSession.Request.IsBodyRead)
{
//If chunked then its easy just read the whole body with the content length mentioned in the request header
using (var requestBodyStream = new MemoryStream())
{
//For chunked request we need to read data as they arrive, until we reach a chunk end symbol
if (WebSession.Request.IsChunked)
{
await ProxyClient.ClientStreamReader.CopyBytesToStreamChunked(requestBodyStream);
}
else
{
//If not chunked then its easy just read the whole body with the content length mentioned in the request header
if (WebSession.Request.ContentLength > 0)
{
//If not chunked then its easy just read the amount of bytes mentioned in content length header of response
await ProxyClient.ClientStreamReader.CopyBytesToStream(requestBodyStream, WebSession.Request.ContentLength);
}
else if (WebSession.Request.HttpVersion.Major == 1 && WebSession.Request.HttpVersion.Minor == 0)
{
await WebSession.ServerConnection.StreamReader.CopyBytesToStream(requestBodyStream, long.MaxValue);
}
}
WebSession.Request.Body = await GetDecompressedResponseBody(WebSession.Request.ContentEncoding, requestBodyStream.ToArray()); //If not already read (not cached yet)
} if (!request.IsBodyRead)
{
var body = await ReadBodyAsync(ProxyClient.ClientStreamReader, request.IsChunked, request.ContentLength, request.ContentEncoding, true);
request.Body = body;
//Now set the flag to true //Now set the flag to true
//So that next time we can deliver body from cache //So that next time we can deliver body from cache
WebSession.Request.IsBodyRead = true; request.IsBodyRead = true;
var body = WebSession.Request.Body;
OnDataSent(body, 0, body.Length); OnDataSent(body, 0, body.Length);
} }
} }
...@@ -181,73 +167,208 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -181,73 +167,208 @@ namespace Titanium.Web.Proxy.EventArguments
internal async Task ClearResponse() internal async Task ClearResponse()
{ {
//siphon out the body //siphon out the body
await ReadResponseBody(); await ReadResponseBodyAsync();
WebSession.Response = new Response(); WebSession.Response = new Response();
} }
internal void OnDataSent(byte[] buffer, int offset, int count) internal void OnDataSent(byte[] buffer, int offset, int count)
{
try
{ {
DataSent?.Invoke(this, new DataEventArgs(buffer, offset, count)); DataSent?.Invoke(this, new DataEventArgs(buffer, offset, count));
} }
catch (Exception ex)
{
var ex2 = new Exception("Exception thrown in user event", ex);
exceptionFunc(ex2);
}
}
internal void OnDataReceived(byte[] buffer, int offset, int count) internal void OnDataReceived(byte[] buffer, int offset, int count)
{
try
{ {
DataReceived?.Invoke(this, new DataEventArgs(buffer, offset, count)); DataReceived?.Invoke(this, new DataEventArgs(buffer, offset, count));
} }
catch (Exception ex)
{
var ex2 = new Exception("Exception thrown in user event", ex);
exceptionFunc(ex2);
}
}
internal void OnMultipartRequestPartSent(string boundary, HeaderCollection headers) internal void OnMultipartRequestPartSent(string boundary, HeaderCollection headers)
{
try
{ {
MultipartRequestPartSent?.Invoke(this, new MultipartRequestPartSentEventArgs(boundary, headers)); MultipartRequestPartSent?.Invoke(this, new MultipartRequestPartSentEventArgs(boundary, headers));
} }
catch (Exception ex)
{
var ex2 = new Exception("Exception thrown in user event", ex);
exceptionFunc(ex2);
}
}
/// <summary> /// <summary>
/// Read response body as byte[] for current response /// Read response body as byte[] for current response
/// </summary> /// </summary>
private async Task ReadResponseBody() private async Task ReadResponseBodyAsync()
{ {
if (!WebSession.Request.RequestLocked) if (!WebSession.Request.RequestLocked)
{ {
throw new Exception("You cannot read the response body before request is made to server."); throw new Exception("You cannot read the response body before request is made to server.");
} }
var response = WebSession.Response;
if (!response.HasBody)
{
return;
}
//If not already read (not cached yet) //If not already read (not cached yet)
if (!WebSession.Response.IsBodyRead) if (!response.IsBodyRead)
{ {
if (WebSession.Response.HasBody) var body = await ReadBodyAsync(WebSession.ServerConnection.StreamReader, response.IsChunked, response.ContentLength, response.ContentEncoding, false);
response.Body = body;
//Now set the flag to true
//So that next time we can deliver body from cache
response.IsBodyRead = true;
OnDataReceived(body, 0, body.Length);
}
}
private async Task<byte[]> ReadBodyAsync(CustomBinaryReader reader, bool isChunked, long contentLength, string contentEncoding, bool isRequest)
{ {
using (var responseBodyStream = new MemoryStream()) using (var bodyStream = new MemoryStream())
{ {
//If chuncked the read chunk by chunk until we hit chunk end symbol var writer = new HttpWriter(bodyStream, bufferSize);
if (WebSession.Response.IsChunked) if (isRequest)
{ {
await WebSession.ServerConnection.StreamReader.CopyBytesToStreamChunked(responseBodyStream); await CopyRequestBodyAsync(writer, true);
} }
else else
{ {
if (WebSession.Response.ContentLength > 0) await CopyResponseBodyAsync(writer, true);
}
return await GetDecompressedBodyAsync(contentEncoding, bodyStream.ToArray());
}
}
/// <summary>
/// This is called when the request is PUT/POST/PATCH to read the body
/// </summary>
/// <returns></returns>
internal async Task CopyRequestBodyAsync(HttpWriter writer, bool removeChunkedEncoding)
{
// End the operation
var request = WebSession.Request;
var reader = ProxyClient.ClientStreamReader;
long contentLength = request.ContentLength;
//send the request body bytes to server
if (contentLength > 0 && hasMulipartEventSubscribers && request.IsMultipartFormData)
{
string boundary = HttpHelper.GetBoundaryFromContentType(request.ContentType);
using (var copyStream = new CopyStream(reader, writer, bufferSize))
using (var copyStreamReader = new CustomBinaryReader(copyStream, bufferSize))
{
while (contentLength > copyStream.ReadBytes)
{ {
//If not chunked then its easy just read the amount of bytes mentioned in content length header of response long read = await ReadUntilBoundaryAsync(copyStreamReader, contentLength, boundary);
await WebSession.ServerConnection.StreamReader.CopyBytesToStream(responseBodyStream, WebSession.Response.ContentLength); if (read == 0)
{
break;
} }
else if (WebSession.Response.HttpVersion.Major == 1 && WebSession.Response.HttpVersion.Minor == 0 ||
WebSession.Response.ContentLength == -1) if (contentLength > copyStream.ReadBytes)
{ {
await WebSession.ServerConnection.StreamReader.CopyBytesToStream(responseBodyStream, long.MaxValue); var headers = new HeaderCollection();
await HeaderParser.ReadHeaders(copyStreamReader, headers);
OnMultipartRequestPartSent(boundary, headers);
} }
} }
WebSession.Response.Body = await GetDecompressedResponseBody(WebSession.Response.ContentEncoding, responseBodyStream.ToArray()); await copyStream.FlushAsync();
} }
} }
else else
{ {
WebSession.Response.Body = new byte[0]; await writer.CopyBodyAsync(reader, request.IsChunked, contentLength, removeChunkedEncoding);
}
} }
//set this to true for caching internal async Task CopyResponseBodyAsync(HttpWriter writer, bool removeChunkedEncoding)
WebSession.Response.IsBodyRead = true; {
var body = WebSession.Response.Body; var response = WebSession.Response;
OnDataReceived(body, 0, body.Length); var reader = WebSession.ServerConnection.StreamReader;
await writer.CopyBodyAsync(reader, response.IsChunked, response.ContentLength, removeChunkedEncoding);
}
/// <summary>
/// Read a line from the byte stream
/// </summary>
/// <returns></returns>
private async Task<long> ReadUntilBoundaryAsync(CustomBinaryReader reader, long totalBytesToRead, string boundary)
{
int bufferDataLength = 0;
var buffer = BufferPool.GetBuffer(bufferSize);
try
{
int boundaryLength = boundary.Length + 4;
long bytesRead = 0;
while (bytesRead < totalBytesToRead && (reader.DataAvailable || await reader.FillBufferAsync()))
{
byte newChar = reader.ReadByteFromBuffer();
buffer[bufferDataLength] = newChar;
bufferDataLength++;
bytesRead++;
if (bufferDataLength >= boundaryLength)
{
int startIdx = bufferDataLength - boundaryLength;
if (buffer[startIdx] == '-' && buffer[startIdx + 1] == '-')
{
startIdx += 2;
bool ok = true;
for (int i = 0; i < boundary.Length; i++)
{
if (buffer[startIdx + i] != boundary[i])
{
ok = false;
break;
}
}
if (ok)
{
break;
}
}
}
if (bufferDataLength == buffer.Length)
{
//boundary is not longer than 70 bytes according to the specification, so keeping the last 100 (minimum 74) bytes is enough
const int bytesToKeep = 100;
Buffer.BlockCopy(buffer, buffer.Length - bytesToKeep, buffer, 0, bytesToKeep);
bufferDataLength = bytesToKeep;
}
}
return bytesRead;
}
finally
{
BufferPool.ReturnBuffer(buffer);
} }
} }
...@@ -259,7 +380,7 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -259,7 +380,7 @@ namespace Titanium.Web.Proxy.EventArguments
{ {
if (!WebSession.Request.IsBodyRead) if (!WebSession.Request.IsBodyRead)
{ {
await ReadRequestBody(); await ReadRequestBodyAsync();
} }
return WebSession.Request.Body; return WebSession.Request.Body;
...@@ -273,7 +394,7 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -273,7 +394,7 @@ namespace Titanium.Web.Proxy.EventArguments
{ {
if (!WebSession.Request.IsBodyRead) if (!WebSession.Request.IsBodyRead)
{ {
await ReadRequestBody(); await ReadRequestBodyAsync();
} }
return WebSession.Request.BodyString; return WebSession.Request.BodyString;
...@@ -293,7 +414,7 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -293,7 +414,7 @@ namespace Titanium.Web.Proxy.EventArguments
//syphon out the request body from client before setting the new body //syphon out the request body from client before setting the new body
if (!WebSession.Request.IsBodyRead) if (!WebSession.Request.IsBodyRead)
{ {
await ReadRequestBody(); await ReadRequestBodyAsync();
} }
WebSession.Request.Body = body; WebSession.Request.Body = body;
...@@ -314,7 +435,7 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -314,7 +435,7 @@ namespace Titanium.Web.Proxy.EventArguments
//syphon out the request body from client before setting the new body //syphon out the request body from client before setting the new body
if (!WebSession.Request.IsBodyRead) if (!WebSession.Request.IsBodyRead)
{ {
await ReadRequestBody(); await ReadRequestBodyAsync();
} }
await SetRequestBody(WebSession.Request.Encoding.GetBytes(body)); await SetRequestBody(WebSession.Request.Encoding.GetBytes(body));
...@@ -328,7 +449,7 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -328,7 +449,7 @@ namespace Titanium.Web.Proxy.EventArguments
{ {
if (!WebSession.Response.IsBodyRead) if (!WebSession.Response.IsBodyRead)
{ {
await ReadResponseBody(); await ReadResponseBodyAsync();
} }
return WebSession.Response.Body; return WebSession.Response.Body;
...@@ -342,7 +463,7 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -342,7 +463,7 @@ namespace Titanium.Web.Proxy.EventArguments
{ {
if (!WebSession.Response.IsBodyRead) if (!WebSession.Response.IsBodyRead)
{ {
await ReadResponseBody(); await ReadResponseBodyAsync();
} }
return WebSession.Response.BodyString; return WebSession.Response.BodyString;
...@@ -400,12 +521,12 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -400,12 +521,12 @@ namespace Titanium.Web.Proxy.EventArguments
await SetResponseBody(bodyBytes); await SetResponseBody(bodyBytes);
} }
private async Task<byte[]> GetDecompressedResponseBody(string encodingType, byte[] responseBodyStream) private async Task<byte[]> GetDecompressedBodyAsync(string encodingType, byte[] bodyStream)
{ {
var decompressionFactory = new DecompressionFactory(); var decompressionFactory = new DecompressionFactory();
var decompressor = decompressionFactory.Create(encodingType); var decompressor = decompressionFactory.Create(encodingType);
return await decompressor.Decompress(responseBodyStream, bufferSize); return await decompressor.Decompress(bodyStream, bufferSize);
} }
/// <summary> /// <summary>
...@@ -493,8 +614,8 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -493,8 +614,8 @@ namespace Titanium.Web.Proxy.EventArguments
{ {
var response = new RedirectResponse(); var response = new RedirectResponse();
response.HttpVersion = WebSession.Request.HttpVersion; response.HttpVersion = WebSession.Request.HttpVersion;
response.Headers.AddHeader("Location", url); response.Headers.AddHeader(KnownHeaders.Location, url);
response.Body = new byte[0]; response.Body = emptyData;
await Respond(response); await Respond(response);
} }
...@@ -527,6 +648,10 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -527,6 +648,10 @@ namespace Titanium.Web.Proxy.EventArguments
httpResponseHandler = null; httpResponseHandler = null;
CustomUpStreamProxyUsed = null; CustomUpStreamProxyUsed = null;
DataSent = null;
DataReceived = null;
MultipartRequestPartSent = null;
WebSession.FinishSession(); WebSession.FinishSession();
} }
} }
......
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,16 +18,33 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -18,16 +18,33 @@ 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) }
/// <summary>
/// Copy streams asynchronously
/// </summary>
/// <param name="input"></param>
/// <param name="output"></param>
/// <param name="onCopy"></param>
/// <param name="bufferSize"></param>
/// <param name="cancellationToken"></param>
internal static async Task CopyToAsync(this Stream input, Stream output, Action<byte[], int, int> onCopy, int bufferSize, CancellationToken cancellationToken)
{ {
int num = await input.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false); byte[] buffer = BufferPool.GetBuffer(bufferSize);
try
{
while (!cancellationToken.IsCancellationRequested)
{
// 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; int bytesRead;
if ((bytesRead = num) != 0) if ((bytesRead = num) != 0 && !cancellationToken.IsCancellationRequested)
{ {
await output.WriteAsync(buffer, 0, bytesRead).ConfigureAwait(false); await output.WriteAsync(buffer, 0, bytesRead, CancellationToken.None);
onCopy?.Invoke(buffer, 0, bytesRead); onCopy?.Invoke(buffer, 0, bytesRead);
} }
else else
...@@ -36,65 +53,24 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -36,65 +53,24 @@ namespace Titanium.Web.Proxy.Extensions
} }
} }
} }
finally
/// <summary>
/// copies the specified bytes to the stream from the input stream
/// </summary>
/// <param name="streamReader"></param>
/// <param name="stream"></param>
/// <param name="totalBytesToRead"></param>
/// <returns></returns>
internal static async Task CopyBytesToStream(this CustomBinaryReader streamReader, Stream stream, long totalBytesToRead)
{
var buffer = streamReader.Buffer;
long remainingBytes = totalBytesToRead;
while (remainingBytes > 0)
{
int bytesToRead = buffer.Length;
if (remainingBytes < bytesToRead)
{ {
bytesToRead = (int)remainingBytes; BufferPool.ReturnBuffer(buffer);
}
int bytesRead = await streamReader.ReadBytesAsync(buffer, bytesToRead);
if (bytesRead == 0)
{
break;
}
remainingBytes -= bytesRead;
await stream.WriteAsync(buffer, 0, bytesRead);
} }
} }
/// <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)
{ {
string chuchkHead = await clientStreamReader.ReadLineAsync(); var tcs = new TaskCompletionSource<bool>();
int chunkSize = int.Parse(chuchkHead, NumberStyles.HexNumber); using (cancellationToken.Register(s => ((TaskCompletionSource<bool>)s).TrySetResult(true), tcs))
if (chunkSize != 0)
{ {
await CopyBytesToStream(clientStreamReader, stream, chunkSize); if (task != await Task.WhenAny(task, tcs.Task))
//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)
{ {
foreach (var header in headers) int charCount = value.Length;
value.CopyTo(0, charBuffer, 0, charCount);
if (charCount < BufferSize)
{ {
header.WriteToStream(this); var buffer = BufferPool.GetBuffer(BufferSize);
try
{
int idx = encoder.GetBytes(charBuffer, 0, charCount, buffer, 0, true);
await WriteAsync(buffer, 0, idx);
} }
finally
{
BufferPool.ReturnBuffer(buffer);
} }
}
WriteLine(); else
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;
...@@ -80,26 +79,17 @@ namespace Titanium.Web.Proxy.Http ...@@ -80,26 +79,17 @@ namespace Titanium.Web.Proxy.Http
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
internal async Task SendRequest(bool enable100ContinueBehaviour) internal async Task SendRequest(bool enable100ContinueBehaviour)
{
var stream = ServerConnection.Stream;
byte[] requestBytes;
using (var ms = new MemoryStream())
using (var writer = new HttpRequestWriter(ms, bufferSize))
{ {
var upstreamProxy = ServerConnection.UpStreamProxy; var upstreamProxy = ServerConnection.UpStreamProxy;
bool useUpstreamProxy = upstreamProxy != null && ServerConnection.IsHttps == false; 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
...@@ -108,27 +98,20 @@ namespace Titanium.Web.Proxy.Http ...@@ -108,27 +98,20 @@ namespace Titanium.Web.Proxy.Http
&& !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 != "Proxy-Authorization") if (header.Name != KnownHeaders.ProxyAuthorization)
{ {
header.WriteToStream(writer); await header.WriteToStreamAsync(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,23 +69,20 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -68,23 +69,20 @@ 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("Connection: Keep-Alive"); await writer.WriteLineAsync($"{KnownHeaders.Connection}: {KnownHeaders.ConnectionKeepAlive}");
if (!string.IsNullOrEmpty(externalProxy.UserName) && externalProxy.Password != null) if (!string.IsNullOrEmpty(externalProxy.UserName) && externalProxy.Password != null)
{ {
await HttpHeader.ProxyConnectionKeepAlive.WriteToStreamAsync(writer); await HttpHeader.ProxyConnectionKeepAlive.WriteToStreamAsync(writer);
await writer.WriteLineAsync("Proxy-Authorization" + ": Basic " + await writer.WriteLineAsync(KnownHeaders.ProxyAuthorization + ": Basic " +
Convert.ToBase64String(Encoding.UTF8.GetBytes( Convert.ToBase64String(Encoding.UTF8.GetBytes(
externalProxy.UserName + ":" + externalProxy.Password))); externalProxy.UserName + ":" + externalProxy.Password)));
} }
await writer.WriteLineAsync(); await writer.WriteLineAsync();
await writer.FlushAsync();
}
using (var reader = new CustomBinaryReader(stream, server.BufferSize)) using (var reader = new CustomBinaryReader(stream, server.BufferSize))
{ {
...@@ -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
{
}
} }
} }
......
...@@ -49,7 +49,6 @@ namespace Titanium.Web.Proxy ...@@ -49,7 +49,6 @@ namespace Titanium.Web.Proxy
{ {
//read the first line HTTP command //read the first line HTTP command
string httpCmd = await clientStreamReader.ReadLineAsync(); string httpCmd = await clientStreamReader.ReadLineAsync();
if (string.IsNullOrEmpty(httpCmd)) if (string.IsNullOrEmpty(httpCmd))
{ {
return; return;
...@@ -86,20 +85,20 @@ namespace Titanium.Web.Proxy ...@@ -86,20 +85,20 @@ namespace Titanium.Web.Proxy
await HeaderParser.ReadHeaders(clientStreamReader, connectRequest.Headers); await HeaderParser.ReadHeaders(clientStreamReader, connectRequest.Headers);
var connectArgs = new TunnelConnectSessionEventArgs(BufferSize, endPoint, connectRequest); var connectArgs = new TunnelConnectSessionEventArgs(BufferSize, endPoint, connectRequest, ExceptionFunc);
connectArgs.ProxyClient.TcpClient = tcpClient; connectArgs.ProxyClient.TcpClient = tcpClient;
connectArgs.ProxyClient.ClientStream = clientStream; connectArgs.ProxyClient.ClientStream = clientStream;
if (TunnelConnectRequest != null) if (TunnelConnectRequest != null)
{ {
await TunnelConnectRequest.InvokeParallelAsync(this, connectArgs, ExceptionFunc); await TunnelConnectRequest.InvokeAsync(this, connectArgs, ExceptionFunc);
} }
if (await CheckAuthorization(clientStreamWriter, connectArgs) == false) if (await CheckAuthorization(clientStreamWriter, connectArgs) == false)
{ {
if (TunnelConnectResponse != null) if (TunnelConnectResponse != null)
{ {
await TunnelConnectResponse.InvokeParallelAsync(this, connectArgs, ExceptionFunc); await TunnelConnectResponse.InvokeAsync(this, connectArgs, ExceptionFunc);
} }
return; return;
...@@ -119,7 +118,7 @@ namespace Titanium.Web.Proxy ...@@ -119,7 +118,7 @@ namespace Titanium.Web.Proxy
if (TunnelConnectResponse != null) if (TunnelConnectResponse != null)
{ {
connectArgs.IsHttpsConnect = isClientHello; connectArgs.IsHttpsConnect = isClientHello;
await TunnelConnectResponse.InvokeParallelAsync(this, connectArgs, ExceptionFunc); await TunnelConnectResponse.InvokeAsync(this, connectArgs, ExceptionFunc);
} }
if (!excluded && isClientHello) if (!excluded && isClientHello)
...@@ -153,26 +152,42 @@ namespace Titanium.Web.Proxy ...@@ -153,26 +152,42 @@ namespace Titanium.Web.Proxy
return; return;
} }
if (await CanBeHttpMethod(clientStream))
{
//Now read the actual HTTPS request line //Now read the actual HTTPS request line
httpCmd = await clientStreamReader.ReadLineAsync(); httpCmd = await clientStreamReader.ReadLineAsync();
} }
//Hostname is excluded or it is not an HTTPS connect
else else
{
// It can be for example some Google (Cloude Messaging for Chrome) magic
excluded = true;
}
}
//Hostname is excluded or it is not an HTTPS connect
if (excluded || !isClientHello)
{ {
//create new connection //create new connection
using (var connection = await GetServerConnection(connectArgs, true)) using (var connection = await GetServerConnection(connectArgs, true))
{
try
{ {
if (isClientHello) if (isClientHello)
{ {
if (clientStream.Available > 0) int available = clientStream.Available;
if (available > 0)
{ {
//send the buffered data //send the buffered data
var data = new byte[clientStream.Available]; var data = BufferPool.GetBuffer(BufferSize);
await clientStream.ReadAsync(data, 0, data.Length);
await connection.Stream.WriteAsync(data, 0, data.Length); try
await connection.Stream.FlushAsync(); {
// clientStream.Available sbould be at most BufferSize because it is using the same buffer size
await clientStream.ReadAsync(data, 0, available);
await connection.StreamWriter.WriteAsync(data, 0, available, true);
}
finally
{
BufferPool.ReturnBuffer(data);
}
} }
var serverHelloInfo = await SslTools.PeekServerHello(connection.Stream); var serverHelloInfo = await SslTools.PeekServerHello(connection.Stream);
...@@ -180,12 +195,8 @@ namespace Titanium.Web.Proxy ...@@ -180,12 +195,8 @@ namespace Titanium.Web.Proxy
} }
await TcpHelper.SendRaw(clientStream, connection.Stream, BufferSize, await TcpHelper.SendRaw(clientStream, connection.Stream, BufferSize,
(buffer, offset, count) => { connectArgs.OnDataSent(buffer, offset, count); }, (buffer, offset, count) => { connectArgs.OnDataReceived(buffer, offset, count); }); (buffer, offset, count) => { connectArgs.OnDataSent(buffer, offset, count); },
} (buffer, offset, count) => { connectArgs.OnDataReceived(buffer, offset, count); });
finally
{
UpdateServerConnectionCount(false);
}
} }
return; return;
...@@ -196,8 +207,17 @@ namespace Titanium.Web.Proxy ...@@ -196,8 +207,17 @@ namespace Titanium.Web.Proxy
disposed = await HandleHttpSessionRequest(tcpClient, httpCmd, clientStream, clientStreamReader, clientStreamWriter, disposed = await HandleHttpSessionRequest(tcpClient, httpCmd, clientStream, clientStreamReader, clientStreamWriter,
httpRemoteUri.Scheme == UriSchemeHttps ? httpRemoteUri.Host : null, endPoint, connectRequest); httpRemoteUri.Scheme == UriSchemeHttps ? httpRemoteUri.Host : null, endPoint, connectRequest);
} }
catch (IOException e)
{
ExceptionFunc(new Exception("Connection was aborted", e));
}
catch (SocketException e)
{
ExceptionFunc(new Exception("Could not connect", e));
}
catch (Exception e) catch (Exception e)
{ {
// is this the correct error message?
ExceptionFunc(new Exception("Error whilst authorizing request", e)); ExceptionFunc(new Exception("Error whilst authorizing request", e));
} }
finally finally
...@@ -209,6 +229,34 @@ namespace Titanium.Web.Proxy ...@@ -209,6 +229,34 @@ namespace Titanium.Web.Proxy
} }
} }
private async Task<bool> CanBeHttpMethod(CustomBufferedStream clientStream)
{
int legthToCheck = 10;
for (int i = 0; i < legthToCheck; i++)
{
int b = await clientStream.PeekByteAsync(i);
if (b == -1)
{
return false;
}
if (b == ' ' && i > 2)
{
// at least 3 letters and a space
return true;
}
if (!char.IsLetter((char)b))
{
// non letter or too short
return false;
}
}
// only letters
return true;
}
/// <summary> /// <summary>
/// This is called when this proxy acts as a reverse proxy (like a real http server) /// This is called when this proxy acts as a reverse proxy (like a real http server)
/// So for HTTPS requests we would start SSL negotiation right away without expecting a CONNECT request from client /// So for HTTPS requests we would start SSL negotiation right away without expecting a CONNECT request from client
...@@ -228,18 +276,14 @@ namespace Titanium.Web.Proxy ...@@ -228,18 +276,14 @@ namespace Titanium.Web.Proxy
{ {
if (endPoint.EnableSsl) if (endPoint.EnableSsl)
{ {
var clientSslHelloInfo = await SslTools.PeekClientHello(clientStream); var clientHelloInfo = await SslTools.PeekClientHello(clientStream);
if (clientSslHelloInfo != null) if (clientHelloInfo != null)
{ {
var sslStream = new SslStream(clientStream); var sslStream = new SslStream(clientStream);
clientStream = new CustomBufferedStream(sslStream, BufferSize); clientStream = new CustomBufferedStream(sslStream, BufferSize);
string sniHostName = null; string sniHostName = clientHelloInfo.GetServerName();
if (clientSslHelloInfo.Extensions != null && clientSslHelloInfo.Extensions.TryGetValue("server_name", out var serverNameExtension))
{
sniHostName = serverNameExtension.Data;
}
string certName = HttpHelper.GetWildCardDomainName(sniHostName ?? endPoint.GenericCertificateName); string certName = HttpHelper.GetWildCardDomainName(sniHostName ?? endPoint.GenericCertificateName);
var certificate = CertificateManager.CreateCertificate(certName, false); var certificate = CertificateManager.CreateCertificate(certName, false);
...@@ -302,7 +346,7 @@ namespace Titanium.Web.Proxy ...@@ -302,7 +346,7 @@ namespace Titanium.Web.Proxy
break; break;
} }
var args = new SessionEventArgs(BufferSize, endPoint, HandleHttpSessionResponse) var args = new SessionEventArgs(BufferSize, endPoint, HandleHttpSessionResponse, ExceptionFunc)
{ {
ProxyClient = { TcpClient = client }, ProxyClient = { TcpClient = client },
WebSession = { ConnectRequest = connectRequest } WebSession = { ConnectRequest = connectRequest }
...@@ -349,7 +393,7 @@ namespace Titanium.Web.Proxy ...@@ -349,7 +393,7 @@ namespace Titanium.Web.Proxy
//If user requested interception do it //If user requested interception do it
if (BeforeRequest != null) if (BeforeRequest != null)
{ {
await BeforeRequest.InvokeParallelAsync(this, args, ExceptionFunc); await BeforeRequest.InvokeAsync(this, args, ExceptionFunc);
} }
if (args.WebSession.Request.CancelRequest) if (args.WebSession.Request.CancelRequest)
...@@ -366,7 +410,6 @@ namespace Titanium.Web.Proxy ...@@ -366,7 +410,6 @@ namespace Titanium.Web.Proxy
{ {
connection.Dispose(); connection.Dispose();
connection = null; connection = null;
UpdateServerConnectionCount(false);
} }
if (connection == null) if (connection == null)
...@@ -379,16 +422,8 @@ namespace Titanium.Web.Proxy ...@@ -379,16 +422,8 @@ namespace Titanium.Web.Proxy
{ {
//prepare the prefix content //prepare the prefix content
var requestHeaders = args.WebSession.Request.Headers; var requestHeaders = args.WebSession.Request.Headers;
byte[] requestBytes; await connection.StreamWriter.WriteLineAsync(httpCmd);
using (var ms = new MemoryStream()) await connection.StreamWriter.WriteHeadersAsync(requestHeaders);
using (var writer = new HttpRequestWriter(ms, BufferSize))
{
writer.WriteLine(httpCmd);
writer.WriteHeaders(requestHeaders);
requestBytes = ms.ToArray();
}
await connection.Stream.WriteAsync(requestBytes, 0, requestBytes.Length);
string httpStatus = await connection.StreamReader.ReadLineAsync(); string httpStatus = await connection.StreamReader.ReadLineAsync();
Response.ParseResponseLine(httpStatus, out var responseVersion, out int responseStatusCode, out string responseStatusDescription); Response.ParseResponseLine(httpStatus, out var responseVersion, out int responseStatusCode, out string responseStatusDescription);
...@@ -403,11 +438,12 @@ namespace Titanium.Web.Proxy ...@@ -403,11 +438,12 @@ namespace Titanium.Web.Proxy
//If user requested call back then do it //If user requested call back then do it
if (BeforeResponse != null && !args.WebSession.Response.ResponseLocked) if (BeforeResponse != null && !args.WebSession.Response.ResponseLocked)
{ {
await BeforeResponse.InvokeParallelAsync(this, args, ExceptionFunc); await BeforeResponse.InvokeAsync(this, args, ExceptionFunc);
} }
await TcpHelper.SendRaw(clientStream, connection.Stream, BufferSize, await TcpHelper.SendRaw(clientStream, connection.Stream, BufferSize,
(buffer, offset, count) => { args.OnDataSent(buffer, offset, count); }, (buffer, offset, count) => { args.OnDataReceived(buffer, offset, count); }); (buffer, offset, count) => { args.OnDataSent(buffer, offset, count); },
(buffer, offset, count) => { args.OnDataReceived(buffer, offset, count); });
args.Dispose(); args.Dispose();
break; break;
...@@ -478,15 +514,17 @@ namespace Titanium.Web.Proxy ...@@ -478,15 +514,17 @@ namespace Titanium.Web.Proxy
//If 100 continue was the response inform that to the client //If 100 continue was the response inform that to the client
if (Enable100ContinueBehaviour) if (Enable100ContinueBehaviour)
{ {
var clientStreamWriter = args.ProxyClient.ClientStreamWriter;
if (request.Is100Continue) if (request.Is100Continue)
{ {
await args.ProxyClient.ClientStreamWriter.WriteResponseStatusAsync(args.WebSession.Response.HttpVersion, (int)HttpStatusCode.Continue, "Continue"); await clientStreamWriter.WriteResponseStatusAsync(args.WebSession.Response.HttpVersion, (int)HttpStatusCode.Continue, "Continue");
await args.ProxyClient.ClientStreamWriter.WriteLineAsync(); await clientStreamWriter.WriteLineAsync();
} }
else if (request.ExpectationFailed) else if (request.ExpectationFailed)
{ {
await args.ProxyClient.ClientStreamWriter.WriteResponseStatusAsync(args.WebSession.Response.HttpVersion, (int)HttpStatusCode.ExpectationFailed, "Expectation Failed"); await clientStreamWriter.WriteResponseStatusAsync(args.WebSession.Response.HttpVersion, (int)HttpStatusCode.ExpectationFailed, "Expectation Failed");
await args.ProxyClient.ClientStreamWriter.WriteLineAsync(); await clientStreamWriter.WriteLineAsync();
} }
} }
...@@ -513,8 +551,7 @@ namespace Titanium.Web.Proxy ...@@ -513,8 +551,7 @@ namespace Titanium.Web.Proxy
//chunked send is not supported as of now //chunked send is not supported as of now
request.ContentLength = body.Length; request.ContentLength = body.Length;
var newStream = args.WebSession.ServerConnection.Stream; await args.WebSession.ServerConnection.StreamWriter.WriteAsync(body);
await newStream.WriteAsync(body, 0, body.Length);
} }
else else
{ {
...@@ -523,7 +560,8 @@ namespace Titanium.Web.Proxy ...@@ -523,7 +560,8 @@ namespace Titanium.Web.Proxy
//If its a post/put/patch request, then read the client html body and send it to server //If its a post/put/patch request, then read the client html body and send it to server
if (request.HasBody) if (request.HasBody)
{ {
await SendClientRequestBody(args); HttpWriter writer = args.WebSession.ServerConnection.StreamWriter;
await args.CopyRequestBodyAsync(writer, false);
} }
} }
} }
...@@ -609,7 +647,7 @@ namespace Titanium.Web.Proxy ...@@ -609,7 +647,7 @@ namespace Titanium.Web.Proxy
switch (header.Name.ToLower()) switch (header.Name.ToLower())
{ {
//these are the only encoding this proxy can read //these are the only encoding this proxy can read
case "accept-encoding": case KnownHeaders.AcceptEncoding:
header.Value = "gzip,deflate"; header.Value = "gzip,deflate";
break; break;
} }
...@@ -617,118 +655,5 @@ namespace Titanium.Web.Proxy ...@@ -617,118 +655,5 @@ namespace Titanium.Web.Proxy
requestHeaders.FixProxyHeaders(); requestHeaders.FixProxyHeaders();
} }
/// <summary>
/// This is called when the request is PUT/POST/PATCH to read the body
/// </summary>
/// <param name="args"></param>
/// <returns></returns>
private async Task SendClientRequestBody(SessionEventArgs args)
{
// End the operation
var postStream = args.WebSession.ServerConnection.Stream;
//send the request body bytes to server
if (args.WebSession.Request.ContentLength > 0)
{
var request = args.WebSession.Request;
if (args.HasMulipartEventSubscribers && request.IsMultipartFormData)
{
string boundary = HttpHelper.GetBoundaryFromContentType(request.ContentType);
long bytesToSend = args.WebSession.Request.ContentLength;
while (bytesToSend > 0)
{
long read = await SendUntilBoundaryAsync(args.ProxyClient.ClientStream, postStream, bytesToSend, boundary);
if (read == 0)
{
break;
}
bytesToSend -= read;
if (bytesToSend > 0)
{
var headers = new HeaderCollection();
var stream = new CustomBufferedPeekStream(args.ProxyClient.ClientStream);
await HeaderParser.ReadHeaders(new CustomBinaryReader(stream, BufferSize), headers);
args.OnMultipartRequestPartSent(boundary, headers);
}
}
}
else
{
await args.ProxyClient.ClientStreamReader.CopyBytesToStream(postStream, args.WebSession.Request.ContentLength);
}
}
//Need to revist, find any potential bugs
//send the request body bytes to server in chunks
else if (args.WebSession.Request.IsChunked)
{
await args.ProxyClient.ClientStreamReader.CopyBytesToStreamChunked(postStream);
}
}
/// <summary>
/// Read a line from the byte stream
/// </summary>
/// <returns></returns>
public async Task<long> SendUntilBoundaryAsync(CustomBufferedStream inputStream, CustomBufferedStream outputStream, long totalBytesToRead, string boundary)
{
int bufferDataLength = 0;
// try to use the thread static buffer
var buffer = BufferPool.GetBuffer(BufferSize);
int boundaryLength = boundary.Length + 4;
long bytesRead = 0;
while (bytesRead < totalBytesToRead && (inputStream.DataAvailable || await inputStream.FillBufferAsync()))
{
byte newChar = inputStream.ReadByteFromBuffer();
buffer[bufferDataLength] = newChar;
bufferDataLength++;
bytesRead++;
if (bufferDataLength >= boundaryLength)
{
int startIdx = bufferDataLength - boundaryLength;
if (buffer[startIdx] == '-' && buffer[startIdx + 1] == '-'
&& buffer[bufferDataLength - 2] == '\r' && buffer[bufferDataLength - 1] == '\n')
{
startIdx += 2;
bool ok = true;
for (int i = 0; i < boundary.Length; i++)
{
if (buffer[startIdx + i] != boundary[i])
{
ok = false;
break;
}
}
if (ok)
{
break;
}
}
}
if (bufferDataLength == buffer.Length)
{
//boundary is not longer than 70 bytes accounrding to the specification, so keeping the last 100 (minimum 74) bytes is enough
const int bytesToKeep = 100;
await outputStream.WriteAsync(buffer, 0, buffer.Length - bytesToKeep);
Buffer.BlockCopy(buffer, buffer.Length - bytesToKeep, buffer, 0, bytesToKeep);
bufferDataLength = bytesToKeep;
}
}
if (bytesRead > 0)
{
await outputStream.WriteAsync(buffer, 0, bufferDataLength);
}
return bytesRead;
}
} }
} }
...@@ -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