Commit 69aa70c1 authored by Jehonathan Thomas's avatar Jehonathan Thomas Committed by GitHub

Merge pull request #230 from justcoding121/develop

Beta 
parents e8e41ca3 fe81398b
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Net; using System.Net;
using System.Threading.Tasks; using System.Threading.Tasks;
using Titanium.Web.Proxy.EventArguments; using Titanium.Web.Proxy.EventArguments;
...@@ -17,6 +18,12 @@ namespace Titanium.Web.Proxy.Examples.Basic ...@@ -17,6 +18,12 @@ namespace Titanium.Web.Proxy.Examples.Basic
public ProxyTestController() public ProxyTestController()
{ {
proxyServer = new ProxyServer(); proxyServer = new ProxyServer();
//generate root certificate without storing it in file system
//proxyServer.CertificateEngine = Network.CertificateEngine.BouncyCastle;
//proxyServer.CertificateManager.CreateTrustedRootCertificate(false);
//proxyServer.CertificateManager.TrustRootCertificate();
proxyServer.ExceptionFunc = exception => Console.WriteLine(exception.Message); proxyServer.ExceptionFunc = exception => Console.WriteLine(exception.Message);
proxyServer.TrustRootCertificate = true; proxyServer.TrustRootCertificate = true;
...@@ -40,7 +47,7 @@ namespace Titanium.Web.Proxy.Examples.Basic ...@@ -40,7 +47,7 @@ namespace Titanium.Web.Proxy.Examples.Basic
//Exclude Https addresses you don't want to proxy //Exclude Https addresses you don't want to proxy
//Useful for clients that use certificate pinning //Useful for clients that use certificate pinning
//for example google.com and dropbox.com //for example google.com and dropbox.com
// ExcludedHttpsHostNameRegex = new List<string>() { "google.com", "dropbox.com" } ExcludedHttpsHostNameRegex = new List<string>() { "dropbox.com" }
//Include Https addresses you want to proxy (others will be excluded) //Include Https addresses you want to proxy (others will be excluded)
//for example github.com //for example github.com
...@@ -92,18 +99,22 @@ namespace Titanium.Web.Proxy.Examples.Basic ...@@ -92,18 +99,22 @@ namespace Titanium.Web.Proxy.Examples.Basic
proxyServer.ClientCertificateSelectionCallback -= OnCertificateSelection; proxyServer.ClientCertificateSelectionCallback -= OnCertificateSelection;
proxyServer.Stop(); proxyServer.Stop();
//remove the generated certificates
//proxyServer.CertificateManager.RemoveTrustedRootCertificates();
} }
//intecept & cancel redirect or update requests //intecept & cancel redirect or update requests
public async Task OnRequest(object sender, SessionEventArgs e) public async Task OnRequest(object sender, SessionEventArgs e)
{ {
Console.WriteLine("Active Client Connections:" + ((ProxyServer) sender).ClientConnectionCount);
Console.WriteLine(e.WebSession.Request.Url); Console.WriteLine(e.WebSession.Request.Url);
//read request headers //read request headers
var requestHeaders = e.WebSession.Request.RequestHeaders; var requestHeaders = e.WebSession.Request.RequestHeaders;
var method = e.WebSession.Request.Method.ToUpper(); var method = e.WebSession.Request.Method.ToUpper();
if ((method == "POST" || method == "PUT" || method == "PATCH")) if (method == "POST" || method == "PUT" || method == "PATCH")
{ {
//Get/Set request body bytes //Get/Set request body bytes
byte[] bodyBytes = await e.GetRequestBody(); byte[] bodyBytes = await e.GetRequestBody();
...@@ -116,30 +127,32 @@ namespace Titanium.Web.Proxy.Examples.Basic ...@@ -116,30 +127,32 @@ 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"))
{ //{
await e.Redirect("https://www.paypal.com"); // await e.Redirect("https://www.paypal.com");
} //}
} }
//Modify response //Modify response
public async Task OnResponse(object sender, SessionEventArgs e) public async Task OnResponse(object sender, SessionEventArgs e)
{ {
if(requestBodyHistory.ContainsKey(e.Id)) Console.WriteLine("Active Server Connections:" + (sender as ProxyServer).ServerConnectionCount);
if (requestBodyHistory.ContainsKey(e.Id))
{ {
//access request body by looking up the shared dictionary using requestId //access request body by looking up the shared dictionary using requestId
var requestBody = requestBodyHistory[e.Id]; var requestBody = requestBodyHistory[e.Id];
...@@ -149,14 +162,14 @@ namespace Titanium.Web.Proxy.Examples.Basic ...@@ -149,14 +162,14 @@ namespace Titanium.Web.Proxy.Examples.Basic
var responseHeaders = e.WebSession.Response.ResponseHeaders; var responseHeaders = e.WebSession.Response.ResponseHeaders;
// print out process id of current session // print out process id of current session
Console.WriteLine($"PID: {e.WebSession.ProcessId.Value}"); //Console.WriteLine($"PID: {e.WebSession.ProcessId.Value}");
//if (!e.ProxySession.Request.Host.Equals("medeczane.sgk.gov.tr")) return; //if (!e.ProxySession.Request.Host.Equals("medeczane.sgk.gov.tr")) return;
if (e.WebSession.Request.Method == "GET" || e.WebSession.Request.Method == "POST") if (e.WebSession.Request.Method == "GET" || e.WebSession.Request.Method == "POST")
{ {
if (e.WebSession.Response.ResponseStatusCode == "200") if (e.WebSession.Response.ResponseStatusCode == "200")
{ {
if (e.WebSession.Response.ContentType!=null && e.WebSession.Response.ContentType.Trim().ToLower().Contains("text/html")) if (e.WebSession.Response.ContentType != null && e.WebSession.Response.ContentType.Trim().ToLower().Contains("text/html"))
{ {
byte[] bodyBytes = await e.GetResponseBody(); byte[] bodyBytes = await e.GetResponseBody();
await e.SetResponseBody(bodyBytes); await e.SetResponseBody(bodyBytes);
......
...@@ -16,7 +16,7 @@ namespace Titanium.Web.Proxy ...@@ -16,7 +16,7 @@ namespace Titanium.Web.Proxy
/// <param name="chain"></param> /// <param name="chain"></param>
/// <param name="sslPolicyErrors"></param> /// <param name="sslPolicyErrors"></param>
/// <returns></returns> /// <returns></returns>
private bool ValidateServerCertificate( internal bool ValidateServerCertificate(
object sender, object sender,
X509Certificate certificate, X509Certificate certificate,
X509Chain chain, X509Chain chain,
...@@ -65,7 +65,7 @@ namespace Titanium.Web.Proxy ...@@ -65,7 +65,7 @@ namespace Titanium.Web.Proxy
/// <param name="remoteCertificate"></param> /// <param name="remoteCertificate"></param>
/// <param name="acceptableIssuers"></param> /// <param name="acceptableIssuers"></param>
/// <returns></returns> /// <returns></returns>
private X509Certificate SelectClientCertificate( internal X509Certificate SelectClientCertificate(
object sender, object sender,
string targetHost, string targetHost,
X509CertificateCollection localCertificates, X509CertificateCollection localCertificates,
......
...@@ -93,7 +93,7 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -93,7 +93,7 @@ namespace Titanium.Web.Proxy.EventArguments
{ {
//GET request don't have a request body to read //GET request don't have a request body to read
var method = WebSession.Request.Method.ToUpper(); var method = WebSession.Request.Method.ToUpper();
if ((method != "POST" && method != "PUT" && method != "PATCH")) if (method != "POST" && method != "PUT" && method != "PATCH")
{ {
throw new BodyNotFoundException("Request don't have a body. " + throw new BodyNotFoundException("Request don't have a body. " +
"Please verify that this request is a Http POST/PUT/PATCH and request " + "Please verify that this request is a Http POST/PUT/PATCH and request " +
...@@ -411,6 +411,7 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -411,6 +411,7 @@ namespace Titanium.Web.Proxy.EventArguments
{ {
response.ResponseHeaders = headers; response.ResponseHeaders = headers;
} }
response.HttpVersion = WebSession.Request.HttpVersion; response.HttpVersion = WebSession.Request.HttpVersion;
response.ResponseBody = result; response.ResponseBody = result;
......
using System.Net.Sockets; using System.Net.Sockets;
using Titanium.Web.Proxy.Helpers;
namespace Titanium.Web.Proxy.Extensions namespace Titanium.Web.Proxy.Extensions
{ {
...@@ -32,5 +33,25 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -32,5 +33,25 @@ namespace Titanium.Web.Proxy.Extensions
client.Blocking = blockingState; client.Blocking = blockingState;
} }
} }
/// <summary>
/// Gets the local port from a native TCP row object.
/// </summary>
/// <param name="tcpRow">The TCP row.</param>
/// <returns>The local port</returns>
internal static int GetLocalPort(this NativeMethods.TcpRow tcpRow)
{
return (tcpRow.localPort1 << 8) + tcpRow.localPort2 + (tcpRow.localPort3 << 24) + (tcpRow.localPort4 << 16);
}
/// <summary>
/// Gets the remote port from a native TCP row object.
/// </summary>
/// <param name="tcpRow">The TCP row.</param>
/// <returns>The remote port</returns>
internal static int GetRemotePort(this NativeMethods.TcpRow tcpRow)
{
return (tcpRow.remotePort1 << 8) + tcpRow.remotePort2 + (tcpRow.remotePort3 << 24) + (tcpRow.remotePort4 << 16);
}
} }
} }
...@@ -15,27 +15,14 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -15,27 +15,14 @@ namespace Titanium.Web.Proxy.Helpers
{ {
private readonly CustomBufferedStream stream; private readonly CustomBufferedStream stream;
private readonly int bufferSize; private readonly int bufferSize;
private readonly byte[] staticBuffer;
private readonly Encoding encoding; private readonly Encoding encoding;
[ThreadStatic]
private static byte[] staticBufferField;
private byte[] staticBuffer
{
get
{
if (staticBufferField == null || staticBufferField.Length != bufferSize)
{
staticBufferField = new byte[bufferSize];
}
return staticBufferField;
}
}
internal CustomBinaryReader(CustomBufferedStream stream, int bufferSize) internal CustomBinaryReader(CustomBufferedStream stream, int bufferSize)
{ {
this.stream = stream; this.stream = stream;
staticBuffer = new byte[bufferSize];
this.bufferSize = bufferSize; this.bufferSize = bufferSize;
//default to UTF-8 //default to UTF-8
...@@ -122,10 +109,13 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -122,10 +109,13 @@ namespace Titanium.Web.Proxy.Helpers
{ {
int bytesToRead = bufferSize; int bytesToRead = bufferSize;
var buffer = staticBuffer;
if (totalBytesToRead < bufferSize) if (totalBytesToRead < bufferSize)
{
bytesToRead = (int) totalBytesToRead; bytesToRead = (int) totalBytesToRead;
buffer = new byte[bytesToRead];
}
var buffer = staticBuffer;
int bytesRead; int bytesRead;
var totalBytesRead = 0; var totalBytesRead = 0;
......
...@@ -168,6 +168,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -168,6 +168,7 @@ namespace Titanium.Web.Proxy.Helpers
if (bufferLength > 0) if (bufferLength > 0)
{ {
await destination.WriteAsync(streamBuffer, bufferPos, bufferLength, cancellationToken); await destination.WriteAsync(streamBuffer, bufferPos, bufferLength, cancellationToken);
bufferLength = 0;
} }
await baseStream.CopyToAsync(destination, bufferSize, cancellationToken); await baseStream.CopyToAsync(destination, bufferSize, cancellationToken);
...@@ -307,6 +308,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -307,6 +308,7 @@ namespace Titanium.Web.Proxy.Helpers
/// <returns> /// <returns>
/// A task that represents the asynchronous write operation. /// A task that represents the asynchronous write operation.
/// </returns> /// </returns>
[DebuggerStepThrough]
public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{ {
return baseStream.WriteAsync(buffer, offset, count, cancellationToken); return baseStream.WriteAsync(buffer, offset, count, cancellationToken);
......
...@@ -8,8 +8,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -8,8 +8,7 @@ namespace Titanium.Web.Proxy.Helpers
{ {
private static int FindProcessIdFromLocalPort(int port, IpVersion ipVersion) private static int FindProcessIdFromLocalPort(int port, IpVersion ipVersion)
{ {
var tcpRow = TcpHelper.GetExtendedTcpTable(ipVersion).FirstOrDefault( var tcpRow = TcpHelper.GetTcpRowByLocalPort(ipVersion, port);
row => row.LocalEndPoint.Port == port);
return tcpRow?.ProcessId ?? 0; return tcpRow?.ProcessId ?? 0;
} }
......
...@@ -93,21 +93,21 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -93,21 +93,21 @@ namespace Titanium.Web.Proxy.Helpers
var ipVersionValue = ipVersion == IpVersion.Ipv4 ? NativeMethods.AfInet : NativeMethods.AfInet6; var ipVersionValue = ipVersion == IpVersion.Ipv4 ? NativeMethods.AfInet : NativeMethods.AfInet6;
if (NativeMethods.GetExtendedTcpTable(tcpTable, ref tcpTableLength, false, ipVersionValue, (int) NativeMethods.TcpTableType.OwnerPidAll, 0) != 0) if (NativeMethods.GetExtendedTcpTable(tcpTable, ref tcpTableLength, false, ipVersionValue, (int)NativeMethods.TcpTableType.OwnerPidAll, 0) != 0)
{ {
try try
{ {
tcpTable = Marshal.AllocHGlobal(tcpTableLength); tcpTable = Marshal.AllocHGlobal(tcpTableLength);
if (NativeMethods.GetExtendedTcpTable(tcpTable, ref tcpTableLength, true, ipVersionValue, (int) NativeMethods.TcpTableType.OwnerPidAll, 0) == 0) if (NativeMethods.GetExtendedTcpTable(tcpTable, ref tcpTableLength, true, ipVersionValue, (int)NativeMethods.TcpTableType.OwnerPidAll, 0) == 0)
{ {
NativeMethods.TcpTable table = (NativeMethods.TcpTable) Marshal.PtrToStructure(tcpTable, typeof(NativeMethods.TcpTable)); NativeMethods.TcpTable table = (NativeMethods.TcpTable)Marshal.PtrToStructure(tcpTable, typeof(NativeMethods.TcpTable));
IntPtr rowPtr = (IntPtr) ((long) tcpTable + Marshal.SizeOf(table.length)); IntPtr rowPtr = (IntPtr)((long)tcpTable + Marshal.SizeOf(table.length));
for (int i = 0; i < table.length; ++i) for (int i = 0; i < table.length; ++i)
{ {
tcpRows.Add(new TcpRow((NativeMethods.TcpRow) Marshal.PtrToStructure(rowPtr, typeof(NativeMethods.TcpRow)))); tcpRows.Add(new TcpRow((NativeMethods.TcpRow)Marshal.PtrToStructure(rowPtr, typeof(NativeMethods.TcpRow))));
rowPtr = (IntPtr) ((long) rowPtr + Marshal.SizeOf(typeof(NativeMethods.TcpRow))); rowPtr = (IntPtr)((long)rowPtr + Marshal.SizeOf(typeof(NativeMethods.TcpRow)));
} }
} }
} }
...@@ -123,30 +123,71 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -123,30 +123,71 @@ namespace Titanium.Web.Proxy.Helpers
return new TcpTable(tcpRows); return new TcpTable(tcpRows);
} }
/// <summary>
/// Gets the TCP row by local port number.
/// </summary>
/// <returns><see cref="TcpRow"/>.</returns>
internal static TcpRow GetTcpRowByLocalPort(IpVersion ipVersion, int localPort)
{
IntPtr tcpTable = IntPtr.Zero;
int tcpTableLength = 0;
var ipVersionValue = ipVersion == IpVersion.Ipv4 ? NativeMethods.AfInet : NativeMethods.AfInet6;
if (NativeMethods.GetExtendedTcpTable(tcpTable, ref tcpTableLength, false, ipVersionValue, (int)NativeMethods.TcpTableType.OwnerPidAll, 0) != 0)
{
try
{
tcpTable = Marshal.AllocHGlobal(tcpTableLength);
if (NativeMethods.GetExtendedTcpTable(tcpTable, ref tcpTableLength, true, ipVersionValue, (int)NativeMethods.TcpTableType.OwnerPidAll, 0) == 0)
{
NativeMethods.TcpTable table = (NativeMethods.TcpTable)Marshal.PtrToStructure(tcpTable, typeof(NativeMethods.TcpTable));
IntPtr rowPtr = (IntPtr)((long)tcpTable + Marshal.SizeOf(table.length));
for (int i = 0; i < table.length; ++i)
{
var tcpRow = (NativeMethods.TcpRow)Marshal.PtrToStructure(rowPtr, typeof(NativeMethods.TcpRow));
if (tcpRow.GetLocalPort() == localPort)
{
return new TcpRow(tcpRow);
}
rowPtr = (IntPtr)((long)rowPtr + Marshal.SizeOf(typeof(NativeMethods.TcpRow)));
}
}
}
finally
{
if (tcpTable != IntPtr.Zero)
{
Marshal.FreeHGlobal(tcpTable);
}
}
}
return null;
}
/// <summary> /// <summary>
/// relays the input clientStream to the server at the specified host name and port with the given httpCmd and headers as prefix /// relays the input clientStream to the server at the specified host name and port with the given httpCmd and headers as prefix
/// Usefull for websocket requests /// Usefull for websocket requests
/// </summary> /// </summary>
/// <param name="bufferSize"></param> /// <param name="server"></param>
/// <param name="connectionTimeOutSeconds"></param>
/// <param name="remoteHostName"></param> /// <param name="remoteHostName"></param>
/// <param name="remotePort"></param>
/// <param name="httpCmd"></param> /// <param name="httpCmd"></param>
/// <param name="httpVersion"></param> /// <param name="httpVersion"></param>
/// <param name="requestHeaders"></param> /// <param name="requestHeaders"></param>
/// <param name="isHttps"></param> /// <param name="isHttps"></param>
/// <param name="remotePort"></param>
/// <param name="supportedProtocols"></param>
/// <param name="remoteCertificateValidationCallback"></param>
/// <param name="localCertificateSelectionCallback"></param>
/// <param name="clientStream"></param> /// <param name="clientStream"></param>
/// <param name="tcpConnectionFactory"></param> /// <param name="tcpConnectionFactory"></param>
/// <param name="upStreamEndPoint"></param>
/// <returns></returns> /// <returns></returns>
internal static async Task SendRaw(int bufferSize, int connectionTimeOutSeconds, internal static async Task SendRaw(ProxyServer server,
string remoteHostName, int remotePort, string httpCmd, Version httpVersion, Dictionary<string, HttpHeader> requestHeaders, string remoteHostName, int remotePort,
bool isHttps, SslProtocols supportedProtocols, string httpCmd, Version httpVersion, Dictionary<string, HttpHeader> requestHeaders,
RemoteCertificateValidationCallback remoteCertificateValidationCallback, LocalCertificateSelectionCallback localCertificateSelectionCallback, bool isHttps,
Stream clientStream, TcpConnectionFactory tcpConnectionFactory, IPEndPoint upStreamEndPoint) Stream clientStream, TcpConnectionFactory tcpConnectionFactory)
{ {
//prepare the prefix content //prepare the prefix content
StringBuilder sb = null; StringBuilder sb = null;
...@@ -172,11 +213,10 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -172,11 +213,10 @@ namespace Titanium.Web.Proxy.Helpers
sb.Append(ProxyConstants.NewLine); sb.Append(ProxyConstants.NewLine);
} }
var tcpConnection = await tcpConnectionFactory.CreateClient(bufferSize, connectionTimeOutSeconds, var tcpConnection = await tcpConnectionFactory.CreateClient(server,
remoteHostName, remotePort, remoteHostName, remotePort,
httpVersion, isHttps, httpVersion, isHttps,
supportedProtocols, remoteCertificateValidationCallback, localCertificateSelectionCallback, null, null, clientStream);
null, null, clientStream, upStreamEndPoint);
try try
{ {
...@@ -192,6 +232,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -192,6 +232,7 @@ namespace Titanium.Web.Proxy.Helpers
finally finally
{ {
tcpConnection.Dispose(); tcpConnection.Dispose();
server.ServerConnectionCount--;
} }
} }
} }
......
using System.Collections.Generic;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Http
{
internal static class HeaderParser
{
internal static async Task ReadHeaders(CustomBinaryReader reader,
Dictionary<string, List<HttpHeader>> nonUniqueResponseHeaders,
Dictionary<string, HttpHeader> headers)
{
string tmpLine;
while (!string.IsNullOrEmpty(tmpLine = await reader.ReadLineAsync()))
{
var header = tmpLine.Split(ProxyConstants.ColonSplit, 2);
var newHeader = new HttpHeader(header[0], header[1]);
//if header exist in non-unique header collection add it there
if (nonUniqueResponseHeaders.ContainsKey(newHeader.Name))
{
nonUniqueResponseHeaders[newHeader.Name].Add(newHeader);
}
//if header is alread in unique header collection then move both to non-unique collection
else if (headers.ContainsKey(newHeader.Name))
{
var existing = headers[newHeader.Name];
var nonUniqueHeaders = new List<HttpHeader> { existing, newHeader };
nonUniqueResponseHeaders.Add(newHeader.Name, nonUniqueHeaders);
headers.Remove(newHeader.Name);
}
//add to unique header collection
else
{
headers.Add(newHeader.Name, newHeader);
}
}
}
}
}
...@@ -170,10 +170,10 @@ namespace Titanium.Web.Proxy.Http ...@@ -170,10 +170,10 @@ namespace Titanium.Web.Proxy.Http
var httpVersion = httpResult[0].Trim().ToLower(); var httpVersion = httpResult[0].Trim().ToLower();
var version = new Version(1, 1); var version = HttpHeader.Version11;
if (0 == string.CompareOrdinal(httpVersion, "http/1.0")) if (string.Equals(httpVersion, "HTTP/1.0", StringComparison.OrdinalIgnoreCase))
{ {
version = new Version(1, 0); version = HttpHeader.Version10;
} }
Response.HttpVersion = version; Response.HttpVersion = version;
...@@ -192,7 +192,8 @@ namespace Titanium.Web.Proxy.Http ...@@ -192,7 +192,8 @@ namespace Titanium.Web.Proxy.Http
await ReceiveResponse(); await ReceiveResponse();
return; return;
} }
else if (Response.ResponseStatusCode.Equals("417")
if (Response.ResponseStatusCode.Equals("417")
&& Response.ResponseStatusDescription.Equals("expectation failed", StringComparison.CurrentCultureIgnoreCase)) && Response.ResponseStatusDescription.Equals("expectation failed", StringComparison.CurrentCultureIgnoreCase))
{ {
//read next line after expectation failed response //read next line after expectation failed response
...@@ -204,36 +205,8 @@ namespace Titanium.Web.Proxy.Http ...@@ -204,36 +205,8 @@ namespace Titanium.Web.Proxy.Http
return; return;
} }
//Read the Response headers
//Read the response headers in to unique and non-unique header collections //Read the response headers in to unique and non-unique header collections
string tmpLine; await HeaderParser.ReadHeaders(ServerConnection.StreamReader, Response.NonUniqueResponseHeaders, Response.ResponseHeaders);
while (!string.IsNullOrEmpty(tmpLine = await ServerConnection.StreamReader.ReadLineAsync()))
{
var header = tmpLine.Split(ProxyConstants.ColonSplit, 2);
var newHeader = new HttpHeader(header[0], header[1]);
//if header exist in non-unique header collection add it there
if (Response.NonUniqueResponseHeaders.ContainsKey(newHeader.Name))
{
Response.NonUniqueResponseHeaders[newHeader.Name].Add(newHeader);
}
//if header is alread in unique header collection then move both to non-unique collection
else if (Response.ResponseHeaders.ContainsKey(newHeader.Name))
{
var existing = Response.ResponseHeaders[newHeader.Name];
var nonUniqueHeaders = new List<HttpHeader> {existing, newHeader};
Response.NonUniqueResponseHeaders.Add(newHeader.Name, nonUniqueHeaders);
Response.ResponseHeaders.Remove(newHeader.Name);
}
//add to unique header collection
else
{
Response.ResponseHeaders.Add(newHeader.Name, newHeader);
}
}
} }
} }
} }
...@@ -242,8 +242,8 @@ namespace Titanium.Web.Proxy.Http ...@@ -242,8 +242,8 @@ namespace Titanium.Web.Proxy.Http
/// </summary> /// </summary>
internal string RequestBodyString { get; set; } internal string RequestBodyString { get; set; }
internal bool RequestBodyRead { get; set; } internal bool RequestBodyRead { get; set; }
internal bool RequestLocked { get; set; } internal bool RequestLocked { get; set; }
/// <summary> /// <summary>
......
...@@ -9,6 +9,10 @@ namespace Titanium.Web.Proxy.Models ...@@ -9,6 +9,10 @@ namespace Titanium.Web.Proxy.Models
/// </summary> /// </summary>
public class HttpHeader public class HttpHeader
{ {
internal static Version Version10 = new Version(1, 0);
internal static Version Version11 = new Version(1, 1);
/// <summary> /// <summary>
/// Constructor. /// Constructor.
/// </summary> /// </summary>
......
...@@ -29,9 +29,9 @@ namespace Titanium.Web.Proxy.Network ...@@ -29,9 +29,9 @@ namespace Titanium.Web.Proxy.Network
/// <summary> /// <summary>
/// A class to manage SSL certificates used by this proxy server /// A class to manage SSL certificates used by this proxy server
/// </summary> /// </summary>
internal class CertificateManager : IDisposable public class CertificateManager : IDisposable
{ {
public CertificateEngine Engine internal CertificateEngine Engine
{ {
get { return engine; } get { return engine; }
set set
...@@ -164,10 +164,13 @@ namespace Titanium.Web.Proxy.Network ...@@ -164,10 +164,13 @@ namespace Titanium.Web.Proxy.Network
/// <summary> /// <summary>
/// Attempts to create a RootCertificate /// Attempts to create a RootCertificate
/// </summary> /// </summary>
/// <returns>true if succeeded, else false</returns> /// <param name="persistToFile">if set to <c>true</c> try to load/save the certificate from rootCert.pfx.</param>
internal bool CreateTrustedRootCertificate() /// <returns>
/// true if succeeded, else false
/// </returns>
public bool CreateTrustedRootCertificate(bool persistToFile = true)
{ {
if (RootCertificate == null) if (persistToFile && RootCertificate == null)
{ {
RootCertificate = LoadRootCertificate(); RootCertificate = LoadRootCertificate();
} }
...@@ -186,7 +189,7 @@ namespace Titanium.Web.Proxy.Network ...@@ -186,7 +189,7 @@ namespace Titanium.Web.Proxy.Network
exceptionFunc(e); exceptionFunc(e);
} }
if (RootCertificate != null) if (persistToFile && RootCertificate != null)
{ {
try try
{ {
...@@ -205,7 +208,7 @@ namespace Titanium.Web.Proxy.Network ...@@ -205,7 +208,7 @@ namespace Titanium.Web.Proxy.Network
/// <summary> /// <summary>
/// Trusts the root certificate. /// Trusts the root certificate.
/// </summary> /// </summary>
internal void TrustRootCertificate() public void TrustRootCertificate()
{ {
//current user //current user
TrustRootCertificate(StoreLocation.CurrentUser); TrustRootCertificate(StoreLocation.CurrentUser);
...@@ -214,6 +217,18 @@ namespace Titanium.Web.Proxy.Network ...@@ -214,6 +217,18 @@ namespace Titanium.Web.Proxy.Network
TrustRootCertificate(StoreLocation.LocalMachine); TrustRootCertificate(StoreLocation.LocalMachine);
} }
/// <summary>
/// Removes the trusted certificates.
/// </summary>
public void RemoveTrustedRootCertificates()
{
//current user
RemoveTrustedRootCertificates(StoreLocation.CurrentUser);
//current system
RemoveTrustedRootCertificates(StoreLocation.LocalMachine);
}
/// <summary> /// <summary>
/// Create an SSL certificate /// Create an SSL certificate
/// </summary> /// </summary>
...@@ -336,6 +351,46 @@ namespace Titanium.Web.Proxy.Network ...@@ -336,6 +351,46 @@ namespace Titanium.Web.Proxy.Network
} }
} }
/// <summary>
/// Remove the Root Certificate trust
/// </summary>
/// <param name="storeLocation"></param>
/// <returns></returns>
internal void RemoveTrustedRootCertificates(StoreLocation storeLocation)
{
if (RootCertificate == null)
{
exceptionFunc(
new Exception("Could not set root certificate"
+ " as system proxy since it is null or empty."));
return;
}
X509Store x509RootStore = new X509Store(StoreName.Root, storeLocation);
var x509PersonalStore = new X509Store(StoreName.My, storeLocation);
try
{
x509RootStore.Open(OpenFlags.ReadWrite);
x509PersonalStore.Open(OpenFlags.ReadWrite);
x509RootStore.Remove(RootCertificate);
x509PersonalStore.Remove(RootCertificate);
}
catch (Exception e)
{
exceptionFunc(
new Exception("Failed to make system trust root certificate "
+ $" for {storeLocation} store location. You may need admin rights.", e));
}
finally
{
x509RootStore.Close();
x509PersonalStore.Close();
}
}
public void Dispose() public void Dispose()
{ {
} }
......
...@@ -55,10 +55,10 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -55,10 +55,10 @@ namespace Titanium.Web.Proxy.Network.Tcp
{ {
Stream?.Close(); Stream?.Close();
Stream?.Dispose(); Stream?.Dispose();
StreamReader?.Dispose(); StreamReader?.Dispose();
TcpClient.LingerState = new LingerOption(true, 0); TcpClient?.Close();
TcpClient.Close();
} }
} }
} }
...@@ -6,43 +6,35 @@ using System.IO; ...@@ -6,43 +6,35 @@ using System.IO;
using System.Net.Security; using System.Net.Security;
using Titanium.Web.Proxy.Helpers; using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Models; using Titanium.Web.Proxy.Models;
using System.Security.Authentication;
using System.Linq; using System.Linq;
using Titanium.Web.Proxy.Extensions; using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Shared; using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Network.Tcp namespace Titanium.Web.Proxy.Network.Tcp
{ {
using System.Net;
/// <summary> /// <summary>
/// A class that manages Tcp Connection to server used by this proxy server /// A class that manages Tcp Connection to server used by this proxy server
/// </summary> /// </summary>
internal class TcpConnectionFactory internal class TcpConnectionFactory
{ {
/// <summary> /// <summary>
/// Creates a TCP connection to server /// Creates a TCP connection to server
/// </summary> /// </summary>
/// <param name="bufferSize"></param> /// <param name="server"></param>
/// <param name="connectionTimeOutSeconds"></param>
/// <param name="remoteHostName"></param> /// <param name="remoteHostName"></param>
/// <param name="remotePort"></param>
/// <param name="httpVersion"></param> /// <param name="httpVersion"></param>
/// <param name="isHttps"></param> /// <param name="isHttps"></param>
/// <param name="remotePort"></param>
/// <param name="supportedSslProtocols"></param>
/// <param name="remoteCertificateValidationCallback"></param>
/// <param name="localCertificateSelectionCallback"></param>
/// <param name="externalHttpProxy"></param> /// <param name="externalHttpProxy"></param>
/// <param name="externalHttpsProxy"></param> /// <param name="externalHttpsProxy"></param>
/// <param name="clientStream"></param> /// <param name="clientStream"></param>
/// <param name="upStreamEndPoint"></param>
/// <returns></returns> /// <returns></returns>
internal async Task<TcpConnection> CreateClient(int bufferSize, int connectionTimeOutSeconds, internal async Task<TcpConnection> CreateClient(ProxyServer server,
string remoteHostName, int remotePort, Version httpVersion, string remoteHostName, int remotePort, Version httpVersion,
bool isHttps, SslProtocols supportedSslProtocols, bool isHttps,
RemoteCertificateValidationCallback remoteCertificateValidationCallback, LocalCertificateSelectionCallback localCertificateSelectionCallback,
ExternalProxy externalHttpProxy, ExternalProxy externalHttpsProxy, ExternalProxy externalHttpProxy, ExternalProxy externalHttpsProxy,
Stream clientStream, IPEndPoint upStreamEndPoint) Stream clientStream)
{ {
TcpClient client; TcpClient client;
CustomBufferedStream stream; CustomBufferedStream stream;
...@@ -59,11 +51,11 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -59,11 +51,11 @@ namespace Titanium.Web.Proxy.Network.Tcp
//If this proxy uses another external proxy then create a tunnel request for HTTPS connections //If this proxy uses another external proxy then create a tunnel request for HTTPS connections
if (useHttpsProxy) if (useHttpsProxy)
{ {
client = new TcpClient(upStreamEndPoint); client = new TcpClient(server.UpStreamEndPoint);
await client.ConnectAsync(externalHttpsProxy.HostName, externalHttpsProxy.Port); await client.ConnectAsync(externalHttpsProxy.HostName, externalHttpsProxy.Port);
stream = new CustomBufferedStream(client.GetStream(), bufferSize); stream = new CustomBufferedStream(client.GetStream(), server.BufferSize);
using (var writer = new StreamWriter(stream, Encoding.ASCII, bufferSize, true) {NewLine = ProxyConstants.NewLine}) using (var writer = new StreamWriter(stream, Encoding.ASCII, server.BufferSize, true) {NewLine = ProxyConstants.NewLine})
{ {
await writer.WriteLineAsync($"CONNECT {remoteHostName}:{remotePort} HTTP/{httpVersion}"); await writer.WriteLineAsync($"CONNECT {remoteHostName}:{remotePort} HTTP/{httpVersion}");
await writer.WriteLineAsync($"Host: {remoteHostName}:{remotePort}"); await writer.WriteLineAsync($"Host: {remoteHostName}:{remotePort}");
...@@ -79,7 +71,7 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -79,7 +71,7 @@ namespace Titanium.Web.Proxy.Network.Tcp
writer.Close(); writer.Close();
} }
using (var reader = new CustomBinaryReader(stream, bufferSize)) using (var reader = new CustomBinaryReader(stream, server.BufferSize))
{ {
var result = await reader.ReadLineAsync(); var result = await reader.ReadLineAsync();
...@@ -93,19 +85,19 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -93,19 +85,19 @@ namespace Titanium.Web.Proxy.Network.Tcp
} }
else else
{ {
client = new TcpClient(upStreamEndPoint); client = new TcpClient(server.UpStreamEndPoint);
await client.ConnectAsync(remoteHostName, remotePort); await client.ConnectAsync(remoteHostName, remotePort);
stream = new CustomBufferedStream(client.GetStream(), bufferSize); stream = new CustomBufferedStream(client.GetStream(), server.BufferSize);
} }
try try
{ {
sslStream = new SslStream(stream, true, remoteCertificateValidationCallback, sslStream = new SslStream(stream, true, server.ValidateServerCertificate,
localCertificateSelectionCallback); server.SelectClientCertificate);
await sslStream.AuthenticateAsClientAsync(remoteHostName, null, supportedSslProtocols, false); await sslStream.AuthenticateAsClientAsync(remoteHostName, null, server.SupportedSslProtocols, false);
stream = new CustomBufferedStream(sslStream, bufferSize); stream = new CustomBufferedStream(sslStream, server.BufferSize);
} }
catch catch
{ {
...@@ -118,24 +110,24 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -118,24 +110,24 @@ namespace Titanium.Web.Proxy.Network.Tcp
{ {
if (useHttpProxy) if (useHttpProxy)
{ {
client = new TcpClient(upStreamEndPoint); client = new TcpClient(server.UpStreamEndPoint);
await client.ConnectAsync(externalHttpProxy.HostName, externalHttpProxy.Port); await client.ConnectAsync(externalHttpProxy.HostName, externalHttpProxy.Port);
stream = new CustomBufferedStream(client.GetStream(), bufferSize); stream = new CustomBufferedStream(client.GetStream(), server.BufferSize);
} }
else else
{ {
client = new TcpClient(upStreamEndPoint); client = new TcpClient(server.UpStreamEndPoint);
await client.ConnectAsync(remoteHostName, remotePort); await client.ConnectAsync(remoteHostName, remotePort);
stream = new CustomBufferedStream(client.GetStream(), bufferSize); stream = new CustomBufferedStream(client.GetStream(), server.BufferSize);
} }
} }
client.ReceiveTimeout = connectionTimeOutSeconds * 1000; client.ReceiveTimeout = server.ConnectionTimeOutSeconds * 1000;
client.SendTimeout = connectionTimeOutSeconds * 1000; client.SendTimeout = server.ConnectionTimeOutSeconds * 1000;
stream.ReadTimeout = connectionTimeOutSeconds * 1000; client.LingerState = new LingerOption(true, 0);
stream.WriteTimeout = connectionTimeOutSeconds * 1000;
server.ServerConnectionCount++;
return new TcpConnection return new TcpConnection
{ {
...@@ -145,7 +137,7 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -145,7 +137,7 @@ namespace Titanium.Web.Proxy.Network.Tcp
Port = remotePort, Port = remotePort,
IsHttps = isHttps, IsHttps = isHttps,
TcpClient = client, TcpClient = client,
StreamReader = new CustomBinaryReader(stream, bufferSize), StreamReader = new CustomBinaryReader(stream, server.BufferSize),
Stream = stream, Stream = stream,
Version = httpVersion Version = httpVersion
}; };
......
using System.Net; using System.Net;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Helpers; using Titanium.Web.Proxy.Helpers;
namespace Titanium.Web.Proxy.Network.Tcp namespace Titanium.Web.Proxy.Network.Tcp
...@@ -17,24 +18,42 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -17,24 +18,42 @@ namespace Titanium.Web.Proxy.Network.Tcp
{ {
ProcessId = tcpRow.owningPid; ProcessId = tcpRow.owningPid;
int localPort = (tcpRow.localPort1 << 8) + (tcpRow.localPort2) + (tcpRow.localPort3 << 24) + (tcpRow.localPort4 << 16); LocalPort = tcpRow.GetLocalPort();
long localAddress = tcpRow.localAddr; LocalAddress = tcpRow.localAddr;
LocalEndPoint = new IPEndPoint(localAddress, localPort);
int remotePort = (tcpRow.remotePort1 << 8) + (tcpRow.remotePort2) + (tcpRow.remotePort3 << 24) + (tcpRow.remotePort4 << 16); RemotePort = tcpRow.GetRemotePort();
long remoteAddress = tcpRow.remoteAddr; RemoteAddress = tcpRow.remoteAddr;
RemoteEndPoint = new IPEndPoint(remoteAddress, remotePort);
} }
/// <summary>
/// Gets the local end point address.
/// </summary>
internal long LocalAddress { get; }
/// <summary>
/// Gets the local end point port.
/// </summary>
internal int LocalPort { get; }
/// <summary> /// <summary>
/// Gets the local end point. /// Gets the local end point.
/// </summary> /// </summary>
internal IPEndPoint LocalEndPoint { get; } internal IPEndPoint LocalEndPoint => new IPEndPoint(LocalAddress, LocalPort);
/// <summary>
/// Gets the remote end point address.
/// </summary>
internal long RemoteAddress { get; }
/// <summary>
/// Gets the remote end point port.
/// </summary>
internal int RemotePort { get; }
/// <summary> /// <summary>
/// Gets the remote end point. /// Gets the remote end point.
/// </summary> /// </summary>
internal IPEndPoint RemoteEndPoint { get; } internal IPEndPoint RemoteEndPoint => new IPEndPoint(RemoteAddress, RemotePort);
/// <summary> /// <summary>
/// Gets the process identifier. /// Gets the process identifier.
......
...@@ -19,78 +19,36 @@ namespace Titanium.Web.Proxy ...@@ -19,78 +19,36 @@ namespace Titanium.Web.Proxy
return true; return true;
} }
var httpHeaders = headers as HttpHeader[] ?? headers.ToArray(); var httpHeaders = headers as ICollection<HttpHeader> ?? headers.ToArray();
try try
{ {
if (httpHeaders.All(t => t.Name != "Proxy-Authorization")) if (httpHeaders.All(t => t.Name != "Proxy-Authorization"))
{ {
await WriteResponseStatus(new Version(1, 1), "407", await SendAuthentication407Response(clientStreamWriter, "Proxy Authentication Required");
"Proxy Authentication Required", clientStreamWriter);
var response = new Response
{
ResponseHeaders = new Dictionary<string, HttpHeader>
{
{
"Proxy-Authenticate",
new HttpHeader("Proxy-Authenticate", "Basic realm=\"TitaniumProxy\"")
},
{"Proxy-Connection", new HttpHeader("Proxy-Connection", "close")}
}
};
await WriteResponseHeaders(clientStreamWriter, response);
await clientStreamWriter.WriteLineAsync();
return false; return false;
} }
var header = httpHeaders.FirstOrDefault(t => t.Name == "Proxy-Authorization"); var header = httpHeaders.FirstOrDefault(t => t.Name == "Proxy-Authorization");
if (null == header) throw new NullReferenceException(); if (header == null) throw new NullReferenceException();
var headerValue = header.Value.Trim(); var headerValue = header.Value.Trim();
if (!headerValue.StartsWith("basic", StringComparison.CurrentCultureIgnoreCase)) if (!headerValue.StartsWith("basic", StringComparison.CurrentCultureIgnoreCase))
{ {
//Return not authorized //Return not authorized
await WriteResponseStatus(new Version(1, 1), "407", await SendAuthentication407Response(clientStreamWriter, "Proxy Authentication Invalid");
"Proxy Authentication Invalid", clientStreamWriter);
var response = new Response
{
ResponseHeaders = new Dictionary<string, HttpHeader>
{
{
"Proxy-Authenticate",
new HttpHeader("Proxy-Authenticate", "Basic realm=\"TitaniumProxy\"")
},
{"Proxy-Connection", new HttpHeader("Proxy-Connection", "close")}
}
};
await WriteResponseHeaders(clientStreamWriter, response);
await clientStreamWriter.WriteLineAsync();
return false; return false;
} }
headerValue = headerValue.Substring(5).Trim(); headerValue = headerValue.Substring(5).Trim();
var decoded = Encoding.UTF8.GetString(Convert.FromBase64String(headerValue)); var decoded = Encoding.UTF8.GetString(Convert.FromBase64String(headerValue));
if (decoded.Contains(":") == false) if (decoded.Contains(":") == false)
{ {
//Return not authorized //Return not authorized
await WriteResponseStatus(new Version(1, 1), "407", await SendAuthentication407Response(clientStreamWriter, "Proxy Authentication Invalid");
"Proxy Authentication Invalid", clientStreamWriter);
var response = new Response
{
ResponseHeaders = new Dictionary<string, HttpHeader>
{
{
"Proxy-Authenticate",
new HttpHeader("Proxy-Authenticate", "Basic realm=\"TitaniumProxy\"")
},
{"Proxy-Connection", new HttpHeader("Proxy-Connection", "close")}
}
};
await WriteResponseHeaders(clientStreamWriter, response);
await clientStreamWriter.WriteLineAsync();
return false; return false;
} }
var username = decoded.Substring(0, decoded.IndexOf(':')); var username = decoded.Substring(0, decoded.IndexOf(':'));
var password = decoded.Substring(decoded.IndexOf(':') + 1); var password = decoded.Substring(decoded.IndexOf(':') + 1);
return await AuthenticateUserFunc(username, password); return await AuthenticateUserFunc(username, password);
...@@ -98,9 +56,16 @@ namespace Titanium.Web.Proxy ...@@ -98,9 +56,16 @@ namespace Titanium.Web.Proxy
catch (Exception e) catch (Exception e)
{ {
ExceptionFunc(new ProxyAuthorizationException("Error whilst authorizing request", e, httpHeaders)); ExceptionFunc(new ProxyAuthorizationException("Error whilst authorizing request", e, httpHeaders));
//Return not authorized //Return not authorized
await WriteResponseStatus(new Version(1, 1), "407", await SendAuthentication407Response(clientStreamWriter, "Proxy Authentication Invalid");
"Proxy Authentication Invalid", clientStreamWriter); return false;
}
}
private async Task SendAuthentication407Response(StreamWriter clientStreamWriter, string description)
{
await WriteResponseStatus(HttpHeader.Version11, "407", description, clientStreamWriter);
var response = new Response var response = new Response
{ {
ResponseHeaders = new Dictionary<string, HttpHeader> ResponseHeaders = new Dictionary<string, HttpHeader>
...@@ -112,8 +77,6 @@ namespace Titanium.Web.Proxy ...@@ -112,8 +77,6 @@ namespace Titanium.Web.Proxy
await WriteResponseHeaders(clientStreamWriter, response); await WriteResponseHeaders(clientStreamWriter, response);
await clientStreamWriter.WriteLineAsync(); await clientStreamWriter.WriteLineAsync();
return false;
}
} }
} }
} }
...@@ -24,11 +24,6 @@ namespace Titanium.Web.Proxy ...@@ -24,11 +24,6 @@ namespace Titanium.Web.Proxy
/// </summary> /// </summary>
private bool proxyRunning { get; set; } private bool proxyRunning { get; set; }
/// <summary>
/// Manages certificates used by this proxy
/// </summary>
private CertificateManager certificateManager { get; set; }
/// <summary> /// <summary>
/// An default exception log func /// An default exception log func
/// </summary> /// </summary>
...@@ -64,13 +59,18 @@ namespace Titanium.Web.Proxy ...@@ -64,13 +59,18 @@ namespace Titanium.Web.Proxy
/// </summary> /// </summary>
public int BufferSize { get; set; } = 8192; public int BufferSize { get; set; } = 8192;
/// <summary>
/// Manages certificates used by this proxy
/// </summary>
public CertificateManager CertificateManager { get; }
/// <summary> /// <summary>
/// The root certificate /// The root certificate
/// </summary> /// </summary>
public X509Certificate2 RootCertificate public X509Certificate2 RootCertificate
{ {
get { return certificateManager.RootCertificate; } get { return CertificateManager.RootCertificate; }
set { certificateManager.RootCertificate = value; } set { CertificateManager.RootCertificate = value; }
} }
/// <summary> /// <summary>
...@@ -79,8 +79,8 @@ namespace Titanium.Web.Proxy ...@@ -79,8 +79,8 @@ namespace Titanium.Web.Proxy
/// </summary> /// </summary>
public string RootCertificateIssuerName public string RootCertificateIssuerName
{ {
get { return certificateManager.Issuer; } get { return CertificateManager.Issuer; }
set { certificateManager.RootCertificateName = value; } set { CertificateManager.Issuer = value; }
} }
/// <summary> /// <summary>
...@@ -92,8 +92,8 @@ namespace Titanium.Web.Proxy ...@@ -92,8 +92,8 @@ namespace Titanium.Web.Proxy
/// </summary> /// </summary>
public string RootCertificateName public string RootCertificateName
{ {
get { return certificateManager.RootCertificateName; } get { return CertificateManager.RootCertificateName; }
set { certificateManager.Issuer = value; } set { CertificateManager.RootCertificateName = value; }
} }
/// <summary> /// <summary>
...@@ -121,8 +121,8 @@ namespace Titanium.Web.Proxy ...@@ -121,8 +121,8 @@ namespace Titanium.Web.Proxy
/// </summary> /// </summary>
public CertificateEngine CertificateEngine public CertificateEngine CertificateEngine
{ {
get { return certificateManager.Engine; } get { return CertificateManager.Engine; }
set { certificateManager.Engine = value; } set { CertificateManager.Engine = value; }
} }
/// <summary> /// <summary>
...@@ -226,6 +226,17 @@ namespace Titanium.Web.Proxy ...@@ -226,6 +226,17 @@ namespace Titanium.Web.Proxy
public SslProtocols SupportedSslProtocols { get; set; } = SslProtocols.Tls public SslProtocols SupportedSslProtocols { get; set; } = SslProtocols.Tls
| SslProtocols.Tls11 | SslProtocols.Tls12 | SslProtocols.Ssl3; | SslProtocols.Tls11 | SslProtocols.Tls12 | SslProtocols.Ssl3;
/// <summary>
/// Total number of active client connections
/// </summary>
public int ClientConnectionCount { get; private set; }
/// <summary>
/// Total number of active server connections
/// </summary>
public int ServerConnectionCount { get; internal set; }
/// <summary> /// <summary>
/// Constructor /// Constructor
/// </summary> /// </summary>
...@@ -241,7 +252,7 @@ namespace Titanium.Web.Proxy ...@@ -241,7 +252,7 @@ namespace Titanium.Web.Proxy
public ProxyServer(string rootCertificateName, string rootCertificateIssuerName) public ProxyServer(string rootCertificateName, string rootCertificateIssuerName)
{ {
//default values //default values
ConnectionTimeOutSeconds = 120; ConnectionTimeOutSeconds = 30;
CertificateCacheTimeOutMinutes = 60; CertificateCacheTimeOutMinutes = 60;
ProxyEndPoints = new List<ProxyEndPoint>(); ProxyEndPoints = new List<ProxyEndPoint>();
...@@ -251,7 +262,7 @@ namespace Titanium.Web.Proxy ...@@ -251,7 +262,7 @@ namespace Titanium.Web.Proxy
new FireFoxProxySettingsManager(); new FireFoxProxySettingsManager();
#endif #endif
certificateManager = new CertificateManager(ExceptionFunc); CertificateManager = new CertificateManager(ExceptionFunc);
if (rootCertificateName != null) if (rootCertificateName != null)
{ {
RootCertificateName = rootCertificateName; RootCertificateName = rootCertificateName;
...@@ -356,7 +367,7 @@ namespace Titanium.Web.Proxy ...@@ -356,7 +367,7 @@ namespace Titanium.Web.Proxy
EnsureRootCertificate(); EnsureRootCertificate();
//If certificate was trusted by the machine //If certificate was trusted by the machine
if (certificateManager.CertValidated) if (CertificateManager.CertValidated)
{ {
systemProxySettingsManager.SetHttpsProxy( systemProxySettingsManager.SetHttpsProxy(
Equals(endPoint.IpAddress, IPAddress.Any) | Equals(endPoint.IpAddress, IPAddress.Any) |
...@@ -435,7 +446,7 @@ namespace Titanium.Web.Proxy ...@@ -435,7 +446,7 @@ namespace Titanium.Web.Proxy
Listen(endPoint); Listen(endPoint);
} }
certificateManager.ClearIdleCertificates(CertificateCacheTimeOutMinutes); CertificateManager.ClearIdleCertificates(CertificateCacheTimeOutMinutes);
proxyRunning = true; proxyRunning = true;
} }
...@@ -468,7 +479,7 @@ namespace Titanium.Web.Proxy ...@@ -468,7 +479,7 @@ namespace Titanium.Web.Proxy
ProxyEndPoints.Clear(); ProxyEndPoints.Clear();
certificateManager?.StopClearIdleCertificates(); CertificateManager?.StopClearIdleCertificates();
proxyRunning = false; proxyRunning = false;
} }
...@@ -483,7 +494,7 @@ namespace Titanium.Web.Proxy ...@@ -483,7 +494,7 @@ namespace Titanium.Web.Proxy
Stop(); Stop();
} }
certificateManager?.Dispose(); CertificateManager?.Dispose();
} }
/// <summary> /// <summary>
...@@ -495,7 +506,7 @@ namespace Titanium.Web.Proxy ...@@ -495,7 +506,7 @@ namespace Titanium.Web.Proxy
endPoint.Listener = new TcpListener(endPoint.IpAddress, endPoint.Port); endPoint.Listener = new TcpListener(endPoint.IpAddress, endPoint.Port);
endPoint.Listener.Start(); endPoint.Listener.Start();
endPoint.Port = ((IPEndPoint) endPoint.Listener.LocalEndpoint).Port; endPoint.Port = ((IPEndPoint)endPoint.Listener.LocalEndpoint).Port;
// accept clients asynchronously // accept clients asynchronously
endPoint.Listener.BeginAcceptTcpClient(OnAcceptConnection, endPoint); endPoint.Listener.BeginAcceptTcpClient(OnAcceptConnection, endPoint);
} }
...@@ -543,13 +554,13 @@ namespace Titanium.Web.Proxy ...@@ -543,13 +554,13 @@ namespace Titanium.Web.Proxy
private void EnsureRootCertificate() private void EnsureRootCertificate()
{ {
if (!certificateManager.CertValidated) if (!CertificateManager.CertValidated)
{ {
certificateManager.CreateTrustedRootCertificate(); CertificateManager.CreateTrustedRootCertificate();
if (TrustRootCertificate) if (TrustRootCertificate)
{ {
certificateManager.TrustRootCertificate(); CertificateManager.TrustRootCertificate();
} }
} }
} }
...@@ -560,7 +571,7 @@ namespace Titanium.Web.Proxy ...@@ -560,7 +571,7 @@ namespace Titanium.Web.Proxy
/// <param name="asyn"></param> /// <param name="asyn"></param>
private void OnAcceptConnection(IAsyncResult asyn) private void OnAcceptConnection(IAsyncResult asyn)
{ {
var endPoint = (ProxyEndPoint) asyn.AsyncState; var endPoint = (ProxyEndPoint)asyn.AsyncState;
TcpClient tcpClient = null; TcpClient tcpClient = null;
...@@ -581,11 +592,18 @@ namespace Titanium.Web.Proxy ...@@ -581,11 +592,18 @@ namespace Titanium.Web.Proxy
//Other errors are discarded to keep proxy running //Other errors are discarded to keep proxy running
} }
if (tcpClient != null) if (tcpClient != null)
{ {
Task.Run(async () => Task.Run(async () =>
{ {
ClientConnectionCount++;
//This line is important!
//contributors please don't remove it without discussion
//It helps to avoid eventual deterioration of performance due to TCP port exhaustion
//due to default TCP CLOSE_WAIT timeout for 4 minutes
tcpClient.LingerState = new LingerOption(true, 0);
try try
{ {
if (endPoint.GetType() == typeof(TransparentProxyEndPoint)) if (endPoint.GetType() == typeof(TransparentProxyEndPoint))
...@@ -599,15 +617,8 @@ namespace Titanium.Web.Proxy ...@@ -599,15 +617,8 @@ namespace Titanium.Web.Proxy
} }
finally finally
{ {
if (tcpClient != null) ClientConnectionCount--;
{ tcpClient?.Close();
//This line is important!
//contributors please don't remove it without discussion
//It helps to avoid eventual deterioration of performance due to TCP port exhaustion
//due to default TCP CLOSE_WAIT timeout for 4 minutes
tcpClient.LingerState = new LingerOption(true, 0);
tcpClient.Close();
}
} }
}); });
} }
......
This diff is collapsed.
...@@ -9,6 +9,7 @@ using Titanium.Web.Proxy.Exceptions; ...@@ -9,6 +9,7 @@ using Titanium.Web.Proxy.Exceptions;
using Titanium.Web.Proxy.Extensions; using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Http; using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Helpers; using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Network.Tcp;
namespace Titanium.Web.Proxy namespace Titanium.Web.Proxy
{ {
...@@ -21,14 +22,14 @@ namespace Titanium.Web.Proxy ...@@ -21,14 +22,14 @@ namespace Titanium.Web.Proxy
/// Called asynchronously when a request was successfully and we received the response /// Called asynchronously when a request was successfully and we received the response
/// </summary> /// </summary>
/// <param name="args"></param> /// <param name="args"></param>
/// <returns></returns> /// <returns>true if no errors</returns>
private async Task HandleHttpSessionResponse(SessionEventArgs args) private async Task<bool> HandleHttpSessionResponse(SessionEventArgs args)
{
try
{ {
//read response & headers from server //read response & headers from server
await args.WebSession.ReceiveResponse(); await args.WebSession.ReceiveResponse();
try
{
if (!args.WebSession.Response.ResponseBodyRead) if (!args.WebSession.Response.ResponseBodyRead)
{ {
args.WebSession.Response.ResponseStream = args.WebSession.ServerConnection.Stream; args.WebSession.Response.ResponseStream = args.WebSession.ServerConnection.Stream;
...@@ -44,7 +45,7 @@ namespace Titanium.Web.Proxy ...@@ -44,7 +45,7 @@ namespace Titanium.Web.Proxy
for (int i = 0; i < invocationList.Length; i++) for (int i = 0; i < invocationList.Length; i++)
{ {
handlerTasks[i] = ((Func<object, SessionEventArgs, Task>) invocationList[i])(this, args); handlerTasks[i] = ((Func<object, SessionEventArgs, Task>)invocationList[i])(this, args);
} }
await Task.WhenAll(handlerTasks); await Task.WhenAll(handlerTasks);
...@@ -52,8 +53,15 @@ namespace Titanium.Web.Proxy ...@@ -52,8 +53,15 @@ namespace Titanium.Web.Proxy
if (args.ReRequest) if (args.ReRequest)
{ {
await HandleHttpSessionRequestInternal(null, args, null, null, true); if(args.WebSession.ServerConnection != null)
return; {
args.WebSession.ServerConnection.Dispose();
ServerConnectionCount--;
}
var connection = await GetServerConnection(args);
var result = await HandleHttpSessionRequestInternal(null, args, true);
return result;
} }
args.WebSession.Response.ResponseLocked = true; args.WebSession.Response.ResponseLocked = true;
...@@ -125,14 +133,16 @@ namespace Titanium.Web.Proxy ...@@ -125,14 +133,16 @@ namespace Titanium.Web.Proxy
} }
catch (Exception e) catch (Exception e)
{ {
ExceptionFunc(new ProxyHttpException("Error occured wilst handling session response", e, args)); ExceptionFunc(new ProxyHttpException("Error occured whilst handling session response", e, args));
Dispose(args.ProxyClient.ClientStream, args.ProxyClient.ClientStreamReader, Dispose(args.ProxyClient.ClientStream, args.ProxyClient.ClientStreamReader,
args.ProxyClient.ClientStreamWriter, args); args.ProxyClient.ClientStreamWriter, args.WebSession.ServerConnection);
return false;
} }
finally
{
args.Dispose(); args.Dispose();
}
return true;
} }
/// <summary> /// <summary>
...@@ -224,19 +234,21 @@ namespace Titanium.Web.Proxy ...@@ -224,19 +234,21 @@ namespace Titanium.Web.Proxy
/// <param name="clientStream"></param> /// <param name="clientStream"></param>
/// <param name="clientStreamReader"></param> /// <param name="clientStreamReader"></param>
/// <param name="clientStreamWriter"></param> /// <param name="clientStreamWriter"></param>
/// <param name="args"></param> /// <param name="serverConnection"></param>
private void Dispose(Stream clientStream, CustomBinaryReader clientStreamReader, private void Dispose(Stream clientStream,
StreamWriter clientStreamWriter, IDisposable args) CustomBinaryReader clientStreamReader,
StreamWriter clientStreamWriter,
TcpConnection serverConnection)
{ {
ServerConnectionCount--;
clientStream?.Close(); clientStream?.Close();
clientStream?.Dispose(); clientStream?.Dispose();
clientStreamReader?.Dispose(); clientStreamReader?.Dispose();
clientStreamWriter?.Close();
clientStreamWriter?.Dispose(); clientStreamWriter?.Dispose();
args?.Dispose(); serverConnection?.Dispose();
} }
} }
} }
...@@ -77,6 +77,7 @@ ...@@ -77,6 +77,7 @@
<Compile Include="Helpers\CustomBufferedStream.cs" /> <Compile Include="Helpers\CustomBufferedStream.cs" />
<Compile Include="Helpers\Network.cs" /> <Compile Include="Helpers\Network.cs" />
<Compile Include="Helpers\RunTime.cs" /> <Compile Include="Helpers\RunTime.cs" />
<Compile Include="Http\HeaderParser.cs" />
<Compile Include="Http\Responses\GenericResponse.cs" /> <Compile Include="Http\Responses\GenericResponse.cs" />
<Compile Include="Network\CachedCertificate.cs" /> <Compile Include="Network\CachedCertificate.cs" />
<Compile Include="Network\Certificate\WinCertificateMaker.cs" /> <Compile Include="Network\Certificate\WinCertificateMaker.cs" />
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment