Commit a954c575 authored by Jehonathan's avatar Jehonathan Committed by GitHub

Merge pull request #83 from justcoding121/cleanup

Make proxy non-static object
parents 82326b01 36a721b4
using System; using System;
using System.Collections.Generic;
using System.Net; using System.Net;
using System.Text.RegularExpressions;
using System.Threading.Tasks; using System.Threading.Tasks;
using Titanium.Web.Proxy.EventArguments; using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Models; using Titanium.Web.Proxy.Models;
...@@ -10,12 +8,19 @@ namespace Titanium.Web.Proxy.Examples.Basic ...@@ -10,12 +8,19 @@ namespace Titanium.Web.Proxy.Examples.Basic
{ {
public class ProxyTestController public class ProxyTestController
{ {
private ProxyServer proxyServer;
public ProxyTestController()
{
proxyServer = new ProxyServer();
}
public void StartProxy() public void StartProxy()
{ {
ProxyServer.BeforeRequest += OnRequest; proxyServer.BeforeRequest += OnRequest;
ProxyServer.BeforeResponse += OnResponse; proxyServer.BeforeResponse += OnResponse;
ProxyServer.ServerCertificateValidationCallback += OnCertificateValidation; proxyServer.ServerCertificateValidationCallback += OnCertificateValidation;
ProxyServer.ClientCertificateSelectionCallback += OnCertificateSelection; proxyServer.ClientCertificateSelectionCallback += OnCertificateSelection;
//Exclude Https addresses you don't want to proxy //Exclude Https addresses you don't want to proxy
//Usefull for clients that use certificate pinning //Usefull for clients that use certificate pinning
...@@ -27,8 +32,8 @@ namespace Titanium.Web.Proxy.Examples.Basic ...@@ -27,8 +32,8 @@ namespace Titanium.Web.Proxy.Examples.Basic
//An explicit endpoint is where the client knows about the existance of a proxy //An explicit endpoint is where the client knows about the existance of a proxy
//So client sends request in a proxy friendly manner //So client sends request in a proxy friendly manner
ProxyServer.AddEndPoint(explicitEndPoint); proxyServer.AddEndPoint(explicitEndPoint);
ProxyServer.Start(); proxyServer.Start();
//Transparent endpoint is usefull for reverse proxying (client is not aware of the existance of proxy) //Transparent endpoint is usefull for reverse proxying (client is not aware of the existance of proxy)
...@@ -41,26 +46,28 @@ namespace Titanium.Web.Proxy.Examples.Basic ...@@ -41,26 +46,28 @@ namespace Titanium.Web.Proxy.Examples.Basic
{ {
GenericCertificateName = "google.com" GenericCertificateName = "google.com"
}; };
ProxyServer.AddEndPoint(transparentEndPoint); proxyServer.AddEndPoint(transparentEndPoint);
//ProxyServer.UpStreamHttpProxy = new ExternalProxy() { HostName = "localhost", Port = 8888 }; //ProxyServer.UpStreamHttpProxy = new ExternalProxy() { HostName = "localhost", Port = 8888 };
//ProxyServer.UpStreamHttpsProxy = new ExternalProxy() { HostName = "localhost", Port = 8888 }; //ProxyServer.UpStreamHttpsProxy = new ExternalProxy() { HostName = "localhost", Port = 8888 };
foreach (var endPoint in ProxyServer.ProxyEndPoints) foreach (var endPoint in proxyServer.ProxyEndPoints)
Console.WriteLine("Listening on '{0}' endpoint at Ip {1} and port: {2} ", Console.WriteLine("Listening on '{0}' endpoint at Ip {1} and port: {2} ",
endPoint.GetType().Name, endPoint.IpAddress, endPoint.Port); endPoint.GetType().Name, endPoint.IpAddress, endPoint.Port);
//Only explicit proxies can be set as system proxy! //Only explicit proxies can be set as system proxy!
ProxyServer.SetAsSystemHttpProxy(explicitEndPoint); proxyServer.SetAsSystemHttpProxy(explicitEndPoint);
ProxyServer.SetAsSystemHttpsProxy(explicitEndPoint); proxyServer.SetAsSystemHttpsProxy(explicitEndPoint);
} }
public void Stop() public void Stop()
{ {
ProxyServer.BeforeRequest -= OnRequest; proxyServer.BeforeRequest -= OnRequest;
ProxyServer.BeforeResponse -= OnResponse; proxyServer.BeforeResponse -= OnResponse;
proxyServer.ServerCertificateValidationCallback -= OnCertificateValidation;
proxyServer.ClientCertificateSelectionCallback -= OnCertificateSelection;
ProxyServer.Stop(); proxyServer.Stop();
} }
//intecept & cancel, redirect or update requests //intecept & cancel, redirect or update requests
......
...@@ -35,6 +35,8 @@ After installing nuget package mark following files to be copied to app director ...@@ -35,6 +35,8 @@ After installing nuget package mark following files to be copied to app director
Setup HTTP proxy: Setup HTTP proxy:
```csharp ```csharp
var ProxyServer = new ProxyServer();
ProxyServer.BeforeRequest += OnRequest; ProxyServer.BeforeRequest += OnRequest;
ProxyServer.BeforeResponse += OnResponse; ProxyServer.BeforeResponse += OnResponse;
ProxyServer.ServerCertificateValidationCallback += OnCertificateValidation; ProxyServer.ServerCertificateValidationCallback += OnCertificateValidation;
......
...@@ -17,7 +17,7 @@ namespace Titanium.Web.Proxy ...@@ -17,7 +17,7 @@ namespace Titanium.Web.Proxy
/// <param name="chain"></param> /// <param name="chain"></param>
/// <param name="sslPolicyErrors"></param> /// <param name="sslPolicyErrors"></param>
/// <returns></returns> /// <returns></returns>
internal static bool ValidateServerCertificate( internal bool ValidateServerCertificate(
object sender, object sender,
X509Certificate certificate, X509Certificate certificate,
X509Chain chain, X509Chain chain,
...@@ -62,7 +62,7 @@ namespace Titanium.Web.Proxy ...@@ -62,7 +62,7 @@ namespace Titanium.Web.Proxy
/// <param name="chain"></param> /// <param name="chain"></param>
/// <param name="sslPolicyErrors"></param> /// <param name="sslPolicyErrors"></param>
/// <returns></returns> /// <returns></returns>
internal static X509Certificate SelectClientCertificate( internal X509Certificate SelectClientCertificate(
object sender, object sender,
string targetHost, string targetHost,
X509CertificateCollection localCertificates, X509CertificateCollection localCertificates,
......
...@@ -8,7 +8,7 @@ namespace Titanium.Web.Proxy.Decompression ...@@ -8,7 +8,7 @@ namespace Titanium.Web.Proxy.Decompression
/// </summary> /// </summary>
internal class DefaultDecompression : IDecompression internal class DefaultDecompression : IDecompression
{ {
public Task<byte[]> Decompress(byte[] compressedArray) public Task<byte[]> Decompress(byte[] compressedArray, int bufferSize)
{ {
return Task.FromResult(compressedArray); return Task.FromResult(compressedArray);
} }
......
...@@ -10,13 +10,13 @@ namespace Titanium.Web.Proxy.Decompression ...@@ -10,13 +10,13 @@ namespace Titanium.Web.Proxy.Decompression
/// </summary> /// </summary>
internal class DeflateDecompression : IDecompression internal class DeflateDecompression : IDecompression
{ {
public async Task<byte[]> Decompress(byte[] compressedArray) public async Task<byte[]> Decompress(byte[] compressedArray, int bufferSize)
{ {
var stream = new MemoryStream(compressedArray); var stream = new MemoryStream(compressedArray);
using (var decompressor = new DeflateStream(stream, CompressionMode.Decompress)) using (var decompressor = new DeflateStream(stream, CompressionMode.Decompress))
{ {
var buffer = new byte[ProxyConstants.BUFFER_SIZE]; var buffer = new byte[bufferSize];
using (var output = new MemoryStream()) using (var output = new MemoryStream())
{ {
......
...@@ -10,11 +10,11 @@ namespace Titanium.Web.Proxy.Decompression ...@@ -10,11 +10,11 @@ namespace Titanium.Web.Proxy.Decompression
/// </summary> /// </summary>
internal class GZipDecompression : IDecompression internal class GZipDecompression : IDecompression
{ {
public async Task<byte[]> Decompress(byte[] compressedArray) public async Task<byte[]> Decompress(byte[] compressedArray, int bufferSize)
{ {
using (var decompressor = new GZipStream(new MemoryStream(compressedArray), CompressionMode.Decompress)) using (var decompressor = new GZipStream(new MemoryStream(compressedArray), CompressionMode.Decompress))
{ {
var buffer = new byte[ProxyConstants.BUFFER_SIZE]; var buffer = new byte[bufferSize];
using (var output = new MemoryStream()) using (var output = new MemoryStream())
{ {
int read; int read;
......
...@@ -7,6 +7,6 @@ namespace Titanium.Web.Proxy.Decompression ...@@ -7,6 +7,6 @@ namespace Titanium.Web.Proxy.Decompression
/// </summary> /// </summary>
internal interface IDecompression internal interface IDecompression
{ {
Task<byte[]> Decompress(byte[] compressedArray); Task<byte[]> Decompress(byte[] compressedArray, int bufferSize);
} }
} }
...@@ -10,12 +10,12 @@ namespace Titanium.Web.Proxy.Decompression ...@@ -10,12 +10,12 @@ namespace Titanium.Web.Proxy.Decompression
/// </summary> /// </summary>
internal class ZlibDecompression : IDecompression internal class ZlibDecompression : IDecompression
{ {
public async Task<byte[]> Decompress(byte[] compressedArray) public async Task<byte[]> Decompress(byte[] compressedArray, int bufferSize)
{ {
var memoryStream = new MemoryStream(compressedArray); var memoryStream = new MemoryStream(compressedArray);
using (var decompressor = new ZlibStream(memoryStream, CompressionMode.Decompress)) using (var decompressor = new ZlibStream(memoryStream, CompressionMode.Decompress))
{ {
var buffer = new byte[ProxyConstants.BUFFER_SIZE]; var buffer = new byte[bufferSize];
using (var output = new MemoryStream()) using (var output = new MemoryStream())
{ {
......
...@@ -20,14 +20,16 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -20,14 +20,16 @@ namespace Titanium.Web.Proxy.EventArguments
/// </summary> /// </summary>
public class SessionEventArgs : EventArgs, IDisposable public class SessionEventArgs : EventArgs, IDisposable
{ {
/// <summary> /// <summary>
/// Constructor to initialize the proxy /// Size of Buffers used by this object
/// </summary> /// </summary>
internal SessionEventArgs() private readonly int bufferSize;
{
ProxyClient = new ProxyClient(); /// <summary>
WebSession = new HttpWebClient(); /// Holds a reference to proxy response handler method
} /// </summary>
private readonly Func<SessionEventArgs, Task> httpResponseHandler;
/// <summary> /// <summary>
/// Holds a reference to client /// Holds a reference to client
...@@ -50,13 +52,18 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -50,13 +52,18 @@ namespace Titanium.Web.Proxy.EventArguments
/// <summary> /// <summary>
/// implement any cleanup here /// Constructor to initialize the proxy
/// </summary> /// </summary>
public void Dispose() internal SessionEventArgs(int bufferSize, Func<SessionEventArgs, Task> httpResponseHandler)
{ {
this.bufferSize = bufferSize;
this.httpResponseHandler = httpResponseHandler;
ProxyClient = new ProxyClient();
WebSession = new HttpWebClient();
} }
/// <summary> /// <summary>
/// Read request body content as bytes[] for current session /// Read request body content as bytes[] for current session
/// </summary> /// </summary>
...@@ -80,7 +87,7 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -80,7 +87,7 @@ namespace Titanium.Web.Proxy.EventArguments
//For chunked request we need to read data as they arrive, until we reach a chunk end symbol //For chunked request we need to read data as they arrive, until we reach a chunk end symbol
if (WebSession.Request.IsChunked) if (WebSession.Request.IsChunked)
{ {
await this.ProxyClient.ClientStreamReader.CopyBytesToStreamChunked(requestBodyStream); await this.ProxyClient.ClientStreamReader.CopyBytesToStreamChunked(bufferSize, requestBodyStream);
} }
else else
{ {
...@@ -88,11 +95,11 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -88,11 +95,11 @@ namespace Titanium.Web.Proxy.EventArguments
if (WebSession.Request.ContentLength > 0) if (WebSession.Request.ContentLength > 0)
{ {
//If not chunked then its easy just read the amount of bytes mentioned in content length header of response //If not chunked then its easy just read the amount of bytes mentioned in content length header of response
await this.ProxyClient.ClientStreamReader.CopyBytesToStream(requestBodyStream, WebSession.Request.ContentLength); await this.ProxyClient.ClientStreamReader.CopyBytesToStream(bufferSize, requestBodyStream, WebSession.Request.ContentLength);
} }
else if(WebSession.Request.HttpVersion.Major == 1 && WebSession.Request.HttpVersion.Minor == 0) else if(WebSession.Request.HttpVersion.Major == 1 && WebSession.Request.HttpVersion.Minor == 0)
await WebSession.ServerConnection.StreamReader.CopyBytesToStream(requestBodyStream, long.MaxValue); await WebSession.ServerConnection.StreamReader.CopyBytesToStream(bufferSize, requestBodyStream, long.MaxValue);
} }
WebSession.Request.RequestBody = await GetDecompressedResponseBody(WebSession.Request.ContentEncoding, requestBodyStream.ToArray()); WebSession.Request.RequestBody = await GetDecompressedResponseBody(WebSession.Request.ContentEncoding, requestBodyStream.ToArray());
} }
...@@ -117,18 +124,18 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -117,18 +124,18 @@ namespace Titanium.Web.Proxy.EventArguments
//If chuncked the read chunk by chunk until we hit chunk end symbol //If chuncked the read chunk by chunk until we hit chunk end symbol
if (WebSession.Response.IsChunked) if (WebSession.Response.IsChunked)
{ {
await WebSession.ServerConnection.StreamReader.CopyBytesToStreamChunked(responseBodyStream); await WebSession.ServerConnection.StreamReader.CopyBytesToStreamChunked(bufferSize, responseBodyStream);
} }
else else
{ {
if (WebSession.Response.ContentLength > 0) if (WebSession.Response.ContentLength > 0)
{ {
//If not chunked then its easy just read the amount of bytes mentioned in content length header of response //If not chunked then its easy just read the amount of bytes mentioned in content length header of response
await WebSession.ServerConnection.StreamReader.CopyBytesToStream(responseBodyStream, WebSession.Response.ContentLength); await WebSession.ServerConnection.StreamReader.CopyBytesToStream(bufferSize, responseBodyStream, WebSession.Response.ContentLength);
} }
else if(WebSession.Response.HttpVersion.Major == 1 && WebSession.Response.HttpVersion.Minor == 0) else if(WebSession.Response.HttpVersion.Major == 1 && WebSession.Response.HttpVersion.Minor == 0)
await WebSession.ServerConnection.StreamReader.CopyBytesToStream(responseBodyStream, long.MaxValue); await WebSession.ServerConnection.StreamReader.CopyBytesToStream(bufferSize, responseBodyStream, long.MaxValue);
} }
WebSession.Response.ResponseBody = await GetDecompressedResponseBody(WebSession.Response.ContentEncoding, responseBodyStream.ToArray()); WebSession.Response.ResponseBody = await GetDecompressedResponseBody(WebSession.Response.ContentEncoding, responseBodyStream.ToArray());
...@@ -285,7 +292,7 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -285,7 +292,7 @@ namespace Titanium.Web.Proxy.EventArguments
var decompressionFactory = new DecompressionFactory(); var decompressionFactory = new DecompressionFactory();
var decompressor = decompressionFactory.Create(encodingType); var decompressor = decompressionFactory.Create(encodingType);
return await decompressor.Decompress(responseBodyStream); return await decompressor.Decompress(responseBodyStream, bufferSize);
} }
...@@ -349,8 +356,15 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -349,8 +356,15 @@ namespace Titanium.Web.Proxy.EventArguments
WebSession.Response = response; WebSession.Response = response;
await ProxyServer.HandleHttpSessionResponse(this); await httpResponseHandler(this);
} }
/// <summary>
/// implement any cleanup here
/// </summary>
public void Dispose()
{
}
} }
} }
\ No newline at end of file
...@@ -35,22 +35,22 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -35,22 +35,22 @@ namespace Titanium.Web.Proxy.Extensions
/// <param name="stream"></param> /// <param name="stream"></param>
/// <param name="totalBytesToRead"></param> /// <param name="totalBytesToRead"></param>
/// <returns></returns> /// <returns></returns>
internal static async Task CopyBytesToStream(this CustomBinaryReader streamReader, Stream stream, long totalBytesToRead) internal static async Task CopyBytesToStream(this CustomBinaryReader streamReader, int bufferSize, Stream stream, long totalBytesToRead)
{ {
var totalbytesRead = 0; var totalbytesRead = 0;
long bytesToRead; long bytesToRead;
if (totalBytesToRead < ProxyConstants.BUFFER_SIZE) if (totalBytesToRead < bufferSize)
{ {
bytesToRead = totalBytesToRead; bytesToRead = totalBytesToRead;
} }
else else
bytesToRead = ProxyConstants.BUFFER_SIZE; bytesToRead = bufferSize;
while (totalbytesRead < totalBytesToRead) while (totalbytesRead < totalBytesToRead)
{ {
var buffer = await streamReader.ReadBytesAsync(bytesToRead); var buffer = await streamReader.ReadBytesAsync(bufferSize, bytesToRead);
if (buffer.Length == 0) if (buffer.Length == 0)
break; break;
...@@ -72,7 +72,7 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -72,7 +72,7 @@ namespace Titanium.Web.Proxy.Extensions
/// <param name="clientStreamReader"></param> /// <param name="clientStreamReader"></param>
/// <param name="stream"></param> /// <param name="stream"></param>
/// <returns></returns> /// <returns></returns>
internal static async Task CopyBytesToStreamChunked(this CustomBinaryReader clientStreamReader, Stream stream) internal static async Task CopyBytesToStreamChunked(this CustomBinaryReader clientStreamReader, int bufferSize, Stream stream)
{ {
while (true) while (true)
{ {
...@@ -81,7 +81,7 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -81,7 +81,7 @@ namespace Titanium.Web.Proxy.Extensions
if (chunkSize != 0) if (chunkSize != 0)
{ {
var buffer = await clientStreamReader.ReadBytesAsync(chunkSize); var buffer = await clientStreamReader.ReadBytesAsync(bufferSize, chunkSize);
await stream.WriteAsync(buffer, 0, buffer.Length); await stream.WriteAsync(buffer, 0, buffer.Length);
//chunk trail //chunk trail
await clientStreamReader.ReadLineAsync(); await clientStreamReader.ReadLineAsync();
...@@ -118,7 +118,7 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -118,7 +118,7 @@ namespace Titanium.Web.Proxy.Extensions
/// <param name="isChunked"></param> /// <param name="isChunked"></param>
/// <param name="ContentLength"></param> /// <param name="ContentLength"></param>
/// <returns></returns> /// <returns></returns>
internal static async Task WriteResponseBody(this CustomBinaryReader inStreamReader, Stream outStream, bool isChunked, long ContentLength) internal static async Task WriteResponseBody(this CustomBinaryReader inStreamReader, int bufferSize, Stream outStream, bool isChunked, long ContentLength)
{ {
if (!isChunked) if (!isChunked)
{ {
...@@ -126,12 +126,12 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -126,12 +126,12 @@ namespace Titanium.Web.Proxy.Extensions
if (ContentLength == -1) if (ContentLength == -1)
ContentLength = long.MaxValue; ContentLength = long.MaxValue;
int bytesToRead = ProxyConstants.BUFFER_SIZE; int bytesToRead = bufferSize;
if (ContentLength < ProxyConstants.BUFFER_SIZE) if (ContentLength < bufferSize)
bytesToRead = (int)ContentLength; bytesToRead = (int)ContentLength;
var buffer = new byte[ProxyConstants.BUFFER_SIZE]; var buffer = new byte[bufferSize];
var bytesRead = 0; var bytesRead = 0;
var totalBytesRead = 0; var totalBytesRead = 0;
...@@ -146,11 +146,11 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -146,11 +146,11 @@ namespace Titanium.Web.Proxy.Extensions
bytesRead = 0; bytesRead = 0;
var remainingBytes = (ContentLength - totalBytesRead); var remainingBytes = (ContentLength - totalBytesRead);
bytesToRead = remainingBytes > (long)ProxyConstants.BUFFER_SIZE ? ProxyConstants.BUFFER_SIZE : (int)remainingBytes; bytesToRead = remainingBytes > (long)bufferSize ? bufferSize : (int)remainingBytes;
} }
} }
else else
await WriteResponseBodyChunked(inStreamReader, outStream); await WriteResponseBodyChunked(inStreamReader, bufferSize, outStream);
} }
/// <summary> /// <summary>
...@@ -159,7 +159,7 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -159,7 +159,7 @@ namespace Titanium.Web.Proxy.Extensions
/// <param name="inStreamReader"></param> /// <param name="inStreamReader"></param>
/// <param name="outStream"></param> /// <param name="outStream"></param>
/// <returns></returns> /// <returns></returns>
internal static async Task WriteResponseBodyChunked(this CustomBinaryReader inStreamReader, Stream outStream) internal static async Task WriteResponseBodyChunked(this CustomBinaryReader inStreamReader, int bufferSize, Stream outStream)
{ {
while (true) while (true)
{ {
...@@ -168,7 +168,7 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -168,7 +168,7 @@ namespace Titanium.Web.Proxy.Extensions
if (chunkSize != 0) if (chunkSize != 0)
{ {
var buffer = await inStreamReader.ReadBytesAsync(chunkSize); var buffer = await inStreamReader.ReadBytesAsync(bufferSize, chunkSize);
var chunkHeadBytes = Encoding.ASCII.GetBytes(chunkSize.ToString("x2")); var chunkHeadBytes = Encoding.ASCII.GetBytes(chunkSize.ToString("x2"));
......
...@@ -91,14 +91,14 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -91,14 +91,14 @@ namespace Titanium.Web.Proxy.Helpers
/// </summary> /// </summary>
/// <param name="totalBytesToRead"></param> /// <param name="totalBytesToRead"></param>
/// <returns></returns> /// <returns></returns>
internal async Task<byte[]> ReadBytesAsync(long totalBytesToRead) internal async Task<byte[]> ReadBytesAsync(int bufferSize, long totalBytesToRead)
{ {
int bytesToRead = ProxyConstants.BUFFER_SIZE; int bytesToRead = bufferSize;
if (totalBytesToRead < ProxyConstants.BUFFER_SIZE) if (totalBytesToRead < bufferSize)
bytesToRead = (int)totalBytesToRead; bytesToRead = (int)totalBytesToRead;
var buffer = new byte[ProxyConstants.BUFFER_SIZE]; var buffer = new byte[bufferSize];
var bytesRead = 0; var bytesRead = 0;
var totalBytesRead = 0; var totalBytesRead = 0;
...@@ -115,7 +115,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -115,7 +115,7 @@ namespace Titanium.Web.Proxy.Helpers
bytesRead = 0; bytesRead = 0;
var remainingBytes = (totalBytesToRead - totalBytesRead); var remainingBytes = (totalBytesToRead - totalBytesRead);
bytesToRead = remainingBytes > (long)ProxyConstants.BUFFER_SIZE ? ProxyConstants.BUFFER_SIZE : (int)remainingBytes; bytesToRead = remainingBytes > (long)bufferSize ? bufferSize : (int)remainingBytes;
} }
return outStream.ToArray(); return outStream.ToArray();
......
...@@ -6,9 +6,9 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -6,9 +6,9 @@ namespace Titanium.Web.Proxy.Helpers
/// <summary> /// <summary>
/// A helper class to set proxy settings for firefox /// A helper class to set proxy settings for firefox
/// </summary> /// </summary>
public class FireFoxHelper public class FireFoxProxySettingsManager
{ {
public static void AddFirefox() public void AddFirefox()
{ {
try try
{ {
...@@ -38,7 +38,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -38,7 +38,7 @@ namespace Titanium.Web.Proxy.Helpers
} }
} }
public static void RemoveFirefox() public void RemoveFirefox()
{ {
try try
{ {
......
...@@ -11,7 +11,7 @@ using System.Linq; ...@@ -11,7 +11,7 @@ using System.Linq;
namespace Titanium.Web.Proxy.Helpers namespace Titanium.Web.Proxy.Helpers
{ {
internal static class NativeMethods internal class NativeMethods
{ {
[DllImport("wininet.dll")] [DllImport("wininet.dll")]
internal static extern bool InternetSetOption(IntPtr hInternet, int dwOption, IntPtr lpBuffer, internal static extern bool InternetSetOption(IntPtr hInternet, int dwOption, IntPtr lpBuffer,
...@@ -32,13 +32,15 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -32,13 +32,15 @@ namespace Titanium.Web.Proxy.Helpers
return "https=" + HostName + ":" + Port; return "https=" + HostName + ":" + Port;
} }
} }
/// <summary>
internal static class SystemProxyHelper /// Manage system proxy settings
/// </summary>
internal class SystemProxyManager
{ {
internal const int InternetOptionSettingsChanged = 39; internal const int InternetOptionSettingsChanged = 39;
internal const int InternetOptionRefresh = 37; internal const int InternetOptionRefresh = 37;
internal static void SetHttpProxy(string hostname, int port) internal void SetHttpProxy(string hostname, int port)
{ {
var reg = Registry.CurrentUser.OpenSubKey( var reg = Registry.CurrentUser.OpenSubKey(
"Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings", true); "Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings", true);
...@@ -66,7 +68,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -66,7 +68,7 @@ namespace Titanium.Web.Proxy.Helpers
/// <summary> /// <summary>
/// Remove the http proxy setting from current machine /// Remove the http proxy setting from current machine
/// </summary> /// </summary>
internal static void RemoveHttpProxy() internal void RemoveHttpProxy()
{ {
var reg = Registry.CurrentUser.OpenSubKey( var reg = Registry.CurrentUser.OpenSubKey(
"Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings", true); "Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings", true);
...@@ -100,7 +102,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -100,7 +102,7 @@ namespace Titanium.Web.Proxy.Helpers
/// </summary> /// </summary>
/// <param name="hostname"></param> /// <param name="hostname"></param>
/// <param name="port"></param> /// <param name="port"></param>
internal static void SetHttpsProxy(string hostname, int port) internal void SetHttpsProxy(string hostname, int port)
{ {
var reg = Registry.CurrentUser.OpenSubKey( var reg = Registry.CurrentUser.OpenSubKey(
"Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings", true); "Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings", true);
...@@ -130,7 +132,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -130,7 +132,7 @@ namespace Titanium.Web.Proxy.Helpers
/// <summary> /// <summary>
/// Removes the https proxy setting to nothing /// Removes the https proxy setting to nothing
/// </summary> /// </summary>
internal static void RemoveHttpsProxy() internal void RemoveHttpsProxy()
{ {
var reg = Registry.CurrentUser.OpenSubKey( var reg = Registry.CurrentUser.OpenSubKey(
"Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings", true); "Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings", true);
...@@ -163,7 +165,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -163,7 +165,7 @@ namespace Titanium.Web.Proxy.Helpers
/// <summary> /// <summary>
/// Removes all types of proxy settings (both http & https) /// Removes all types of proxy settings (both http & https)
/// </summary> /// </summary>
internal static void DisableAllProxy() internal void DisableAllProxy()
{ {
var reg = Registry.CurrentUser.OpenSubKey( var reg = Registry.CurrentUser.OpenSubKey(
"Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings", true); "Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings", true);
...@@ -182,7 +184,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -182,7 +184,7 @@ namespace Titanium.Web.Proxy.Helpers
/// </summary> /// </summary>
/// <param name="prevServerValue"></param> /// <param name="prevServerValue"></param>
/// <returns></returns> /// <returns></returns>
private static List<HttpSystemProxyValue> GetSystemProxyValues(string prevServerValue) private List<HttpSystemProxyValue> GetSystemProxyValues(string prevServerValue)
{ {
var result = new List<HttpSystemProxyValue>(); var result = new List<HttpSystemProxyValue>();
...@@ -215,7 +217,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -215,7 +217,7 @@ namespace Titanium.Web.Proxy.Helpers
/// </summary> /// </summary>
/// <param name="value"></param> /// <param name="value"></param>
/// <returns></returns> /// <returns></returns>
private static HttpSystemProxyValue parseProxyValue(string value) private HttpSystemProxyValue parseProxyValue(string value)
{ {
var tmp = Regex.Replace(value, @"\s+", " ").Trim().ToLower(); var tmp = Regex.Replace(value, @"\s+", " ").Trim().ToLower();
if (tmp.StartsWith("http=")) if (tmp.StartsWith("http="))
...@@ -245,7 +247,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -245,7 +247,7 @@ namespace Titanium.Web.Proxy.Helpers
/// Prepares the proxy server registry (create empty values if they don't exist) /// Prepares the proxy server registry (create empty values if they don't exist)
/// </summary> /// </summary>
/// <param name="reg"></param> /// <param name="reg"></param>
private static void prepareRegistry(RegistryKey reg) private void prepareRegistry(RegistryKey reg)
{ {
if (reg.GetValue("ProxyEnable") == null) if (reg.GetValue("ProxyEnable") == null)
{ {
...@@ -262,7 +264,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -262,7 +264,7 @@ namespace Titanium.Web.Proxy.Helpers
/// <summary> /// <summary>
/// Refresh the settings so that the system know about a change in proxy setting /// Refresh the settings so that the system know about a change in proxy setting
/// </summary> /// </summary>
private static void Refresh() private void Refresh()
{ {
NativeMethods.InternetSetOption(IntPtr.Zero, InternetOptionSettingsChanged, IntPtr.Zero, 0); NativeMethods.InternetSetOption(IntPtr.Zero, InternetOptionSettingsChanged, IntPtr.Zero, 0);
NativeMethods.InternetSetOption(IntPtr.Zero, InternetOptionRefresh, IntPtr.Zero, 0); NativeMethods.InternetSetOption(IntPtr.Zero, InternetOptionRefresh, IntPtr.Zero, 0);
......
...@@ -4,6 +4,7 @@ using System.IO; ...@@ -4,6 +4,7 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Net.Security; using System.Net.Security;
using System.Net.Sockets; using System.Net.Sockets;
using System.Security.Authentication;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Titanium.Web.Proxy.Extensions; using Titanium.Web.Proxy.Extensions;
...@@ -26,8 +27,8 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -26,8 +27,8 @@ namespace Titanium.Web.Proxy.Helpers
/// <param name="tunnelPort"></param> /// <param name="tunnelPort"></param>
/// <param name="isHttps"></param> /// <param name="isHttps"></param>
/// <returns></returns> /// <returns></returns>
internal async static Task SendRaw(Stream clientStream, string httpCmd, List<HttpHeader> requestHeaders, string hostName, internal static async Task SendRaw(Stream clientStream, string httpCmd, List<HttpHeader> requestHeaders, string hostName,
int tunnelPort, bool isHttps) int tunnelPort, bool isHttps, SslProtocols supportedProtocols)
{ {
//prepare the prefix content //prepare the prefix content
StringBuilder sb = null; StringBuilder sb = null;
...@@ -63,7 +64,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -63,7 +64,7 @@ namespace Titanium.Web.Proxy.Helpers
try try
{ {
sslStream = new SslStream(tunnelStream); sslStream = new SslStream(tunnelStream);
await sslStream.AuthenticateAsClientAsync(hostName, null, ProxyConstants.SupportedSslProtocols, false); await sslStream.AuthenticateAsClientAsync(hostName, null, supportedProtocols, false);
tunnelStream = sslStream; tunnelStream = sslStream;
} }
catch catch
......
...@@ -17,7 +17,7 @@ namespace Titanium.Web.Proxy.Http ...@@ -17,7 +17,7 @@ namespace Titanium.Web.Proxy.Http
/// <summary> /// <summary>
/// Connection to server /// Connection to server
/// </summary> /// </summary>
internal TcpConnection ServerConnection { get; set; } internal TcpConnectionCache ServerConnection { get; set; }
public Request Request { get; set; } public Request Request { get; set; }
public Response Response { get; set; } public Response Response { get; set; }
...@@ -37,7 +37,7 @@ namespace Titanium.Web.Proxy.Http ...@@ -37,7 +37,7 @@ namespace Titanium.Web.Proxy.Http
/// Set the tcp connection to server used by this webclient /// Set the tcp connection to server used by this webclient
/// </summary> /// </summary>
/// <param name="Connection"></param> /// <param name="Connection"></param>
internal void SetConnection(TcpConnection Connection) internal void SetConnection(TcpConnectionCache Connection)
{ {
Connection.LastAccess = DateTime.Now; Connection.LastAccess = DateTime.Now;
ServerConnection = Connection; ServerConnection = Connection;
...@@ -53,7 +53,7 @@ namespace Titanium.Web.Proxy.Http ...@@ -53,7 +53,7 @@ namespace Titanium.Web.Proxy.Http
/// Prepare & send the http(s) request /// Prepare & send the http(s) request
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
internal async Task SendRequest() internal async Task SendRequest(bool enable100ContinueBehaviour)
{ {
Stream stream = ServerConnection.Stream; Stream stream = ServerConnection.Stream;
...@@ -79,7 +79,7 @@ namespace Titanium.Web.Proxy.Http ...@@ -79,7 +79,7 @@ namespace Titanium.Web.Proxy.Http
await stream.WriteAsync(requestBytes, 0, requestBytes.Length); await stream.WriteAsync(requestBytes, 0, requestBytes.Length);
await stream.FlushAsync(); await stream.FlushAsync();
if (ProxyServer.Enable100ContinueBehaviour) if (enable100ContinueBehaviour)
if (this.Request.ExpectContinue) if (this.Request.ExpectContinue)
{ {
var httpResult = (await ServerConnection.StreamReader.ReadLineAsync()).Split(ProxyConstants.SpaceSplit, 3); var httpResult = (await ServerConnection.StreamReader.ReadLineAsync()).Split(ProxyConstants.SpaceSplit, 3);
...@@ -133,18 +133,22 @@ namespace Titanium.Web.Proxy.Http ...@@ -133,18 +133,22 @@ namespace Titanium.Web.Proxy.Http
if (this.Response.ResponseStatusCode.Equals("100") if (this.Response.ResponseStatusCode.Equals("100")
&& this.Response.ResponseStatusDescription.ToLower().Equals("continue")) && this.Response.ResponseStatusDescription.ToLower().Equals("continue"))
{ {
//Read the next line after 100-continue
this.Response.Is100Continue = true; this.Response.Is100Continue = true;
this.Response.ResponseStatusCode = null; this.Response.ResponseStatusCode = null;
await ServerConnection.StreamReader.ReadLineAsync(); await ServerConnection.StreamReader.ReadLineAsync();
//now receive response
await ReceiveResponse(); await ReceiveResponse();
return; return;
} }
else if (this.Response.ResponseStatusCode.Equals("417") else if (this.Response.ResponseStatusCode.Equals("417")
&& this.Response.ResponseStatusDescription.ToLower().Equals("expectation failed")) && this.Response.ResponseStatusDescription.ToLower().Equals("expectation failed"))
{ {
//read next line after expectation failed response
this.Response.ExpectationFailed = true; this.Response.ExpectationFailed = true;
this.Response.ResponseStatusCode = null; this.Response.ResponseStatusCode = null;
await ServerConnection.StreamReader.ReadLineAsync(); await ServerConnection.StreamReader.ReadLineAsync();
//now receive response
await ReceiveResponse(); await ReceiveResponse();
return; return;
} }
......
...@@ -18,8 +18,15 @@ namespace Titanium.Web.Proxy.Network ...@@ -18,8 +18,15 @@ namespace Titanium.Web.Proxy.Network
private const string CertCreateFormat = private const string CertCreateFormat =
"-ss {0} -n \"CN={1}, O={2}\" -sky {3} -cy {4} -m 120 -a sha256 -eku 1.3.6.1.5.5.7.3.1 {5}"; "-ss {0} -n \"CN={1}, O={2}\" -sky {3} -cy {4} -m 120 -a sha256 -eku 1.3.6.1.5.5.7.3.1 {5}";
/// <summary>
/// Cache dictionary
/// </summary>
private readonly IDictionary<string, CachedCertificate> certificateCache; private readonly IDictionary<string, CachedCertificate> certificateCache;
private static SemaphoreSlim semaphoreLock = new SemaphoreSlim(1);
/// <summary>
/// A lock to manage concurrency
/// </summary>
private SemaphoreSlim semaphoreLock = new SemaphoreSlim(1);
internal string Issuer { get; private set; } internal string Issuer { get; private set; }
internal string RootCertificateName { get; private set; } internal string RootCertificateName { get; private set; }
...@@ -241,7 +248,7 @@ namespace Titanium.Web.Proxy.Network ...@@ -241,7 +248,7 @@ namespace Titanium.Web.Proxy.Network
return certCreatArgs; return certCreatArgs;
} }
private static bool clearCertificates { get; set; } private bool clearCertificates { get; set; }
/// <summary> /// <summary>
/// Stops the certificate cache clear process /// Stops the certificate cache clear process
...@@ -254,7 +261,7 @@ namespace Titanium.Web.Proxy.Network ...@@ -254,7 +261,7 @@ namespace Titanium.Web.Proxy.Network
/// <summary> /// <summary>
/// A method to clear outdated certificates /// A method to clear outdated certificates
/// </summary> /// </summary>
internal async void ClearIdleCertificates() internal async void ClearIdleCertificates(int certificateCacheTimeOutMinutes)
{ {
clearCertificates = true; clearCertificates = true;
while (clearCertificates) while (clearCertificates)
...@@ -263,7 +270,7 @@ namespace Titanium.Web.Proxy.Network ...@@ -263,7 +270,7 @@ namespace Titanium.Web.Proxy.Network
try try
{ {
var cutOff = DateTime.Now.AddMinutes(-1 * ProxyServer.CertificateCacheTimeOutMinutes); var cutOff = DateTime.Now.AddMinutes(-1 * certificateCacheTimeOutMinutes);
var outdated = certificateCache var outdated = certificateCache
.Where(x => x.Value.LastAccess < cutOff) .Where(x => x.Value.LastAccess < cutOff)
......
...@@ -8,7 +8,7 @@ namespace Titanium.Web.Proxy.Network ...@@ -8,7 +8,7 @@ namespace Titanium.Web.Proxy.Network
/// <summary> /// <summary>
/// An object that holds TcpConnection to a particular server & port /// An object that holds TcpConnection to a particular server & port
/// </summary> /// </summary>
public class TcpConnection public class TcpConnectionCache
{ {
internal string HostName { get; set; } internal string HostName { get; set; }
internal int port { get; set; } internal int port { get; set; }
...@@ -37,7 +37,7 @@ namespace Titanium.Web.Proxy.Network ...@@ -37,7 +37,7 @@ namespace Titanium.Web.Proxy.Network
/// </summary> /// </summary>
internal DateTime LastAccess { get; set; } internal DateTime LastAccess { get; set; }
internal TcpConnection() internal TcpConnectionCache()
{ {
LastAccess = DateTime.Now; LastAccess = DateTime.Now;
} }
......
...@@ -10,23 +10,25 @@ using Titanium.Web.Proxy.Helpers; ...@@ -10,23 +10,25 @@ using Titanium.Web.Proxy.Helpers;
using System.Threading; using System.Threading;
using Titanium.Web.Proxy.Extensions; using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Shared; using Titanium.Web.Proxy.Shared;
using Titanium.Web.Proxy.Models;
using System.Security.Authentication;
namespace Titanium.Web.Proxy.Network namespace Titanium.Web.Proxy.Network
{ {
/// <summary> /// <summary>
/// A class that manages Tcp Connection to server used by this proxy server /// A class that manages Tcp Connection to server used by this proxy server
/// </summary> /// </summary>
internal class TcpConnectionManager internal class TcpConnectionCacheManager
{ {
/// <summary> /// <summary>
/// Connection cache /// Connection cache
/// </summary> /// </summary>
static Dictionary<string, List<TcpConnection>> connectionCache = new Dictionary<string, List<TcpConnection>>(); Dictionary<string, List<TcpConnectionCache>> connectionCache = new Dictionary<string, List<TcpConnectionCache>>();
/// <summary> /// <summary>
/// A lock to manage concurrency /// A lock to manage concurrency
/// </summary> /// </summary>
static SemaphoreSlim connectionAccessLock = new SemaphoreSlim(1); SemaphoreSlim connectionAccessLock = new SemaphoreSlim(1);
/// <summary> /// <summary>
/// Get a TcpConnection to the specified host, port optionally HTTPS and a particular HTTP version /// Get a TcpConnection to the specified host, port optionally HTTPS and a particular HTTP version
...@@ -36,10 +38,12 @@ namespace Titanium.Web.Proxy.Network ...@@ -36,10 +38,12 @@ namespace Titanium.Web.Proxy.Network
/// <param name="isHttps"></param> /// <param name="isHttps"></param>
/// <param name="version"></param> /// <param name="version"></param>
/// <returns></returns> /// <returns></returns>
internal static async Task<TcpConnection> GetClient(string hostname, int port, bool isHttps, Version version) internal async Task<TcpConnectionCache> GetClient(string hostname, int port, bool isHttps, Version version,
ExternalProxy upStreamHttpProxy, ExternalProxy upStreamHttpsProxy, int bufferSize, SslProtocols supportedSslProtocols,
RemoteCertificateValidationCallback remoteCertificateValidationCallBack, LocalCertificateSelectionCallback localCertificateSelectionCallback)
{ {
List<TcpConnection> cachedConnections = null; List<TcpConnectionCache> cachedConnections = null;
TcpConnection cached = null; TcpConnectionCache cached = null;
//Get a unique string to identify this connection //Get a unique string to identify this connection
var key = GetConnectionKey(hostname, port, isHttps, version); var key = GetConnectionKey(hostname, port, isHttps, version);
...@@ -77,11 +81,13 @@ namespace Titanium.Web.Proxy.Network ...@@ -77,11 +81,13 @@ namespace Titanium.Web.Proxy.Network
if (cached == null) if (cached == null)
cached = await CreateClient(hostname, port, isHttps, version); cached = await CreateClient(hostname, port, isHttps, version, upStreamHttpProxy, upStreamHttpsProxy, bufferSize, supportedSslProtocols,
remoteCertificateValidationCallBack, localCertificateSelectionCallback);
if (cachedConnections == null || cachedConnections.Count() <= 2) if (cachedConnections == null || cachedConnections.Count() <= 2)
{ {
var task = CreateClient(hostname, port, isHttps, version) var task = CreateClient(hostname, port, isHttps, version, upStreamHttpProxy, upStreamHttpsProxy, bufferSize, supportedSslProtocols,
remoteCertificateValidationCallBack, localCertificateSelectionCallback)
.ContinueWith(async (x) => { if (x.Status == TaskStatus.RanToCompletion) await ReleaseClient(x.Result); }); .ContinueWith(async (x) => { if (x.Status == TaskStatus.RanToCompletion) await ReleaseClient(x.Result); });
} }
...@@ -96,7 +102,7 @@ namespace Titanium.Web.Proxy.Network ...@@ -96,7 +102,7 @@ namespace Titanium.Web.Proxy.Network
/// <param name="isHttps"></param> /// <param name="isHttps"></param>
/// <param name="version"></param> /// <param name="version"></param>
/// <returns></returns> /// <returns></returns>
internal static string GetConnectionKey(string hostname, int port, bool isHttps, Version version) internal string GetConnectionKey(string hostname, int port, bool isHttps, Version version)
{ {
return string.Format("{0}:{1}:{2}:{3}:{4}", hostname.ToLower(), port, isHttps, version.Major, version.Minor); return string.Format("{0}:{1}:{2}:{3}:{4}", hostname.ToLower(), port, isHttps, version.Major, version.Minor);
} }
...@@ -109,7 +115,9 @@ namespace Titanium.Web.Proxy.Network ...@@ -109,7 +115,9 @@ namespace Titanium.Web.Proxy.Network
/// <param name="isHttps"></param> /// <param name="isHttps"></param>
/// <param name="version"></param> /// <param name="version"></param>
/// <returns></returns> /// <returns></returns>
private static async Task<TcpConnection> CreateClient(string hostname, int port, bool isHttps, Version version) private async Task<TcpConnectionCache> CreateClient(string hostname, int port, bool isHttps, Version version,
ExternalProxy upStreamHttpProxy, ExternalProxy upStreamHttpsProxy, int bufferSize, SslProtocols supportedSslProtocols,
RemoteCertificateValidationCallback remoteCertificateValidationCallBack, LocalCertificateSelectionCallback localCertificateSelectionCallback)
{ {
TcpClient client; TcpClient client;
Stream stream; Stream stream;
...@@ -119,12 +127,12 @@ namespace Titanium.Web.Proxy.Network ...@@ -119,12 +127,12 @@ namespace Titanium.Web.Proxy.Network
SslStream sslStream = null; SslStream sslStream = null;
//If this proxy uses another external proxy then create a tunnel request for HTTPS connections //If this proxy uses another external proxy then create a tunnel request for HTTPS connections
if (ProxyServer.UpStreamHttpsProxy != null) if (upStreamHttpsProxy != null)
{ {
client = new TcpClient(ProxyServer.UpStreamHttpsProxy.HostName, ProxyServer.UpStreamHttpsProxy.Port); client = new TcpClient(upStreamHttpsProxy.HostName, upStreamHttpsProxy.Port);
stream = (Stream)client.GetStream(); stream = (Stream)client.GetStream();
using (var writer = new StreamWriter(stream, Encoding.ASCII, ProxyConstants.BUFFER_SIZE, true)) using (var writer = new StreamWriter(stream, Encoding.ASCII, bufferSize, true))
{ {
await writer.WriteLineAsync(string.Format("CONNECT {0}:{1} {2}", hostname, port, version)); await writer.WriteLineAsync(string.Format("CONNECT {0}:{1} {2}", hostname, port, version));
await writer.WriteLineAsync(string.Format("Host: {0}:{1}", hostname, port)); await writer.WriteLineAsync(string.Format("Host: {0}:{1}", hostname, port));
...@@ -152,9 +160,9 @@ namespace Titanium.Web.Proxy.Network ...@@ -152,9 +160,9 @@ namespace Titanium.Web.Proxy.Network
try try
{ {
sslStream = new SslStream(stream, true, new RemoteCertificateValidationCallback(ProxyServer.ValidateServerCertificate), sslStream = new SslStream(stream, true, remoteCertificateValidationCallBack,
new LocalCertificateSelectionCallback(ProxyServer.SelectClientCertificate)); localCertificateSelectionCallback);
await sslStream.AuthenticateAsClientAsync(hostname, null, ProxyConstants.SupportedSslProtocols, false); await sslStream.AuthenticateAsClientAsync(hostname, null, supportedSslProtocols, false);
stream = (Stream)sslStream; stream = (Stream)sslStream;
} }
catch catch
...@@ -166,9 +174,9 @@ namespace Titanium.Web.Proxy.Network ...@@ -166,9 +174,9 @@ namespace Titanium.Web.Proxy.Network
} }
else else
{ {
if (ProxyServer.UpStreamHttpProxy != null) if (upStreamHttpProxy != null)
{ {
client = new TcpClient(ProxyServer.UpStreamHttpProxy.HostName, ProxyServer.UpStreamHttpProxy.Port); client = new TcpClient(upStreamHttpProxy.HostName, upStreamHttpProxy.Port);
stream = (Stream)client.GetStream(); stream = (Stream)client.GetStream();
} }
else else
...@@ -178,7 +186,7 @@ namespace Titanium.Web.Proxy.Network ...@@ -178,7 +186,7 @@ namespace Titanium.Web.Proxy.Network
} }
} }
return new TcpConnection() return new TcpConnectionCache()
{ {
HostName = hostname, HostName = hostname,
port = port, port = port,
...@@ -195,7 +203,7 @@ namespace Titanium.Web.Proxy.Network ...@@ -195,7 +203,7 @@ namespace Titanium.Web.Proxy.Network
/// </summary> /// </summary>
/// <param name="connection"></param> /// <param name="connection"></param>
/// <returns></returns> /// <returns></returns>
internal static async Task ReleaseClient(TcpConnection connection) internal async Task ReleaseClient(TcpConnectionCache connection)
{ {
connection.LastAccess = DateTime.Now; connection.LastAccess = DateTime.Now;
...@@ -203,25 +211,25 @@ namespace Titanium.Web.Proxy.Network ...@@ -203,25 +211,25 @@ namespace Titanium.Web.Proxy.Network
await connectionAccessLock.WaitAsync(); await connectionAccessLock.WaitAsync();
try try
{ {
List<TcpConnection> cachedConnections; List<TcpConnectionCache> cachedConnections;
connectionCache.TryGetValue(key, out cachedConnections); connectionCache.TryGetValue(key, out cachedConnections);
if (cachedConnections != null) if (cachedConnections != null)
cachedConnections.Add(connection); cachedConnections.Add(connection);
else else
connectionCache.Add(key, new List<TcpConnection>() { connection }); connectionCache.Add(key, new List<TcpConnectionCache>() { connection });
} }
finally { connectionAccessLock.Release(); } finally { connectionAccessLock.Release(); }
} }
private static bool clearConenctions { get; set; } private bool clearConenctions { get; set; }
/// <summary> /// <summary>
/// Stop clearing idle connections /// Stop clearing idle connections
/// </summary> /// </summary>
internal static void StopClearIdleConnections() internal void StopClearIdleConnections()
{ {
clearConenctions = false; clearConenctions = false;
} }
...@@ -229,7 +237,7 @@ namespace Titanium.Web.Proxy.Network ...@@ -229,7 +237,7 @@ namespace Titanium.Web.Proxy.Network
/// <summary> /// <summary>
/// A method to clear idle connections /// A method to clear idle connections
/// </summary> /// </summary>
internal async static void ClearIdleConnections() internal async void ClearIdleConnections(int connectionCacheTimeOutMinutes)
{ {
clearConenctions = true; clearConenctions = true;
while (clearConenctions) while (clearConenctions)
...@@ -237,7 +245,7 @@ namespace Titanium.Web.Proxy.Network ...@@ -237,7 +245,7 @@ namespace Titanium.Web.Proxy.Network
await connectionAccessLock.WaitAsync(); await connectionAccessLock.WaitAsync();
try try
{ {
var cutOff = DateTime.Now.AddMinutes(-1 * ProxyServer.ConnectionCacheTimeOutMinutes); var cutOff = DateTime.Now.AddMinutes(-1 * connectionCacheTimeOutMinutes);
connectionCache connectionCache
.SelectMany(x => x.Value) .SelectMany(x => x.Value)
......
...@@ -8,126 +8,144 @@ using Titanium.Web.Proxy.Helpers; ...@@ -8,126 +8,144 @@ using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Models; using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Network; using Titanium.Web.Proxy.Network;
using System.Linq; using System.Linq;
using System.Security.Cryptography.X509Certificates; using System.Security.Authentication;
using System.Net.Security;
namespace Titanium.Web.Proxy namespace Titanium.Web.Proxy
{ {
/// <summary> /// <summary>
/// Proxy Server Main class /// Proxy Server Main class
/// </summary> /// </summary>
public partial class ProxyServer public partial class ProxyServer: IDisposable
{ {
static ProxyServer() /// <summary>
{ /// Manages certificates used by this proxy
ProxyEndPoints = new List<ProxyEndPoint>(); /// </summary>
private CertificateManager certificateCacheManager { get; set; }
//default values /// <summary>
ConnectionCacheTimeOutMinutes = 3; /// A object that manages tcp connection cache to server
CertificateCacheTimeOutMinutes = 60; /// </summary>
} private TcpConnectionCacheManager tcpConnectionCacheManager { get; set; }
/// <summary> /// <summary>
/// Manages certificates used by this proxy /// Manage system proxy settings
/// </summary> /// </summary>
private static CertificateManager CertManager { get; set; } private SystemProxyManager systemProxySettingsManager { get; set; }
private FireFoxProxySettingsManager firefoxProxySettingsManager { get; set; }
/// <summary> /// <summary>
/// Does the root certificate used by this proxy is trusted by the machine? /// Does the root certificate used by this proxy is trusted by the machine?
/// </summary> /// </summary>
private static bool certTrusted { get; set; } private bool certTrusted { get; set; }
/// <summary> /// <summary>
/// Is the proxy currently running /// Is the proxy currently running
/// </summary> /// </summary>
private static bool proxyRunning { get; set; } private bool proxyRunning { get; set; }
/// <summary>
/// Buffer size used throughout this proxy
/// </summary>
public int BUFFER_SIZE { get; set; } = 8192;
/// <summary> /// <summary>
/// Name of the root certificate issuer /// Name of the root certificate issuer
/// </summary> /// </summary>
public static string RootCertificateIssuerName { get; set; } public string RootCertificateIssuerName { get; set; }
/// <summary> /// <summary>
/// Name of the root certificate /// Name of the root certificate
/// </summary> /// </summary>
public static string RootCertificateName { get; set; } public string RootCertificateName { get; set; }
/// <summary> /// <summary>
/// Does this proxy uses the HTTP protocol 100 continue behaviour strictly? /// Does this proxy uses the HTTP protocol 100 continue behaviour strictly?
/// Broken 100 contunue implementations on server/client may cause problems if enabled /// Broken 100 contunue implementations on server/client may cause problems if enabled
/// </summary> /// </summary>
public static bool Enable100ContinueBehaviour { get; set; } public bool Enable100ContinueBehaviour { get; set; }
/// <summary> /// <summary>
/// Minutes TCP connection cache to servers to be kept alive when in idle state /// Minutes TCP connection cache to servers to be kept alive when in idle state
/// </summary> /// </summary>
public static int ConnectionCacheTimeOutMinutes { get; set; } public int ConnectionCacheTimeOutMinutes { get; set; }
/// <summary> /// <summary>
/// Minutes certificates should be kept in cache when not used /// Minutes certificates should be kept in cache when not used
/// </summary> /// </summary>
public static int CertificateCacheTimeOutMinutes { get; set; } public int CertificateCacheTimeOutMinutes { get; set; }
/// <summary> /// <summary>
/// Intercept request to server /// Intercept request to server
/// </summary> /// </summary>
public static event Func<object, SessionEventArgs, Task> BeforeRequest; public event Func<object, SessionEventArgs, Task> BeforeRequest;
/// <summary> /// <summary>
/// Intercept response from server /// Intercept response from server
/// </summary> /// </summary>
public static event Func<object, SessionEventArgs, Task> BeforeResponse; public event Func<object, SessionEventArgs, Task> BeforeResponse;
/// <summary> /// <summary>
/// External proxy for Http /// External proxy for Http
/// </summary> /// </summary>
public static ExternalProxy UpStreamHttpProxy { get; set; } public ExternalProxy UpStreamHttpProxy { get; set; }
/// <summary> /// <summary>
/// External proxy for Http /// External proxy for Http
/// </summary> /// </summary>
public static ExternalProxy UpStreamHttpsProxy { get; set; } public ExternalProxy UpStreamHttpsProxy { get; set; }
/// <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 static event Func<object, CertificateValidationEventArgs, Task> ServerCertificateValidationCallback; public event Func<object, CertificateValidationEventArgs, Task> ServerCertificateValidationCallback;
/// <summary> /// <summary>
/// Callback tooverride client certificate during SSL mutual authentication /// Callback tooverride client certificate during SSL mutual authentication
/// </summary> /// </summary>
public static event Func<object, CertificateSelectionEventArgs, Task> ClientCertificateSelectionCallback; public event Func<object, CertificateSelectionEventArgs, Task> ClientCertificateSelectionCallback;
/// <summary> /// <summary>
/// A list of IpAddress & port this proxy is listening to /// A list of IpAddress & port this proxy is listening to
/// </summary> /// </summary>
public static List<ProxyEndPoint> ProxyEndPoints { get; set; } public List<ProxyEndPoint> ProxyEndPoints { get; set; }
/// <summary> /// <summary>
/// Initialize the proxy /// List of supported Ssl versions
/// </summary> /// </summary>
public static void Initialize() public SslProtocols SupportedSslProtocols { get; set; } = SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12 | SslProtocols.Ssl3;
{
TcpConnectionManager.ClearIdleConnections();
CertManager.ClearIdleCertificates();
}
/// <summary> /// <summary>
/// Quit the proxy /// Constructor
/// </summary> /// </summary>
public static void Quit() public ProxyServer()
{ {
TcpConnectionManager.StopClearIdleConnections(); //default values
CertManager.StopClearIdleCertificates(); ConnectionCacheTimeOutMinutes = 3;
CertificateCacheTimeOutMinutes = 60;
ProxyEndPoints = new List<ProxyEndPoint>();
tcpConnectionCacheManager = new TcpConnectionCacheManager();
systemProxySettingsManager = new SystemProxyManager();
firefoxProxySettingsManager = new FireFoxProxySettingsManager();
RootCertificateName = RootCertificateName ?? "Titanium Root Certificate Authority";
RootCertificateIssuerName = RootCertificateIssuerName ?? "Titanium";
certificateCacheManager = new CertificateManager(RootCertificateIssuerName,
RootCertificateName);
} }
/// <summary> /// <summary>
/// Add a proxy end point /// Add a proxy end point
/// </summary> /// </summary>
/// <param name="endPoint"></param> /// <param name="endPoint"></param>
public static void AddEndPoint(ProxyEndPoint endPoint) public void AddEndPoint(ProxyEndPoint endPoint)
{ {
if (ProxyEndPoints.Any(x=>x.IpAddress == endPoint.IpAddress && x.Port == endPoint.Port))
throw new Exception("Cannot add another endpoint to same port & ip address");
ProxyEndPoints.Add(endPoint); ProxyEndPoints.Add(endPoint);
if (proxyRunning) if (proxyRunning)
...@@ -139,9 +157,8 @@ namespace Titanium.Web.Proxy ...@@ -139,9 +157,8 @@ namespace Titanium.Web.Proxy
/// Will throw error if the end point does'nt exist /// Will throw error if the end point does'nt exist
/// </summary> /// </summary>
/// <param name="endPoint"></param> /// <param name="endPoint"></param>
public static void RemoveEndPoint(ProxyEndPoint endPoint) public void RemoveEndPoint(ProxyEndPoint endPoint)
{ {
if (ProxyEndPoints.Contains(endPoint) == false) if (ProxyEndPoints.Contains(endPoint) == false)
throw new Exception("Cannot remove endPoints not added to proxy"); throw new Exception("Cannot remove endPoints not added to proxy");
...@@ -155,37 +172,30 @@ namespace Titanium.Web.Proxy ...@@ -155,37 +172,30 @@ namespace Titanium.Web.Proxy
/// Set the given explicit end point as the default proxy server for current machine /// Set the given explicit end point as the default proxy server for current machine
/// </summary> /// </summary>
/// <param name="endPoint"></param> /// <param name="endPoint"></param>
public static void SetAsSystemHttpProxy(ExplicitProxyEndPoint endPoint) public void SetAsSystemHttpProxy(ExplicitProxyEndPoint endPoint)
{ {
ValidateEndPointAsSystemProxy(endPoint); ValidateEndPointAsSystemProxy(endPoint);
//clear any settings previously added //clear any settings previously added
ProxyEndPoints.OfType<ExplicitProxyEndPoint>().ToList().ForEach(x => x.IsSystemHttpProxy = false); ProxyEndPoints.OfType<ExplicitProxyEndPoint>().ToList().ForEach(x => x.IsSystemHttpProxy = false);
SystemProxyHelper.SetHttpProxy( systemProxySettingsManager.SetHttpProxy(
Equals(endPoint.IpAddress, IPAddress.Any) | Equals(endPoint.IpAddress, IPAddress.Loopback) ? "127.0.0.1" : endPoint.IpAddress.ToString(), endPoint.Port); Equals(endPoint.IpAddress, IPAddress.Any) | Equals(endPoint.IpAddress, IPAddress.Loopback) ? "127.0.0.1" : endPoint.IpAddress.ToString(), endPoint.Port);
endPoint.IsSystemHttpProxy = true; endPoint.IsSystemHttpProxy = true;
#if !DEBUG #if !DEBUG
FireFoxHelper.AddFirefox(); firefoxProxySettingsManager.AddFirefox();
#endif #endif
Console.WriteLine("Set endpoint at Ip {1} and port: {2} as System HTTPS Proxy", endPoint.GetType().Name, endPoint.IpAddress, endPoint.Port); Console.WriteLine("Set endpoint at Ip {1} and port: {2} as System HTTPS Proxy", endPoint.GetType().Name, endPoint.IpAddress, endPoint.Port);
} }
/// <summary>
/// Remove any HTTP proxy setting of current machien
/// </summary>
public static void DisableSystemHttpProxy()
{
SystemProxyHelper.RemoveHttpProxy();
}
/// <summary> /// <summary>
/// Set the given explicit end point as the default proxy server for current machine /// Set the given explicit end point as the default proxy server for current machine
/// </summary> /// </summary>
/// <param name="endPoint"></param> /// <param name="endPoint"></param>
public static void SetAsSystemHttpsProxy(ExplicitProxyEndPoint endPoint) public void SetAsSystemHttpsProxy(ExplicitProxyEndPoint endPoint)
{ {
ValidateEndPointAsSystemProxy(endPoint); ValidateEndPointAsSystemProxy(endPoint);
...@@ -201,7 +211,7 @@ namespace Titanium.Web.Proxy ...@@ -201,7 +211,7 @@ namespace Titanium.Web.Proxy
//If certificate was trusted by the machine //If certificate was trusted by the machine
if (certTrusted) if (certTrusted)
{ {
SystemProxyHelper.SetHttpsProxy( systemProxySettingsManager.SetHttpsProxy(
Equals(endPoint.IpAddress, IPAddress.Any) | Equals(endPoint.IpAddress, IPAddress.Loopback) ? "127.0.0.1" : endPoint.IpAddress.ToString(), Equals(endPoint.IpAddress, IPAddress.Any) | Equals(endPoint.IpAddress, IPAddress.Loopback) ? "127.0.0.1" : endPoint.IpAddress.ToString(),
endPoint.Port); endPoint.Port);
} }
...@@ -209,49 +219,52 @@ namespace Titanium.Web.Proxy ...@@ -209,49 +219,52 @@ namespace Titanium.Web.Proxy
endPoint.IsSystemHttpsProxy = true; endPoint.IsSystemHttpsProxy = true;
#if !DEBUG #if !DEBUG
FireFoxHelper.AddFirefox(); firefoxProxySettingsManager.AddFirefox();
#endif #endif
Console.WriteLine("Set endpoint at Ip {1} and port: {2} as System HTTPS Proxy", endPoint.GetType().Name, endPoint.IpAddress, endPoint.Port); Console.WriteLine("Set endpoint at Ip {1} and port: {2} as System HTTPS Proxy", endPoint.GetType().Name, endPoint.IpAddress, endPoint.Port);
} }
/// <summary>
/// Remove any HTTP proxy setting of current machien
/// </summary>
public void DisableSystemHttpProxy()
{
systemProxySettingsManager.RemoveHttpProxy();
}
/// <summary> /// <summary>
/// Remove any HTTPS proxy setting for current machine /// Remove any HTTPS proxy setting for current machine
/// </summary> /// </summary>
public static void DisableSystemHttpsProxy() public void DisableSystemHttpsProxy()
{ {
SystemProxyHelper.RemoveHttpsProxy(); systemProxySettingsManager.RemoveHttpsProxy();
} }
/// <summary> /// <summary>
/// Clear all proxy settings for current machine /// Clear all proxy settings for current machine
/// </summary> /// </summary>
public static void DisableAllSystemProxies() public void DisableAllSystemProxies()
{ {
SystemProxyHelper.DisableAllProxy(); systemProxySettingsManager.DisableAllProxy();
} }
/// <summary> /// <summary>
/// Start this proxy server /// Start this proxy server
/// </summary> /// </summary>
public static void Start() public void Start()
{ {
if (proxyRunning) if (proxyRunning)
throw new Exception("Proxy is already running."); throw new Exception("Proxy is already running.");
RootCertificateName = RootCertificateName ?? "Titanium Root Certificate Authority"; certTrusted = certificateCacheManager.CreateTrustedRootCertificate().Result;
RootCertificateIssuerName = RootCertificateIssuerName ?? "Titanium";
CertManager = new CertificateManager(RootCertificateIssuerName,
RootCertificateName);
certTrusted = CertManager.CreateTrustedRootCertificate().Result;
foreach (var endPoint in ProxyEndPoints) foreach (var endPoint in ProxyEndPoints)
{ {
Listen(endPoint); Listen(endPoint);
} }
Initialize(); tcpConnectionCacheManager.ClearIdleConnections(ConnectionCacheTimeOutMinutes);
certificateCacheManager.ClearIdleCertificates(CertificateCacheTimeOutMinutes);
proxyRunning = true; proxyRunning = true;
} }
...@@ -259,7 +272,7 @@ namespace Titanium.Web.Proxy ...@@ -259,7 +272,7 @@ namespace Titanium.Web.Proxy
/// <summary> /// <summary>
/// Stop this proxy server /// Stop this proxy server
/// </summary> /// </summary>
public static void Stop() public void Stop()
{ {
if (!proxyRunning) if (!proxyRunning)
throw new Exception("Proxy is not running."); throw new Exception("Proxy is not running.");
...@@ -268,20 +281,21 @@ namespace Titanium.Web.Proxy ...@@ -268,20 +281,21 @@ namespace Titanium.Web.Proxy
if (setAsSystemProxy) if (setAsSystemProxy)
{ {
SystemProxyHelper.DisableAllProxy(); systemProxySettingsManager.DisableAllProxy();
#if !DEBUG #if !DEBUG
FireFoxHelper.RemoveFirefox(); firefoxProxySettingsManager.RemoveFirefox();
#endif #endif
} }
foreach (var endPoint in ProxyEndPoints) foreach (var endPoint in ProxyEndPoints)
{ {
endPoint.listener.Stop(); QuitListen(endPoint);
} }
CertManager.Dispose(); ProxyEndPoints.Clear();
Quit(); tcpConnectionCacheManager.StopClearIdleConnections();
certificateCacheManager.StopClearIdleCertificates();
proxyRunning = false; proxyRunning = false;
} }
...@@ -290,7 +304,7 @@ namespace Titanium.Web.Proxy ...@@ -290,7 +304,7 @@ namespace Titanium.Web.Proxy
/// Listen on the given end point on local machine /// Listen on the given end point on local machine
/// </summary> /// </summary>
/// <param name="endPoint"></param> /// <param name="endPoint"></param>
private static void Listen(ProxyEndPoint endPoint) private void Listen(ProxyEndPoint endPoint)
{ {
endPoint.listener = new TcpListener(endPoint.IpAddress, endPoint.Port); endPoint.listener = new TcpListener(endPoint.IpAddress, endPoint.Port);
endPoint.listener.Start(); endPoint.listener.Start();
...@@ -304,16 +318,18 @@ namespace Titanium.Web.Proxy ...@@ -304,16 +318,18 @@ namespace Titanium.Web.Proxy
/// Quit listening on the given end point /// Quit listening on the given end point
/// </summary> /// </summary>
/// <param name="endPoint"></param> /// <param name="endPoint"></param>
private static void QuitListen(ProxyEndPoint endPoint) private void QuitListen(ProxyEndPoint endPoint)
{ {
endPoint.listener.Stop(); endPoint.listener.Stop();
endPoint.listener.Server.Close();
endPoint.listener.Server.Dispose();
} }
/// <summary> /// <summary>
/// Verifiy if its safe to set this end point as System proxy /// Verifiy if its safe to set this end point as System proxy
/// </summary> /// </summary>
/// <param name="endPoint"></param> /// <param name="endPoint"></param>
private static void ValidateEndPointAsSystemProxy(ExplicitProxyEndPoint endPoint) private void ValidateEndPointAsSystemProxy(ExplicitProxyEndPoint endPoint)
{ {
if (ProxyEndPoints.Contains(endPoint) == false) if (ProxyEndPoints.Contains(endPoint) == false)
throw new Exception("Cannot set endPoints not added to proxy as system proxy"); throw new Exception("Cannot set endPoints not added to proxy as system proxy");
...@@ -326,7 +342,7 @@ namespace Titanium.Web.Proxy ...@@ -326,7 +342,7 @@ namespace Titanium.Web.Proxy
/// When a connection is received from client act /// When a connection is received from client act
/// </summary> /// </summary>
/// <param name="asyn"></param> /// <param name="asyn"></param>
private static void OnAcceptConnection(IAsyncResult asyn) private void OnAcceptConnection(IAsyncResult asyn)
{ {
var endPoint = (ProxyEndPoint)asyn.AsyncState; var endPoint = (ProxyEndPoint)asyn.AsyncState;
...@@ -356,5 +372,12 @@ namespace Titanium.Web.Proxy ...@@ -356,5 +372,12 @@ namespace Titanium.Web.Proxy
} }
public void Dispose()
{
if (proxyRunning)
Stop();
certificateCacheManager.Dispose();
}
} }
} }
\ No newline at end of file
...@@ -13,8 +13,8 @@ using Titanium.Web.Proxy.Models; ...@@ -13,8 +13,8 @@ using Titanium.Web.Proxy.Models;
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
using Titanium.Web.Proxy.Shared; using Titanium.Web.Proxy.Shared;
using Titanium.Web.Proxy.Http; using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Extensions;
using System.Threading.Tasks; using System.Threading.Tasks;
using Titanium.Web.Proxy.Extensions;
namespace Titanium.Web.Proxy namespace Titanium.Web.Proxy
{ {
...@@ -24,7 +24,7 @@ namespace Titanium.Web.Proxy ...@@ -24,7 +24,7 @@ namespace Titanium.Web.Proxy
partial class ProxyServer partial class ProxyServer
{ {
//This is called when client is aware of proxy //This is called when client is aware of proxy
private static async void HandleClient(ExplicitProxyEndPoint endPoint, TcpClient client) private async void HandleClient(ExplicitProxyEndPoint endPoint, TcpClient client)
{ {
Stream clientStream = client.GetStream(); Stream clientStream = client.GetStream();
var clientStreamReader = new CustomBinaryReader(clientStream); var clientStreamReader = new CustomBinaryReader(clientStream);
...@@ -64,7 +64,7 @@ namespace Titanium.Web.Proxy ...@@ -64,7 +64,7 @@ namespace Titanium.Web.Proxy
version = new Version(1, 0); version = new Version(1, 0);
} }
} }
//filter out excluded host names
var excluded = endPoint.ExcludedHttpsHostNameRegex != null ? var excluded = endPoint.ExcludedHttpsHostNameRegex != null ?
endPoint.ExcludedHttpsHostNameRegex.Any(x => Regex.IsMatch(httpRemoteUri.Host, x)) : false; endPoint.ExcludedHttpsHostNameRegex.Any(x => Regex.IsMatch(httpRemoteUri.Host, x)) : false;
...@@ -82,14 +82,17 @@ namespace Titanium.Web.Proxy ...@@ -82,14 +82,17 @@ namespace Titanium.Web.Proxy
{ {
//create the Tcp Connection to server and then release it to connection cache //create the Tcp Connection to server and then release it to connection cache
//Just doing what CONNECT request is asking as to do //Just doing what CONNECT request is asking as to do
var tunnelClient = await TcpConnectionManager.GetClient(httpRemoteUri.Host, httpRemoteUri.Port, true, version); var tunnelClient = await tcpConnectionCacheManager.GetClient(httpRemoteUri.Host, httpRemoteUri.Port, true, version,
await TcpConnectionManager.ReleaseClient(tunnelClient); UpStreamHttpProxy, UpStreamHttpsProxy, BUFFER_SIZE, SupportedSslProtocols, new RemoteCertificateValidationCallback(ValidateServerCertificate),
new LocalCertificateSelectionCallback(SelectClientCertificate));
await tcpConnectionCacheManager.ReleaseClient(tunnelClient);
sslStream = new SslStream(clientStream, true); sslStream = new SslStream(clientStream, true);
var certificate = await CertManager.CreateCertificate(httpRemoteUri.Host, false); var certificate = await certificateCacheManager.CreateCertificate(httpRemoteUri.Host, false);
//Successfully managed to authenticate the client using the fake certificate //Successfully managed to authenticate the client using the fake certificate
await sslStream.AuthenticateAsServerAsync(certificate, false, await sslStream.AuthenticateAsServerAsync(certificate, false,
ProxyConstants.SupportedSslProtocols, false); SupportedSslProtocols, false);
//HTTPS server created - we can now decrypt the client's traffic //HTTPS server created - we can now decrypt the client's traffic
clientStream = sslStream; clientStream = sslStream;
...@@ -120,7 +123,7 @@ namespace Titanium.Web.Proxy ...@@ -120,7 +123,7 @@ namespace Titanium.Web.Proxy
//Just relay the request/response without decrypting it //Just relay the request/response without decrypting it
await TcpHelper.SendRaw(clientStream, null, null, httpRemoteUri.Host, httpRemoteUri.Port, await TcpHelper.SendRaw(clientStream, null, null, httpRemoteUri.Host, httpRemoteUri.Port,
false); false, SupportedSslProtocols);
Dispose(client, clientStream, clientStreamReader, clientStreamWriter, null); Dispose(client, clientStream, clientStreamReader, clientStreamWriter, null);
return; return;
...@@ -138,7 +141,7 @@ namespace Titanium.Web.Proxy ...@@ -138,7 +141,7 @@ namespace Titanium.Web.Proxy
//This is called when requests are routed through router to this endpoint //This is called when requests are routed through router to this endpoint
//For ssl requests //For ssl requests
private static async void HandleClient(TransparentProxyEndPoint endPoint, TcpClient tcpClient) private async void HandleClient(TransparentProxyEndPoint endPoint, TcpClient tcpClient)
{ {
Stream clientStream = tcpClient.GetStream(); Stream clientStream = tcpClient.GetStream();
CustomBinaryReader clientStreamReader = null; CustomBinaryReader clientStreamReader = null;
...@@ -148,13 +151,9 @@ namespace Titanium.Web.Proxy ...@@ -148,13 +151,9 @@ namespace Titanium.Web.Proxy
if (endPoint.EnableSsl) if (endPoint.EnableSsl)
{ {
var sslStream = new SslStream(clientStream, true); var sslStream = new SslStream(clientStream, true);
//if(endPoint.UseServerNameIndication)
//{ //implement in future once SNI supported by SSL stream, for now use the same certificate
// //implement in future once SNI supported by SSL stream certificate = await certificateCacheManager.CreateCertificate(endPoint.GenericCertificateName, false);
// certificate = CertManager.CreateCertificate(hostName);
//}
//else
certificate = await CertManager.CreateCertificate(endPoint.GenericCertificateName, false);
try try
{ {
...@@ -182,6 +181,7 @@ namespace Titanium.Web.Proxy ...@@ -182,6 +181,7 @@ namespace Titanium.Web.Proxy
clientStreamReader = new CustomBinaryReader(clientStream); clientStreamReader = new CustomBinaryReader(clientStream);
} }
//now read the request line
var httpCmd = await clientStreamReader.ReadLineAsync(); var httpCmd = await clientStreamReader.ReadLineAsync();
//Now create the request //Now create the request
...@@ -198,10 +198,10 @@ namespace Titanium.Web.Proxy ...@@ -198,10 +198,10 @@ namespace Titanium.Web.Proxy
/// <param name="clientStreamWriter"></param> /// <param name="clientStreamWriter"></param>
/// <param name="isHttps"></param> /// <param name="isHttps"></param>
/// <returns></returns> /// <returns></returns>
private static async Task HandleHttpSessionRequest(TcpClient client, string httpCmd, Stream clientStream, private async Task HandleHttpSessionRequest(TcpClient client, string httpCmd, Stream clientStream,
CustomBinaryReader clientStreamReader, StreamWriter clientStreamWriter, bool isHttps) CustomBinaryReader clientStreamReader, StreamWriter clientStreamWriter, bool isHttps)
{ {
TcpConnection connection = null; TcpConnectionCache connection = null;
//Loop through each subsequest request on this particular client connection //Loop through each subsequest request on this particular client connection
//(assuming HTTP connection is kept alive by client) //(assuming HTTP connection is kept alive by client)
...@@ -213,7 +213,7 @@ namespace Titanium.Web.Proxy ...@@ -213,7 +213,7 @@ namespace Titanium.Web.Proxy
break; break;
} }
var args = new SessionEventArgs(); var args = new SessionEventArgs(BUFFER_SIZE, HandleHttpSessionResponse);
args.ProxyClient.TcpClient = client; args.ProxyClient.TcpClient = client;
try try
...@@ -284,13 +284,15 @@ namespace Titanium.Web.Proxy ...@@ -284,13 +284,15 @@ namespace Titanium.Web.Proxy
if (args.WebSession.Request.UpgradeToWebSocket) if (args.WebSession.Request.UpgradeToWebSocket)
{ {
await TcpHelper.SendRaw(clientStream, httpCmd, args.WebSession.Request.RequestHeaders, await TcpHelper.SendRaw(clientStream, httpCmd, args.WebSession.Request.RequestHeaders,
httpRemoteUri.Host, httpRemoteUri.Port, args.IsHttps); httpRemoteUri.Host, httpRemoteUri.Port, args.IsHttps, SupportedSslProtocols);
Dispose(client, clientStream, clientStreamReader, clientStreamWriter, args); Dispose(client, clientStream, clientStreamReader, clientStreamWriter, args);
return; return;
} }
//construct the web request that we are going to issue on behalf of the client. //construct the web request that we are going to issue on behalf of the client.
connection = await TcpConnectionManager.GetClient(args.WebSession.Request.RequestUri.Host, args.WebSession.Request.RequestUri.Port, args.IsHttps, version); connection = await tcpConnectionCacheManager.GetClient(args.WebSession.Request.RequestUri.Host, args.WebSession.Request.RequestUri.Port, args.IsHttps, version,
UpStreamHttpProxy, UpStreamHttpsProxy, BUFFER_SIZE, SupportedSslProtocols, new RemoteCertificateValidationCallback(ValidateServerCertificate),
new LocalCertificateSelectionCallback(SelectClientCertificate));
args.WebSession.Request.RequestLocked = true; args.WebSession.Request.RequestLocked = true;
...@@ -306,7 +308,7 @@ namespace Titanium.Web.Proxy ...@@ -306,7 +308,7 @@ namespace Titanium.Web.Proxy
if (args.WebSession.Request.ExpectContinue) if (args.WebSession.Request.ExpectContinue)
{ {
args.WebSession.SetConnection(connection); args.WebSession.SetConnection(connection);
await args.WebSession.SendRequest(); await args.WebSession.SendRequest(Enable100ContinueBehaviour);
} }
//If 100 continue was the response inform that to the client //If 100 continue was the response inform that to the client
...@@ -328,7 +330,7 @@ namespace Titanium.Web.Proxy ...@@ -328,7 +330,7 @@ namespace Titanium.Web.Proxy
if (!args.WebSession.Request.ExpectContinue) if (!args.WebSession.Request.ExpectContinue)
{ {
args.WebSession.SetConnection(connection); args.WebSession.SetConnection(connection);
await args.WebSession.SendRequest(); await args.WebSession.SendRequest(Enable100ContinueBehaviour);
} }
//If request was modified by user //If request was modified by user
...@@ -370,7 +372,7 @@ namespace Titanium.Web.Proxy ...@@ -370,7 +372,7 @@ namespace Titanium.Web.Proxy
} }
//send the tcp connection to server back to connection cache for reuse //send the tcp connection to server back to connection cache for reuse
await TcpConnectionManager.ReleaseClient(connection); await tcpConnectionCacheManager.ReleaseClient(connection);
// read the next request // read the next request
httpCmd = await clientStreamReader.ReadLineAsync(); httpCmd = await clientStreamReader.ReadLineAsync();
...@@ -392,7 +394,7 @@ namespace Titanium.Web.Proxy ...@@ -392,7 +394,7 @@ namespace Titanium.Web.Proxy
/// <param name="clientStreamWriter"></param> /// <param name="clientStreamWriter"></param>
/// <param name="httpVersion"></param> /// <param name="httpVersion"></param>
/// <returns></returns> /// <returns></returns>
private static async Task WriteConnectResponse(StreamWriter clientStreamWriter, Version httpVersion) private async Task WriteConnectResponse(StreamWriter clientStreamWriter, Version httpVersion)
{ {
await clientStreamWriter.WriteLineAsync(string.Format("HTTP/{0}.{1} {2}", httpVersion.Major, httpVersion.Minor, "200 Connection established")); await clientStreamWriter.WriteLineAsync(string.Format("HTTP/{0}.{1} {2}", httpVersion.Major, httpVersion.Minor, "200 Connection established"));
await clientStreamWriter.WriteLineAsync(string.Format("Timestamp: {0}", DateTime.Now)); await clientStreamWriter.WriteLineAsync(string.Format("Timestamp: {0}", DateTime.Now));
...@@ -405,12 +407,13 @@ namespace Titanium.Web.Proxy ...@@ -405,12 +407,13 @@ namespace Titanium.Web.Proxy
/// </summary> /// </summary>
/// <param name="requestHeaders"></param> /// <param name="requestHeaders"></param>
/// <param name="webRequest"></param> /// <param name="webRequest"></param>
private static void PrepareRequestHeaders(List<HttpHeader> requestHeaders, HttpWebClient webRequest) private void PrepareRequestHeaders(List<HttpHeader> requestHeaders, HttpWebClient webRequest)
{ {
for (var i = 0; i < requestHeaders.Count; i++) for (var i = 0; i < requestHeaders.Count; i++)
{ {
switch (requestHeaders[i].Name.ToLower()) switch (requestHeaders[i].Name.ToLower())
{ {
//these are the only encoding this proxy can read
case "accept-encoding": case "accept-encoding":
requestHeaders[i].Value = "gzip,deflate,zlib"; requestHeaders[i].Value = "gzip,deflate,zlib";
break; break;
...@@ -428,7 +431,7 @@ namespace Titanium.Web.Proxy ...@@ -428,7 +431,7 @@ namespace Titanium.Web.Proxy
/// Fix proxy specific headers /// Fix proxy specific headers
/// </summary> /// </summary>
/// <param name="headers"></param> /// <param name="headers"></param>
private static void FixRequestProxyHeaders(List<HttpHeader> headers) private void FixRequestProxyHeaders(List<HttpHeader> headers)
{ {
//If proxy-connection close was returned inform to close the connection //If proxy-connection close was returned inform to close the connection
var proxyHeader = headers.FirstOrDefault(x => x.Name.ToLower() == "proxy-connection"); var proxyHeader = headers.FirstOrDefault(x => x.Name.ToLower() == "proxy-connection");
...@@ -452,33 +455,23 @@ namespace Titanium.Web.Proxy ...@@ -452,33 +455,23 @@ namespace Titanium.Web.Proxy
/// </summary> /// </summary>
/// <param name="args"></param> /// <param name="args"></param>
/// <returns></returns> /// <returns></returns>
private static async Task SendClientRequestBody(SessionEventArgs args) private async Task SendClientRequestBody(SessionEventArgs args)
{ {
// End the operation // End the operation
var postStream = args.WebSession.ServerConnection.Stream; var postStream = args.WebSession.ServerConnection.Stream;
//send the request body bytes to server
if (args.WebSession.Request.ContentLength > 0) if (args.WebSession.Request.ContentLength > 0)
{ {
try await args.ProxyClient.ClientStreamReader.CopyBytesToStream(BUFFER_SIZE, postStream, args.WebSession.Request.ContentLength);
{
await args.ProxyClient.ClientStreamReader.CopyBytesToStream(postStream, args.WebSession.Request.ContentLength);
}
catch
{
throw;
}
} }
//Need to revist, find any potential bugs //Need to revist, find any potential bugs
//send the request body bytes to server in chunks
else if (args.WebSession.Request.IsChunked) else if (args.WebSession.Request.IsChunked)
{ {
try await args.ProxyClient.ClientStreamReader.CopyBytesToStreamChunked(BUFFER_SIZE, postStream);
{
await args.ProxyClient.ClientStreamReader.CopyBytesToStreamChunked(postStream);
}
catch
{
throw;
}
} }
} }
......
...@@ -17,7 +17,7 @@ namespace Titanium.Web.Proxy ...@@ -17,7 +17,7 @@ namespace Titanium.Web.Proxy
partial class ProxyServer partial class ProxyServer
{ {
//Called asynchronously when a request was successfully and we received the response //Called asynchronously when a request was successfully and we received the response
public static async Task HandleHttpSessionResponse(SessionEventArgs args) public async Task HandleHttpSessionResponse(SessionEventArgs args)
{ {
//read response & headers from server //read response & headers from server
await args.WebSession.ReceiveResponse(); await args.WebSession.ReceiveResponse();
...@@ -83,9 +83,14 @@ namespace Titanium.Web.Proxy ...@@ -83,9 +83,14 @@ namespace Titanium.Web.Proxy
{ {
await WriteResponseHeaders(args.ProxyClient.ClientStreamWriter, args.WebSession.Response.ResponseHeaders); await WriteResponseHeaders(args.ProxyClient.ClientStreamWriter, args.WebSession.Response.ResponseHeaders);
if (args.WebSession.Response.IsChunked || args.WebSession.Response.ContentLength > 0 || //Write body only if response is chunked or content length >0
(args.WebSession.Response.HttpVersion.Major == 1 && args.WebSession.Response.HttpVersion.Minor == 0)) //Is none are true then check if connection:close header exist, if so write response until server or client terminates the connection
await args.WebSession.ServerConnection.StreamReader.WriteResponseBody(args.ProxyClient.ClientStream, args.WebSession.Response.IsChunked, args.WebSession.Response.ContentLength); if (args.WebSession.Response.IsChunked || args.WebSession.Response.ContentLength > 0 || !args.WebSession.Response.ResponseKeepAlive)
await args.WebSession.ServerConnection.StreamReader.WriteResponseBody(BUFFER_SIZE, args.ProxyClient.ClientStream, args.WebSession.Response.IsChunked, args.WebSession.Response.ContentLength);
//write 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)
else if (args.WebSession.Response.ResponseKeepAlive && args.WebSession.Response.HttpVersion.Minor == 0)
await args.WebSession.ServerConnection.StreamReader.WriteResponseBody(BUFFER_SIZE, args.ProxyClient.ClientStream, args.WebSession.Response.IsChunked, args.WebSession.Response.ContentLength);
} }
await args.ProxyClient.ClientStream.FlushAsync(); await args.ProxyClient.ClientStream.FlushAsync();
...@@ -107,7 +112,7 @@ namespace Titanium.Web.Proxy ...@@ -107,7 +112,7 @@ namespace Titanium.Web.Proxy
/// <param name="encodingType"></param> /// <param name="encodingType"></param>
/// <param name="responseBodyStream"></param> /// <param name="responseBodyStream"></param>
/// <returns></returns> /// <returns></returns>
private static async Task<byte[]> GetCompressedResponseBody(string encodingType, byte[] responseBodyStream) private async Task<byte[]> GetCompressedResponseBody(string encodingType, byte[] responseBodyStream)
{ {
var compressionFactory = new CompressionFactory(); var compressionFactory = new CompressionFactory();
var compressor = compressionFactory.Create(encodingType); var compressor = compressionFactory.Create(encodingType);
...@@ -122,7 +127,7 @@ namespace Titanium.Web.Proxy ...@@ -122,7 +127,7 @@ namespace Titanium.Web.Proxy
/// <param name="description"></param> /// <param name="description"></param>
/// <param name="responseWriter"></param> /// <param name="responseWriter"></param>
/// <returns></returns> /// <returns></returns>
private static async Task WriteResponseStatus(Version version, string code, string description, private async Task WriteResponseStatus(Version version, string code, string description,
StreamWriter responseWriter) StreamWriter responseWriter)
{ {
await responseWriter.WriteLineAsync(string.Format("HTTP/{0}.{1} {2} {3}", version.Major, version.Minor, code, description)); await responseWriter.WriteLineAsync(string.Format("HTTP/{0}.{1} {2} {3}", version.Major, version.Minor, code, description));
...@@ -134,7 +139,7 @@ namespace Titanium.Web.Proxy ...@@ -134,7 +139,7 @@ namespace Titanium.Web.Proxy
/// <param name="responseWriter"></param> /// <param name="responseWriter"></param>
/// <param name="headers"></param> /// <param name="headers"></param>
/// <returns></returns> /// <returns></returns>
private static async Task WriteResponseHeaders(StreamWriter responseWriter, List<HttpHeader> headers) private async Task WriteResponseHeaders(StreamWriter responseWriter, List<HttpHeader> headers)
{ {
if (headers != null) if (headers != null)
{ {
...@@ -154,7 +159,7 @@ namespace Titanium.Web.Proxy ...@@ -154,7 +159,7 @@ namespace Titanium.Web.Proxy
/// Fix the proxy specific headers before sending response headers to client /// Fix the proxy specific headers before sending response headers to client
/// </summary> /// </summary>
/// <param name="headers"></param> /// <param name="headers"></param>
private static void FixResponseProxyHeaders(List<HttpHeader> headers) private void FixResponseProxyHeaders(List<HttpHeader> headers)
{ {
//If proxy-connection close was returned inform to close the connection //If proxy-connection close was returned inform to close the connection
var proxyHeader = headers.FirstOrDefault(x => x.Name.ToLower() == "proxy-connection"); var proxyHeader = headers.FirstOrDefault(x => x.Name.ToLower() == "proxy-connection");
...@@ -181,7 +186,7 @@ namespace Titanium.Web.Proxy ...@@ -181,7 +186,7 @@ namespace Titanium.Web.Proxy
/// <param name="clientStreamReader"></param> /// <param name="clientStreamReader"></param>
/// <param name="clientStreamWriter"></param> /// <param name="clientStreamWriter"></param>
/// <param name="args"></param> /// <param name="args"></param>
private static void Dispose(TcpClient client, IDisposable clientStream, IDisposable clientStreamReader, private void Dispose(TcpClient client, IDisposable clientStream, IDisposable clientStreamReader,
IDisposable clientStreamWriter, IDisposable args) IDisposable clientStreamWriter, IDisposable args)
{ {
if (args != null) if (args != null)
......
using System; using System;
using System.Security.Authentication;
using System.Text; using System.Text;
namespace Titanium.Web.Proxy.Shared namespace Titanium.Web.Proxy.Shared
...@@ -18,8 +17,5 @@ namespace Titanium.Web.Proxy.Shared ...@@ -18,8 +17,5 @@ namespace Titanium.Web.Proxy.Shared
internal static readonly byte[] ChunkEnd = internal static readonly byte[] ChunkEnd =
Encoding.ASCII.GetBytes(0.ToString("x2") + Environment.NewLine + Environment.NewLine); Encoding.ASCII.GetBytes(0.ToString("x2") + Environment.NewLine + Environment.NewLine);
public static SslProtocols SupportedSslProtocols = SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12 | SslProtocols.Ssl3;
public static readonly int BUFFER_SIZE = 8192;
} }
} }
...@@ -77,8 +77,8 @@ ...@@ -77,8 +77,8 @@
<Compile Include="Http\Request.cs" /> <Compile Include="Http\Request.cs" />
<Compile Include="Http\Response.cs" /> <Compile Include="Http\Response.cs" />
<Compile Include="Models\ExternalProxy.cs" /> <Compile Include="Models\ExternalProxy.cs" />
<Compile Include="Network\TcpConnection.cs" /> <Compile Include="Network\TcpConnectionCache.cs" />
<Compile Include="Network\TcpConnectionManager.cs" /> <Compile Include="Network\TcpConnectionCacheManager.cs" />
<Compile Include="Models\HttpHeader.cs" /> <Compile Include="Models\HttpHeader.cs" />
<Compile Include="Http\HttpWebClient.cs" /> <Compile Include="Http\HttpWebClient.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
......
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