Commit 5f3e8d42 authored by Jehonathan's avatar Jehonathan Committed by GitHub

Merge pull request #84 from justcoding121/release

Release
parents 703f5219 a954c575
using System;
using System.Collections.Generic;
using System.Net;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Models;
......@@ -10,12 +8,19 @@ namespace Titanium.Web.Proxy.Examples.Basic
{
public class ProxyTestController
{
private ProxyServer proxyServer;
public ProxyTestController()
{
proxyServer = new ProxyServer();
}
public void StartProxy()
{
ProxyServer.BeforeRequest += OnRequest;
ProxyServer.BeforeResponse += OnResponse;
ProxyServer.ServerCertificateValidationCallback += OnCertificateValidation;
ProxyServer.ClientCertificateSelectionCallback += OnCertificateSelection;
proxyServer.BeforeRequest += OnRequest;
proxyServer.BeforeResponse += OnResponse;
proxyServer.ServerCertificateValidationCallback += OnCertificateValidation;
proxyServer.ClientCertificateSelectionCallback += OnCertificateSelection;
//Exclude Https addresses you don't want to proxy
//Usefull for clients that use certificate pinning
......@@ -27,8 +32,8 @@ namespace Titanium.Web.Proxy.Examples.Basic
//An explicit endpoint is where the client knows about the existance of a proxy
//So client sends request in a proxy friendly manner
ProxyServer.AddEndPoint(explicitEndPoint);
ProxyServer.Start();
proxyServer.AddEndPoint(explicitEndPoint);
proxyServer.Start();
//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
{
GenericCertificateName = "google.com"
};
ProxyServer.AddEndPoint(transparentEndPoint);
proxyServer.AddEndPoint(transparentEndPoint);
//ProxyServer.UpStreamHttpProxy = 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} ",
endPoint.GetType().Name, endPoint.IpAddress, endPoint.Port);
//Only explicit proxies can be set as system proxy!
ProxyServer.SetAsSystemHttpProxy(explicitEndPoint);
ProxyServer.SetAsSystemHttpsProxy(explicitEndPoint);
proxyServer.SetAsSystemHttpProxy(explicitEndPoint);
proxyServer.SetAsSystemHttpsProxy(explicitEndPoint);
}
public void Stop()
{
ProxyServer.BeforeRequest -= OnRequest;
ProxyServer.BeforeResponse -= OnResponse;
proxyServer.BeforeRequest -= OnRequest;
proxyServer.BeforeResponse -= OnResponse;
proxyServer.ServerCertificateValidationCallback -= OnCertificateValidation;
proxyServer.ClientCertificateSelectionCallback -= OnCertificateSelection;
ProxyServer.Stop();
proxyServer.Stop();
}
//intecept & cancel, redirect or update requests
......
......@@ -35,6 +35,8 @@ After installing nuget package mark following files to be copied to app director
Setup HTTP proxy:
```csharp
var ProxyServer = new ProxyServer();
ProxyServer.BeforeRequest += OnRequest;
ProxyServer.BeforeResponse += OnResponse;
ProxyServer.ServerCertificateValidationCallback += OnCertificateValidation;
......
......@@ -17,7 +17,7 @@ namespace Titanium.Web.Proxy
/// <param name="chain"></param>
/// <param name="sslPolicyErrors"></param>
/// <returns></returns>
internal static bool ValidateServerCertificate(
internal bool ValidateServerCertificate(
object sender,
X509Certificate certificate,
X509Chain chain,
......@@ -62,7 +62,7 @@ namespace Titanium.Web.Proxy
/// <param name="chain"></param>
/// <param name="sslPolicyErrors"></param>
/// <returns></returns>
internal static X509Certificate SelectClientCertificate(
internal X509Certificate SelectClientCertificate(
object sender,
string targetHost,
X509CertificateCollection localCertificates,
......
......@@ -8,7 +8,7 @@ namespace Titanium.Web.Proxy.Decompression
/// </summary>
internal class DefaultDecompression : IDecompression
{
public Task<byte[]> Decompress(byte[] compressedArray)
public Task<byte[]> Decompress(byte[] compressedArray, int bufferSize)
{
return Task.FromResult(compressedArray);
}
......
......@@ -10,13 +10,13 @@ namespace Titanium.Web.Proxy.Decompression
/// </summary>
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);
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())
{
......
......@@ -10,11 +10,11 @@ namespace Titanium.Web.Proxy.Decompression
/// </summary>
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))
{
var buffer = new byte[ProxyConstants.BUFFER_SIZE];
var buffer = new byte[bufferSize];
using (var output = new MemoryStream())
{
int read;
......
......@@ -7,6 +7,6 @@ namespace Titanium.Web.Proxy.Decompression
/// </summary>
internal interface IDecompression
{
Task<byte[]> Decompress(byte[] compressedArray);
Task<byte[]> Decompress(byte[] compressedArray, int bufferSize);
}
}
......@@ -10,12 +10,12 @@ namespace Titanium.Web.Proxy.Decompression
/// </summary>
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);
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())
{
......
......@@ -20,14 +20,16 @@ namespace Titanium.Web.Proxy.EventArguments
/// </summary>
public class SessionEventArgs : EventArgs, IDisposable
{
/// <summary>
/// Constructor to initialize the proxy
/// Size of Buffers used by this object
/// </summary>
internal SessionEventArgs()
{
ProxyClient = new ProxyClient();
WebSession = new HttpWebClient();
}
private readonly int bufferSize;
/// <summary>
/// Holds a reference to proxy response handler method
/// </summary>
private readonly Func<SessionEventArgs, Task> httpResponseHandler;
/// <summary>
/// Holds a reference to client
......@@ -50,13 +52,18 @@ namespace Titanium.Web.Proxy.EventArguments
/// <summary>
/// implement any cleanup here
/// Constructor to initialize the proxy
/// </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>
/// Read request body content as bytes[] for current session
/// </summary>
......@@ -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
if (WebSession.Request.IsChunked)
{
await this.ProxyClient.ClientStreamReader.CopyBytesToStreamChunked(requestBodyStream);
await this.ProxyClient.ClientStreamReader.CopyBytesToStreamChunked(bufferSize, requestBodyStream);
}
else
{
......@@ -88,11 +95,11 @@ namespace Titanium.Web.Proxy.EventArguments
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 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)
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());
}
......@@ -117,18 +124,18 @@ namespace Titanium.Web.Proxy.EventArguments
//If chuncked the read chunk by chunk until we hit chunk end symbol
if (WebSession.Response.IsChunked)
{
await WebSession.ServerConnection.StreamReader.CopyBytesToStreamChunked(responseBodyStream);
await WebSession.ServerConnection.StreamReader.CopyBytesToStreamChunked(bufferSize, responseBodyStream);
}
else
{
if (WebSession.Response.ContentLength > 0)
{
//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)
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());
......@@ -285,7 +292,7 @@ namespace Titanium.Web.Proxy.EventArguments
var decompressionFactory = new DecompressionFactory();
var decompressor = decompressionFactory.Create(encodingType);
return await decompressor.Decompress(responseBodyStream);
return await decompressor.Decompress(responseBodyStream, bufferSize);
}
......@@ -338,7 +345,7 @@ namespace Titanium.Web.Proxy.EventArguments
WebSession.Request.CancelRequest = true;
}
/// a generic responder method
public async Task Respond(Response response)
{
......@@ -349,8 +356,15 @@ namespace Titanium.Web.Proxy.EventArguments
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
/// <param name="stream"></param>
/// <param name="totalBytesToRead"></param>
/// <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;
long bytesToRead;
if (totalBytesToRead < ProxyConstants.BUFFER_SIZE)
if (totalBytesToRead < bufferSize)
{
bytesToRead = totalBytesToRead;
}
else
bytesToRead = ProxyConstants.BUFFER_SIZE;
bytesToRead = bufferSize;
while (totalbytesRead < totalBytesToRead)
{
var buffer = await streamReader.ReadBytesAsync(bytesToRead);
var buffer = await streamReader.ReadBytesAsync(bufferSize, bytesToRead);
if (buffer.Length == 0)
break;
......@@ -72,7 +72,7 @@ namespace Titanium.Web.Proxy.Extensions
/// <param name="clientStreamReader"></param>
/// <param name="stream"></param>
/// <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)
{
......@@ -81,7 +81,7 @@ namespace Titanium.Web.Proxy.Extensions
if (chunkSize != 0)
{
var buffer = await clientStreamReader.ReadBytesAsync(chunkSize);
var buffer = await clientStreamReader.ReadBytesAsync(bufferSize, chunkSize);
await stream.WriteAsync(buffer, 0, buffer.Length);
//chunk trail
await clientStreamReader.ReadLineAsync();
......@@ -118,7 +118,7 @@ namespace Titanium.Web.Proxy.Extensions
/// <param name="isChunked"></param>
/// <param name="ContentLength"></param>
/// <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)
{
......@@ -126,12 +126,12 @@ namespace Titanium.Web.Proxy.Extensions
if (ContentLength == -1)
ContentLength = long.MaxValue;
int bytesToRead = ProxyConstants.BUFFER_SIZE;
int bytesToRead = bufferSize;
if (ContentLength < ProxyConstants.BUFFER_SIZE)
if (ContentLength < bufferSize)
bytesToRead = (int)ContentLength;
var buffer = new byte[ProxyConstants.BUFFER_SIZE];
var buffer = new byte[bufferSize];
var bytesRead = 0;
var totalBytesRead = 0;
......@@ -146,11 +146,11 @@ namespace Titanium.Web.Proxy.Extensions
bytesRead = 0;
var remainingBytes = (ContentLength - totalBytesRead);
bytesToRead = remainingBytes > (long)ProxyConstants.BUFFER_SIZE ? ProxyConstants.BUFFER_SIZE : (int)remainingBytes;
bytesToRead = remainingBytes > (long)bufferSize ? bufferSize : (int)remainingBytes;
}
}
else
await WriteResponseBodyChunked(inStreamReader, outStream);
await WriteResponseBodyChunked(inStreamReader, bufferSize, outStream);
}
/// <summary>
......@@ -159,7 +159,7 @@ namespace Titanium.Web.Proxy.Extensions
/// <param name="inStreamReader"></param>
/// <param name="outStream"></param>
/// <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)
{
......@@ -168,7 +168,7 @@ namespace Titanium.Web.Proxy.Extensions
if (chunkSize != 0)
{
var buffer = await inStreamReader.ReadBytesAsync(chunkSize);
var buffer = await inStreamReader.ReadBytesAsync(bufferSize, chunkSize);
var chunkHeadBytes = Encoding.ASCII.GetBytes(chunkSize.ToString("x2"));
......
......@@ -91,14 +91,14 @@ namespace Titanium.Web.Proxy.Helpers
/// </summary>
/// <param name="totalBytesToRead"></param>
/// <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;
var buffer = new byte[ProxyConstants.BUFFER_SIZE];
var buffer = new byte[bufferSize];
var bytesRead = 0;
var totalBytesRead = 0;
......@@ -115,7 +115,7 @@ namespace Titanium.Web.Proxy.Helpers
bytesRead = 0;
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();
......
......@@ -6,9 +6,9 @@ namespace Titanium.Web.Proxy.Helpers
/// <summary>
/// A helper class to set proxy settings for firefox
/// </summary>
public class FireFoxHelper
public class FireFoxProxySettingsManager
{
public static void AddFirefox()
public void AddFirefox()
{
try
{
......@@ -38,7 +38,7 @@ namespace Titanium.Web.Proxy.Helpers
}
}
public static void RemoveFirefox()
public void RemoveFirefox()
{
try
{
......
......@@ -11,7 +11,7 @@ using System.Linq;
namespace Titanium.Web.Proxy.Helpers
{
internal static class NativeMethods
internal class NativeMethods
{
[DllImport("wininet.dll")]
internal static extern bool InternetSetOption(IntPtr hInternet, int dwOption, IntPtr lpBuffer,
......@@ -32,13 +32,15 @@ namespace Titanium.Web.Proxy.Helpers
return "https=" + HostName + ":" + Port;
}
}
internal static class SystemProxyHelper
/// <summary>
/// Manage system proxy settings
/// </summary>
internal class SystemProxyManager
{
internal const int InternetOptionSettingsChanged = 39;
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(
"Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings", true);
......@@ -66,7 +68,7 @@ namespace Titanium.Web.Proxy.Helpers
/// <summary>
/// Remove the http proxy setting from current machine
/// </summary>
internal static void RemoveHttpProxy()
internal void RemoveHttpProxy()
{
var reg = Registry.CurrentUser.OpenSubKey(
"Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings", true);
......@@ -100,7 +102,7 @@ namespace Titanium.Web.Proxy.Helpers
/// </summary>
/// <param name="hostname"></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(
"Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings", true);
......@@ -130,7 +132,7 @@ namespace Titanium.Web.Proxy.Helpers
/// <summary>
/// Removes the https proxy setting to nothing
/// </summary>
internal static void RemoveHttpsProxy()
internal void RemoveHttpsProxy()
{
var reg = Registry.CurrentUser.OpenSubKey(
"Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings", true);
......@@ -163,7 +165,7 @@ namespace Titanium.Web.Proxy.Helpers
/// <summary>
/// Removes all types of proxy settings (both http & https)
/// </summary>
internal static void DisableAllProxy()
internal void DisableAllProxy()
{
var reg = Registry.CurrentUser.OpenSubKey(
"Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings", true);
......@@ -182,7 +184,7 @@ namespace Titanium.Web.Proxy.Helpers
/// </summary>
/// <param name="prevServerValue"></param>
/// <returns></returns>
private static List<HttpSystemProxyValue> GetSystemProxyValues(string prevServerValue)
private List<HttpSystemProxyValue> GetSystemProxyValues(string prevServerValue)
{
var result = new List<HttpSystemProxyValue>();
......@@ -215,7 +217,7 @@ namespace Titanium.Web.Proxy.Helpers
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
private static HttpSystemProxyValue parseProxyValue(string value)
private HttpSystemProxyValue parseProxyValue(string value)
{
var tmp = Regex.Replace(value, @"\s+", " ").Trim().ToLower();
if (tmp.StartsWith("http="))
......@@ -245,7 +247,7 @@ namespace Titanium.Web.Proxy.Helpers
/// Prepares the proxy server registry (create empty values if they don't exist)
/// </summary>
/// <param name="reg"></param>
private static void prepareRegistry(RegistryKey reg)
private void prepareRegistry(RegistryKey reg)
{
if (reg.GetValue("ProxyEnable") == null)
{
......@@ -262,7 +264,7 @@ namespace Titanium.Web.Proxy.Helpers
/// <summary>
/// Refresh the settings so that the system know about a change in proxy setting
/// </summary>
private static void Refresh()
private void Refresh()
{
NativeMethods.InternetSetOption(IntPtr.Zero, InternetOptionSettingsChanged, IntPtr.Zero, 0);
NativeMethods.InternetSetOption(IntPtr.Zero, InternetOptionRefresh, IntPtr.Zero, 0);
......
......@@ -4,6 +4,7 @@ using System.IO;
using System.Linq;
using System.Net.Security;
using System.Net.Sockets;
using System.Security.Authentication;
using System.Text;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Extensions;
......@@ -12,7 +13,7 @@ using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Helpers
{
internal class TcpHelper
{
/// <summary>
......@@ -26,8 +27,8 @@ namespace Titanium.Web.Proxy.Helpers
/// <param name="tunnelPort"></param>
/// <param name="isHttps"></param>
/// <returns></returns>
internal async static Task SendRaw(Stream clientStream, string httpCmd, List<HttpHeader> requestHeaders, string hostName,
int tunnelPort, bool isHttps)
internal static async Task SendRaw(Stream clientStream, string httpCmd, List<HttpHeader> requestHeaders, string hostName,
int tunnelPort, bool isHttps, SslProtocols supportedProtocols)
{
//prepare the prefix content
StringBuilder sb = null;
......@@ -63,7 +64,7 @@ namespace Titanium.Web.Proxy.Helpers
try
{
sslStream = new SslStream(tunnelStream);
await sslStream.AuthenticateAsClientAsync(hostName, null, ProxyConstants.SupportedSslProtocols, false);
await sslStream.AuthenticateAsClientAsync(hostName, null, supportedProtocols, false);
tunnelStream = sslStream;
}
catch
......
......@@ -17,7 +17,7 @@ namespace Titanium.Web.Proxy.Http
/// <summary>
/// Connection to server
/// </summary>
internal TcpConnection ServerConnection { get; set; }
internal TcpConnectionCache ServerConnection { get; set; }
public Request Request { get; set; }
public Response Response { get; set; }
......@@ -37,7 +37,7 @@ namespace Titanium.Web.Proxy.Http
/// Set the tcp connection to server used by this webclient
/// </summary>
/// <param name="Connection"></param>
internal void SetConnection(TcpConnection Connection)
internal void SetConnection(TcpConnectionCache Connection)
{
Connection.LastAccess = DateTime.Now;
ServerConnection = Connection;
......@@ -53,7 +53,7 @@ namespace Titanium.Web.Proxy.Http
/// Prepare & send the http(s) request
/// </summary>
/// <returns></returns>
internal async Task SendRequest()
internal async Task SendRequest(bool enable100ContinueBehaviour)
{
Stream stream = ServerConnection.Stream;
......@@ -79,7 +79,7 @@ namespace Titanium.Web.Proxy.Http
await stream.WriteAsync(requestBytes, 0, requestBytes.Length);
await stream.FlushAsync();
if (ProxyServer.Enable100ContinueBehaviour)
if (enable100ContinueBehaviour)
if (this.Request.ExpectContinue)
{
var httpResult = (await ServerConnection.StreamReader.ReadLineAsync()).Split(ProxyConstants.SpaceSplit, 3);
......@@ -133,18 +133,22 @@ namespace Titanium.Web.Proxy.Http
if (this.Response.ResponseStatusCode.Equals("100")
&& this.Response.ResponseStatusDescription.ToLower().Equals("continue"))
{
//Read the next line after 100-continue
this.Response.Is100Continue = true;
this.Response.ResponseStatusCode = null;
await ServerConnection.StreamReader.ReadLineAsync();
//now receive response
await ReceiveResponse();
return;
}
else if (this.Response.ResponseStatusCode.Equals("417")
&& this.Response.ResponseStatusDescription.ToLower().Equals("expectation failed"))
{
//read next line after expectation failed response
this.Response.ExpectationFailed = true;
this.Response.ResponseStatusCode = null;
await ServerConnection.StreamReader.ReadLineAsync();
//now receive response
await ReceiveResponse();
return;
}
......
......@@ -18,8 +18,15 @@ namespace Titanium.Web.Proxy.Network
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}";
/// <summary>
/// Cache dictionary
/// </summary>
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 RootCertificateName { get; private set; }
......@@ -241,7 +248,7 @@ namespace Titanium.Web.Proxy.Network
return certCreatArgs;
}
private static bool clearCertificates { get; set; }
private bool clearCertificates { get; set; }
/// <summary>
/// Stops the certificate cache clear process
......@@ -254,7 +261,7 @@ namespace Titanium.Web.Proxy.Network
/// <summary>
/// A method to clear outdated certificates
/// </summary>
internal async void ClearIdleCertificates()
internal async void ClearIdleCertificates(int certificateCacheTimeOutMinutes)
{
clearCertificates = true;
while (clearCertificates)
......@@ -263,7 +270,7 @@ namespace Titanium.Web.Proxy.Network
try
{
var cutOff = DateTime.Now.AddMinutes(-1 * ProxyServer.CertificateCacheTimeOutMinutes);
var cutOff = DateTime.Now.AddMinutes(-1 * certificateCacheTimeOutMinutes);
var outdated = certificateCache
.Where(x => x.Value.LastAccess < cutOff)
......
......@@ -8,7 +8,7 @@ namespace Titanium.Web.Proxy.Network
/// <summary>
/// An object that holds TcpConnection to a particular server & port
/// </summary>
public class TcpConnection
public class TcpConnectionCache
{
internal string HostName { get; set; }
internal int port { get; set; }
......@@ -37,7 +37,7 @@ namespace Titanium.Web.Proxy.Network
/// </summary>
internal DateTime LastAccess { get; set; }
internal TcpConnection()
internal TcpConnectionCache()
{
LastAccess = DateTime.Now;
}
......
......@@ -10,23 +10,25 @@ using Titanium.Web.Proxy.Helpers;
using System.Threading;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Shared;
using Titanium.Web.Proxy.Models;
using System.Security.Authentication;
namespace Titanium.Web.Proxy.Network
{
/// <summary>
/// A class that manages Tcp Connection to server used by this proxy server
/// </summary>
internal class TcpConnectionManager
internal class TcpConnectionCacheManager
{
/// <summary>
/// Connection cache
/// </summary>
static Dictionary<string, List<TcpConnection>> connectionCache = new Dictionary<string, List<TcpConnection>>();
Dictionary<string, List<TcpConnectionCache>> connectionCache = new Dictionary<string, List<TcpConnectionCache>>();
/// <summary>
/// A lock to manage concurrency
/// </summary>
static SemaphoreSlim connectionAccessLock = new SemaphoreSlim(1);
SemaphoreSlim connectionAccessLock = new SemaphoreSlim(1);
/// <summary>
/// Get a TcpConnection to the specified host, port optionally HTTPS and a particular HTTP version
......@@ -36,10 +38,12 @@ namespace Titanium.Web.Proxy.Network
/// <param name="isHttps"></param>
/// <param name="version"></param>
/// <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;
TcpConnection cached = null;
List<TcpConnectionCache> cachedConnections = null;
TcpConnectionCache cached = null;
//Get a unique string to identify this connection
var key = GetConnectionKey(hostname, port, isHttps, version);
......@@ -77,12 +81,14 @@ namespace Titanium.Web.Proxy.Network
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)
{
var task = CreateClient(hostname, port, isHttps, version)
.ContinueWith(async (x) => { if (x.Status == TaskStatus.RanToCompletion) await ReleaseClient(x.Result); });
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); });
}
return cached;
......@@ -96,7 +102,7 @@ namespace Titanium.Web.Proxy.Network
/// <param name="isHttps"></param>
/// <param name="version"></param>
/// <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);
}
......@@ -109,7 +115,9 @@ namespace Titanium.Web.Proxy.Network
/// <param name="isHttps"></param>
/// <param name="version"></param>
/// <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;
Stream stream;
......@@ -119,12 +127,12 @@ namespace Titanium.Web.Proxy.Network
SslStream sslStream = null;
//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();
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("Host: {0}:{1}", hostname, port));
......@@ -152,9 +160,9 @@ namespace Titanium.Web.Proxy.Network
try
{
sslStream = new SslStream(stream, true, new RemoteCertificateValidationCallback(ProxyServer.ValidateServerCertificate),
new LocalCertificateSelectionCallback(ProxyServer.SelectClientCertificate));
await sslStream.AuthenticateAsClientAsync(hostname, null, ProxyConstants.SupportedSslProtocols, false);
sslStream = new SslStream(stream, true, remoteCertificateValidationCallBack,
localCertificateSelectionCallback);
await sslStream.AuthenticateAsClientAsync(hostname, null, supportedSslProtocols, false);
stream = (Stream)sslStream;
}
catch
......@@ -166,9 +174,9 @@ namespace Titanium.Web.Proxy.Network
}
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();
}
else
......@@ -178,7 +186,7 @@ namespace Titanium.Web.Proxy.Network
}
}
return new TcpConnection()
return new TcpConnectionCache()
{
HostName = hostname,
port = port,
......@@ -195,7 +203,7 @@ namespace Titanium.Web.Proxy.Network
/// </summary>
/// <param name="connection"></param>
/// <returns></returns>
internal static async Task ReleaseClient(TcpConnection connection)
internal async Task ReleaseClient(TcpConnectionCache connection)
{
connection.LastAccess = DateTime.Now;
......@@ -203,25 +211,25 @@ namespace Titanium.Web.Proxy.Network
await connectionAccessLock.WaitAsync();
try
{
List<TcpConnection> cachedConnections;
List<TcpConnectionCache> cachedConnections;
connectionCache.TryGetValue(key, out cachedConnections);
if (cachedConnections != null)
cachedConnections.Add(connection);
else
connectionCache.Add(key, new List<TcpConnection>() { connection });
connectionCache.Add(key, new List<TcpConnectionCache>() { connection });
}
finally { connectionAccessLock.Release(); }
}
private static bool clearConenctions { get; set; }
private bool clearConenctions { get; set; }
/// <summary>
/// Stop clearing idle connections
/// </summary>
internal static void StopClearIdleConnections()
internal void StopClearIdleConnections()
{
clearConenctions = false;
}
......@@ -229,7 +237,7 @@ namespace Titanium.Web.Proxy.Network
/// <summary>
/// A method to clear idle connections
/// </summary>
internal async static void ClearIdleConnections()
internal async void ClearIdleConnections(int connectionCacheTimeOutMinutes)
{
clearConenctions = true;
while (clearConenctions)
......@@ -237,7 +245,7 @@ namespace Titanium.Web.Proxy.Network
await connectionAccessLock.WaitAsync();
try
{
var cutOff = DateTime.Now.AddMinutes(-1 * ProxyServer.ConnectionCacheTimeOutMinutes);
var cutOff = DateTime.Now.AddMinutes(-1 * connectionCacheTimeOutMinutes);
connectionCache
.SelectMany(x => x.Value)
......
This diff is collapsed.
This diff is collapsed.
......@@ -17,7 +17,7 @@ namespace Titanium.Web.Proxy
partial class ProxyServer
{
//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
await args.WebSession.ReceiveResponse();
......@@ -83,9 +83,14 @@ namespace Titanium.Web.Proxy
{
await WriteResponseHeaders(args.ProxyClient.ClientStreamWriter, args.WebSession.Response.ResponseHeaders);
if (args.WebSession.Response.IsChunked || args.WebSession.Response.ContentLength > 0 ||
(args.WebSession.Response.HttpVersion.Major == 1 && args.WebSession.Response.HttpVersion.Minor == 0))
await args.WebSession.ServerConnection.StreamReader.WriteResponseBody(args.ProxyClient.ClientStream, args.WebSession.Response.IsChunked, args.WebSession.Response.ContentLength);
//Write body only if response is chunked or content length >0
//Is none are true then check if connection:close header exist, if so write response until server or client terminates the connection
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();
......@@ -107,7 +112,7 @@ namespace Titanium.Web.Proxy
/// <param name="encodingType"></param>
/// <param name="responseBodyStream"></param>
/// <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 compressor = compressionFactory.Create(encodingType);
......@@ -122,7 +127,7 @@ namespace Titanium.Web.Proxy
/// <param name="description"></param>
/// <param name="responseWriter"></param>
/// <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)
{
await responseWriter.WriteLineAsync(string.Format("HTTP/{0}.{1} {2} {3}", version.Major, version.Minor, code, description));
......@@ -134,7 +139,7 @@ namespace Titanium.Web.Proxy
/// <param name="responseWriter"></param>
/// <param name="headers"></param>
/// <returns></returns>
private static async Task WriteResponseHeaders(StreamWriter responseWriter, List<HttpHeader> headers)
private async Task WriteResponseHeaders(StreamWriter responseWriter, List<HttpHeader> headers)
{
if (headers != null)
{
......@@ -154,7 +159,7 @@ namespace Titanium.Web.Proxy
/// Fix the proxy specific headers before sending response headers to client
/// </summary>
/// <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
var proxyHeader = headers.FirstOrDefault(x => x.Name.ToLower() == "proxy-connection");
......@@ -172,7 +177,7 @@ namespace Titanium.Web.Proxy
headers.RemoveAll(x => x.Name.ToLower() == "proxy-connection");
}
/// <summary>
/// Handle dispose of a client/server session
/// </summary>
......@@ -181,7 +186,7 @@ namespace Titanium.Web.Proxy
/// <param name="clientStreamReader"></param>
/// <param name="clientStreamWriter"></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)
{
if (args != null)
......
using System;
using System.Security.Authentication;
using System.Text;
namespace Titanium.Web.Proxy.Shared
......@@ -9,7 +8,7 @@ namespace Titanium.Web.Proxy.Shared
/// </summary>
internal class ProxyConstants
{
internal static readonly char[] SpaceSplit = { ' ' };
internal static readonly char[] SpaceSplit = { ' ' };
internal static readonly char[] ColonSplit = { ':' };
internal static readonly char[] SemiColonSplit = { ';' };
......@@ -18,8 +17,5 @@ namespace Titanium.Web.Proxy.Shared
internal static readonly byte[] ChunkEnd =
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 @@
<Compile Include="Http\Request.cs" />
<Compile Include="Http\Response.cs" />
<Compile Include="Models\ExternalProxy.cs" />
<Compile Include="Network\TcpConnection.cs" />
<Compile Include="Network\TcpConnectionManager.cs" />
<Compile Include="Network\TcpConnectionCache.cs" />
<Compile Include="Network\TcpConnectionCacheManager.cs" />
<Compile Include="Models\HttpHeader.cs" />
<Compile Include="Http\HttpWebClient.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