diff --git a/Titanium.Web.Proxy/EventArguments/SessionEventArgs.cs b/Titanium.Web.Proxy/EventArguments/SessionEventArgs.cs
index 9f2824575..869dcf62d 100644
--- a/Titanium.Web.Proxy/EventArguments/SessionEventArgs.cs
+++ b/Titanium.Web.Proxy/EventArguments/SessionEventArgs.cs
@@ -30,7 +30,7 @@ public class SessionEventArgs : EventArgs, IDisposable
///
/// Holds a reference to proxy response handler method
///
- private readonly Func httpResponseHandler;
+ private Func httpResponseHandler;
///
/// Holds a reference to client
@@ -522,6 +522,10 @@ public async Task Respond(Response response)
///
public void Dispose()
{
+ httpResponseHandler = null;
+ CustomUpStreamHttpProxyUsed = null;
+ CustomUpStreamHttpsProxyUsed = null;
+
WebSession.Dispose();
}
}
diff --git a/Titanium.Web.Proxy/Http/HttpWebClient.cs b/Titanium.Web.Proxy/Http/HttpWebClient.cs
index bd4278320..ac61bbea1 100644
--- a/Titanium.Web.Proxy/Http/HttpWebClient.cs
+++ b/Titanium.Web.Proxy/Http/HttpWebClient.cs
@@ -214,14 +214,10 @@ internal async Task ReceiveResponse()
///
public void Dispose()
{
- //not really needed since GC will collect it
- //but just to be on safe side
- Request.RequestBody = null;
- Response.ResponseBody = null;
-
- Request.RequestBodyString = null;
- Response.ResponseBodyString = null;
+ ConnectHeaders = null;
+ Request.Dispose();
+ Response.Dispose();
}
}
}
diff --git a/Titanium.Web.Proxy/Http/Request.cs b/Titanium.Web.Proxy/Http/Request.cs
index 0afdea85c..2784511a9 100644
--- a/Titanium.Web.Proxy/Http/Request.cs
+++ b/Titanium.Web.Proxy/Http/Request.cs
@@ -9,7 +9,7 @@ namespace Titanium.Web.Proxy.Http
///
/// A HTTP(S) request object
///
- public class Request
+ public class Request : IDisposable
{
///
/// Request Method
@@ -294,5 +294,20 @@ public Request()
RequestHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase);
NonUniqueRequestHeaders = new Dictionary>(StringComparer.OrdinalIgnoreCase);
}
+
+ ///
+ /// Dispose off
+ ///
+ public void Dispose()
+ {
+ //not really needed since GC will collect it
+ //but just to be on safe side
+
+ RequestHeaders = null;
+ NonUniqueRequestHeaders = null;
+
+ RequestBody = null;
+ RequestBody = null;
+ }
}
}
diff --git a/Titanium.Web.Proxy/Http/Response.cs b/Titanium.Web.Proxy/Http/Response.cs
index 0393f4875..82b5fdd6c 100644
--- a/Titanium.Web.Proxy/Http/Response.cs
+++ b/Titanium.Web.Proxy/Http/Response.cs
@@ -10,7 +10,7 @@ namespace Titanium.Web.Proxy.Http
///
/// Http(s) response object
///
- public class Response
+ public class Response : IDisposable
{
///
/// Response Status Code.
@@ -234,5 +234,20 @@ public Response()
ResponseHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase);
NonUniqueResponseHeaders = new Dictionary>(StringComparer.OrdinalIgnoreCase);
}
+
+ ///
+ /// Dispose off
+ ///
+ public void Dispose()
+ {
+ //not really needed since GC will collect it
+ //but just to be on safe side
+
+ ResponseHeaders = null;
+ NonUniqueResponseHeaders = null;
+
+ ResponseBody = null;
+ ResponseBodyString = null;
+ }
}
}
diff --git a/Titanium.Web.Proxy/Network/CertificateManager.cs b/Titanium.Web.Proxy/Network/CertificateManager.cs
index 828e4f8fa..ddc9cd2ed 100644
--- a/Titanium.Web.Proxy/Network/CertificateManager.cs
+++ b/Titanium.Web.Proxy/Network/CertificateManager.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
+using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
@@ -30,7 +31,7 @@ public enum CertificateEngine
///
/// A class to manage SSL certificates used by this proxy server
///
- public class CertificateManager : IDisposable
+ public sealed class CertificateManager : IDisposable
{
internal CertificateEngine Engine
{
@@ -147,7 +148,7 @@ private string GetRootCertificatePath()
return fileName;
}
- internal X509Certificate2 LoadRootCertificate()
+ private X509Certificate2 LoadRootCertificate()
{
var fileName = GetRootCertificatePath();
if (!File.Exists(fileName)) return null;
@@ -218,6 +219,51 @@ public void TrustRootCertificate()
TrustRootCertificate(StoreLocation.LocalMachine);
}
+ ///
+ /// Puts the certificate to the local machine's certificate store.
+ /// Needs elevated permission. Works only on Windows.
+ ///
+ ///
+ public bool TrustRootCertificateAsAdministrator()
+ {
+ if (RunTime.IsRunningOnMono())
+ {
+ return false;
+ }
+
+ var fileName = Path.GetTempFileName();
+ File.WriteAllBytes(fileName, RootCertificate.Export(X509ContentType.Pkcs12));
+
+ var info = new ProcessStartInfo
+ {
+ FileName = "certutil.exe",
+ Arguments = "-importPFX -p \"\" -f \"" + fileName + "\"",
+ CreateNoWindow = true,
+ UseShellExecute = true,
+ Verb = "runas",
+ ErrorDialog = false,
+ };
+
+ try
+ {
+ var process = Process.Start(info);
+ if (process == null)
+ {
+ return false;
+ }
+
+ process.WaitForExit();
+
+ File.Delete(fileName);
+ }
+ catch
+ {
+ return false;
+ }
+
+ return true;
+ }
+
///
/// Removes the trusted certificates.
///
@@ -230,13 +276,49 @@ public void RemoveTrustedRootCertificates()
RemoveTrustedRootCertificates(StoreLocation.LocalMachine);
}
+ ///
+ /// Determines whether the root certificate is trusted.
+ ///
+ public bool IsRootCertificateTrusted()
+ {
+ return FindRootCertificate(StoreLocation.CurrentUser) || IsRootCertificateMachineTrusted();
+ }
+
+ ///
+ /// Determines whether the root certificate is machine trusted.
+ ///
+ public bool IsRootCertificateMachineTrusted()
+ {
+ return FindRootCertificate(StoreLocation.LocalMachine);
+ }
+
+ private bool FindRootCertificate(StoreLocation storeLocation)
+ {
+ string value = $"{RootCertificate.Issuer}";
+ return FindCertificates(StoreName.Root, storeLocation, value).Count > 0;
+ }
+
+ private X509Certificate2Collection FindCertificates(StoreName storeName, StoreLocation storeLocation, string findValue)
+ {
+ X509Store x509Store = new X509Store(storeName, storeLocation);
+ try
+ {
+ x509Store.Open(OpenFlags.OpenExistingOnly);
+ return x509Store.Certificates.Find(X509FindType.FindBySubjectDistinguishedName, findValue, false);
+ }
+ finally
+ {
+ x509Store.Close();
+ }
+ }
+
///
/// Create an SSL certificate
///
///
///
///
- internal virtual X509Certificate2 CreateCertificate(string certificateName, bool isRootCertificate)
+ internal X509Certificate2 CreateCertificate(string certificateName, bool isRootCertificate)
{
if (certificateCache.ContainsKey(certificateName))
{
@@ -317,7 +399,7 @@ internal async void ClearIdleCertificates(int certificateCacheTimeOutMinutes)
///
///
///
- internal void TrustRootCertificate(StoreLocation storeLocation)
+ private void TrustRootCertificate(StoreLocation storeLocation)
{
if (RootCertificate == null)
{
@@ -328,7 +410,7 @@ internal void TrustRootCertificate(StoreLocation storeLocation)
return;
}
- X509Store x509RootStore = new X509Store(StoreName.Root, storeLocation);
+ var x509RootStore = new X509Store(StoreName.Root, storeLocation);
var x509PersonalStore = new X509Store(StoreName.My, storeLocation);
try
@@ -357,7 +439,7 @@ internal void TrustRootCertificate(StoreLocation storeLocation)
///
///
///
- internal void RemoveTrustedRootCertificates(StoreLocation storeLocation)
+ private void RemoveTrustedRootCertificates(StoreLocation storeLocation)
{
if (RootCertificate == null)
{
@@ -368,7 +450,7 @@ internal void RemoveTrustedRootCertificates(StoreLocation storeLocation)
return;
}
- X509Store x509RootStore = new X509Store(StoreName.Root, storeLocation);
+ var x509RootStore = new X509Store(StoreName.Root, storeLocation);
var x509PersonalStore = new X509Store(StoreName.My, storeLocation);
try
@@ -382,7 +464,7 @@ internal void RemoveTrustedRootCertificates(StoreLocation storeLocation)
catch (Exception e)
{
exceptionFunc(
- new Exception("Failed to make system trust root certificate "
+ new Exception("Failed to remove root certificate trust "
+ $" for {storeLocation} store location. You may need admin rights.", e));
}
finally
@@ -392,6 +474,9 @@ internal void RemoveTrustedRootCertificates(StoreLocation storeLocation)
}
}
+ ///
+ /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
+ ///
public void Dispose()
{
}
diff --git a/Titanium.Web.Proxy/ProxyServer.cs b/Titanium.Web.Proxy/ProxyServer.cs
index a912b0d53..fefd59b4d 100644
--- a/Titanium.Web.Proxy/ProxyServer.cs
+++ b/Titanium.Web.Proxy/ProxyServer.cs
@@ -647,46 +647,5 @@ private void QuitListen(ProxyEndPoint endPoint)
endPoint.Listener.Server.Close();
endPoint.Listener.Server.Dispose();
}
-
- ///
- /// Invocator for BeforeRequest event.
- ///
- ///
- ///
- protected virtual void OnBeforeRequest(object sender, SessionEventArgs e)
- {
- BeforeRequest?.Invoke(sender, e);
- }
-
- ///
- /// Invocator for BeforeResponse event.
- ///
- ///
- ///
- ///
- protected virtual void OnBeforeResponse(object sender, SessionEventArgs e)
- {
- BeforeResponse?.Invoke(sender, e);
- }
-
- ///
- /// Invocator for ServerCertificateValidationCallback event.
- ///
- ///
- ///
- protected virtual void OnServerCertificateValidationCallback(object sender, CertificateValidationEventArgs e)
- {
- ServerCertificateValidationCallback?.Invoke(sender, e);
- }
-
- ///
- /// Invocator for ClientCertifcateSelectionCallback event.
- ///
- ///
- ///
- protected virtual void OnClientCertificateSelectionCallback(object sender, CertificateSelectionEventArgs e)
- {
- ClientCertificateSelectionCallback?.Invoke(sender, e);
- }
}
}
diff --git a/Titanium.Web.Proxy/RequestHandler.cs b/Titanium.Web.Proxy/RequestHandler.cs
index 99918465d..d1a7a28d0 100644
--- a/Titanium.Web.Proxy/RequestHandler.cs
+++ b/Titanium.Web.Proxy/RequestHandler.cs
@@ -23,8 +23,13 @@ namespace Titanium.Web.Proxy
///
partial class ProxyServer
{
- //This is called when client is aware of proxy
- //So for HTTPS requests client would send CONNECT header to negotiate a secure tcp tunnel via proxy
+ ///
+ /// This is called when client is aware of proxy
+ /// So for HTTPS requests client would send CONNECT header to negotiate a secure tcp tunnel via proxy
+ ///
+ ///
+ ///
+ ///
private async Task HandleClient(ExplicitProxyEndPoint endPoint, TcpClient tcpClient)
{
var disposed = false;
@@ -139,7 +144,7 @@ await sslStream.AuthenticateAsServerAsync(certificate, false,
{
//Siphon out CONNECT request headers
await clientStreamReader.ReadAndIgnoreAllLinesAsync();
-
+
//write back successfull CONNECT response
await WriteConnectResponse(clientStreamWriter, version);
@@ -170,8 +175,13 @@ await TcpHelper.SendRaw(this,
}
}
- //This is called when this proxy acts as a reverse proxy (like a real http server)
- //So for HTTPS requests we would start SSL negotiation right away without expecting a CONNECT request from client
+ ///
+ /// This is called when this proxy acts as a reverse proxy (like a real http server)
+ /// So for HTTPS requests we would start SSL negotiation right away without expecting a CONNECT request from client
+ ///
+ ///
+ ///
+ ///
private async Task HandleClient(TransparentProxyEndPoint endPoint, TcpClient tcpClient)
{
bool disposed = false;
@@ -219,162 +229,10 @@ await sslStream.AuthenticateAsServerAsync(certificate, false,
}
}
- ///
- /// Create a Server Connection
- ///
- ///
- ///
- private async Task GetServerConnection(
- SessionEventArgs args)
- {
- ExternalProxy customUpStreamHttpProxy = null;
- ExternalProxy customUpStreamHttpsProxy = null;
-
- if (args.WebSession.Request.RequestUri.Scheme == "http")
- {
- if (GetCustomUpStreamHttpProxyFunc != null)
- {
- customUpStreamHttpProxy = await GetCustomUpStreamHttpProxyFunc(args);
- }
- }
- else
- {
- if (GetCustomUpStreamHttpsProxyFunc != null)
- {
- customUpStreamHttpsProxy = await GetCustomUpStreamHttpsProxyFunc(args);
- }
- }
-
- args.CustomUpStreamHttpProxyUsed = customUpStreamHttpProxy;
- args.CustomUpStreamHttpsProxyUsed = customUpStreamHttpsProxy;
-
- return await tcpConnectionFactory.CreateClient(this,
- args.WebSession.Request.RequestUri.Host,
- args.WebSession.Request.RequestUri.Port,
- args.WebSession.Request.HttpVersion,
- args.IsHttps,
- customUpStreamHttpProxy ?? UpStreamHttpProxy,
- customUpStreamHttpsProxy ?? UpStreamHttpsProxy,
- args.ProxyClient.ClientStream);
- }
-
-
- private async Task HandleHttpSessionRequestInternal(TcpConnection connection, SessionEventArgs args, bool closeConnection)
- {
- bool disposed = false;
- bool keepAlive = false;
-
- try
- {
- args.WebSession.Request.RequestLocked = true;
-
- //If request was cancelled by user then dispose the client
- if (args.WebSession.Request.CancelRequest)
- {
- return true;
- }
-
- //if expect continue is enabled then send the headers first
- //and see if server would return 100 conitinue
- if (args.WebSession.Request.ExpectContinue)
- {
- args.WebSession.SetConnection(connection);
- await args.WebSession.SendRequest(Enable100ContinueBehaviour);
- }
-
- //If 100 continue was the response inform that to the client
- if (Enable100ContinueBehaviour)
- {
- if (args.WebSession.Request.Is100Continue)
- {
- await WriteResponseStatus(args.WebSession.Response.HttpVersion, "100",
- "Continue", args.ProxyClient.ClientStreamWriter);
- await args.ProxyClient.ClientStreamWriter.WriteLineAsync();
- }
- else if (args.WebSession.Request.ExpectationFailed)
- {
- await WriteResponseStatus(args.WebSession.Response.HttpVersion, "417",
- "Expectation Failed", args.ProxyClient.ClientStreamWriter);
- await args.ProxyClient.ClientStreamWriter.WriteLineAsync();
- }
- }
-
- //If expect continue is not enabled then set the connectio and send request headers
- if (!args.WebSession.Request.ExpectContinue)
- {
- args.WebSession.SetConnection(connection);
- await args.WebSession.SendRequest(Enable100ContinueBehaviour);
- }
-
- //If request was modified by user
- if (args.WebSession.Request.RequestBodyRead)
- {
- if (args.WebSession.Request.ContentEncoding != null)
- {
- args.WebSession.Request.RequestBody = await GetCompressedResponseBody(args.WebSession.Request.ContentEncoding, args.WebSession.Request.RequestBody);
- }
- //chunked send is not supported as of now
- args.WebSession.Request.ContentLength = args.WebSession.Request.RequestBody.Length;
-
- var newStream = args.WebSession.ServerConnection.Stream;
- await newStream.WriteAsync(args.WebSession.Request.RequestBody, 0, args.WebSession.Request.RequestBody.Length);
- }
- else
- {
- if (!args.WebSession.Request.ExpectationFailed)
- {
- //If its a post/put/patch request, then read the client html body and send it to server
- var method = args.WebSession.Request.Method.ToUpper();
- if (method == "POST" || method == "PUT" || method == "PATCH")
- {
- await SendClientRequestBody(args);
- }
- }
- }
-
- //If not expectation failed response was returned by server then parse response
- if (!args.WebSession.Request.ExpectationFailed)
- {
- disposed = await HandleHttpSessionResponse(args);
-
- //already disposed inside above method
- if (disposed)
- {
- return true;
- }
- }
-
- //if connection is closing exit
- if (args.WebSession.Response.ResponseKeepAlive == false)
- {
- return true;
- }
-
- 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)
- {
- //dispose
- Dispose(args.ProxyClient.ClientStream, args.ProxyClient.ClientStreamReader, args.ProxyClient.ClientStreamWriter, args.WebSession.ServerConnection);
- }
- }
-
- return true;
- }
-
///
/// This is the core request handler method for a particular connection from client
+ /// Will create new session (request/response) sequence until
+ /// client/server abruptly terminates connection or by normal HTTP termination
///
///
///
@@ -533,6 +391,168 @@ await TcpHelper.SendRaw(this,
return true;
}
+ ///
+ /// Handle a specific session (request/response sequence)
+ ///
+ ///
+ ///
+ ///
+ ///
+ private async Task HandleHttpSessionRequestInternal(TcpConnection connection,
+ SessionEventArgs args, bool closeConnection)
+ {
+ bool disposed = false;
+ bool keepAlive = false;
+
+ try
+ {
+ args.WebSession.Request.RequestLocked = true;
+
+ //If request was cancelled by user then dispose the client
+ if (args.WebSession.Request.CancelRequest)
+ {
+ return true;
+ }
+
+ //if expect continue is enabled then send the headers first
+ //and see if server would return 100 conitinue
+ if (args.WebSession.Request.ExpectContinue)
+ {
+ args.WebSession.SetConnection(connection);
+ await args.WebSession.SendRequest(Enable100ContinueBehaviour);
+ }
+
+ //If 100 continue was the response inform that to the client
+ if (Enable100ContinueBehaviour)
+ {
+ if (args.WebSession.Request.Is100Continue)
+ {
+ await WriteResponseStatus(args.WebSession.Response.HttpVersion, "100",
+ "Continue", args.ProxyClient.ClientStreamWriter);
+ await args.ProxyClient.ClientStreamWriter.WriteLineAsync();
+ }
+ else if (args.WebSession.Request.ExpectationFailed)
+ {
+ await WriteResponseStatus(args.WebSession.Response.HttpVersion, "417",
+ "Expectation Failed", args.ProxyClient.ClientStreamWriter);
+ await args.ProxyClient.ClientStreamWriter.WriteLineAsync();
+ }
+ }
+
+ //If expect continue is not enabled then set the connectio and send request headers
+ if (!args.WebSession.Request.ExpectContinue)
+ {
+ args.WebSession.SetConnection(connection);
+ await args.WebSession.SendRequest(Enable100ContinueBehaviour);
+ }
+
+ //If request was modified by user
+ if (args.WebSession.Request.RequestBodyRead)
+ {
+ if (args.WebSession.Request.ContentEncoding != null)
+ {
+ args.WebSession.Request.RequestBody = await GetCompressedResponseBody(args.WebSession.Request.ContentEncoding, args.WebSession.Request.RequestBody);
+ }
+ //chunked send is not supported as of now
+ args.WebSession.Request.ContentLength = args.WebSession.Request.RequestBody.Length;
+
+ var newStream = args.WebSession.ServerConnection.Stream;
+ await newStream.WriteAsync(args.WebSession.Request.RequestBody, 0, args.WebSession.Request.RequestBody.Length);
+ }
+ else
+ {
+ if (!args.WebSession.Request.ExpectationFailed)
+ {
+ //If its a post/put/patch request, then read the client html body and send it to server
+ var method = args.WebSession.Request.Method.ToUpper();
+ if (method == "POST" || method == "PUT" || method == "PATCH")
+ {
+ await SendClientRequestBody(args);
+ }
+ }
+ }
+
+ //If not expectation failed response was returned by server then parse response
+ if (!args.WebSession.Request.ExpectationFailed)
+ {
+ disposed = await HandleHttpSessionResponse(args);
+
+ //already disposed inside above method
+ if (disposed)
+ {
+ return true;
+ }
+ }
+
+ //if connection is closing exit
+ if (args.WebSession.Response.ResponseKeepAlive == false)
+ {
+ return true;
+ }
+
+ 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)
+ {
+ //dispose
+ Dispose(args.ProxyClient.ClientStream, args.ProxyClient.ClientStreamReader, args.ProxyClient.ClientStreamWriter, args.WebSession.ServerConnection);
+ }
+ }
+
+ return true;
+ }
+
+ ///
+ /// Create a Server Connection
+ ///
+ ///
+ ///
+ private async Task GetServerConnection(
+ SessionEventArgs args)
+ {
+ ExternalProxy customUpStreamHttpProxy = null;
+ ExternalProxy customUpStreamHttpsProxy = null;
+
+ if (args.WebSession.Request.RequestUri.Scheme == "http")
+ {
+ if (GetCustomUpStreamHttpProxyFunc != null)
+ {
+ customUpStreamHttpProxy = await GetCustomUpStreamHttpProxyFunc(args);
+ }
+ }
+ else
+ {
+ if (GetCustomUpStreamHttpsProxyFunc != null)
+ {
+ customUpStreamHttpsProxy = await GetCustomUpStreamHttpsProxyFunc(args);
+ }
+ }
+
+ args.CustomUpStreamHttpProxyUsed = customUpStreamHttpProxy;
+ args.CustomUpStreamHttpsProxyUsed = customUpStreamHttpsProxy;
+
+ return await tcpConnectionFactory.CreateClient(this,
+ args.WebSession.Request.RequestUri.Host,
+ args.WebSession.Request.RequestUri.Port,
+ args.WebSession.Request.HttpVersion,
+ args.IsHttps,
+ customUpStreamHttpProxy ?? UpStreamHttpProxy,
+ customUpStreamHttpsProxy ?? UpStreamHttpsProxy,
+ args.ProxyClient.ClientStream);
+ }
+
+
///
/// Write successfull CONNECT response to client
///