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 ...@@ -170,18 +170,18 @@ namespace Titanium.Web.Proxy.Examples.Basic
//requestBodyHistory[e.Id] = bodyString; //requestBodyHistory[e.Id] = bodyString;
} }
////To cancel a request with a custom HTML content //To cancel a request with a custom HTML content
////Filter URL //Filter URL
//if (e.WebSession.Request.RequestUri.AbsoluteUri.Contains("google.com")) if (e.WebSession.Request.RequestUri.AbsoluteUri.Contains("google.com"))
//{ {
// await e.Ok("<!DOCTYPE html>" + await e.Ok("<!DOCTYPE html>" +
// "<html><body><h1>" + "<html><body><h1>" +
// "Website Blocked" + "Website Blocked" +
// "</h1>" + "</h1>" +
// "<p>Blocked by titanium web proxy.</p>" + "<p>Blocked by titanium web proxy.</p>" +
// "</body>" + "</body>" +
// "</html>"); "</html>");
//} }
////Redirect example ////Redirect example
//if (e.WebSession.Request.RequestUri.AbsoluteUri.Contains("wikipedia.org")) //if (e.WebSession.Request.RequestUri.AbsoluteUri.Contains("wikipedia.org"))
......
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Linq; using System.Linq;
...@@ -23,7 +23,7 @@ namespace Titanium.Web.Proxy.Examples.Wpf ...@@ -23,7 +23,7 @@ namespace Titanium.Web.Proxy.Examples.Wpf
private int lastSessionNumber; private int lastSessionNumber;
public ObservableCollection<SessionListItem> Sessions { get; } = new ObservableCollection<SessionListItem>(); public ObservableCollection<SessionListItem> Sessions { get; } = new ObservableCollection<SessionListItem>();
public SessionListItem SelectedSession public SessionListItem SelectedSession
{ {
...@@ -63,10 +63,35 @@ namespace Titanium.Web.Proxy.Examples.Wpf ...@@ -63,10 +63,35 @@ namespace Titanium.Web.Proxy.Examples.Wpf
{ {
proxyServer = new ProxyServer(); proxyServer = new ProxyServer();
//proxyServer.CertificateEngine = CertificateEngine.DefaultWindows; //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.TrustRootCertificate = true;
proxyServer.CertificateManager.TrustRootCertificateAsAdministrator(); proxyServer.TrustRootCertificateAsAdministrator = true;
proxyServer.ForwardToUpstreamGateway = 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) var explicitEndPoint = new ExplicitProxyEndPoint(IPAddress.Any, 8000, true)
{ {
ExcludedHttpsHostNameRegex = new[] { "ssllabs.com" }, ExcludedHttpsHostNameRegex = new[] { "ssllabs.com" },
......
...@@ -29,11 +29,6 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -29,11 +29,6 @@ namespace Titanium.Web.Proxy.EventArguments
/// </summary> /// </summary>
private readonly int bufferSize; private readonly int bufferSize;
/// <summary>
/// Holds a reference to proxy response handler method
/// </summary>
private Func<SessionEventArgs, Task> httpResponseHandler;
private readonly Action<Exception> exceptionFunc; private readonly Action<Exception> exceptionFunc;
/// <summary> /// <summary>
...@@ -108,11 +103,9 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -108,11 +103,9 @@ namespace Titanium.Web.Proxy.EventArguments
/// </summary> /// </summary>
internal SessionEventArgs(int bufferSize, internal SessionEventArgs(int bufferSize,
ProxyEndPoint endPoint, ProxyEndPoint endPoint,
Func<SessionEventArgs, Task> httpResponseHandler,
Action<Exception> exceptionFunc) Action<Exception> exceptionFunc)
{ {
this.bufferSize = bufferSize; this.bufferSize = bufferSize;
this.httpResponseHandler = httpResponseHandler;
this.exceptionFunc = exceptionFunc; this.exceptionFunc = exceptionFunc;
ProxyClient = new ProxyClient(); ProxyClient = new ProxyClient();
...@@ -536,16 +529,18 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -536,16 +529,18 @@ namespace Titanium.Web.Proxy.EventArguments
/// </summary> /// </summary>
/// <param name="html"></param> /// <param name="html"></param>
/// <param name="headers"></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(); var response = new OkResponse();
response.Headers.AddHeaders(headers); if (headers != null)
{
response.Headers.AddHeaders(headers);
}
response.HttpVersion = WebSession.Request.HttpVersion; response.HttpVersion = WebSession.Request.HttpVersion;
response.Body = response.Encoding.GetBytes(html ?? string.Empty); response.Body = response.Encoding.GetBytes(html ?? string.Empty);
await Respond(response); await Respond(response);
WebSession.Request.CancelRequest = true;
} }
/// <summary> /// <summary>
...@@ -635,8 +630,6 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -635,8 +630,6 @@ namespace Titanium.Web.Proxy.EventArguments
WebSession.Response = response; WebSession.Response = response;
await httpResponseHandler(this);
WebSession.Request.CancelRequest = true; WebSession.Request.CancelRequest = true;
} }
...@@ -645,7 +638,6 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -645,7 +638,6 @@ namespace Titanium.Web.Proxy.EventArguments
/// </summary> /// </summary>
public void Dispose() public void Dispose()
{ {
httpResponseHandler = null;
CustomUpStreamProxyUsed = null; CustomUpStreamProxyUsed = null;
DataSent = null; DataSent = null;
......
...@@ -9,7 +9,7 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -9,7 +9,7 @@ namespace Titanium.Web.Proxy.EventArguments
public bool IsHttpsConnect { get; set; } public bool IsHttpsConnect { get; set; }
internal TunnelConnectSessionEventArgs(int bufferSize, ProxyEndPoint endPoint, ConnectRequest connectRequest, Action<Exception> exceptionFunc) internal TunnelConnectSessionEventArgs(int bufferSize, ProxyEndPoint endPoint, ConnectRequest connectRequest, Action<Exception> exceptionFunc)
: base(bufferSize, endPoint, null, exceptionFunc) : base(bufferSize, endPoint, exceptionFunc)
{ {
WebSession.Request = connectRequest; WebSession.Request = connectRequest;
} }
......
using System; using System;
using System.IO; using System.IO;
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
using System.Text.RegularExpressions;
using System.Threading; using System.Threading;
using Org.BouncyCastle.Asn1; using Org.BouncyCastle.Asn1;
using Org.BouncyCastle.Asn1.Pkcs; using Org.BouncyCastle.Asn1.Pkcs;
...@@ -24,6 +25,8 @@ namespace Titanium.Web.Proxy.Network.Certificate ...@@ -24,6 +25,8 @@ namespace Titanium.Web.Proxy.Network.Certificate
/// </summary> /// </summary>
internal class BCCertificateMaker : ICertificateMaker 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 certificateValidDays = 1825;
private const int certificateGraceDays = 366; private const int certificateGraceDays = 366;
...@@ -146,7 +149,7 @@ namespace Titanium.Web.Proxy.Network.Certificate ...@@ -146,7 +149,7 @@ namespace Titanium.Web.Proxy.Network.Certificate
{ {
try try
{ {
x509Certificate.FriendlyName = subjectName; x509Certificate.FriendlyName = CNRemoverRegex.Replace(subjectName, string.Empty);
} }
catch (PlatformNotSupportedException) catch (PlatformNotSupportedException)
{ {
......
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
...@@ -69,10 +69,24 @@ namespace Titanium.Web.Proxy.Network ...@@ -69,10 +69,24 @@ namespace Titanium.Web.Proxy.Network
private string rootCertificateName; private string rootCertificateName;
private bool pfxFileExists = false;
private bool clearCertificates { get; set; } private bool clearCertificates { get; set; }
private X509Certificate2 rootCertificate; 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> /// <summary>
/// Cache dictionary /// Cache dictionary
/// </summary> /// </summary>
...@@ -86,7 +100,7 @@ namespace Titanium.Web.Proxy.Network ...@@ -86,7 +100,7 @@ namespace Titanium.Web.Proxy.Network
set set
{ {
issuer = value; issuer = value;
ClearRootCertificate(); //ClearRootCertificate();
} }
} }
...@@ -96,7 +110,7 @@ namespace Titanium.Web.Proxy.Network ...@@ -96,7 +110,7 @@ namespace Titanium.Web.Proxy.Network
set set
{ {
rootCertificateName = value; rootCertificateName = value;
ClearRootCertificate(); //ClearRootCertificate();
} }
} }
...@@ -115,7 +129,6 @@ namespace Titanium.Web.Proxy.Network ...@@ -115,7 +129,6 @@ namespace Titanium.Web.Proxy.Network
/// </summary> /// </summary>
internal bool CertValidated => RootCertificate != null; internal bool CertValidated => RootCertificate != null;
internal CertificateManager(Action<Exception> exceptionFunc) internal CertificateManager(Action<Exception> exceptionFunc)
{ {
this.exceptionFunc = exceptionFunc; this.exceptionFunc = exceptionFunc;
...@@ -124,13 +137,13 @@ namespace Titanium.Web.Proxy.Network ...@@ -124,13 +137,13 @@ namespace Titanium.Web.Proxy.Network
certificateCache = new ConcurrentDictionary<string, CachedCertificate>(); certificateCache = new ConcurrentDictionary<string, CachedCertificate>();
} }
private void ClearRootCertificate() public void ClearRootCertificate()
{ {
certificateCache.Clear(); certificateCache.Clear();
rootCertificate = null; rootCertificate = null;
} }
private string GetRootCertificatePath() private string GetRootCertificateDirectory()
{ {
string assemblyLocation = Assembly.GetExecutingAssembly().Location; string assemblyLocation = Assembly.GetExecutingAssembly().Location;
...@@ -142,19 +155,50 @@ namespace Titanium.Web.Proxy.Network ...@@ -142,19 +155,50 @@ namespace Titanium.Web.Proxy.Network
string path = Path.GetDirectoryName(assemblyLocation); string path = Path.GetDirectoryName(assemblyLocation);
if (null == path) if (null == path)
throw new NullReferenceException(); 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; return fileName;
} }
private X509Certificate2 LoadRootCertificate() public X509Certificate2 LoadRootCertificate()
{ {
string fileName = GetRootCertificatePath(); string fileName = GetRootCertificatePath();
if (!File.Exists(fileName)) pfxFileExists = File.Exists(fileName);
if (!pfxFileExists)
{
return null; return null;
}
try try
{ {
return new X509Certificate2(fileName, string.Empty, X509KeyStorageFlags.Exportable); return new X509Certificate2(fileName, PfxPassword, StorageFlag);
} }
catch (Exception e) catch (Exception e)
{ {
...@@ -182,6 +226,11 @@ namespace Titanium.Web.Proxy.Network ...@@ -182,6 +226,11 @@ namespace Titanium.Web.Proxy.Network
return true; return true;
} }
if (!OverwritePfXFile && pfxFileExists)
{
return false;
}
try try
{ {
RootCertificate = CreateCertificate(RootCertificateName, true); RootCertificate = CreateCertificate(RootCertificateName, true);
...@@ -195,8 +244,17 @@ namespace Titanium.Web.Proxy.Network ...@@ -195,8 +244,17 @@ namespace Titanium.Web.Proxy.Network
{ {
try try
{ {
string fileName = GetRootCertificatePath(); try
File.WriteAllBytes(fileName, RootCertificate.Export(X509ContentType.Pkcs12)); {
Directory.Delete(GetCertPath(), true);
}
catch
{
// ignore
}
string fileName = GetRootCertificatePath();
File.WriteAllBytes(fileName, RootCertificate.Export(X509ContentType.Pkcs12, PfxPassword));
} }
catch (Exception e) catch (Exception e)
{ {
...@@ -207,6 +265,28 @@ namespace Titanium.Web.Proxy.Network ...@@ -207,6 +265,28 @@ namespace Titanium.Web.Proxy.Network
return RootCertificate != null; 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> /// <summary>
/// Trusts the root certificate. /// Trusts the root certificate.
/// </summary> /// </summary>
...@@ -220,7 +300,7 @@ namespace Titanium.Web.Proxy.Network ...@@ -220,7 +300,7 @@ namespace Titanium.Web.Proxy.Network
} }
/// <summary> /// <summary>
/// Puts the certificate to the local machine's certificate store. /// Puts the certificate to the local machine's certificate store.
/// Needs elevated permission. Works only on Windows. /// Needs elevated permission. Works only on Windows.
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
...@@ -232,12 +312,12 @@ namespace Titanium.Web.Proxy.Network ...@@ -232,12 +312,12 @@ namespace Titanium.Web.Proxy.Network
} }
string fileName = Path.GetTempFileName(); string fileName = Path.GetTempFileName();
File.WriteAllBytes(fileName, RootCertificate.Export(X509ContentType.Pkcs12)); File.WriteAllBytes(fileName, RootCertificate.Export(X509ContentType.Pkcs12, PfxPassword));
var info = new ProcessStartInfo var info = new ProcessStartInfo
{ {
FileName = "certutil.exe", FileName = "certutil.exe",
Arguments = "-importPFX -p \"\" -f \"" + fileName + "\"", Arguments = "-importPFX -p \""+ PfxPassword + "\" -f \"" + fileName + "\"",
CreateNoWindow = true, CreateNoWindow = true,
UseShellExecute = true, UseShellExecute = true,
Verb = "runas", Verb = "runas",
...@@ -277,7 +357,7 @@ namespace Titanium.Web.Proxy.Network ...@@ -277,7 +357,7 @@ namespace Titanium.Web.Proxy.Network
} }
/// <summary> /// <summary>
/// Removes the trusted certificates from the local machine's certificate store. /// Removes the trusted certificates from the local machine's certificate store.
/// Needs elevated permission. Works only on Windows. /// Needs elevated permission. Works only on Windows.
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
...@@ -352,6 +432,16 @@ namespace Titanium.Web.Proxy.Network ...@@ -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> /// <summary>
/// Create an SSL certificate /// Create an SSL certificate
/// </summary> /// </summary>
...@@ -374,17 +464,40 @@ namespace Titanium.Web.Proxy.Network ...@@ -374,17 +464,40 @@ namespace Titanium.Web.Proxy.Network
{ {
try 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");
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);
} }
certificate = certEngine.MakeCertificate(certificateName, isRootCertificate, RootCertificate);
} }
catch (Exception e) catch (Exception e)
{ {
exceptionFunc(e); exceptionFunc(e);
} }
if (certificate != null && !certificateCache.ContainsKey(certificateName)) if (certificate != null && !certificateCache.ContainsKey(certificateName))
{ {
certificateCache.Add(certificateName, new CachedCertificate certificateCache.Add(certificateName, new CachedCertificate
...@@ -402,7 +515,7 @@ namespace Titanium.Web.Proxy.Network ...@@ -402,7 +515,7 @@ namespace Titanium.Web.Proxy.Network
return cached.Certificate; return cached.Certificate;
} }
} }
} }
return certificate; return certificate;
} }
......
...@@ -88,10 +88,10 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -88,10 +88,10 @@ namespace Titanium.Web.Proxy.Network.Tcp
{ {
string httpStatus = await reader.ReadLineAsync(); 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") if (statusCode != 200 && !statusDescription.EqualsIgnoreCase("OK")
&& !statusDescription.EqualsIgnoreCase("connection established")) && !statusDescription.EqualsIgnoreCase("Connection Established"))
{ {
throw new Exception("Upstream proxy failed to create a secure tunnel"); throw new Exception("Upstream proxy failed to create a secure tunnel");
} }
......
...@@ -17,7 +17,7 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -17,7 +17,7 @@ namespace Titanium.Web.Proxy.Network.Tcp
/// <param name="tcpRows">TcpRow collection to initialize with.</param> /// <param name="tcpRows">TcpRow collection to initialize with.</param>
internal TcpTable(IEnumerable<TcpRow> tcpRows) internal TcpTable(IEnumerable<TcpRow> tcpRows)
{ {
this.TcpRows = tcpRows; TcpRows = tcpRows;
} }
/// <summary> /// <summary>
......
using StreamExtended.Network; using StreamExtended.Network;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
...@@ -37,11 +37,6 @@ namespace Titanium.Web.Proxy ...@@ -37,11 +37,6 @@ namespace Titanium.Web.Proxy
/// </summary> /// </summary>
private Action<Exception> exceptionFunc; private Action<Exception> exceptionFunc;
/// <summary>
/// Backing field for corresponding public property
/// </summary>
private bool trustRootCertificate;
/// <summary> /// <summary>
/// Backing field for corresponding public property /// Backing field for corresponding public property
/// </summary> /// </summary>
...@@ -52,6 +47,8 @@ namespace Titanium.Web.Proxy ...@@ -52,6 +47,8 @@ namespace Titanium.Web.Proxy
/// </summary> /// </summary>
private int serverConnectionCount; private int serverConnectionCount;
private X509KeyStorageFlags storageFlag = X509KeyStorageFlags.Exportable;
/// <summary> /// <summary>
/// A object that creates tcp connection to server /// A object that creates tcp connection to server
/// </summary> /// </summary>
...@@ -102,7 +99,7 @@ namespace Titanium.Web.Proxy ...@@ -102,7 +99,7 @@ namespace Titanium.Web.Proxy
/// Name of the root certificate /// Name of the root certificate
/// (This is valid only when RootCertificate property is not set) /// (This is valid only when RootCertificate property is not set)
/// If no certificate is provided then a default Root Certificate will be created and used /// If no certificate is provided then a default Root Certificate will be created and used
/// The provided root certificate will be stored in proxy exe directory with the private key /// The provided root certificate will be stored in proxy exe directory with the private key
/// Root certificate file will be named as "rootCert.pfx" /// Root certificate file will be named as "rootCert.pfx"
/// </summary> /// </summary>
public string RootCertificateName public string RootCertificateName
...@@ -118,14 +115,64 @@ namespace Titanium.Web.Proxy ...@@ -118,14 +115,64 @@ namespace Titanium.Web.Proxy
/// </summary> /// </summary>
public bool TrustRootCertificate public bool TrustRootCertificate
{ {
get => trustRootCertificate; 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
{
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
{
get => CertificateManager.PfxFilePath;
set => CertificateManager.PfxFilePath = value;
}
public X509KeyStorageFlags StorageFlag
{
get => storageFlag;
set set
{ {
trustRootCertificate = value; storageFlag = value;
if (value) CertificateManager.StorageFlag = storageFlag;
{
EnsureRootCertificate();
}
} }
} }
...@@ -346,7 +393,7 @@ namespace Titanium.Web.Proxy ...@@ -346,7 +393,7 @@ namespace Titanium.Web.Proxy
/// <summary> /// <summary>
/// Remove a proxy end point /// Remove a proxy end point
/// 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 void RemoveEndPoint(ProxyEndPoint endPoint) public void RemoveEndPoint(ProxyEndPoint endPoint)
...@@ -373,7 +420,6 @@ namespace Titanium.Web.Proxy ...@@ -373,7 +420,6 @@ namespace Titanium.Web.Proxy
SetAsSystemProxy(endPoint, ProxyProtocolType.Http); SetAsSystemProxy(endPoint, ProxyProtocolType.Http);
} }
/// <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>
...@@ -520,6 +566,11 @@ namespace Titanium.Web.Proxy ...@@ -520,6 +566,11 @@ namespace Titanium.Web.Proxy
throw new Exception("Proxy is already running."); 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) //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 //due to non gracious proxy shutdown before or something else
if (systemProxySettingsManager != null && RunTime.IsWindows) if (systemProxySettingsManager != null && RunTime.IsWindows)
...@@ -571,7 +622,6 @@ namespace Titanium.Web.Proxy ...@@ -571,7 +622,6 @@ namespace Titanium.Web.Proxy
} }
} }
/// <summary> /// <summary>
/// Stop this proxy server /// Stop this proxy server
/// </summary> /// </summary>
...@@ -604,26 +654,6 @@ namespace Titanium.Web.Proxy ...@@ -604,26 +654,6 @@ namespace Titanium.Web.Proxy
ProxyRunning = false; 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> /// <summary>
/// Dispose Proxy. /// Dispose Proxy.
/// </summary> /// </summary>
...@@ -692,7 +722,17 @@ namespace Titanium.Web.Proxy ...@@ -692,7 +722,17 @@ namespace Titanium.Web.Proxy
return Task.FromResult(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) if (!CertificateManager.CertValidated)
{ {
...@@ -702,6 +742,11 @@ namespace Titanium.Web.Proxy ...@@ -702,6 +742,11 @@ namespace Titanium.Web.Proxy
{ {
CertificateManager.TrustRootCertificate(); CertificateManager.TrustRootCertificate();
} }
if (TrustRootCertificateAsAdministrator)
{
CertificateManager.TrustRootCertificateAsAdministrator();
}
} }
} }
...@@ -753,13 +798,13 @@ namespace Titanium.Web.Proxy ...@@ -753,13 +798,13 @@ namespace Titanium.Web.Proxy
try try
{ {
if (endPoint.GetType() == typeof(TransparentProxyEndPoint)) if (endPoint is TransparentProxyEndPoint tep)
{ {
await HandleClient(endPoint as TransparentProxyEndPoint, tcpClient); await HandleClient(tep, tcpClient);
} }
else else
{ {
await HandleClient(endPoint as ExplicitProxyEndPoint, tcpClient); await HandleClient((ExplicitProxyEndPoint)endPoint, tcpClient);
} }
} }
finally finally
......
...@@ -38,46 +38,45 @@ namespace Titanium.Web.Proxy ...@@ -38,46 +38,45 @@ namespace Titanium.Web.Proxy
/// <returns></returns> /// <returns></returns>
private async Task HandleClient(ExplicitProxyEndPoint endPoint, TcpClient tcpClient) private async Task HandleClient(ExplicitProxyEndPoint endPoint, TcpClient tcpClient)
{ {
bool disposed = false;
var clientStream = new CustomBufferedStream(tcpClient.GetStream(), BufferSize); var clientStream = new CustomBufferedStream(tcpClient.GetStream(), BufferSize);
var clientStreamReader = new CustomBinaryReader(clientStream, BufferSize); var clientStreamReader = new CustomBinaryReader(clientStream, BufferSize);
var clientStreamWriter = new HttpResponseWriter(clientStream, BufferSize); var clientStreamWriter = new HttpResponseWriter(clientStream, BufferSize);
Uri httpRemoteUri;
try try
{ {
//read the first line HTTP command string connectHostname = null;
string httpCmd = await clientStreamReader.ReadLineAsync();
if (string.IsNullOrEmpty(httpCmd))
{
return;
}
Request.ParseRequestLine(httpCmd, out string httpMethod, out string httpUrl, out var version); ConnectRequest connectRequest = null;
httpRemoteUri = httpMethod == "CONNECT" ? new Uri("http://" + httpUrl) : new Uri(httpUrl); //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();
if (string.IsNullOrEmpty(httpCmd))
{
return;
}
//filter out excluded host names Request.ParseRequestLine(httpCmd, out string _, out string httpUrl, out var version);
bool excluded = false;
if (endPoint.ExcludedHttpsHostNameRegex != null) var httpRemoteUri = new Uri("http://" + httpUrl);
{ connectHostname = httpRemoteUri.Host;
excluded = endPoint.ExcludedHttpsHostNameRegexList.Any(x => x.IsMatch(httpRemoteUri.Host));
}
if (endPoint.IncludedHttpsHostNameRegex != null) //filter out excluded host names
{ bool excluded = false;
excluded = !endPoint.IncludedHttpsHostNameRegexList.Any(x => x.IsMatch(httpRemoteUri.Host));
}
ConnectRequest connectRequest = null; if (endPoint.ExcludedHttpsHostNameRegex != null)
{
excluded = endPoint.ExcludedHttpsHostNameRegexList.Any(x => x.IsMatch(connectHostname));
}
if (endPoint.IncludedHttpsHostNameRegex != null)
{
excluded = !endPoint.IncludedHttpsHostNameRegexList.Any(x => x.IsMatch(connectHostname));
}
//Client wants to create a secure tcp tunnel (probably its a HTTPS or Websocket request)
if (httpMethod == "CONNECT")
{
connectRequest = new ConnectRequest connectRequest = new ConnectRequest
{ {
RequestUri = httpRemoteUri, RequestUri = httpRemoteUri,
...@@ -125,8 +124,7 @@ namespace Titanium.Web.Proxy ...@@ -125,8 +124,7 @@ namespace Titanium.Web.Proxy
if (!excluded && isClientHello) if (!excluded && isClientHello)
{ {
httpRemoteUri = new Uri("https://" + httpUrl); connectRequest.RequestUri = new Uri("https://" + httpUrl);
connectRequest.RequestUri = httpRemoteUri;
SslStream sslStream = null; SslStream sslStream = null;
...@@ -134,7 +132,7 @@ namespace Titanium.Web.Proxy ...@@ -134,7 +132,7 @@ namespace Titanium.Web.Proxy
{ {
sslStream = new SslStream(clientStream); sslStream = new SslStream(clientStream);
string certName = HttpHelper.GetWildCardDomainName(httpRemoteUri.Host); string certName = HttpHelper.GetWildCardDomainName(connectHostname);
var certificate = endPoint.GenericCertificate ?? CertificateManager.CreateCertificate(certName, false); var certificate = endPoint.GenericCertificate ?? CertificateManager.CreateCertificate(certName, false);
...@@ -154,12 +152,7 @@ namespace Titanium.Web.Proxy ...@@ -154,12 +152,7 @@ namespace Titanium.Web.Proxy
return; return;
} }
if (await CanBeHttpMethod(clientStream)) if (await IsConnectMethod(clientStream) == -1)
{
//Now read the actual HTTPS request line
httpCmd = await clientStreamReader.ReadLineAsync();
}
else
{ {
// It can be for example some Google (Cloude Messaging for Chrome) magic // It can be for example some Google (Cloude Messaging for Chrome) magic
excluded = true; excluded = true;
...@@ -207,8 +200,11 @@ namespace Titanium.Web.Proxy ...@@ -207,8 +200,11 @@ namespace Titanium.Web.Proxy
} }
//Now create the request //Now create the request
disposed = await HandleHttpSessionRequest(tcpClient, httpCmd, clientStream, clientStreamReader, clientStreamWriter, await HandleHttpSessionRequest(tcpClient, clientStream, clientStreamReader, clientStreamWriter, connectHostname, endPoint, connectRequest);
httpRemoteUri.Scheme == UriSchemeHttps ? httpRemoteUri.Host : null, endPoint, connectRequest); }
catch (ProxyHttpException e)
{
ExceptionFunc(e);
} }
catch (IOException e) catch (IOException e)
{ {
...@@ -220,44 +216,53 @@ namespace Titanium.Web.Proxy ...@@ -220,44 +216,53 @@ namespace Titanium.Web.Proxy
} }
catch (Exception e) catch (Exception e)
{ {
// is this the correct error message? ExceptionFunc(new Exception("Error occured in whilst handling the client", e));
ExceptionFunc(new Exception("Error whilst authorizing request", e));
} }
finally finally
{ {
if (!disposed) clientStreamReader.Dispose();
{ clientStream.Dispose();
Dispose(clientStream, clientStreamReader, clientStreamWriter, null);
}
} }
} }
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; int legthToCheck = 10;
for (int i = 0; i < legthToCheck; i++) for (int i = 0; i < legthToCheck; i++)
{ {
int b = await clientStream.PeekByteAsync(i); int b = await clientStream.PeekByteAsync(i);
if (b == -1) if (b == -1)
{ {
return false; return -1;
} }
if (b == ' ' && i > 2) if (b == ' ' && i > 2)
{ {
// at least 3 letters and a space // 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 // non letter or too short
return false; return -1;
}
if (i > 6 || ch != "CONNECT"[i])
{
isConnect = false;
} }
} }
// only letters // only letters
return true; return isConnect ? 1 : 0;
} }
/// <summary> /// <summary>
...@@ -269,11 +274,10 @@ namespace Titanium.Web.Proxy ...@@ -269,11 +274,10 @@ namespace Titanium.Web.Proxy
/// <returns></returns> /// <returns></returns>
private async Task HandleClient(TransparentProxyEndPoint endPoint, TcpClient tcpClient) private async Task HandleClient(TransparentProxyEndPoint endPoint, TcpClient tcpClient)
{ {
bool disposed = false;
var clientStream = new CustomBufferedStream(tcpClient.GetStream(), BufferSize); var clientStream = new CustomBufferedStream(tcpClient.GetStream(), BufferSize);
CustomBinaryReader clientStreamReader = null; var clientStreamReader = new CustomBinaryReader(clientStream, BufferSize);
HttpResponseWriter clientStreamWriter = null; var clientStreamWriter = new HttpResponseWriter(clientStream, BufferSize);
try try
{ {
...@@ -287,7 +291,7 @@ namespace Titanium.Web.Proxy ...@@ -287,7 +291,7 @@ namespace Titanium.Web.Proxy
clientStream = new CustomBufferedStream(sslStream, BufferSize); clientStream = new CustomBufferedStream(sslStream, BufferSize);
string sniHostName = clientHelloInfo.GetServerName(); string sniHostName = clientHelloInfo.GetServerName();
string certName = HttpHelper.GetWildCardDomainName(sniHostName ?? endPoint.GenericCertificateName); string certName = HttpHelper.GetWildCardDomainName(sniHostName ?? endPoint.GenericCertificateName);
var certificate = CertificateManager.CreateCertificate(certName, false); var certificate = CertificateManager.CreateCertificate(certName, false);
...@@ -298,22 +302,14 @@ namespace Titanium.Web.Proxy ...@@ -298,22 +302,14 @@ namespace Titanium.Web.Proxy
//HTTPS server created - we can now decrypt the client's traffic //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 //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); endPoint.EnableSsl ? endPoint.GenericCertificateName : null, endPoint, null, true);
} }
finally finally
{ {
if (!disposed) clientStreamReader.Dispose();
{ clientStream.Dispose();
Dispose(clientStream, clientStreamReader, clientStreamWriter, null);
}
} }
} }
...@@ -323,7 +319,6 @@ namespace Titanium.Web.Proxy ...@@ -323,7 +319,6 @@ namespace Titanium.Web.Proxy
/// client/server abruptly terminates connection or by normal HTTP termination /// client/server abruptly terminates connection or by normal HTTP termination
/// </summary> /// </summary>
/// <param name="client"></param> /// <param name="client"></param>
/// <param name="httpCmd"></param>
/// <param name="clientStream"></param> /// <param name="clientStream"></param>
/// <param name="clientStreamReader"></param> /// <param name="clientStreamReader"></param>
/// <param name="clientStreamWriter"></param> /// <param name="clientStreamWriter"></param>
...@@ -332,193 +327,181 @@ namespace Titanium.Web.Proxy ...@@ -332,193 +327,181 @@ namespace Titanium.Web.Proxy
/// <param name="connectRequest"></param> /// <param name="connectRequest"></param>
/// <param name="isTransparentEndPoint"></param> /// <param name="isTransparentEndPoint"></param>
/// <returns></returns> /// <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, CustomBinaryReader clientStreamReader, HttpResponseWriter clientStreamWriter, string httpsConnectHostname,
ProxyEndPoint endPoint, ConnectRequest connectRequest, bool isTransparentEndPoint = false) ProxyEndPoint endPoint, ConnectRequest connectRequest, bool isTransparentEndPoint = false)
{ {
bool disposed = false;
TcpConnection connection = null; TcpConnection connection = null;
//Loop through each subsequest request on this particular client connection try
//(assuming HTTP connection is kept alive by client)
while (true)
{ {
if (string.IsNullOrEmpty(httpCmd)) //Loop through each subsequest request on this particular client connection
//(assuming HTTP connection is kept alive by client)
while (true)
{ {
break; // 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 }, ProxyClient = { TcpClient = client },
WebSession = { ConnectRequest = connectRequest } WebSession = { ConnectRequest = connectRequest }
}; };
try try
{ {
Request.ParseRequestLine(httpCmd, out string httpMethod, out string httpUrl, out var version); Request.ParseRequestLine(httpCmd, out string httpMethod, out string httpUrl, out var version);
//Read the request headers in to unique and non-unique header collections //Read the request headers in to unique and non-unique header collections
await HeaderParser.ReadHeaders(clientStreamReader, args.WebSession.Request.Headers); await HeaderParser.ReadHeaders(clientStreamReader, args.WebSession.Request.Headers);
Uri httpRemoteUri; Uri httpRemoteUri;
if (uriSchemeRegex.IsMatch(httpUrl)) if (uriSchemeRegex.IsMatch(httpUrl))
{
try
{ {
httpRemoteUri = new Uri(httpUrl); try
{
httpRemoteUri = new Uri(httpUrl);
}
catch (Exception ex)
{
throw new Exception($"Invalid URI: '{httpUrl}'", ex);
}
} }
catch (Exception ex) else
{ {
throw new Exception($"Invalid URI: '{httpUrl}'", ex); string host = args.WebSession.Request.Host ?? httpsConnectHostname;
string hostAndPath = host;
if (httpUrl.StartsWith("/"))
{
hostAndPath += httpUrl;
}
string url = string.Concat(httpsConnectHostname == null ? "http://" : "https://", hostAndPath);
try
{
httpRemoteUri = new Uri(url);
}
catch (Exception ex)
{
throw new Exception($"Invalid URI: '{url}'", ex);
}
} }
}
else args.WebSession.Request.RequestUri = httpRemoteUri;
{ args.WebSession.Request.OriginalUrl = httpUrl;
string host = args.WebSession.Request.Host ?? httpsConnectHostname;
string hostAndPath = host; args.WebSession.Request.Method = httpMethod;
if (httpUrl.StartsWith("/")) args.WebSession.Request.HttpVersion = version;
args.ProxyClient.ClientStream = clientStream;
args.ProxyClient.ClientStreamReader = clientStreamReader;
args.ProxyClient.ClientStreamWriter = clientStreamWriter;
//proxy authorization check
if (httpsConnectHostname == null && await CheckAuthorization(clientStreamWriter, args) == false)
{ {
hostAndPath += httpUrl; break;
} }
string url = string.Concat(httpsConnectHostname == null ? "http://" : "https://", hostAndPath); PrepareRequestHeaders(args.WebSession.Request.Headers);
try if (!isTransparentEndPoint)
{ {
httpRemoteUri = new Uri(url); args.WebSession.Request.Host = args.WebSession.Request.RequestUri.Authority;
} }
catch (Exception ex)
//if win auth is enabled
//we need a cache of request body
//so that we can send it after authentication in WinAuthHandler.cs
if (isWindowsAuthenticationEnabledAndSupported && args.WebSession.Request.HasBody)
{ {
throw new Exception($"Invalid URI: '{url}'", ex); await args.GetRequestBody();
} }
}
args.WebSession.Request.RequestUri = httpRemoteUri; //If user requested interception do it
args.WebSession.Request.OriginalUrl = httpUrl; if (BeforeRequest != null)
{
args.WebSession.Request.Method = httpMethod; await BeforeRequest.InvokeAsync(this, args, ExceptionFunc);
args.WebSession.Request.HttpVersion = version; }
args.ProxyClient.ClientStream = clientStream;
args.ProxyClient.ClientStreamReader = clientStreamReader;
args.ProxyClient.ClientStreamWriter = clientStreamWriter;
//proxy authorization check if (args.WebSession.Request.CancelRequest)
if (httpsConnectHostname == null && await CheckAuthorization(clientStreamWriter, args) == false) {
{ await HandleHttpSessionResponse(args);
args.Dispose(); break;
break; }
}
PrepareRequestHeaders(args.WebSession.Request.Headers); //create a new connection if hostname/upstream end point changes
if (!isTransparentEndPoint) if (connection != null
{ && (!connection.HostName.Equals(args.WebSession.Request.RequestUri.Host, StringComparison.OrdinalIgnoreCase)
args.WebSession.Request.Host = args.WebSession.Request.RequestUri.Authority; || (args.WebSession.UpStreamEndPoint != null
} && !args.WebSession.UpStreamEndPoint.Equals(connection.UpStreamEndPoint))))
{
connection.Dispose();
connection = null;
}
//if win auth is enabled if (connection == null)
//we need a cache of request body {
//so that we can send it after authentication in WinAuthHandler.cs connection = await GetServerConnection(args, false);
if (isWindowsAuthenticationEnabledAndSupported && args.WebSession.Request.HasBody) }
{
await args.GetRequestBody();
}
//If user requested interception do it //if upgrading to websocket then relay the requet without reading the contents
if (BeforeRequest != null) if (args.WebSession.Request.UpgradeToWebSocket)
{ {
await BeforeRequest.InvokeAsync(this, args, ExceptionFunc); //prepare the prefix content
} var requestHeaders = args.WebSession.Request.Headers;
await connection.StreamWriter.WriteLineAsync(httpCmd);
await connection.StreamWriter.WriteHeadersAsync(requestHeaders);
string httpStatus = await connection.StreamReader.ReadLineAsync();
if (args.WebSession.Request.CancelRequest) Response.ParseResponseLine(httpStatus, out var responseVersion, out int responseStatusCode, out string responseStatusDescription);
{ args.WebSession.Response.HttpVersion = responseVersion;
args.Dispose(); args.WebSession.Response.StatusCode = responseStatusCode;
break; args.WebSession.Response.StatusDescription = responseStatusDescription;
}
//create a new connection if hostname/upstream end point changes await HeaderParser.ReadHeaders(connection.StreamReader, args.WebSession.Response.Headers);
if (connection != null
&& (!connection.HostName.Equals(args.WebSession.Request.RequestUri.Host, StringComparison.OrdinalIgnoreCase)
|| (args.WebSession.UpStreamEndPoint != null
&& !args.WebSession.UpStreamEndPoint.Equals(connection.UpStreamEndPoint))))
{
connection.Dispose();
connection = null;
}
if (connection == null) await clientStreamWriter.WriteResponseAsync(args.WebSession.Response);
{
connection = await GetServerConnection(args, false);
}
//if upgrading to websocket then relay the requet without reading the contents //If user requested call back then do it
if (args.WebSession.Request.UpgradeToWebSocket) if (BeforeResponse != null && !args.WebSession.Response.ResponseLocked)
{ {
//prepare the prefix content await BeforeResponse.InvokeAsync(this, args, ExceptionFunc);
var requestHeaders = args.WebSession.Request.Headers; }
await connection.StreamWriter.WriteLineAsync(httpCmd);
await connection.StreamWriter.WriteHeadersAsync(requestHeaders);
string httpStatus = await connection.StreamReader.ReadLineAsync();
Response.ParseResponseLine(httpStatus, out var responseVersion, out int responseStatusCode, out string responseStatusDescription); await TcpHelper.SendRaw(clientStream, connection.Stream, BufferSize,
args.WebSession.Response.HttpVersion = responseVersion; (buffer, offset, count) => { args.OnDataSent(buffer, offset, count); },
args.WebSession.Response.StatusCode = responseStatusCode; (buffer, offset, count) => { args.OnDataReceived(buffer, offset, count); },
args.WebSession.Response.StatusDescription = responseStatusDescription; ExceptionFunc);
await HeaderParser.ReadHeaders(connection.StreamReader, args.WebSession.Response.Headers); break;
}
await clientStreamWriter.WriteResponseAsync(args.WebSession.Response); //construct the web request that we are going to issue on behalf of the client.
await HandleHttpSessionRequestInternal(connection, args);
//If user requested call back then do it //if connection is closing exit
if (BeforeResponse != null && !args.WebSession.Response.ResponseLocked) if (args.WebSession.Response.KeepAlive == false)
{ {
await BeforeResponse.InvokeAsync(this, args, ExceptionFunc); break;
} }
await TcpHelper.SendRaw(clientStream, connection.Stream, BufferSize,
(buffer, offset, count) => { args.OnDataSent(buffer, offset, count); },
(buffer, offset, count) => { args.OnDataReceived(buffer, offset, count); },
ExceptionFunc);
args.Dispose();
break;
} }
catch (Exception e) when (!(e is ProxyHttpException))
//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 throw new ProxyHttpException("Error occured whilst handling session request", e, args);
args.Dispose();
break;
} }
finally
//if connection is closing exit
if (args.WebSession.Response.KeepAlive == false)
{ {
args.Dispose(); args.Dispose();
break;
} }
args.Dispose();
// read the next request
httpCmd = await clientStreamReader.ReadLineAsync();
}
catch (Exception e)
{
ExceptionFunc(new ProxyHttpException("Error occured whilst handling session request", e, args));
break;
} }
} }
finally
if (!disposed)
{ {
Dispose(clientStream, clientStreamReader, clientStreamWriter, connection); connection?.Dispose();
} }
return true;
} }
/// <summary> /// <summary>
...@@ -526,13 +509,9 @@ namespace Titanium.Web.Proxy ...@@ -526,13 +509,9 @@ namespace Titanium.Web.Proxy
/// </summary> /// </summary>
/// <param name="connection"></param> /// <param name="connection"></param>
/// <param name="args"></param> /// <param name="args"></param>
/// <param name="closeConnection"></param> /// <returns>True if close the connection</returns>
/// <returns></returns> private async Task HandleHttpSessionRequestInternal(TcpConnection connection, SessionEventArgs args)
private async Task<bool> HandleHttpSessionRequestInternal(TcpConnection connection, SessionEventArgs args, bool closeConnection)
{ {
bool disposed = false;
bool keepAlive = false;
try try
{ {
var request = args.WebSession.Request; var request = args.WebSession.Request;
...@@ -605,43 +584,13 @@ namespace Titanium.Web.Proxy ...@@ -605,43 +584,13 @@ namespace Titanium.Web.Proxy
//If not expectation failed response was returned by server then parse response //If not expectation failed response was returned by server then parse response
if (!request.ExpectationFailed) if (!request.ExpectationFailed)
{ {
disposed = await HandleHttpSessionResponse(args); 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;
}
if (!closeConnection)
{
keepAlive = true;
return false;
} }
} }
catch (Exception e) catch (Exception e) when (!(e is ProxyHttpException))
{ {
ExceptionFunc(new ProxyHttpException("Error occured whilst handling session request (internal)", e, args)); throw new ProxyHttpException("Error occured whilst handling session request (internal)", e, args);
return true;
} }
finally
{
if (!disposed && !keepAlive)
{
//dispose
Dispose(args.ProxyClient.ClientStream, args.ProxyClient.ClientStreamReader, args.ProxyClient.ClientStreamWriter,
args.WebSession.ServerConnection);
}
}
return true;
} }
/// <summary> /// <summary>
......
...@@ -18,7 +18,7 @@ namespace Titanium.Web.Proxy ...@@ -18,7 +18,7 @@ namespace Titanium.Web.Proxy
/// </summary> /// </summary>
/// <param name="args"></param> /// <param name="args"></param>
/// <returns>true if client/server connection was terminated (and disposed) </returns> /// <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 try
{ {
...@@ -30,12 +30,7 @@ namespace Titanium.Web.Proxy ...@@ -30,12 +30,7 @@ namespace Titanium.Web.Proxy
//check for windows authentication //check for windows authentication
if (isWindowsAuthenticationEnabledAndSupported && response.StatusCode == (int)HttpStatusCode.Unauthorized) if (isWindowsAuthenticationEnabledAndSupported && response.StatusCode == (int)HttpStatusCode.Unauthorized)
{ {
bool disposed = await Handle401UnAuthorized(args); await Handle401UnAuthorized(args);
if (disposed)
{
return true;
}
} }
args.ReRequest = false; args.ReRequest = false;
...@@ -52,8 +47,8 @@ namespace Titanium.Web.Proxy ...@@ -52,8 +47,8 @@ namespace Titanium.Web.Proxy
{ {
//clear current response //clear current response
await args.ClearResponse(); await args.ClearResponse();
bool disposed = await HandleHttpSessionRequestInternal(args.WebSession.ServerConnection, args, false); await HandleHttpSessionRequestInternal(args.WebSession.ServerConnection, args);
return disposed; return;
} }
response.ResponseLocked = true; response.ResponseLocked = true;
...@@ -109,16 +104,10 @@ namespace Titanium.Web.Proxy ...@@ -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)); throw 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;
} }
return false;
} }
/// <summary> /// <summary>
......
...@@ -135,8 +135,7 @@ namespace Titanium.Web.Proxy ...@@ -135,8 +135,7 @@ namespace Titanium.Web.Proxy
//request again with updated authorization header //request again with updated authorization header
//and server cookies //and server cookies
bool disposed = await HandleHttpSessionRequestInternal(args.WebSession.ServerConnection, args, false); await HandleHttpSessionRequestInternal(args.WebSession.ServerConnection, args);
return disposed;
} }
return false; 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