Unverified Commit 63ce4b3d authored by honfika's avatar honfika Committed by GitHub

Merge pull request #376 from justcoding121/develop

Beta
parents e817f10c f11b6b60
......@@ -170,18 +170,18 @@ namespace Titanium.Web.Proxy.Examples.Basic
//requestBodyHistory[e.Id] = bodyString;
}
////To cancel a request with a custom HTML content
////Filter URL
//if (e.WebSession.Request.RequestUri.AbsoluteUri.Contains("google.com"))
//{
// await e.Ok("<!DOCTYPE html>" +
// "<html><body><h1>" +
// "Website Blocked" +
// "</h1>" +
// "<p>Blocked by titanium web proxy.</p>" +
// "</body>" +
// "</html>");
//}
//To cancel a request with a custom HTML content
//Filter URL
if (e.WebSession.Request.RequestUri.AbsoluteUri.Contains("google.com"))
{
await e.Ok("<!DOCTYPE html>" +
"<html><body><h1>" +
"Website Blocked" +
"</h1>" +
"<p>Blocked by titanium web proxy.</p>" +
"</body>" +
"</html>");
}
////Redirect example
//if (e.WebSession.Request.RequestUri.AbsoluteUri.Contains("wikipedia.org"))
......
using System;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
......@@ -63,10 +63,35 @@ namespace Titanium.Web.Proxy.Examples.Wpf
{
proxyServer = new ProxyServer();
//proxyServer.CertificateEngine = CertificateEngine.DefaultWindows;
////Set a password for the .pfx file
//proxyServer.PfxPassword = "PfxPassword";
////Set Name(path) of the Root certificate file
//proxyServer.PfxFilePath = @"C:\NameFolder\rootCert.pfx";
////do you want Replace an existing Root certificate file(.pfx) if password is incorrect(RootCertificate=null)? yes====>true
//proxyServer.OverwritePfxFile = true;
////save all fake certificates in folder "crts"(will be created in proxy dll directory)
////if create new Root certificate file(.pfx) ====> delete folder "crts"
//proxyServer.SaveFakeCertificates = true;
//Trust Root Certificate
proxyServer.TrustRootCertificate = true;
proxyServer.CertificateManager.TrustRootCertificateAsAdministrator();
proxyServer.TrustRootCertificateAsAdministrator = true;
proxyServer.ForwardToUpstreamGateway = true;
////if you need Load or Create Certificate now. ////// "true" if you need Enable===> Trust the RootCertificate used by this proxy server
//proxyServer.EnsureRootCertificate(true);
////or load directly certificate(As Administrator if need this)
////and At the same time chose path and password
////if password is incorrect and (overwriteRootCert=true)(RootCertificate=null) ====> replace an existing .pfx file
////note : load now (if existed)
//proxyServer.CertificateManager.LoadRootCertificate(@"C:\NameFolder\rootCert.pfx", "PfxPassword");
var explicitEndPoint = new ExplicitProxyEndPoint(IPAddress.Any, 8000, true)
{
ExcludedHttpsHostNameRegex = new[] { "ssllabs.com" },
......
......@@ -29,11 +29,6 @@ namespace Titanium.Web.Proxy.EventArguments
/// </summary>
private readonly int bufferSize;
/// <summary>
/// Holds a reference to proxy response handler method
/// </summary>
private Func<SessionEventArgs, Task> httpResponseHandler;
private readonly Action<Exception> exceptionFunc;
/// <summary>
......@@ -108,11 +103,9 @@ namespace Titanium.Web.Proxy.EventArguments
/// </summary>
internal SessionEventArgs(int bufferSize,
ProxyEndPoint endPoint,
Func<SessionEventArgs, Task> httpResponseHandler,
Action<Exception> exceptionFunc)
{
this.bufferSize = bufferSize;
this.httpResponseHandler = httpResponseHandler;
this.exceptionFunc = exceptionFunc;
ProxyClient = new ProxyClient();
......@@ -536,16 +529,18 @@ namespace Titanium.Web.Proxy.EventArguments
/// </summary>
/// <param name="html"></param>
/// <param name="headers"></param>
public async Task Ok(string html, Dictionary<string, HttpHeader> headers)
public async Task Ok(string html, Dictionary<string, HttpHeader> headers = null)
{
var response = new OkResponse();
if (headers != null)
{
response.Headers.AddHeaders(headers);
}
response.HttpVersion = WebSession.Request.HttpVersion;
response.Body = response.Encoding.GetBytes(html ?? string.Empty);
await Respond(response);
WebSession.Request.CancelRequest = true;
}
/// <summary>
......@@ -635,8 +630,6 @@ namespace Titanium.Web.Proxy.EventArguments
WebSession.Response = response;
await httpResponseHandler(this);
WebSession.Request.CancelRequest = true;
}
......@@ -645,7 +638,6 @@ namespace Titanium.Web.Proxy.EventArguments
/// </summary>
public void Dispose()
{
httpResponseHandler = null;
CustomUpStreamProxyUsed = null;
DataSent = null;
......
......@@ -9,7 +9,7 @@ namespace Titanium.Web.Proxy.EventArguments
public bool IsHttpsConnect { get; set; }
internal TunnelConnectSessionEventArgs(int bufferSize, ProxyEndPoint endPoint, ConnectRequest connectRequest, Action<Exception> exceptionFunc)
: base(bufferSize, endPoint, null, exceptionFunc)
: base(bufferSize, endPoint, exceptionFunc)
{
WebSession.Request = connectRequest;
}
......
using System;
using System;
using System.IO;
using System.Security.Cryptography.X509Certificates;
using System.Text.RegularExpressions;
using System.Threading;
using Org.BouncyCastle.Asn1;
using Org.BouncyCastle.Asn1.Pkcs;
......@@ -24,6 +25,8 @@ namespace Titanium.Web.Proxy.Network.Certificate
/// </summary>
internal class BCCertificateMaker : ICertificateMaker
{
public static readonly Regex CNRemoverRegex = new Regex(@"^CN\s*=\s*", RegexOptions.IgnoreCase | RegexOptions.Compiled);
private const int certificateValidDays = 1825;
private const int certificateGraceDays = 366;
......@@ -146,7 +149,7 @@ namespace Titanium.Web.Proxy.Network.Certificate
{
try
{
x509Certificate.FriendlyName = subjectName;
x509Certificate.FriendlyName = CNRemoverRegex.Replace(subjectName, string.Empty);
}
catch (PlatformNotSupportedException)
{
......
using System;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
......@@ -69,10 +69,24 @@ namespace Titanium.Web.Proxy.Network
private string rootCertificateName;
private bool pfxFileExists = false;
private bool clearCertificates { get; set; }
private X509Certificate2 rootCertificate;
internal bool trustRootCertificate { get; set; } = false;
internal bool OverwritePfXFile { get; set; } = true;
internal string PfxPassword { get; set; } = string.Empty;
internal string PfxFilePath { get; set; } = string.Empty;
internal X509KeyStorageFlags StorageFlag { get; set; } = X509KeyStorageFlags.Exportable;
internal bool SaveFakeCertificates { get; set; } = false;
/// <summary>
/// Cache dictionary
/// </summary>
......@@ -86,7 +100,7 @@ namespace Titanium.Web.Proxy.Network
set
{
issuer = value;
ClearRootCertificate();
//ClearRootCertificate();
}
}
......@@ -96,7 +110,7 @@ namespace Titanium.Web.Proxy.Network
set
{
rootCertificateName = value;
ClearRootCertificate();
//ClearRootCertificate();
}
}
......@@ -115,7 +129,6 @@ namespace Titanium.Web.Proxy.Network
/// </summary>
internal bool CertValidated => RootCertificate != null;
internal CertificateManager(Action<Exception> exceptionFunc)
{
this.exceptionFunc = exceptionFunc;
......@@ -124,13 +137,13 @@ namespace Titanium.Web.Proxy.Network
certificateCache = new ConcurrentDictionary<string, CachedCertificate>();
}
private void ClearRootCertificate()
public void ClearRootCertificate()
{
certificateCache.Clear();
rootCertificate = null;
}
private string GetRootCertificatePath()
private string GetRootCertificateDirectory()
{
string assemblyLocation = Assembly.GetExecutingAssembly().Location;
......@@ -143,18 +156,49 @@ namespace Titanium.Web.Proxy.Network
string path = Path.GetDirectoryName(assemblyLocation);
if (null == path)
throw new NullReferenceException();
string fileName = Path.Combine(path, "rootCert.pfx");
return path;
}
private string GetCertPath()
{
string path = GetRootCertificateDirectory();
string certPath = Path.Combine(path, "crts");
if (!Directory.Exists(certPath))
{
Directory.CreateDirectory(certPath);
}
return certPath;
}
private string GetRootCertificatePath()
{
string path = GetRootCertificateDirectory();
string fileName = PfxFilePath;
if (fileName == string.Empty)
{
fileName = Path.Combine(path, "rootCert.pfx");
StorageFlag = X509KeyStorageFlags.Exportable;
}
return fileName;
}
private X509Certificate2 LoadRootCertificate()
public X509Certificate2 LoadRootCertificate()
{
string fileName = GetRootCertificatePath();
if (!File.Exists(fileName))
pfxFileExists = File.Exists(fileName);
if (!pfxFileExists)
{
return null;
}
try
{
return new X509Certificate2(fileName, string.Empty, X509KeyStorageFlags.Exportable);
return new X509Certificate2(fileName, PfxPassword, StorageFlag);
}
catch (Exception e)
{
......@@ -182,6 +226,11 @@ namespace Titanium.Web.Proxy.Network
return true;
}
if (!OverwritePfXFile && pfxFileExists)
{
return false;
}
try
{
RootCertificate = CreateCertificate(RootCertificateName, true);
......@@ -195,8 +244,17 @@ namespace Titanium.Web.Proxy.Network
{
try
{
try
{
Directory.Delete(GetCertPath(), true);
}
catch
{
// ignore
}
string fileName = GetRootCertificatePath();
File.WriteAllBytes(fileName, RootCertificate.Export(X509ContentType.Pkcs12));
File.WriteAllBytes(fileName, RootCertificate.Export(X509ContentType.Pkcs12, PfxPassword));
}
catch (Exception e)
{
......@@ -207,6 +265,28 @@ namespace Titanium.Web.Proxy.Network
return RootCertificate != null;
}
/// <summary>
/// Manually load a Root certificate file(.pfx file)
/// </summary>
/// <param name="pfxFilePath">Set the name(path) of the .pfx file. If it is string.Empty Root certificate file will be named as "rootCert.pfx" (and will be saved in proxy dll directory)</param>
/// <param name="password">Set a password for the .pfx file</param>
/// <param name="overwritePfXFile">true : replace an existing .pfx file if password is incorect or if RootCertificate==null</param>
/// <param name="storageFlag"></param>
/// <returns>
/// true if succeeded, else false
/// </returns>
public bool LoadRootCertificate(string pfxFilePath, string password, bool overwritePfXFile = true, X509KeyStorageFlags storageFlag = X509KeyStorageFlags.Exportable)
{
PfxFilePath = pfxFilePath;
PfxPassword = password;
OverwritePfXFile = overwritePfXFile;
StorageFlag = storageFlag;
RootCertificate = LoadRootCertificate();
return (RootCertificate != null);
}
/// <summary>
/// Trusts the root certificate.
/// </summary>
......@@ -232,12 +312,12 @@ namespace Titanium.Web.Proxy.Network
}
string fileName = Path.GetTempFileName();
File.WriteAllBytes(fileName, RootCertificate.Export(X509ContentType.Pkcs12));
File.WriteAllBytes(fileName, RootCertificate.Export(X509ContentType.Pkcs12, PfxPassword));
var info = new ProcessStartInfo
{
FileName = "certutil.exe",
Arguments = "-importPFX -p \"\" -f \"" + fileName + "\"",
Arguments = "-importPFX -p \""+ PfxPassword + "\" -f \"" + fileName + "\"",
CreateNoWindow = true,
UseShellExecute = true,
Verb = "runas",
......@@ -352,6 +432,16 @@ namespace Titanium.Web.Proxy.Network
}
}
private X509Certificate2 MakeCertificate(string certificateName, bool isRootCertificate)
{
if (!isRootCertificate && RootCertificate == null)
{
CreateTrustedRootCertificate();
}
return certEngine.MakeCertificate(certificateName, isRootCertificate, RootCertificate);
}
/// <summary>
/// Create an SSL certificate
/// </summary>
......@@ -374,17 +464,40 @@ namespace Titanium.Web.Proxy.Network
{
try
{
if (!isRootCertificate && RootCertificate == null)
if (!isRootCertificate && SaveFakeCertificates)
{
CreateTrustedRootCertificate();
}
string path = GetCertPath();
string subjectName = BCCertificateMaker.CNRemoverRegex.Replace(certificateName, string.Empty);
subjectName = subjectName.Replace("*", "$x$");
subjectName = Path.Combine(path, subjectName + ".pfx");
certificate = certEngine.MakeCertificate(certificateName, isRootCertificate, RootCertificate);
if (!File.Exists(subjectName))
{
certificate = MakeCertificate(certificateName, isRootCertificate);
File.WriteAllBytes(subjectName, certificate.Export(X509ContentType.Pkcs12));
}
else
{
try
{
certificate = new X509Certificate2(subjectName, string.Empty, StorageFlag);
}
catch /* (Exception e)*/
{
certificate = MakeCertificate(certificateName, isRootCertificate);
}
}
}
else
{
certificate = MakeCertificate(certificateName, isRootCertificate);
}
}
catch (Exception e)
{
exceptionFunc(e);
}
if (certificate != null && !certificateCache.ContainsKey(certificateName))
{
certificateCache.Add(certificateName, new CachedCertificate
......
......@@ -88,10 +88,10 @@ namespace Titanium.Web.Proxy.Network.Tcp
{
string httpStatus = await reader.ReadLineAsync();
Response.ParseResponseLine(httpStatus, out var version, out int statusCode, out string statusDescription);
Response.ParseResponseLine(httpStatus, out _, out int statusCode, out string statusDescription);
if (!statusDescription.EqualsIgnoreCase("200 OK")
&& !statusDescription.EqualsIgnoreCase("connection established"))
if (statusCode != 200 && !statusDescription.EqualsIgnoreCase("OK")
&& !statusDescription.EqualsIgnoreCase("Connection Established"))
{
throw new Exception("Upstream proxy failed to create a secure tunnel");
}
......
......@@ -17,7 +17,7 @@ namespace Titanium.Web.Proxy.Network.Tcp
/// <param name="tcpRows">TcpRow collection to initialize with.</param>
internal TcpTable(IEnumerable<TcpRow> tcpRows)
{
this.TcpRows = tcpRows;
TcpRows = tcpRows;
}
/// <summary>
......
using StreamExtended.Network;
using StreamExtended.Network;
using System;
using System.Collections.Generic;
using System.Linq;
......@@ -37,11 +37,6 @@ namespace Titanium.Web.Proxy
/// </summary>
private Action<Exception> exceptionFunc;
/// <summary>
/// Backing field for corresponding public property
/// </summary>
private bool trustRootCertificate;
/// <summary>
/// Backing field for corresponding public property
/// </summary>
......@@ -52,6 +47,8 @@ namespace Titanium.Web.Proxy
/// </summary>
private int serverConnectionCount;
private X509KeyStorageFlags storageFlag = X509KeyStorageFlags.Exportable;
/// <summary>
/// A object that creates tcp connection to server
/// </summary>
......@@ -118,14 +115,64 @@ namespace Titanium.Web.Proxy
/// </summary>
public bool TrustRootCertificate
{
get => trustRootCertificate;
set
get => CertificateManager.trustRootCertificate;
set => CertificateManager.trustRootCertificate = value;
}
/// <summary>
/// Needs elevated permission. Works only on Windows.
/// <para>Puts the certificate to the local machine's certificate store.</para>
/// <para>Certutil.exe is a command-line program that is installed as part of Certificate Services</para>
/// </summary>
public bool TrustRootCertificateAsAdministrator { get; set; } = false;
/// <summary>
/// Save all fake certificates in folder "crts"(will be created in proxy dll directory)
/// <para>for can load the certificate and not make new certificate every time </para>
/// </summary>
public bool SaveFakeCertificates
{
get => CertificateManager.SaveFakeCertificates;
set => CertificateManager.SaveFakeCertificates = value;
}
/// <summary>
/// Overwrite Root certificate file
/// <para>true : replace an existing .pfx file if password is incorect or if RootCertificate = null</para>
/// </summary>
public bool OverwritePfxFile
{
get => CertificateManager.OverwritePfXFile;
set => CertificateManager.OverwritePfXFile = value;
}
/// <summary>
/// Password of the Root certificate file
/// <para>Set a password for the .pfx file</para>
/// </summary>
public string PfxPassword
{
trustRootCertificate = value;
if (value)
get => CertificateManager.PfxPassword;
set => CertificateManager.PfxPassword = value;
}
/// <summary>
/// Name(path) of the Root certificate file
/// <para>Set the name(path) of the .pfx file. If it is string.Empty Root certificate file will be named as "rootCert.pfx" (and will be saved in proxy dll directory)</para>
/// </summary>
public string PfxFilePath
{
EnsureRootCertificate();
get => CertificateManager.PfxFilePath;
set => CertificateManager.PfxFilePath = value;
}
public X509KeyStorageFlags StorageFlag
{
get => storageFlag;
set
{
storageFlag = value;
CertificateManager.StorageFlag = storageFlag;
}
}
......@@ -373,7 +420,6 @@ namespace Titanium.Web.Proxy
SetAsSystemProxy(endPoint, ProxyProtocolType.Http);
}
/// <summary>
/// Set the given explicit end point as the default proxy server for current machine
/// </summary>
......@@ -520,6 +566,11 @@ namespace Titanium.Web.Proxy
throw new Exception("Proxy is already running.");
}
if (ProxyEndPoints.OfType<ExplicitProxyEndPoint>().Any(x => x.GenericCertificate == null))
{
EnsureRootCertificate();
}
//clear any system proxy settings which is pointing to our own endpoint (causing a cycle)
//due to non gracious proxy shutdown before or something else
if (systemProxySettingsManager != null && RunTime.IsWindows)
......@@ -571,7 +622,6 @@ namespace Titanium.Web.Proxy
}
}
/// <summary>
/// Stop this proxy server
/// </summary>
......@@ -604,26 +654,6 @@ namespace Titanium.Web.Proxy
ProxyRunning = false;
}
/// <summary>
/// Handle dispose of a client/server session
/// </summary>
/// <param name="clientStream"></param>
/// <param name="clientStreamReader"></param>
/// <param name="clientStreamWriter"></param>
/// <param name="serverConnection"></param>
private void Dispose(CustomBufferedStream clientStream, CustomBinaryReader clientStreamReader, HttpResponseWriter clientStreamWriter, TcpConnection serverConnection)
{
clientStreamReader?.Dispose();
clientStream?.Dispose();
if (serverConnection != null)
{
serverConnection.Dispose();
serverConnection = null;
}
}
/// <summary>
/// Dispose Proxy.
/// </summary>
......@@ -692,7 +722,17 @@ namespace Titanium.Web.Proxy
return Task.FromResult(proxy);
}
private void EnsureRootCertificate()
/// <summary>
/// Load or Create Certificate : after "Test Is the root certificate used by this proxy is valid?"
/// <param name="trustRootCertificate">"Make current machine trust the Root Certificate used by this proxy" ==> True or False</param>
/// </summary>
public void EnsureRootCertificate(bool trustRootCertificate)
{
TrustRootCertificate = trustRootCertificate;
EnsureRootCertificate();
}
public void EnsureRootCertificate()
{
if (!CertificateManager.CertValidated)
{
......@@ -702,6 +742,11 @@ namespace Titanium.Web.Proxy
{
CertificateManager.TrustRootCertificate();
}
if (TrustRootCertificateAsAdministrator)
{
CertificateManager.TrustRootCertificateAsAdministrator();
}
}
}
......@@ -753,13 +798,13 @@ namespace Titanium.Web.Proxy
try
{
if (endPoint.GetType() == typeof(TransparentProxyEndPoint))
if (endPoint is TransparentProxyEndPoint tep)
{
await HandleClient(endPoint as TransparentProxyEndPoint, tcpClient);
await HandleClient(tep, tcpClient);
}
else
{
await HandleClient(endPoint as ExplicitProxyEndPoint, tcpClient);
await HandleClient((ExplicitProxyEndPoint)endPoint, tcpClient);
}
}
finally
......
......@@ -38,16 +38,19 @@ namespace Titanium.Web.Proxy
/// <returns></returns>
private async Task HandleClient(ExplicitProxyEndPoint endPoint, TcpClient tcpClient)
{
bool disposed = false;
var clientStream = new CustomBufferedStream(tcpClient.GetStream(), BufferSize);
var clientStreamReader = new CustomBinaryReader(clientStream, BufferSize);
var clientStreamWriter = new HttpResponseWriter(clientStream, BufferSize);
Uri httpRemoteUri;
try
{
string connectHostname = null;
ConnectRequest connectRequest = null;
//Client wants to create a secure tcp tunnel (probably its a HTTPS or Websocket request)
if (await IsConnectMethod(clientStream) == 1)
{
//read the first line HTTP command
string httpCmd = await clientStreamReader.ReadLineAsync();
......@@ -56,28 +59,24 @@ namespace Titanium.Web.Proxy
return;
}
Request.ParseRequestLine(httpCmd, out string httpMethod, out string httpUrl, out var version);
Request.ParseRequestLine(httpCmd, out string _, out string httpUrl, out var version);
httpRemoteUri = httpMethod == "CONNECT" ? new Uri("http://" + httpUrl) : new Uri(httpUrl);
var httpRemoteUri = new Uri("http://" + httpUrl);
connectHostname = httpRemoteUri.Host;
//filter out excluded host names
bool excluded = false;
if (endPoint.ExcludedHttpsHostNameRegex != null)
{
excluded = endPoint.ExcludedHttpsHostNameRegexList.Any(x => x.IsMatch(httpRemoteUri.Host));
excluded = endPoint.ExcludedHttpsHostNameRegexList.Any(x => x.IsMatch(connectHostname));
}
if (endPoint.IncludedHttpsHostNameRegex != null)
{
excluded = !endPoint.IncludedHttpsHostNameRegexList.Any(x => x.IsMatch(httpRemoteUri.Host));
excluded = !endPoint.IncludedHttpsHostNameRegexList.Any(x => x.IsMatch(connectHostname));
}
ConnectRequest connectRequest = null;
//Client wants to create a secure tcp tunnel (probably its a HTTPS or Websocket request)
if (httpMethod == "CONNECT")
{
connectRequest = new ConnectRequest
{
RequestUri = httpRemoteUri,
......@@ -125,8 +124,7 @@ namespace Titanium.Web.Proxy
if (!excluded && isClientHello)
{
httpRemoteUri = new Uri("https://" + httpUrl);
connectRequest.RequestUri = httpRemoteUri;
connectRequest.RequestUri = new Uri("https://" + httpUrl);
SslStream sslStream = null;
......@@ -134,7 +132,7 @@ namespace Titanium.Web.Proxy
{
sslStream = new SslStream(clientStream);
string certName = HttpHelper.GetWildCardDomainName(httpRemoteUri.Host);
string certName = HttpHelper.GetWildCardDomainName(connectHostname);
var certificate = endPoint.GenericCertificate ?? CertificateManager.CreateCertificate(certName, false);
......@@ -154,12 +152,7 @@ namespace Titanium.Web.Proxy
return;
}
if (await CanBeHttpMethod(clientStream))
{
//Now read the actual HTTPS request line
httpCmd = await clientStreamReader.ReadLineAsync();
}
else
if (await IsConnectMethod(clientStream) == -1)
{
// It can be for example some Google (Cloude Messaging for Chrome) magic
excluded = true;
......@@ -207,8 +200,11 @@ namespace Titanium.Web.Proxy
}
//Now create the request
disposed = await HandleHttpSessionRequest(tcpClient, httpCmd, clientStream, clientStreamReader, clientStreamWriter,
httpRemoteUri.Scheme == UriSchemeHttps ? httpRemoteUri.Host : null, endPoint, connectRequest);
await HandleHttpSessionRequest(tcpClient, clientStream, clientStreamReader, clientStreamWriter, connectHostname, endPoint, connectRequest);
}
catch (ProxyHttpException e)
{
ExceptionFunc(e);
}
catch (IOException e)
{
......@@ -220,44 +216,53 @@ namespace Titanium.Web.Proxy
}
catch (Exception e)
{
// is this the correct error message?
ExceptionFunc(new Exception("Error whilst authorizing request", e));
ExceptionFunc(new Exception("Error occured in whilst handling the client", e));
}
finally
{
if (!disposed)
{
Dispose(clientStream, clientStreamReader, clientStreamWriter, null);
}
clientStreamReader.Dispose();
clientStream.Dispose();
}
}
private async Task<bool> CanBeHttpMethod(CustomBufferedStream clientStream)
/// <summary>
/// Determines whether is connect method.
/// </summary>
/// <param name="clientStream">The client stream.</param>
/// <returns>1: when CONNECT, 0: when valid HTTP method, -1: otherwise</returns>
private async Task<int> IsConnectMethod(CustomBufferedStream clientStream)
{
bool isConnect = true;
int legthToCheck = 10;
for (int i = 0; i < legthToCheck; i++)
{
int b = await clientStream.PeekByteAsync(i);
if (b == -1)
{
return false;
return -1;
}
if (b == ' ' && i > 2)
{
// at least 3 letters and a space
return true;
return isConnect ? 1 : 0;
}
if (!char.IsLetter((char)b))
char ch = (char)b;
if (!char.IsLetter(ch))
{
// non letter or too short
return false;
return -1;
}
if (i > 6 || ch != "CONNECT"[i])
{
isConnect = false;
}
}
// only letters
return true;
return isConnect ? 1 : 0;
}
/// <summary>
......@@ -269,11 +274,10 @@ namespace Titanium.Web.Proxy
/// <returns></returns>
private async Task HandleClient(TransparentProxyEndPoint endPoint, TcpClient tcpClient)
{
bool disposed = false;
var clientStream = new CustomBufferedStream(tcpClient.GetStream(), BufferSize);
CustomBinaryReader clientStreamReader = null;
HttpResponseWriter clientStreamWriter = null;
var clientStreamReader = new CustomBinaryReader(clientStream, BufferSize);
var clientStreamWriter = new HttpResponseWriter(clientStream, BufferSize);
try
{
......@@ -298,22 +302,14 @@ namespace Titanium.Web.Proxy
//HTTPS server created - we can now decrypt the client's traffic
}
clientStreamReader = new CustomBinaryReader(clientStream, BufferSize);
clientStreamWriter = new HttpResponseWriter(clientStream, BufferSize);
//now read the request line
string httpCmd = await clientStreamReader.ReadLineAsync();
//Now create the request
disposed = await HandleHttpSessionRequest(tcpClient, httpCmd, clientStream, clientStreamReader, clientStreamWriter,
await HandleHttpSessionRequest(tcpClient, clientStream, clientStreamReader, clientStreamWriter,
endPoint.EnableSsl ? endPoint.GenericCertificateName : null, endPoint, null, true);
}
finally
{
if (!disposed)
{
Dispose(clientStream, clientStreamReader, clientStreamWriter, null);
}
clientStreamReader.Dispose();
clientStream.Dispose();
}
}
......@@ -323,7 +319,6 @@ namespace Titanium.Web.Proxy
/// client/server abruptly terminates connection or by normal HTTP termination
/// </summary>
/// <param name="client"></param>
/// <param name="httpCmd"></param>
/// <param name="clientStream"></param>
/// <param name="clientStreamReader"></param>
/// <param name="clientStreamWriter"></param>
......@@ -332,24 +327,26 @@ namespace Titanium.Web.Proxy
/// <param name="connectRequest"></param>
/// <param name="isTransparentEndPoint"></param>
/// <returns></returns>
private async Task<bool> HandleHttpSessionRequest(TcpClient client, string httpCmd, CustomBufferedStream clientStream,
private async Task HandleHttpSessionRequest(TcpClient client, CustomBufferedStream clientStream,
CustomBinaryReader clientStreamReader, HttpResponseWriter clientStreamWriter, string httpsConnectHostname,
ProxyEndPoint endPoint, ConnectRequest connectRequest, bool isTransparentEndPoint = false)
{
bool disposed = false;
TcpConnection connection = null;
try
{
//Loop through each subsequest request on this particular client connection
//(assuming HTTP connection is kept alive by client)
while (true)
{
// read the request line
string httpCmd = await clientStreamReader.ReadLineAsync();
if (string.IsNullOrEmpty(httpCmd))
{
break;
}
var args = new SessionEventArgs(BufferSize, endPoint, HandleHttpSessionResponse, ExceptionFunc)
var args = new SessionEventArgs(BufferSize, endPoint, ExceptionFunc)
{
ProxyClient = { TcpClient = client },
WebSession = { ConnectRequest = connectRequest }
......@@ -406,7 +403,6 @@ namespace Titanium.Web.Proxy
//proxy authorization check
if (httpsConnectHostname == null && await CheckAuthorization(clientStreamWriter, args) == false)
{
args.Dispose();
break;
}
......@@ -432,7 +428,7 @@ namespace Titanium.Web.Proxy
if (args.WebSession.Request.CancelRequest)
{
args.Dispose();
await HandleHttpSessionResponse(args);
break;
}
......@@ -480,45 +476,32 @@ namespace Titanium.Web.Proxy
(buffer, offset, count) => { args.OnDataReceived(buffer, offset, count); },
ExceptionFunc);
args.Dispose();
break;
}
//construct the web request that we are going to issue on behalf of the client.
disposed = await HandleHttpSessionRequestInternal(connection, args, false);
if (disposed)
{
//already disposed inside above method
args.Dispose();
break;
}
await HandleHttpSessionRequestInternal(connection, args);
//if connection is closing exit
if (args.WebSession.Response.KeepAlive == false)
{
args.Dispose();
break;
}
args.Dispose();
// read the next request
httpCmd = await clientStreamReader.ReadLineAsync();
}
catch (Exception e)
catch (Exception e) when (!(e is ProxyHttpException))
{
ExceptionFunc(new ProxyHttpException("Error occured whilst handling session request", e, args));
break;
throw new ProxyHttpException("Error occured whilst handling session request", e, args);
}
finally
{
args.Dispose();
}
}
if (!disposed)
}
finally
{
Dispose(clientStream, clientStreamReader, clientStreamWriter, connection);
connection?.Dispose();
}
return true;
}
/// <summary>
......@@ -526,13 +509,9 @@ namespace Titanium.Web.Proxy
/// </summary>
/// <param name="connection"></param>
/// <param name="args"></param>
/// <param name="closeConnection"></param>
/// <returns></returns>
private async Task<bool> HandleHttpSessionRequestInternal(TcpConnection connection, SessionEventArgs args, bool closeConnection)
/// <returns>True if close the connection</returns>
private async Task HandleHttpSessionRequestInternal(TcpConnection connection, SessionEventArgs args)
{
bool disposed = false;
bool keepAlive = false;
try
{
var request = args.WebSession.Request;
......@@ -605,45 +584,15 @@ namespace Titanium.Web.Proxy
//If not expectation failed response was returned by server then parse response
if (!request.ExpectationFailed)
{
disposed = await HandleHttpSessionResponse(args);
//already disposed inside above method
if (disposed)
{
return true;
}
}
//if connection is closing exit
if (args.WebSession.Response.KeepAlive == false)
{
return true;
await HandleHttpSessionResponse(args);
}
if (!closeConnection)
{
keepAlive = true;
return false;
}
}
catch (Exception e)
{
ExceptionFunc(new ProxyHttpException("Error occured whilst handling session request (internal)", e, args));
return true;
}
finally
{
if (!disposed && !keepAlive)
catch (Exception e) when (!(e is ProxyHttpException))
{
//dispose
Dispose(args.ProxyClient.ClientStream, args.ProxyClient.ClientStreamReader, args.ProxyClient.ClientStreamWriter,
args.WebSession.ServerConnection);
throw new ProxyHttpException("Error occured whilst handling session request (internal)", e, args);
}
}
return true;
}
/// <summary>
/// Create a Server Connection
/// </summary>
......
......@@ -18,7 +18,7 @@ namespace Titanium.Web.Proxy
/// </summary>
/// <param name="args"></param>
/// <returns>true if client/server connection was terminated (and disposed) </returns>
private async Task<bool> HandleHttpSessionResponse(SessionEventArgs args)
private async Task HandleHttpSessionResponse(SessionEventArgs args)
{
try
{
......@@ -30,12 +30,7 @@ namespace Titanium.Web.Proxy
//check for windows authentication
if (isWindowsAuthenticationEnabledAndSupported && response.StatusCode == (int)HttpStatusCode.Unauthorized)
{
bool disposed = await Handle401UnAuthorized(args);
if (disposed)
{
return true;
}
await Handle401UnAuthorized(args);
}
args.ReRequest = false;
......@@ -52,8 +47,8 @@ namespace Titanium.Web.Proxy
{
//clear current response
await args.ClearResponse();
bool disposed = await HandleHttpSessionRequestInternal(args.WebSession.ServerConnection, args, false);
return disposed;
await HandleHttpSessionRequestInternal(args.WebSession.ServerConnection, args);
return;
}
response.ResponseLocked = true;
......@@ -109,16 +104,10 @@ namespace Titanium.Web.Proxy
}
}
}
catch (Exception e)
catch (Exception e) when (!(e is ProxyHttpException))
{
ExceptionFunc(new ProxyHttpException("Error occured whilst handling session response", e, args));
Dispose(args.ProxyClient.ClientStream, args.ProxyClient.ClientStreamReader, args.ProxyClient.ClientStreamWriter,
args.WebSession.ServerConnection);
return true;
throw new ProxyHttpException("Error occured whilst handling session response", e, args);
}
return false;
}
/// <summary>
......
......@@ -135,8 +135,7 @@ namespace Titanium.Web.Proxy
//request again with updated authorization header
//and server cookies
bool disposed = await HandleHttpSessionRequestInternal(args.WebSession.ServerConnection, args, false);
return disposed;
await HandleHttpSessionRequestInternal(args.WebSession.ServerConnection, args);
}
return false;
......
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