diff --git a/Examples/Titanium.Web.Proxy.Examples.Basic/Helpers/ConsoleHelper.cs b/Examples/Titanium.Web.Proxy.Examples.Basic/Helpers/ConsoleHelper.cs new file mode 100644 index 000000000..86b06a857 --- /dev/null +++ b/Examples/Titanium.Web.Proxy.Examples.Basic/Helpers/ConsoleHelper.cs @@ -0,0 +1,52 @@ +using System; +using System.Runtime.InteropServices; + +namespace Titanium.Web.Proxy.Examples.Basic.Helpers +{ + /// + /// Adapated from + /// http://stackoverflow.com/questions/13656846/how-to-programmatic-disable-c-sharp-console-applications-quick-edit-mode + /// + internal static class ConsoleHelper + { + const uint ENABLE_QUICK_EDIT = 0x0040; + + // STD_INPUT_HANDLE (DWORD): -10 is the standard input device. + const int STD_INPUT_HANDLE = -10; + + [DllImport("kernel32.dll", SetLastError = true)] + static extern IntPtr GetStdHandle(int nStdHandle); + + [DllImport("kernel32.dll")] + static extern bool GetConsoleMode(IntPtr hConsoleHandle, out uint lpMode); + + [DllImport("kernel32.dll")] + static extern bool SetConsoleMode(IntPtr hConsoleHandle, uint dwMode); + + internal static bool DisableQuickEditMode() + { + + IntPtr consoleHandle = GetStdHandle(STD_INPUT_HANDLE); + + // get current console mode + uint consoleMode; + if (!GetConsoleMode(consoleHandle, out consoleMode)) + { + // ERROR: Unable to get console mode. + return false; + } + + // Clear the quick edit bit in the mode flags + consoleMode &= ~ENABLE_QUICK_EDIT; + + // set the new mode + if (!SetConsoleMode(consoleHandle, consoleMode)) + { + // ERROR: Unable to set console mode + return false; + } + + return true; + } + } +} diff --git a/Examples/Titanium.Web.Proxy.Examples.Basic/Program.cs b/Examples/Titanium.Web.Proxy.Examples.Basic/Program.cs index 13cc0fe86..bbfb5a25f 100644 --- a/Examples/Titanium.Web.Proxy.Examples.Basic/Program.cs +++ b/Examples/Titanium.Web.Proxy.Examples.Basic/Program.cs @@ -1,5 +1,7 @@ using System; +using System.Diagnostics; using System.Runtime.InteropServices; +using Titanium.Web.Proxy.Examples.Basic.Helpers; namespace Titanium.Web.Proxy.Examples.Basic { @@ -9,11 +11,13 @@ public class Program public static void Main(string[] args) { + //fix console hang due to QuickEdit mode + ConsoleHelper.DisableQuickEditMode(); + //On Console exit make sure we also exit the proxy NativeMethods.Handler = ConsoleEventCallback; NativeMethods.SetConsoleCtrlHandler(NativeMethods.Handler, true); - //Start proxy controller controller.StartProxy(); @@ -50,5 +54,7 @@ internal static class NativeMethods // Pinvoke internal delegate bool ConsoleEventDelegate(int eventType); + } -} \ No newline at end of file + +} diff --git a/Examples/Titanium.Web.Proxy.Examples.Basic/ProxyTestController.cs b/Examples/Titanium.Web.Proxy.Examples.Basic/ProxyTestController.cs index fe9f16c1c..c88d2c483 100644 --- a/Examples/Titanium.Web.Proxy.Examples.Basic/ProxyTestController.cs +++ b/Examples/Titanium.Web.Proxy.Examples.Basic/ProxyTestController.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; -using System.IO; using System.Net; +using System.Net.Security; using System.Threading.Tasks; using Titanium.Web.Proxy.EventArguments; using Titanium.Web.Proxy.Models; @@ -47,18 +47,18 @@ public void StartProxy() //Exclude Https addresses you don't want to proxy //Useful for clients that use certificate pinning //for example google.com and dropbox.com - ExcludedHttpsHostNameRegex = new List() { "dropbox.com" } + ExcludedHttpsHostNameRegex = new List { "dropbox.com" } //Include Https addresses you want to proxy (others will be excluded) //for example github.com - // IncludedHttpsHostNameRegex = new List() { "github.com" } + //IncludedHttpsHostNameRegex = new List { "github.com" } //You can set only one of the ExcludedHttpsHostNameRegex and IncludedHttpsHostNameRegex properties, otherwise ArgumentException will be thrown //Use self-issued generic certificate on all https requests //Optimizes performance by not creating a certificate for each https-enabled domain //Useful when certificate trust is not required by proxy clients - // GenericCertificate = new X509Certificate2(Path.Combine(System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), "genericcert.pfx"), "password") + //GenericCertificate = new X509Certificate2(Path.Combine(System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), "genericcert.pfx"), "password") }; //An explicit endpoint is where the client knows about the existence of a proxy @@ -107,7 +107,7 @@ public void Stop() //intecept & cancel redirect or update requests public async Task OnRequest(object sender, SessionEventArgs e) { - Console.WriteLine("Active Client Connections:" + ((ProxyServer) sender).ClientConnectionCount); + Console.WriteLine("Active Client Connections:" + ((ProxyServer)sender).ClientConnectionCount); Console.WriteLine(e.WebSession.Request.Url); //read request headers @@ -150,7 +150,7 @@ public async Task OnRequest(object sender, SessionEventArgs e) //Modify response public async Task OnResponse(object sender, SessionEventArgs e) { - Console.WriteLine("Active Server Connections:" + (sender as ProxyServer).ServerConnectionCount); + Console.WriteLine("Active Server Connections:" + ((ProxyServer)sender).ServerConnectionCount); if (requestBodyHistory.ContainsKey(e.Id)) { @@ -189,7 +189,7 @@ public async Task OnResponse(object sender, SessionEventArgs e) public Task OnCertificateValidation(object sender, CertificateValidationEventArgs e) { //set IsValid to true/false based on Certificate Errors - if (e.SslPolicyErrors == System.Net.Security.SslPolicyErrors.None) + if (e.SslPolicyErrors == SslPolicyErrors.None) { e.IsValid = true; } diff --git a/Examples/Titanium.Web.Proxy.Examples.Basic/Titanium.Web.Proxy.Examples.Basic.csproj b/Examples/Titanium.Web.Proxy.Examples.Basic/Titanium.Web.Proxy.Examples.Basic.csproj index 84c0b9f89..3ebd19cb6 100644 --- a/Examples/Titanium.Web.Proxy.Examples.Basic/Titanium.Web.Proxy.Examples.Basic.csproj +++ b/Examples/Titanium.Web.Proxy.Examples.Basic/Titanium.Web.Proxy.Examples.Basic.csproj @@ -55,6 +55,7 @@ + diff --git a/Tests/Titanium.Web.Proxy.IntegrationTests/Properties/AssemblyInfo.cs b/Tests/Titanium.Web.Proxy.IntegrationTests/Properties/AssemblyInfo.cs index e2f3d0432..2b5ed878b 100644 --- a/Tests/Titanium.Web.Proxy.IntegrationTests/Properties/AssemblyInfo.cs +++ b/Tests/Titanium.Web.Proxy.IntegrationTests/Properties/AssemblyInfo.cs @@ -1,5 +1,4 @@ using System.Reflection; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; // General Information about an assembly is controlled through the following diff --git a/Tests/Titanium.Web.Proxy.IntegrationTests/SslTests.cs b/Tests/Titanium.Web.Proxy.IntegrationTests/SslTests.cs index c9f07f40c..1f851580e 100644 --- a/Tests/Titanium.Web.Proxy.IntegrationTests/SslTests.cs +++ b/Tests/Titanium.Web.Proxy.IntegrationTests/SslTests.cs @@ -1,11 +1,12 @@ using System; -using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Diagnostics; using System.Net; +using System.Net.Http; +using System.Net.Security; using System.Threading.Tasks; +using Microsoft.VisualStudio.TestTools.UnitTesting; using Titanium.Web.Proxy.EventArguments; using Titanium.Web.Proxy.Models; -using System.Net.Http; -using System.Diagnostics; namespace Titanium.Web.Proxy.IntegrationTests { @@ -13,7 +14,6 @@ namespace Titanium.Web.Proxy.IntegrationTests public class SslTests { [TestMethod] - public void TestSsl() { //expand this to stress test to find @@ -103,7 +103,7 @@ public async Task OnResponse(object sender, SessionEventArgs e) public Task OnCertificateValidation(object sender, CertificateValidationEventArgs e) { //set IsValid to true/false based on Certificate Errors - if (e.SslPolicyErrors == System.Net.Security.SslPolicyErrors.None) + if (e.SslPolicyErrors == SslPolicyErrors.None) { e.IsValid = true; } diff --git a/Tests/Titanium.Web.Proxy.UnitTests/CertificateManagerTests.cs b/Tests/Titanium.Web.Proxy.UnitTests/CertificateManagerTests.cs index 651142271..e4388e2e5 100644 --- a/Tests/Titanium.Web.Proxy.UnitTests/CertificateManagerTests.cs +++ b/Tests/Titanium.Web.Proxy.UnitTests/CertificateManagerTests.cs @@ -1,8 +1,8 @@ using System; +using System.Collections.Generic; +using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; using Titanium.Web.Proxy.Network; -using System.Threading.Tasks; -using System.Collections.Generic; namespace Titanium.Web.Proxy.UnitTests { @@ -10,8 +10,7 @@ namespace Titanium.Web.Proxy.UnitTests public class CertificateManagerTests { private static readonly string[] hostNames - = new string[] { "facebook.com", "youtube.com", "google.com", - "bing.com", "yahoo.com"}; + = { "facebook.com", "youtube.com", "google.com", "bing.com", "yahoo.com" }; private readonly Random random = new Random(); @@ -36,16 +35,13 @@ public async Task Simple_Create_Certificate_Stress_Test() var certificate = mgr.CreateCertificate(host, false); Assert.IsNotNull(certificate); - })); - } } await Task.WhenAll(tasks.ToArray()); mgr.StopClearIdleCertificates(); - } } } diff --git a/Titanium.Web.Proxy.sln.DotSettings b/Titanium.Web.Proxy.sln.DotSettings index 1fc658854..5d870b7df 100644 --- a/Titanium.Web.Proxy.sln.DotSettings +++ b/Titanium.Web.Proxy.sln.DotSettings @@ -1,8 +1,21 @@  True + False + True + 240 BC + CN + DN + EKU + KU + MTA + OID + OIDS <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> - <Policy><Descriptor Staticness="Static, Instance" AccessRightKinds="Private" Description="Property (private)"><ElementKinds><Kind Name="PROPERTY" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></Policy> \ No newline at end of file + <Policy><Descriptor Staticness="Static, Instance" AccessRightKinds="Private" Description="Property (private)"><ElementKinds><Kind Name="PROPERTY" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></Policy> + True + True + True \ No newline at end of file diff --git a/Titanium.Web.Proxy/CertificateHandler.cs b/Titanium.Web.Proxy/CertificateHandler.cs index 134798937..b13ccfd82 100644 --- a/Titanium.Web.Proxy/CertificateHandler.cs +++ b/Titanium.Web.Proxy/CertificateHandler.cs @@ -1,8 +1,8 @@ using System; using System.Net.Security; using System.Security.Cryptography.X509Certificates; -using System.Threading.Tasks; using Titanium.Web.Proxy.EventArguments; +using Titanium.Web.Proxy.Extensions; namespace Titanium.Web.Proxy { @@ -32,17 +32,8 @@ internal bool ValidateServerCertificate( SslPolicyErrors = sslPolicyErrors }; - - Delegate[] invocationList = ServerCertificateValidationCallback.GetInvocationList(); - Task[] handlerTasks = new Task[invocationList.Length]; - - for (int i = 0; i < invocationList.Length; i++) - { - handlerTasks[i] = ((Func) invocationList[i])(null, args); - } - - Task.WhenAll(handlerTasks).Wait(); - + //why is the sender null? + ServerCertificateValidationCallback.InvokeParallel(this, args); return args.IsValid; } @@ -108,17 +99,8 @@ internal X509Certificate SelectClientCertificate( ClientCertificate = clientCertificate }; - - Delegate[] invocationList = ClientCertificateSelectionCallback.GetInvocationList(); - Task[] handlerTasks = new Task[invocationList.Length]; - - for (int i = 0; i < invocationList.Length; i++) - { - handlerTasks[i] = ((Func) invocationList[i])(null, args); - } - - Task.WhenAll(handlerTasks).Wait(); - + //why is the sender null? + ClientCertificateSelectionCallback.InvokeParallel(this, args); return args.ClientCertificate; } diff --git a/Titanium.Web.Proxy/EventArguments/SessionEventArgs.cs b/Titanium.Web.Proxy/EventArguments/SessionEventArgs.cs index f70bf8550..9f2824575 100644 --- a/Titanium.Web.Proxy/EventArguments/SessionEventArgs.cs +++ b/Titanium.Web.Proxy/EventArguments/SessionEventArgs.cs @@ -1,16 +1,16 @@ using System; using System.Collections.Generic; using System.IO; +using System.Net; using System.Text; -using Titanium.Web.Proxy.Exceptions; +using System.Threading.Tasks; using Titanium.Web.Proxy.Decompression; +using Titanium.Web.Proxy.Exceptions; +using Titanium.Web.Proxy.Extensions; using Titanium.Web.Proxy.Http; using Titanium.Web.Proxy.Http.Responses; -using Titanium.Web.Proxy.Extensions; -using System.Threading.Tasks; -using Titanium.Web.Proxy.Network; -using System.Net; using Titanium.Web.Proxy.Models; +using Titanium.Web.Proxy.Network; namespace Titanium.Web.Proxy.EventArguments { @@ -56,7 +56,7 @@ public class SessionEventArgs : EventArgs, IDisposable /// /// Client End Point. /// - public IPEndPoint ClientEndPoint => (IPEndPoint) ProxyClient.TcpClient.Client.RemoteEndPoint; + public IPEndPoint ClientEndPoint => (IPEndPoint)ProxyClient.TcpClient.Client.RemoteEndPoint; /// /// A web session corresponding to a single request/response sequence @@ -158,7 +158,7 @@ private async Task ReadResponseBody() await WebSession.ServerConnection.StreamReader.CopyBytesToStream(bufferSize, responseBodyStream, WebSession.Response.ContentLength); } - else if ((WebSession.Response.HttpVersion.Major == 1 && WebSession.Response.HttpVersion.Minor == 0) || WebSession.Response.ContentLength == -1) + else if (WebSession.Response.HttpVersion.Major == 1 && WebSession.Response.HttpVersion.Minor == 0 || WebSession.Response.ContentLength == -1) { await WebSession.ServerConnection.StreamReader.CopyBytesToStream(bufferSize, responseBodyStream, long.MaxValue); } @@ -522,6 +522,7 @@ public async Task Respond(Response response) /// public void Dispose() { + WebSession.Dispose(); } } } diff --git a/Titanium.Web.Proxy/Extensions/FuncExtensions.cs b/Titanium.Web.Proxy/Extensions/FuncExtensions.cs new file mode 100644 index 000000000..cfc1a2c78 --- /dev/null +++ b/Titanium.Web.Proxy/Extensions/FuncExtensions.cs @@ -0,0 +1,34 @@ +using System; +using System.Threading.Tasks; + +namespace Titanium.Web.Proxy.Extensions +{ + internal static class FuncExtensions + { + public static void InvokeParallel(this Func callback, object sender, T args) + { + Delegate[] invocationList = callback.GetInvocationList(); + Task[] handlerTasks = new Task[invocationList.Length]; + + for (int i = 0; i < invocationList.Length; i++) + { + handlerTasks[i] = ((Func)invocationList[i])(sender, args); + } + + Task.WhenAll(handlerTasks).Wait(); + } + + public static async Task InvokeParallelAsync(this Func callback, object sender, T args) + { + Delegate[] invocationList = callback.GetInvocationList(); + Task[] handlerTasks = new Task[invocationList.Length]; + + for (int i = 0; i < invocationList.Length; i++) + { + handlerTasks[i] = ((Func)invocationList[i])(sender, args); + } + + await Task.WhenAll(handlerTasks); + } + } +} diff --git a/Titanium.Web.Proxy/Extensions/StreamExtensions.cs b/Titanium.Web.Proxy/Extensions/StreamExtensions.cs index 4cf6be2bc..289e02290 100644 --- a/Titanium.Web.Proxy/Extensions/StreamExtensions.cs +++ b/Titanium.Web.Proxy/Extensions/StreamExtensions.cs @@ -136,7 +136,7 @@ internal static async Task WriteResponseBody(this CustomBinaryReader inStreamRea if (contentLength < bufferSize) { - bytesToRead = (int) contentLength; + bytesToRead = (int)contentLength; } var buffer = new byte[bufferSize]; @@ -153,8 +153,8 @@ internal static async Task WriteResponseBody(this CustomBinaryReader inStreamRea break; bytesRead = 0; - var remainingBytes = (contentLength - totalBytesRead); - bytesToRead = remainingBytes > (long) bufferSize ? bufferSize : (int) remainingBytes; + var remainingBytes = contentLength - totalBytesRead; + bytesToRead = remainingBytes > (long)bufferSize ? bufferSize : (int)remainingBytes; } } else diff --git a/Titanium.Web.Proxy/Helpers/CustomBinaryReader.cs b/Titanium.Web.Proxy/Helpers/CustomBinaryReader.cs index 6d10c3d91..7217a2719 100644 --- a/Titanium.Web.Proxy/Helpers/CustomBinaryReader.cs +++ b/Titanium.Web.Proxy/Helpers/CustomBinaryReader.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Text; @@ -18,10 +19,18 @@ internal class CustomBinaryReader : IDisposable private readonly byte[] staticBuffer; private readonly Encoding encoding; + private static readonly ConcurrentQueue buffers + = new ConcurrentQueue(); + + private volatile bool disposed; + internal CustomBinaryReader(CustomBufferedStream stream, int bufferSize) { this.stream = stream; - staticBuffer = new byte[bufferSize]; + if (!buffers.TryDequeue(out staticBuffer) || staticBuffer.Length != bufferSize) + { + staticBuffer = new byte[bufferSize]; + } this.bufferSize = bufferSize; @@ -112,7 +121,7 @@ internal async Task ReadBytesAsync(long totalBytesToRead) var buffer = staticBuffer; if (totalBytesToRead < bufferSize) { - bytesToRead = (int) totalBytesToRead; + bytesToRead = (int)totalBytesToRead; buffer = new byte[bytesToRead]; } @@ -127,7 +136,7 @@ internal async Task ReadBytesAsync(long totalBytesToRead) break; var remainingBytes = totalBytesToRead - totalBytesRead; - bytesToRead = Math.Min(bufferSize, (int) remainingBytes); + bytesToRead = Math.Min(bufferSize, (int)remainingBytes); if (totalBytesRead + bytesToRead > buffer.Length) { @@ -148,8 +157,18 @@ internal async Task ReadBytesAsync(long totalBytesToRead) public void Dispose() { + if (!disposed) + { + disposed = true; + buffers.Enqueue(staticBuffer); + } } + /// + /// Increase size of buffer and copy existing content to new buffer + /// + /// + /// private void ResizeBuffer(ref byte[] buffer, long size) { var newBuffer = new byte[size]; diff --git a/Titanium.Web.Proxy/Helpers/CustomBufferedStream.cs b/Titanium.Web.Proxy/Helpers/CustomBufferedStream.cs index 78fa6db21..98281be68 100644 --- a/Titanium.Web.Proxy/Helpers/CustomBufferedStream.cs +++ b/Titanium.Web.Proxy/Helpers/CustomBufferedStream.cs @@ -198,7 +198,7 @@ public override int EndRead(IAsyncResult asyncResult) { if (asyncResult is ReadAsyncResult) { - return ((ReadAsyncResult) asyncResult).ReadBytes; + return ((ReadAsyncResult)asyncResult).ReadBytes; } return baseStream.EndRead(asyncResult); diff --git a/Titanium.Web.Proxy/Helpers/SystemProxy.cs b/Titanium.Web.Proxy/Helpers/SystemProxy.cs index ec348319f..1edeb91b9 100644 --- a/Titanium.Web.Proxy/Helpers/SystemProxy.cs +++ b/Titanium.Web.Proxy/Helpers/SystemProxy.cs @@ -1,9 +1,9 @@ using System; -using System.Runtime.InteropServices; -using Microsoft.Win32; -using System.Text.RegularExpressions; using System.Collections.Generic; using System.Linq; +using System.Runtime.InteropServices; +using System.Text.RegularExpressions; +using Microsoft.Win32; // Helper classes for setting system proxy settings namespace Titanium.Web.Proxy.Helpers @@ -84,7 +84,7 @@ private void SetProxy(string hostname, int port, ProxyProtocolType protocolType) var exisitingContent = reg.GetValue("ProxyServer") as string; var existingSystemProxyValues = GetSystemProxyValues(exisitingContent); existingSystemProxyValues.RemoveAll(x => protocolType == ProxyProtocolType.Https ? x.IsHttps : !x.IsHttps); - existingSystemProxyValues.Add(new HttpSystemProxyValue() + existingSystemProxyValues.Add(new HttpSystemProxyValue { HostName = hostname, IsHttps = protocolType == ProxyProtocolType.Https, diff --git a/Titanium.Web.Proxy/Helpers/Tcp.cs b/Titanium.Web.Proxy/Helpers/Tcp.cs index e64da17b6..5cac82141 100644 --- a/Titanium.Web.Proxy/Helpers/Tcp.cs +++ b/Titanium.Web.Proxy/Helpers/Tcp.cs @@ -3,10 +3,9 @@ using System.IO; using System.Linq; using System.Net.NetworkInformation; -using System.Net.Security; using System.Runtime.InteropServices; -using System.Security.Authentication; using System.Text; +using System.Threading; using System.Threading.Tasks; using Titanium.Web.Proxy.Extensions; using Titanium.Web.Proxy.Models; @@ -15,8 +14,6 @@ namespace Titanium.Web.Proxy.Helpers { - using System.Net; - internal enum IpVersion { Ipv4 = 1, @@ -232,7 +229,7 @@ internal static async Task SendRaw(ProxyServer server, finally { tcpConnection.Dispose(); - server.ServerConnectionCount--; + Interlocked.Decrement(ref server.ServerConnectionCountField); } } } diff --git a/Titanium.Web.Proxy/Http/HeaderParser.cs b/Titanium.Web.Proxy/Http/HeaderParser.cs index 27d8e051a..bf69aecbb 100644 --- a/Titanium.Web.Proxy/Http/HeaderParser.cs +++ b/Titanium.Web.Proxy/Http/HeaderParser.cs @@ -8,8 +8,8 @@ namespace Titanium.Web.Proxy.Http { internal static class HeaderParser { - internal static async Task ReadHeaders(CustomBinaryReader reader, - Dictionary> nonUniqueResponseHeaders, + internal static async Task ReadHeaders(CustomBinaryReader reader, + Dictionary> nonUniqueResponseHeaders, Dictionary headers) { string tmpLine; diff --git a/Titanium.Web.Proxy/Http/HttpWebClient.cs b/Titanium.Web.Proxy/Http/HttpWebClient.cs index 46aa379c7..bd4278320 100644 --- a/Titanium.Web.Proxy/Http/HttpWebClient.cs +++ b/Titanium.Web.Proxy/Http/HttpWebClient.cs @@ -11,7 +11,7 @@ namespace Titanium.Web.Proxy.Http /// /// Used to communicate with the server over HTTP(S) /// - public class HttpWebClient + public class HttpWebClient : IDisposable { /// /// Connection to server @@ -79,7 +79,7 @@ internal async Task SendRequest(bool enable100ContinueBehaviour) var requestLines = new StringBuilder(); //prepare the request & headers - if ((ServerConnection.UpStreamHttpProxy != null && ServerConnection.IsHttps == false) || (ServerConnection.UpStreamHttpsProxy != null && ServerConnection.IsHttps)) + if (ServerConnection.UpStreamHttpProxy != null && ServerConnection.IsHttps == false || ServerConnection.UpStreamHttpsProxy != null && ServerConnection.IsHttps) { requestLines.AppendLine($"{Request.Method} {Request.RequestUri.AbsoluteUri} HTTP/{Request.HttpVersion.Major}.{Request.HttpVersion.Minor}"); } @@ -93,7 +93,7 @@ internal async Task SendRequest(bool enable100ContinueBehaviour) { requestLines.AppendLine("Proxy-Connection: keep-alive"); requestLines.AppendLine("Proxy-Authorization" + ": Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes( - $"{ServerConnection.UpStreamHttpProxy.UserName}:{ServerConnection.UpStreamHttpProxy.Password}"))); + $"{ServerConnection.UpStreamHttpProxy.UserName}:{ServerConnection.UpStreamHttpProxy.Password}"))); } //write request headers foreach (var headerItem in Request.RequestHeaders) @@ -208,5 +208,20 @@ internal async Task ReceiveResponse() //Read the response headers in to unique and non-unique header collections await HeaderParser.ReadHeaders(ServerConnection.StreamReader, Response.NonUniqueResponseHeaders, Response.ResponseHeaders); } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + 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; + + } } } diff --git a/Titanium.Web.Proxy/Http/Request.cs b/Titanium.Web.Proxy/Http/Request.cs index 949cd5570..0afdea85c 100644 --- a/Titanium.Web.Proxy/Http/Request.cs +++ b/Titanium.Web.Proxy/Http/Request.cs @@ -1,8 +1,8 @@ using System; using System.Collections.Generic; using System.Text; -using Titanium.Web.Proxy.Models; using Titanium.Web.Proxy.Extensions; +using Titanium.Web.Proxy.Models; namespace Titanium.Web.Proxy.Http { @@ -241,7 +241,7 @@ public bool ExpectContinue /// request body as string /// internal string RequestBodyString { get; set; } - + internal bool RequestBodyRead { get; set; } internal bool RequestLocked { get; set; } diff --git a/Titanium.Web.Proxy/Http/Response.cs b/Titanium.Web.Proxy/Http/Response.cs index 08163a4b9..0393f4875 100644 --- a/Titanium.Web.Proxy/Http/Response.cs +++ b/Titanium.Web.Proxy/Http/Response.cs @@ -1,9 +1,9 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.IO; using System.Text; -using Titanium.Web.Proxy.Models; using Titanium.Web.Proxy.Extensions; -using System; +using Titanium.Web.Proxy.Models; namespace Titanium.Web.Proxy.Http { diff --git a/Titanium.Web.Proxy/Http/Responses/GenericResponse.cs b/Titanium.Web.Proxy/Http/Responses/GenericResponse.cs index bf3a0753b..d2ad39f05 100644 --- a/Titanium.Web.Proxy/Http/Responses/GenericResponse.cs +++ b/Titanium.Web.Proxy/Http/Responses/GenericResponse.cs @@ -13,7 +13,7 @@ public class GenericResponse : Response /// public GenericResponse(HttpStatusCode status) { - ResponseStatusCode = ((int) status).ToString(); + ResponseStatusCode = ((int)status).ToString(); ResponseStatusDescription = status.ToString(); } diff --git a/Titanium.Web.Proxy/Network/Certificate/BCCertificateMaker.cs b/Titanium.Web.Proxy/Network/Certificate/BCCertificateMaker.cs index e7a8b7eb0..fba922e72 100644 --- a/Titanium.Web.Proxy/Network/Certificate/BCCertificateMaker.cs +++ b/Titanium.Web.Proxy/Network/Certificate/BCCertificateMaker.cs @@ -110,7 +110,7 @@ private static X509Certificate2 GenerateCertificate(string hostName, // Corresponding private key var privateKeyInfo = PrivateKeyInfoFactory.CreatePrivateKeyInfo(subjectKeyPair.Private); - var seq = (Asn1Sequence) Asn1Object.FromByteArray(privateKeyInfo.ParsePrivateKey().GetDerEncoded()); + var seq = (Asn1Sequence)Asn1Object.FromByteArray(privateKeyInfo.ParsePrivateKey().GetDerEncoded()); if (seq.Count != 9) { diff --git a/Titanium.Web.Proxy/Network/Certificate/WinCertificateMaker.cs b/Titanium.Web.Proxy/Network/Certificate/WinCertificateMaker.cs index fed9f2ecb..42a5db01e 100644 --- a/Titanium.Web.Proxy/Network/Certificate/WinCertificateMaker.cs +++ b/Titanium.Web.Proxy/Network/Certificate/WinCertificateMaker.cs @@ -89,7 +89,7 @@ private X509Certificate2 MakeCertificate(bool isRoot, string subject, string ful } var x500CertDN = Activator.CreateInstance(typeX500DN); - var typeValue = new object[] {fullSubject, 0}; + var typeValue = new object[] { fullSubject, 0 }; typeX500DN.InvokeMember("Encode", BindingFlags.InvokeMethod, null, x500CertDN, typeValue); var x500RootCertDN = Activator.CreateInstance(typeX500DN); @@ -110,16 +110,16 @@ private X509Certificate2 MakeCertificate(bool isRoot, string subject, string ful if (sharedPrivateKey == null) { sharedPrivateKey = Activator.CreateInstance(typeX509PrivateKey); - typeValue = new object[] {sProviderName}; + typeValue = new object[] { sProviderName }; typeX509PrivateKey.InvokeMember("ProviderName", BindingFlags.PutDispProperty, null, sharedPrivateKey, typeValue); typeValue[0] = 2; typeX509PrivateKey.InvokeMember("ExportPolicy", BindingFlags.PutDispProperty, null, sharedPrivateKey, typeValue); - typeValue = new object[] {(isRoot ? 2 : 1)}; + typeValue = new object[] { isRoot ? 2 : 1 }; typeX509PrivateKey.InvokeMember("KeySpec", BindingFlags.PutDispProperty, null, sharedPrivateKey, typeValue); if (!isRoot) { - typeValue = new object[] {176}; + typeValue = new object[] { 176 }; typeX509PrivateKey.InvokeMember("KeyUsage", BindingFlags.PutDispProperty, null, sharedPrivateKey, typeValue); } @@ -149,9 +149,9 @@ private X509Certificate2 MakeCertificate(bool isRoot, string subject, string ful var requestCert = Activator.CreateInstance(typeRequestCert); - typeValue = new[] {1, sharedPrivateKey, string.Empty}; + typeValue = new[] { 1, sharedPrivateKey, string.Empty }; typeRequestCert.InvokeMember("InitializeFromPrivateKey", BindingFlags.InvokeMethod, null, requestCert, typeValue); - typeValue = new[] {x500CertDN}; + typeValue = new[] { x500CertDN }; typeRequestCert.InvokeMember("Subject", BindingFlags.PutDispProperty, null, requestCert, typeValue); typeValue[0] = x500RootCertDN; typeRequestCert.InvokeMember("Issuer", BindingFlags.PutDispProperty, null, requestCert, typeValue); @@ -186,14 +186,14 @@ private X509Certificate2 MakeCertificate(bool isRoot, string subject, string ful var extNames = Activator.CreateInstance(typeExtNames); var altDnsNames = Activator.CreateInstance(typeCAlternativeName); - typeValue = new object[] {3, subject}; + typeValue = new object[] { 3, subject }; typeCAlternativeName.InvokeMember("InitializeFromString", BindingFlags.InvokeMethod, null, altDnsNames, typeValue); - typeValue = new[] {altDnsNames}; + typeValue = new[] { altDnsNames }; typeAltNamesCollection.InvokeMember("Add", BindingFlags.InvokeMethod, null, altNameCollection, typeValue); - typeValue = new[] {altNameCollection}; + typeValue = new[] { altNameCollection }; typeExtNames.InvokeMember("InitializeEncode", BindingFlags.InvokeMethod, null, extNames, typeValue); typeValue[0] = extNames; @@ -204,27 +204,27 @@ private X509Certificate2 MakeCertificate(bool isRoot, string subject, string ful { var signerCertificate = Activator.CreateInstance(typeSignerCertificate); - typeValue = new object[] {0, 0, 12, signingCertificate.Thumbprint}; + typeValue = new object[] { 0, 0, 12, signingCertificate.Thumbprint }; typeSignerCertificate.InvokeMember("Initialize", BindingFlags.InvokeMethod, null, signerCertificate, typeValue); - typeValue = new[] {signerCertificate}; + typeValue = new[] { signerCertificate }; typeRequestCert.InvokeMember("SignerCertificate", BindingFlags.PutDispProperty, null, requestCert, typeValue); } else { var basicConstraints = Activator.CreateInstance(typeBasicConstraints); - typeValue = new object[] {"true", "0"}; + typeValue = new object[] { "true", "0" }; typeBasicConstraints.InvokeMember("InitializeEncode", BindingFlags.InvokeMethod, null, basicConstraints, typeValue); - typeValue = new[] {basicConstraints}; + typeValue = new[] { basicConstraints }; typeX509Extensions.InvokeMember("Add", BindingFlags.InvokeMethod, null, certificate, typeValue); } oid = Activator.CreateInstance(typeOID); - typeValue = new object[] {1, 0, 0, hashAlg}; + typeValue = new object[] { 1, 0, 0, hashAlg }; typeOID.InvokeMember("InitializeFromAlgorithmName", BindingFlags.InvokeMethod, null, oid, typeValue); - typeValue = new[] {oid}; + typeValue = new[] { oid }; typeRequestCert.InvokeMember("HashAlgorithm", BindingFlags.PutDispProperty, null, requestCert, typeValue); typeRequestCert.InvokeMember("Encode", BindingFlags.InvokeMethod, null, requestCert, null); @@ -243,15 +243,15 @@ private X509Certificate2 MakeCertificate(bool isRoot, string subject, string ful typeValue[0] = 0; - var createCertRequest = typeX509Enrollment.InvokeMember("CreateRequest", BindingFlags.InvokeMethod, null, x509Enrollment, typeValue); - typeValue = new[] {2, createCertRequest, 0, string.Empty}; + var createCertRequest = typeX509Enrollment.InvokeMember("CreateRequest", BindingFlags.InvokeMethod, null, x509Enrollment, typeValue); + typeValue = new[] { 2, createCertRequest, 0, string.Empty }; typeX509Enrollment.InvokeMember("InstallResponse", BindingFlags.InvokeMethod, null, x509Enrollment, typeValue); - typeValue = new object[] {null, 0, 1}; + typeValue = new object[] { null, 0, 1 }; try { - var empty = (string) typeX509Enrollment.InvokeMember("CreatePFX", BindingFlags.InvokeMethod, null, x509Enrollment, typeValue); + var empty = (string)typeX509Enrollment.InvokeMember("CreatePFX", BindingFlags.InvokeMethod, null, x509Enrollment, typeValue); return new X509Certificate2(Convert.FromBase64String(empty), string.Empty, X509KeyStorageFlags.Exportable); } catch (Exception) @@ -293,8 +293,7 @@ private X509Certificate2 MakeCertificateInternal(string sSubjectCN, bool isRoot, var graceTime = DateTime.Now.AddDays(GraceDays); var now = DateTime.Now; - rCert = !isRoot ? MakeCertificate(false, sSubjectCN, fullSubject, keyLength, HashAlgo, graceTime, now.AddDays(ValidDays), signingCert) : - MakeCertificate(true, sSubjectCN, fullSubject, keyLength, HashAlgo, graceTime, now.AddDays(ValidDays), null); + rCert = MakeCertificate(isRoot, sSubjectCN, fullSubject, keyLength, HashAlgo, graceTime, now.AddDays(ValidDays), isRoot ? null : signingCert); return rCert; } } diff --git a/Titanium.Web.Proxy/Network/CertificateManager.cs b/Titanium.Web.Proxy/Network/CertificateManager.cs index d3e7547de..828e4f8fa 100644 --- a/Titanium.Web.Proxy/Network/CertificateManager.cs +++ b/Titanium.Web.Proxy/Network/CertificateManager.cs @@ -1,12 +1,13 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; -using System.Linq; -using System.Collections.Concurrent; -using System.IO; -using Titanium.Web.Proxy.Network.Certificate; using Titanium.Web.Proxy.Helpers; +using Titanium.Web.Proxy.Network.Certificate; namespace Titanium.Web.Proxy.Network { @@ -51,7 +52,7 @@ internal CertificateEngine Engine if (certEngine == null) { certEngine = engine == CertificateEngine.BouncyCastle - ? (ICertificateMaker) new BCCertificateMaker() + ? (ICertificateMaker)new BCCertificateMaker() : new WinCertificateMaker(); } } @@ -132,12 +133,12 @@ private void ClearRootCertificate() private string GetRootCertificatePath() { - var assemblyLocation = System.Reflection.Assembly.GetExecutingAssembly().Location; + var assemblyLocation = Assembly.GetExecutingAssembly().Location; // dynamically loaded assemblies returns string.Empty location if (assemblyLocation == string.Empty) { - assemblyLocation = System.Reflection.Assembly.GetEntryAssembly().Location; + assemblyLocation = Assembly.GetEntryAssembly().Location; } var path = Path.GetDirectoryName(assemblyLocation); @@ -264,7 +265,7 @@ internal virtual X509Certificate2 CreateCertificate(string certificateName, bool } if (certificate != null && !certificateCache.ContainsKey(certificateName)) { - certificateCache.Add(certificateName, new CachedCertificate {Certificate = certificate}); + certificateCache.Add(certificateName, new CachedCertificate { Certificate = certificate }); } } else diff --git a/Titanium.Web.Proxy/Network/Tcp/TcpConnection.cs b/Titanium.Web.Proxy/Network/Tcp/TcpConnection.cs index 70ced1fc0..ab621061f 100644 --- a/Titanium.Web.Proxy/Network/Tcp/TcpConnection.cs +++ b/Titanium.Web.Proxy/Network/Tcp/TcpConnection.cs @@ -58,7 +58,21 @@ public void Dispose() StreamReader?.Dispose(); - TcpClient?.Close(); + try + { + if (TcpClient != null) + { + //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(); + } + } + catch + { + } } } } diff --git a/Titanium.Web.Proxy/Network/Tcp/TcpConnectionFactory.cs b/Titanium.Web.Proxy/Network/Tcp/TcpConnectionFactory.cs index 2bef02038..0471f0b73 100644 --- a/Titanium.Web.Proxy/Network/Tcp/TcpConnectionFactory.cs +++ b/Titanium.Web.Proxy/Network/Tcp/TcpConnectionFactory.cs @@ -1,13 +1,14 @@ using System; +using System.IO; +using System.Linq; +using System.Net.Security; using System.Net.Sockets; using System.Text; +using System.Threading; using System.Threading.Tasks; -using System.IO; -using System.Net.Security; +using Titanium.Web.Proxy.Extensions; using Titanium.Web.Proxy.Helpers; using Titanium.Web.Proxy.Models; -using System.Linq; -using Titanium.Web.Proxy.Extensions; using Titanium.Web.Proxy.Shared; namespace Titanium.Web.Proxy.Network.Tcp @@ -17,7 +18,6 @@ namespace Titanium.Web.Proxy.Network.Tcp /// internal class TcpConnectionFactory { - /// /// Creates a TCP connection to server /// @@ -30,19 +30,19 @@ internal class TcpConnectionFactory /// /// /// - internal async Task CreateClient(ProxyServer server, + internal async Task CreateClient(ProxyServer server, string remoteHostName, int remotePort, Version httpVersion, - bool isHttps, + bool isHttps, ExternalProxy externalHttpProxy, ExternalProxy externalHttpsProxy, Stream clientStream) { TcpClient client; CustomBufferedStream stream; - bool isLocalhost = (externalHttpsProxy == null && externalHttpProxy == null) ? false : NetworkHelper.IsLocalIpAddress(remoteHostName); + bool isLocalhost = (externalHttpsProxy != null || externalHttpProxy != null) && NetworkHelper.IsLocalIpAddress(remoteHostName); - bool useHttpsProxy = externalHttpsProxy != null && externalHttpsProxy.HostName != remoteHostName && (externalHttpsProxy.BypassForLocalhost && !isLocalhost); - bool useHttpProxy = externalHttpProxy != null && externalHttpProxy.HostName != remoteHostName && (externalHttpProxy.BypassForLocalhost && !isLocalhost); + bool useHttpsProxy = externalHttpsProxy != null && externalHttpsProxy.HostName != remoteHostName && externalHttpsProxy.BypassForLocalhost && !isLocalhost; + bool useHttpProxy = externalHttpProxy != null && externalHttpProxy.HostName != remoteHostName && externalHttpProxy.BypassForLocalhost && !isLocalhost; if (isHttps) { @@ -55,7 +55,7 @@ internal async Task CreateClient(ProxyServer server, await client.ConnectAsync(externalHttpsProxy.HostName, externalHttpsProxy.Port); stream = new CustomBufferedStream(client.GetStream(), server.BufferSize); - using (var writer = new StreamWriter(stream, Encoding.ASCII, server.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($"Host: {remoteHostName}:{remotePort}"); @@ -75,7 +75,7 @@ internal async Task CreateClient(ProxyServer server, { var result = await reader.ReadLineAsync(); - if (!new[] {"200 OK", "connection established"}.Any(s => result.ContainsIgnoreCase(s))) + if (!new[] { "200 OK", "connection established" }.Any(s => result.ContainsIgnoreCase(s))) { throw new Exception("Upstream proxy failed to create a secure tunnel"); } @@ -125,9 +125,7 @@ internal async Task CreateClient(ProxyServer server, client.ReceiveTimeout = server.ConnectionTimeOutSeconds * 1000; client.SendTimeout = server.ConnectionTimeOutSeconds * 1000; - client.LingerState = new LingerOption(true, 0); - - server.ServerConnectionCount++; + Interlocked.Increment(ref server.ServerConnectionCountField); return new TcpConnection { diff --git a/Titanium.Web.Proxy/ProxyAuthorizationHandler.cs b/Titanium.Web.Proxy/ProxyAuthorizationHandler.cs index c8279925c..ad03921fe 100644 --- a/Titanium.Web.Proxy/ProxyAuthorizationHandler.cs +++ b/Titanium.Web.Proxy/ProxyAuthorizationHandler.cs @@ -70,8 +70,8 @@ private async Task SendAuthentication407Response(StreamWriter clientStreamWriter { ResponseHeaders = new Dictionary { - {"Proxy-Authenticate", new HttpHeader("Proxy-Authenticate", "Basic realm=\"TitaniumProxy\"")}, - {"Proxy-Connection", new HttpHeader("Proxy-Connection", "close")} + { "Proxy-Authenticate", new HttpHeader("Proxy-Authenticate", "Basic realm=\"TitaniumProxy\"") }, + { "Proxy-Connection", new HttpHeader("Proxy-Connection", "close") } } }; await WriteResponseHeaders(clientStreamWriter, response); diff --git a/Titanium.Web.Proxy/ProxyServer.cs b/Titanium.Web.Proxy/ProxyServer.cs index de3a0d928..a912b0d53 100644 --- a/Titanium.Web.Proxy/ProxyServer.cs +++ b/Titanium.Web.Proxy/ProxyServer.cs @@ -1,16 +1,17 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Net; using System.Net.Sockets; +using System.Security.Authentication; +using System.Security.Cryptography.X509Certificates; +using System.Threading; using System.Threading.Tasks; using Titanium.Web.Proxy.EventArguments; using Titanium.Web.Proxy.Helpers; using Titanium.Web.Proxy.Models; using Titanium.Web.Proxy.Network; -using System.Linq; -using System.Security.Authentication; using Titanium.Web.Proxy.Network.Tcp; -using System.Security.Cryptography.X509Certificates; namespace Titanium.Web.Proxy { @@ -35,6 +36,8 @@ public partial class ProxyServer : IDisposable private Action exceptionFunc; private bool trustRootCertificate; + private int clientConnectionCountField; + internal int ServerConnectionCountField; /// /// A object that creates tcp connection to server @@ -50,8 +53,7 @@ public partial class ProxyServer : IDisposable /// /// Set firefox to use default system proxy /// - private FireFoxProxySettingsManager firefoxProxySettingsManager - = new FireFoxProxySettingsManager(); + private FireFoxProxySettingsManager firefoxProxySettingsManager = new FireFoxProxySettingsManager(); #endif /// @@ -224,18 +226,18 @@ public Action ExceptionFunc /// List of supported Ssl versions /// public SslProtocols SupportedSslProtocols { get; set; } = SslProtocols.Tls - | SslProtocols.Tls11 | SslProtocols.Tls12 | SslProtocols.Ssl3; + | SslProtocols.Tls11 | SslProtocols.Tls12 | SslProtocols.Ssl3; /// /// Total number of active client connections /// - public int ClientConnectionCount { get; private set; } + public int ClientConnectionCount => clientConnectionCountField; /// /// Total number of active server connections /// - public int ServerConnectionCount { get; internal set; } + public int ServerConnectionCount => ServerConnectionCountField; /// /// Constructor @@ -381,8 +383,7 @@ public void SetAsSystemHttpsProxy(ExplicitProxyEndPoint endPoint) #if !DEBUG firefoxProxySettingsManager.AddFirefox(); #endif - Console.WriteLine("Set endpoint at Ip {0} and port: {1} as System HTTPS Proxy", - endPoint.IpAddress, endPoint.Port); + Console.WriteLine("Set endpoint at Ip {0} and port: {1} as System HTTPS Proxy", endPoint.IpAddress, endPoint.Port); } /// @@ -596,13 +597,7 @@ private void OnAcceptConnection(IAsyncResult asyn) { 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); + Interlocked.Increment(ref clientConnectionCountField); try { @@ -617,8 +612,23 @@ private void OnAcceptConnection(IAsyncResult asyn) } finally { - ClientConnectionCount--; - tcpClient?.Close(); + Interlocked.Decrement(ref clientConnectionCountField); + + try + { + if (tcpClient != null) + { + //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(); + } + } + catch + { + } } }); } diff --git a/Titanium.Web.Proxy/RequestHandler.cs b/Titanium.Web.Proxy/RequestHandler.cs index 60f3975e8..99918465d 100644 --- a/Titanium.Web.Proxy/RequestHandler.cs +++ b/Titanium.Web.Proxy/RequestHandler.cs @@ -6,15 +6,15 @@ using System.Net.Security; using System.Net.Sockets; using System.Security.Authentication; -using Titanium.Web.Proxy.Exceptions; +using System.Threading.Tasks; using Titanium.Web.Proxy.EventArguments; +using Titanium.Web.Proxy.Exceptions; +using Titanium.Web.Proxy.Extensions; using Titanium.Web.Proxy.Helpers; -using Titanium.Web.Proxy.Models; -using Titanium.Web.Proxy.Shared; using Titanium.Web.Proxy.Http; -using System.Threading.Tasks; -using Titanium.Web.Proxy.Extensions; +using Titanium.Web.Proxy.Models; using Titanium.Web.Proxy.Network.Tcp; +using Titanium.Web.Proxy.Shared; namespace Titanium.Web.Proxy { @@ -27,7 +27,9 @@ partial class ProxyServer //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) { - CustomBufferedStream clientStream = new CustomBufferedStream(tcpClient.GetStream(), BufferSize); + var disposed = false; + + var clientStream = new CustomBufferedStream(tcpClient.GetStream(), BufferSize); clientStream.ReadTimeout = ConnectionTimeOutSeconds * 1000; clientStream.WriteTimeout = ConnectionTimeOutSeconds * 1000; @@ -44,7 +46,6 @@ private async Task HandleClient(ExplicitProxyEndPoint endPoint, TcpClient tcpCli if (string.IsNullOrEmpty(httpCmd)) { - Dispose(clientStream, clientStreamReader, clientStreamWriter, null); return; } @@ -99,7 +100,6 @@ private async Task HandleClient(ExplicitProxyEndPoint endPoint, TcpClient tcpCli if (await CheckAuthorization(clientStreamWriter, connectRequestHeaders) == false) { - Dispose(clientStream, clientStreamReader, clientStreamWriter, null); return; } @@ -111,22 +111,23 @@ private async Task HandleClient(ExplicitProxyEndPoint endPoint, TcpClient tcpCli { sslStream = new SslStream(clientStream, true); - var certificate = endPoint.GenericCertificate ?? CertificateManager.CreateCertificate(httpRemoteUri.Host, false); + var certificate = endPoint.GenericCertificate ?? + CertificateManager.CreateCertificate(httpRemoteUri.Host, false); //Successfully managed to authenticate the client using the fake certificate await sslStream.AuthenticateAsServerAsync(certificate, false, SupportedSslProtocols, false); + //HTTPS server created - we can now decrypt the client's traffic clientStream = new CustomBufferedStream(sslStream, BufferSize); + clientStreamReader.Dispose(); clientStreamReader = new CustomBinaryReader(clientStream, BufferSize); clientStreamWriter = new StreamWriter(clientStream) { NewLine = ProxyConstants.NewLine }; } catch { sslStream?.Dispose(); - - Dispose(clientStream, clientStreamReader, clientStreamWriter, null); return; } @@ -138,6 +139,7 @@ await sslStream.AuthenticateAsServerAsync(certificate, false, { //Siphon out CONNECT request headers await clientStreamReader.ReadAndIgnoreAllLinesAsync(); + //write back successfull CONNECT response await WriteConnectResponse(clientStreamWriter, version); @@ -147,18 +149,24 @@ await TcpHelper.SendRaw(this, false, clientStream, tcpConnectionFactory); - Dispose(clientStream, clientStreamReader, clientStreamWriter, null); return; } + //Now create the request - await HandleHttpSessionRequest(tcpClient, httpCmd, clientStream, clientStreamReader, clientStreamWriter, - httpRemoteUri.Scheme == Uri.UriSchemeHttps ? httpRemoteUri.Host : null, endPoint, connectRequestHeaders); + disposed = await HandleHttpSessionRequest(tcpClient, httpCmd, clientStream, clientStreamReader, clientStreamWriter, + httpRemoteUri.Scheme == Uri.UriSchemeHttps ? httpRemoteUri.Host : null, endPoint, + connectRequestHeaders); } - catch (Exception) + catch (Exception e) { - Dispose(clientStream, - clientStreamReader, - clientStreamWriter, null); + ExceptionFunc(new Exception("Error whilst authorizing request", e)); + } + finally + { + if (!disposed) + { + Dispose(clientStream, clientStreamReader, clientStreamWriter, null); + } } } @@ -166,7 +174,8 @@ await HandleHttpSessionRequest(tcpClient, httpCmd, clientStream, clientStreamRea //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) { - CustomBufferedStream clientStream = new CustomBufferedStream(tcpClient.GetStream(), BufferSize); + bool disposed = false; + var clientStream = new CustomBufferedStream(tcpClient.GetStream(), BufferSize); clientStream.ReadTimeout = ConnectionTimeOutSeconds * 1000; clientStream.WriteTimeout = ConnectionTimeOutSeconds * 1000; @@ -174,47 +183,40 @@ private async Task HandleClient(TransparentProxyEndPoint endPoint, TcpClient tcp CustomBinaryReader clientStreamReader = null; StreamWriter clientStreamWriter = null; - if (endPoint.EnableSsl) + try { - var sslStream = new SslStream(clientStream, true); + if (endPoint.EnableSsl) + { + var sslStream = new SslStream(clientStream, true); + clientStream = new CustomBufferedStream(sslStream, BufferSize); - //implement in future once SNI supported by SSL stream, for now use the same certificate - var certificate = CertificateManager.CreateCertificate(endPoint.GenericCertificateName, false); + //implement in future once SNI supported by SSL stream, for now use the same certificate + var certificate = CertificateManager.CreateCertificate(endPoint.GenericCertificateName, false); - try - { //Successfully managed to authenticate the client using the fake certificate await sslStream.AuthenticateAsServerAsync(certificate, false, SslProtocols.Tls, false); - clientStream = new CustomBufferedStream(sslStream, BufferSize); - clientStreamReader = new CustomBinaryReader(clientStream, BufferSize); - clientStreamWriter = new StreamWriter(clientStream) { NewLine = ProxyConstants.NewLine }; //HTTPS server created - we can now decrypt the client's traffic } - catch (Exception) - { - sslStream.Dispose(); - - Dispose(sslStream, - clientStreamReader, - clientStreamWriter, null); - return; - } - } - else - { clientStreamReader = new CustomBinaryReader(clientStream, BufferSize); clientStreamWriter = new StreamWriter(clientStream) { NewLine = ProxyConstants.NewLine }; - } - //now read the request line - var httpCmd = await clientStreamReader.ReadLineAsync(); + //now read the request line + var httpCmd = await clientStreamReader.ReadLineAsync(); - //Now create the request - await HandleHttpSessionRequest(tcpClient, httpCmd, clientStream, clientStreamReader, clientStreamWriter, - endPoint.EnableSsl ? endPoint.GenericCertificateName : null, endPoint, null); + //Now create the request + disposed = await HandleHttpSessionRequest(tcpClient, httpCmd, clientStream, clientStreamReader, clientStreamWriter, + endPoint.EnableSsl ? endPoint.GenericCertificateName : null, endPoint, null); + } + finally + { + if (!disposed) + { + Dispose(clientStream, clientStreamReader, clientStreamWriter, null); + } + } } /// @@ -257,9 +259,11 @@ private async Task GetServerConnection( } - private async Task HandleHttpSessionRequestInternal(TcpConnection connection, - SessionEventArgs args, bool closeConnection) + private async Task HandleHttpSessionRequestInternal(TcpConnection connection, SessionEventArgs args, bool closeConnection) { + bool disposed = false; + bool keepAlive = false; + try { args.WebSession.Request.RequestLocked = true; @@ -267,12 +271,7 @@ private async Task HandleHttpSessionRequestInternal(TcpConnection connecti //If request was cancelled by user then dispose the client if (args.WebSession.Request.CancelRequest) { - Dispose(args.ProxyClient.ClientStream, - args.ProxyClient.ClientStreamReader, - args.ProxyClient.ClientStreamWriter, - args.WebSession.ServerConnection); - - return false; + return true; } //if expect continue is enabled then send the headers first @@ -336,47 +335,39 @@ await WriteResponseStatus(args.WebSession.Response.HttpVersion, "417", //If not expectation failed response was returned by server then parse response if (!args.WebSession.Request.ExpectationFailed) { - var result = await HandleHttpSessionResponse(args); + disposed = await HandleHttpSessionResponse(args); //already disposed inside above method - if (result == false) + if (disposed) { - return false; + return true; } } //if connection is closing exit if (args.WebSession.Response.ResponseKeepAlive == false) { - Dispose(args.ProxyClient.ClientStream, - args.ProxyClient.ClientStreamReader, - args.ProxyClient.ClientStreamWriter, - args.WebSession.ServerConnection); + return true; + } + if (!closeConnection) + { + keepAlive = true; return false; } } catch (Exception e) { ExceptionFunc(new ProxyHttpException("Error occured whilst handling session request (internal)", e, args)); - - Dispose(args.ProxyClient.ClientStream, - args.ProxyClient.ClientStreamReader, - args.ProxyClient.ClientStreamWriter, - args.WebSession.ServerConnection); - - return false; + return true; } - - if (closeConnection) + finally { - //dispose - Dispose(args.ProxyClient.ClientStream, - args.ProxyClient.ClientStreamReader, - args.ProxyClient.ClientStreamWriter, - args.WebSession.ServerConnection); - - return false; + if (!disposed && !keepAlive) + { + //dispose + Dispose(args.ProxyClient.ClientStream, args.ProxyClient.ClientStreamReader, args.ProxyClient.ClientStreamWriter, args.WebSession.ServerConnection); + } } return true; @@ -393,13 +384,13 @@ await WriteResponseStatus(args.WebSession.Response.HttpVersion, "417", /// /// /// - /// - /// /// - private async Task HandleHttpSessionRequest(TcpClient client, string httpCmd, Stream clientStream, + private async Task HandleHttpSessionRequest(TcpClient client, string httpCmd, Stream clientStream, CustomBinaryReader clientStreamReader, StreamWriter clientStreamWriter, string httpsHostName, ProxyEndPoint endPoint, List connectHeaders) { + bool disposed = false; + TcpConnection connection = null; //Loop through each subsequest request on this particular client connection @@ -408,19 +399,14 @@ private async Task HandleHttpSessionRequest(TcpClient client, string httpCmd, St { if (string.IsNullOrEmpty(httpCmd)) { - Dispose(clientStream, - clientStreamReader, - clientStreamWriter, - connection); - break; } var args = new SessionEventArgs(BufferSize, HandleHttpSessionResponse) - { - ProxyClient = { TcpClient = client }, - WebSession = { ConnectHeaders = connectHeaders } - }; + { + ProxyClient = { TcpClient = client }, + WebSession = { ConnectHeaders = connectHeaders } + }; args.WebSession.ProcessId = new Lazy(() => { @@ -458,7 +444,8 @@ private async Task HandleHttpSessionRequest(TcpClient client, string httpCmd, St //Read the request headers in to unique and non-unique header collections await HeaderParser.ReadHeaders(clientStreamReader, args.WebSession.Request.NonUniqueRequestHeaders, args.WebSession.Request.RequestHeaders); - var httpRemoteUri = new Uri(httpsHostName == null ? httpCmdSplit[1] + var httpRemoteUri = new Uri(httpsHostName == null + ? httpCmdSplit[1] : string.Concat("https://", args.WebSession.Request.Host ?? httpsHostName, httpCmdSplit[1])); args.WebSession.Request.RequestUri = httpRemoteUri; @@ -469,15 +456,11 @@ private async Task HandleHttpSessionRequest(TcpClient client, string httpCmd, St args.ProxyClient.ClientStreamReader = clientStreamReader; args.ProxyClient.ClientStreamWriter = clientStreamWriter; - if (httpsHostName == null && - await CheckAuthorization(clientStreamWriter, + if (httpsHostName == null && + await CheckAuthorization(clientStreamWriter, args.WebSession.Request.RequestHeaders.Values) == false) { - Dispose(clientStream, - clientStreamReader, - clientStreamWriter, - connection); - + args.Dispose(); break; } @@ -487,15 +470,7 @@ await CheckAuthorization(clientStreamWriter, //If user requested interception do it if (BeforeRequest != null) { - var invocationList = BeforeRequest.GetInvocationList(); - var handlerTasks = new Task[invocationList.Length]; - - for (var i = 0; i < invocationList.Length; i++) - { - handlerTasks[i] = ((Func)invocationList[i])(this, args); - } - - await Task.WhenAll(handlerTasks); + await BeforeRequest.InvokeParallelAsync(this, args); } //if upgrading to websocket then relay the requet without reading the contents @@ -506,11 +481,7 @@ await TcpHelper.SendRaw(this, httpCmd, httpVersion, args.WebSession.Request.RequestHeaders, args.IsHttps, clientStream, tcpConnectionFactory); - Dispose(clientStream, - clientStreamReader, - clientStreamWriter, - connection); - + args.Dispose(); break; } @@ -520,50 +491,46 @@ await TcpHelper.SendRaw(this, } //construct the web request that we are going to issue on behalf of the client. - var result = await HandleHttpSessionRequestInternal(connection, args, false); + disposed = await HandleHttpSessionRequestInternal(connection, args, false); - if (result == false) + if (disposed) { //already disposed inside above method + args.Dispose(); break; } if (args.WebSession.Request.CancelRequest) { - Dispose(clientStream, - clientStreamReader, - clientStreamWriter, - connection); - + args.Dispose(); break; } //if connection is closing exit if (args.WebSession.Response.ResponseKeepAlive == false) { - Dispose(clientStream, - clientStreamReader, - clientStreamWriter, - connection); - + 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)); - - Dispose(clientStream, - clientStreamReader, - clientStreamWriter, - connection); break; } } + if (!disposed) + { + Dispose(clientStream, clientStreamReader, clientStreamWriter, connection); + } + + return true; } /// diff --git a/Titanium.Web.Proxy/ResponseHandler.cs b/Titanium.Web.Proxy/ResponseHandler.cs index 0fadbbee0..63275e69f 100644 --- a/Titanium.Web.Proxy/ResponseHandler.cs +++ b/Titanium.Web.Proxy/ResponseHandler.cs @@ -1,14 +1,15 @@ using System; using System.Collections.Generic; using System.IO; -using Titanium.Web.Proxy.EventArguments; -using Titanium.Web.Proxy.Models; -using Titanium.Web.Proxy.Compression; +using System.Threading; using System.Threading.Tasks; +using Titanium.Web.Proxy.Compression; +using Titanium.Web.Proxy.EventArguments; using Titanium.Web.Proxy.Exceptions; using Titanium.Web.Proxy.Extensions; -using Titanium.Web.Proxy.Http; using Titanium.Web.Proxy.Helpers; +using Titanium.Web.Proxy.Http; +using Titanium.Web.Proxy.Models; using Titanium.Web.Proxy.Network.Tcp; namespace Titanium.Web.Proxy @@ -22,7 +23,7 @@ partial class ProxyServer /// Called asynchronously when a request was successfully and we received the response /// /// - /// true if no errors + /// true if client/server connection was terminated (and disposed) private async Task HandleHttpSessionResponse(SessionEventArgs args) { try @@ -40,28 +41,20 @@ private async Task HandleHttpSessionResponse(SessionEventArgs args) //If user requested call back then do it if (BeforeResponse != null && !args.WebSession.Response.ResponseLocked) { - Delegate[] invocationList = BeforeResponse.GetInvocationList(); - Task[] handlerTasks = new Task[invocationList.Length]; - - for (int i = 0; i < invocationList.Length; i++) - { - handlerTasks[i] = ((Func)invocationList[i])(this, args); - } - - await Task.WhenAll(handlerTasks); + await BeforeResponse.InvokeParallelAsync(this, args); } if (args.ReRequest) { - if(args.WebSession.ServerConnection != null) + if (args.WebSession.ServerConnection != null) { args.WebSession.ServerConnection.Dispose(); - ServerConnectionCount--; + Interlocked.Decrement(ref ServerConnectionCountField); } var connection = await GetServerConnection(args); - var result = await HandleHttpSessionRequestInternal(null, args, true); - return result; + var disposed = await HandleHttpSessionRequestInternal(null, args, true); + return disposed; } args.WebSession.Response.ResponseLocked = true; @@ -137,12 +130,10 @@ await args.WebSession.ServerConnection.StreamReader Dispose(args.ProxyClient.ClientStream, args.ProxyClient.ClientStreamReader, args.ProxyClient.ClientStreamWriter, args.WebSession.ServerConnection); - return false; + return true; } - args.Dispose(); - - return true; + return false; } /// @@ -240,15 +231,17 @@ private void Dispose(Stream clientStream, StreamWriter clientStreamWriter, TcpConnection serverConnection) { - ServerConnectionCount--; - clientStream?.Close(); clientStream?.Dispose(); clientStreamReader?.Dispose(); clientStreamWriter?.Dispose(); - serverConnection?.Dispose(); + if (serverConnection != null) + { + serverConnection.Dispose(); + Interlocked.Decrement(ref ServerConnectionCountField); + } } } } diff --git a/Titanium.Web.Proxy/Shared/ProxyConstants.cs b/Titanium.Web.Proxy/Shared/ProxyConstants.cs index 6e2a1f29b..d2535b651 100644 --- a/Titanium.Web.Proxy/Shared/ProxyConstants.cs +++ b/Titanium.Web.Proxy/Shared/ProxyConstants.cs @@ -7,9 +7,9 @@ namespace Titanium.Web.Proxy.Shared /// internal class ProxyConstants { - internal static readonly char[] SpaceSplit = {' '}; - internal static readonly char[] ColonSplit = {':'}; - internal static readonly char[] SemiColonSplit = {';'}; + internal static readonly char[] SpaceSplit = { ' ' }; + internal static readonly char[] ColonSplit = { ':' }; + internal static readonly char[] SemiColonSplit = { ';' }; internal static readonly byte[] NewLineBytes = Encoding.ASCII.GetBytes(NewLine); diff --git a/Titanium.Web.Proxy/Titanium.Web.Proxy.csproj b/Titanium.Web.Proxy/Titanium.Web.Proxy.csproj index 6b186297c..0e211df08 100644 --- a/Titanium.Web.Proxy/Titanium.Web.Proxy.csproj +++ b/Titanium.Web.Proxy/Titanium.Web.Proxy.csproj @@ -73,6 +73,7 @@ +