Commit bbbb0c57 authored by Jehonathan Thomas's avatar Jehonathan Thomas Committed by GitHub

Merge pull request #244 from justcoding121/beta

Stable
parents 5a683862 a3562f03
using System;
using System.Runtime.InteropServices;
namespace Titanium.Web.Proxy.Examples.Basic.Helpers
{
/// <summary>
/// Adapated from
/// http://stackoverflow.com/questions/13656846/how-to-programmatic-disable-c-sharp-console-applications-quick-edit-mode
/// </summary>
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;
}
}
}
using System; using System;
using System.Diagnostics;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using Titanium.Web.Proxy.Examples.Basic.Helpers;
namespace Titanium.Web.Proxy.Examples.Basic namespace Titanium.Web.Proxy.Examples.Basic
{ {
...@@ -9,11 +11,13 @@ namespace Titanium.Web.Proxy.Examples.Basic ...@@ -9,11 +11,13 @@ namespace Titanium.Web.Proxy.Examples.Basic
public static void Main(string[] args) 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 //On Console exit make sure we also exit the proxy
NativeMethods.Handler = ConsoleEventCallback; NativeMethods.Handler = ConsoleEventCallback;
NativeMethods.SetConsoleCtrlHandler(NativeMethods.Handler, true); NativeMethods.SetConsoleCtrlHandler(NativeMethods.Handler, true);
//Start proxy controller //Start proxy controller
controller.StartProxy(); controller.StartProxy();
...@@ -50,5 +54,7 @@ namespace Titanium.Web.Proxy.Examples.Basic ...@@ -50,5 +54,7 @@ namespace Titanium.Web.Proxy.Examples.Basic
// Pinvoke // Pinvoke
internal delegate bool ConsoleEventDelegate(int eventType); internal delegate bool ConsoleEventDelegate(int eventType);
} }
}
\ No newline at end of file }
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Net; using System.Net;
using System.Net.Security;
using System.Threading.Tasks; using System.Threading.Tasks;
using Titanium.Web.Proxy.EventArguments; using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Models; using Titanium.Web.Proxy.Models;
...@@ -47,18 +47,18 @@ namespace Titanium.Web.Proxy.Examples.Basic ...@@ -47,18 +47,18 @@ 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>() { "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
// IncludedHttpsHostNameRegex = new List<string>() { "github.com" } //IncludedHttpsHostNameRegex = new List<string> { "github.com" }
//You can set only one of the ExcludedHttpsHostNameRegex and IncludedHttpsHostNameRegex properties, otherwise ArgumentException will be thrown //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 //Use self-issued generic certificate on all https requests
//Optimizes performance by not creating a certificate for each https-enabled domain //Optimizes performance by not creating a certificate for each https-enabled domain
//Useful when certificate trust is not required by proxy clients //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 //An explicit endpoint is where the client knows about the existence of a proxy
...@@ -107,7 +107,7 @@ namespace Titanium.Web.Proxy.Examples.Basic ...@@ -107,7 +107,7 @@ namespace Titanium.Web.Proxy.Examples.Basic
//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("Active Client Connections:" + ((ProxyServer)sender).ClientConnectionCount);
Console.WriteLine(e.WebSession.Request.Url); Console.WriteLine(e.WebSession.Request.Url);
//read request headers //read request headers
...@@ -150,7 +150,7 @@ namespace Titanium.Web.Proxy.Examples.Basic ...@@ -150,7 +150,7 @@ namespace Titanium.Web.Proxy.Examples.Basic
//Modify response //Modify response
public async Task OnResponse(object sender, SessionEventArgs e) 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)) if (requestBodyHistory.ContainsKey(e.Id))
{ {
...@@ -189,7 +189,7 @@ namespace Titanium.Web.Proxy.Examples.Basic ...@@ -189,7 +189,7 @@ namespace Titanium.Web.Proxy.Examples.Basic
public Task OnCertificateValidation(object sender, CertificateValidationEventArgs e) public Task OnCertificateValidation(object sender, CertificateValidationEventArgs e)
{ {
//set IsValid to true/false based on Certificate Errors //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; e.IsValid = true;
} }
......
...@@ -55,6 +55,7 @@ ...@@ -55,6 +55,7 @@
<Reference Include="System.Xml" /> <Reference Include="System.Xml" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="Helpers\ConsoleHelper.cs" />
<Compile Include="Program.cs" /> <Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ProxyTestController.cs" /> <Compile Include="ProxyTestController.cs" />
......
...@@ -17,8 +17,7 @@ Features ...@@ -17,8 +17,7 @@ Features
* Safely relays Web Socket requests over HTTP * Safely relays Web Socket requests over HTTP
* Support mutual SSL authentication * Support mutual SSL authentication
* Fully asynchronous proxy * Fully asynchronous proxy
* Supports proxy authentication * Supports proxy authentication & automatic proxy detection
Usage Usage
===== =====
...@@ -204,10 +203,8 @@ public Task OnCertificateSelection(object sender, CertificateSelectionEventArgs ...@@ -204,10 +203,8 @@ public Task OnCertificateSelection(object sender, CertificateSelectionEventArgs
``` ```
Future road map (Pull requests are welcome!) Future road map (Pull requests are welcome!)
============ ============
* Implement Kerberos/NTLM authentication over HTTP protocols for windows domain
* Support Server Name Indication (SNI) for transparent endpoints * Support Server Name Indication (SNI) for transparent endpoints
* Support HTTP 2.0 * Support HTTP 2.0
* Support upstream AutoProxy detection
* Support SOCKS protocol * Support SOCKS protocol
* Implement Kerberos/NTLM authentication over HTTP protocols for windows domain
using System.Reflection; using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following // General Information about an assembly is controlled through the following
......
using System; using System;
using Microsoft.VisualStudio.TestTools.UnitTesting; using System.Diagnostics;
using System.Net; using System.Net;
using System.Net.Http;
using System.Net.Security;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Titanium.Web.Proxy.EventArguments; using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Models; using Titanium.Web.Proxy.Models;
using System.Net.Http;
using System.Diagnostics;
namespace Titanium.Web.Proxy.IntegrationTests namespace Titanium.Web.Proxy.IntegrationTests
{ {
...@@ -13,7 +14,6 @@ namespace Titanium.Web.Proxy.IntegrationTests ...@@ -13,7 +14,6 @@ namespace Titanium.Web.Proxy.IntegrationTests
public class SslTests public class SslTests
{ {
[TestMethod] [TestMethod]
public void TestSsl() public void TestSsl()
{ {
//expand this to stress test to find //expand this to stress test to find
...@@ -103,7 +103,7 @@ namespace Titanium.Web.Proxy.IntegrationTests ...@@ -103,7 +103,7 @@ namespace Titanium.Web.Proxy.IntegrationTests
public Task OnCertificateValidation(object sender, CertificateValidationEventArgs e) public Task OnCertificateValidation(object sender, CertificateValidationEventArgs e)
{ {
//set IsValid to true/false based on Certificate Errors //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; e.IsValid = true;
} }
......
using System; using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using Titanium.Web.Proxy.Network; using Titanium.Web.Proxy.Network;
using System.Threading.Tasks;
using System.Collections.Generic;
namespace Titanium.Web.Proxy.UnitTests namespace Titanium.Web.Proxy.UnitTests
{ {
...@@ -10,8 +10,7 @@ namespace Titanium.Web.Proxy.UnitTests ...@@ -10,8 +10,7 @@ namespace Titanium.Web.Proxy.UnitTests
public class CertificateManagerTests public class CertificateManagerTests
{ {
private static readonly string[] hostNames private static readonly string[] hostNames
= new string[] { "facebook.com", "youtube.com", "google.com", = { "facebook.com", "youtube.com", "google.com", "bing.com", "yahoo.com" };
"bing.com", "yahoo.com"};
private readonly Random random = new Random(); private readonly Random random = new Random();
...@@ -36,16 +35,13 @@ namespace Titanium.Web.Proxy.UnitTests ...@@ -36,16 +35,13 @@ namespace Titanium.Web.Proxy.UnitTests
var certificate = mgr.CreateCertificate(host, false); var certificate = mgr.CreateCertificate(host, false);
Assert.IsNotNull(certificate); Assert.IsNotNull(certificate);
})); }));
} }
} }
await Task.WhenAll(tasks.ToArray()); await Task.WhenAll(tasks.ToArray());
mgr.StopClearIdleCertificates(); mgr.StopClearIdleCertificates();
} }
} }
} }
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation"> <wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/LINE_FEED_AT_FILE_END/@EntryValue">True</s:Boolean> <s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/LINE_FEED_AT_FILE_END/@EntryValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_AFTER_TYPECAST_PARENTHESES/@EntryValue">False</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_WITHIN_SINGLE_LINE_ARRAY_INITIALIZER_BRACES/@EntryValue">True</s:Boolean>
<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_LIMIT/@EntryValue">240</s:Int64>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=BC/@EntryIndexedValue">BC</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=BC/@EntryIndexedValue">BC</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=CN/@EntryIndexedValue">CN</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=DN/@EntryIndexedValue">DN</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=EKU/@EntryIndexedValue">EKU</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=KU/@EntryIndexedValue">KU</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=MTA/@EntryIndexedValue">MTA</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=OID/@EntryIndexedValue">OID</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=OIDS/@EntryIndexedValue">OIDS</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateConstants/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateConstants/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateInstanceFields/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateInstanceFields/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateStaticFields/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateStaticFields/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateStaticReadonly/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateStaticReadonly/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=dda2ffa1_002D435c_002D4111_002D88eb_002D1a7c93c382f0/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Static, Instance" AccessRightKinds="Private" Description="Property (private)"&gt;&lt;ElementKinds&gt;&lt;Kind Name="PROPERTY" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;&lt;/Policy&gt;</s:String></wpf:ResourceDictionary> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=dda2ffa1_002D435c_002D4111_002D88eb_002D1a7c93c382f0/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Static, Instance" AccessRightKinds="Private" Description="Property (private)"&gt;&lt;ElementKinds&gt;&lt;Kind Name="PROPERTY" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;&lt;/Policy&gt;</s:String>
\ No newline at end of file <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EAddAccessorOwnerDeclarationBracesMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateThisQualifierSettings/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
\ No newline at end of file
using System; using System;
using System.Net.Security; using System.Net.Security;
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
using Titanium.Web.Proxy.EventArguments; using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Extensions;
namespace Titanium.Web.Proxy namespace Titanium.Web.Proxy
{ {
...@@ -32,17 +32,8 @@ namespace Titanium.Web.Proxy ...@@ -32,17 +32,8 @@ namespace Titanium.Web.Proxy
SslPolicyErrors = sslPolicyErrors SslPolicyErrors = sslPolicyErrors
}; };
//why is the sender null?
Delegate[] invocationList = ServerCertificateValidationCallback.GetInvocationList(); ServerCertificateValidationCallback.InvokeParallel(this, args);
Task[] handlerTasks = new Task[invocationList.Length];
for (int i = 0; i < invocationList.Length; i++)
{
handlerTasks[i] = ((Func<object, CertificateValidationEventArgs, Task>) invocationList[i])(null, args);
}
Task.WhenAll(handlerTasks).Wait();
return args.IsValid; return args.IsValid;
} }
...@@ -108,17 +99,8 @@ namespace Titanium.Web.Proxy ...@@ -108,17 +99,8 @@ namespace Titanium.Web.Proxy
ClientCertificate = clientCertificate ClientCertificate = clientCertificate
}; };
//why is the sender null?
Delegate[] invocationList = ClientCertificateSelectionCallback.GetInvocationList(); ClientCertificateSelectionCallback.InvokeParallel(this, args);
Task[] handlerTasks = new Task[invocationList.Length];
for (int i = 0; i < invocationList.Length; i++)
{
handlerTasks[i] = ((Func<object, CertificateSelectionEventArgs, Task>) invocationList[i])(null, args);
}
Task.WhenAll(handlerTasks).Wait();
return args.ClientCertificate; return args.ClientCertificate;
} }
......
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Net;
using System.Text; using System.Text;
using Titanium.Web.Proxy.Exceptions; using System.Threading.Tasks;
using Titanium.Web.Proxy.Decompression; 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;
using Titanium.Web.Proxy.Http.Responses; 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.Models;
using Titanium.Web.Proxy.Network;
namespace Titanium.Web.Proxy.EventArguments namespace Titanium.Web.Proxy.EventArguments
{ {
...@@ -30,7 +30,7 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -30,7 +30,7 @@ namespace Titanium.Web.Proxy.EventArguments
/// <summary> /// <summary>
/// Holds a reference to proxy response handler method /// Holds a reference to proxy response handler method
/// </summary> /// </summary>
private readonly Func<SessionEventArgs, Task> httpResponseHandler; private Func<SessionEventArgs, Task> httpResponseHandler;
/// <summary> /// <summary>
/// Holds a reference to client /// Holds a reference to client
...@@ -56,7 +56,7 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -56,7 +56,7 @@ namespace Titanium.Web.Proxy.EventArguments
/// <summary> /// <summary>
/// Client End Point. /// Client End Point.
/// </summary> /// </summary>
public IPEndPoint ClientEndPoint => (IPEndPoint) ProxyClient.TcpClient.Client.RemoteEndPoint; public IPEndPoint ClientEndPoint => (IPEndPoint)ProxyClient.TcpClient.Client.RemoteEndPoint;
/// <summary> /// <summary>
/// A web session corresponding to a single request/response sequence /// A web session corresponding to a single request/response sequence
...@@ -158,7 +158,7 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -158,7 +158,7 @@ namespace Titanium.Web.Proxy.EventArguments
await WebSession.ServerConnection.StreamReader.CopyBytesToStream(bufferSize, responseBodyStream, await WebSession.ServerConnection.StreamReader.CopyBytesToStream(bufferSize, responseBodyStream,
WebSession.Response.ContentLength); 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); await WebSession.ServerConnection.StreamReader.CopyBytesToStream(bufferSize, responseBodyStream, long.MaxValue);
} }
...@@ -522,6 +522,11 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -522,6 +522,11 @@ namespace Titanium.Web.Proxy.EventArguments
/// </summary> /// </summary>
public void Dispose() public void Dispose()
{ {
httpResponseHandler = null;
CustomUpStreamHttpProxyUsed = null;
CustomUpStreamHttpsProxyUsed = null;
WebSession.Dispose();
} }
} }
} }
using System;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Extensions
{
internal static class FuncExtensions
{
public static void InvokeParallel<T>(this Func<object, T, Task> 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<object, T, Task>)invocationList[i])(sender, args);
}
Task.WhenAll(handlerTasks).Wait();
}
public static async Task InvokeParallelAsync<T>(this Func<object, T, Task> 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<object, T, Task>)invocationList[i])(sender, args);
}
await Task.WhenAll(handlerTasks);
}
}
}
...@@ -136,7 +136,7 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -136,7 +136,7 @@ namespace Titanium.Web.Proxy.Extensions
if (contentLength < bufferSize) if (contentLength < bufferSize)
{ {
bytesToRead = (int) contentLength; bytesToRead = (int)contentLength;
} }
var buffer = new byte[bufferSize]; var buffer = new byte[bufferSize];
...@@ -153,8 +153,8 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -153,8 +153,8 @@ namespace Titanium.Web.Proxy.Extensions
break; break;
bytesRead = 0; bytesRead = 0;
var remainingBytes = (contentLength - totalBytesRead); var remainingBytes = contentLength - totalBytesRead;
bytesToRead = remainingBytes > (long) bufferSize ? bufferSize : (int) remainingBytes; bytesToRead = remainingBytes > (long)bufferSize ? bufferSize : (int)remainingBytes;
} }
} }
else else
......
using System; using System;
using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Text; using System.Text;
...@@ -18,10 +19,18 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -18,10 +19,18 @@ namespace Titanium.Web.Proxy.Helpers
private readonly byte[] staticBuffer; private readonly byte[] staticBuffer;
private readonly Encoding encoding; private readonly Encoding encoding;
private static readonly ConcurrentQueue<byte[]> buffers
= new ConcurrentQueue<byte[]>();
private volatile bool disposed;
internal CustomBinaryReader(CustomBufferedStream stream, int bufferSize) internal CustomBinaryReader(CustomBufferedStream stream, int bufferSize)
{ {
this.stream = stream; this.stream = stream;
staticBuffer = new byte[bufferSize]; if (!buffers.TryDequeue(out staticBuffer) || staticBuffer.Length != bufferSize)
{
staticBuffer = new byte[bufferSize];
}
this.bufferSize = bufferSize; this.bufferSize = bufferSize;
...@@ -112,7 +121,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -112,7 +121,7 @@ namespace Titanium.Web.Proxy.Helpers
var buffer = staticBuffer; var buffer = staticBuffer;
if (totalBytesToRead < bufferSize) if (totalBytesToRead < bufferSize)
{ {
bytesToRead = (int) totalBytesToRead; bytesToRead = (int)totalBytesToRead;
buffer = new byte[bytesToRead]; buffer = new byte[bytesToRead];
} }
...@@ -127,7 +136,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -127,7 +136,7 @@ namespace Titanium.Web.Proxy.Helpers
break; break;
var remainingBytes = totalBytesToRead - totalBytesRead; var remainingBytes = totalBytesToRead - totalBytesRead;
bytesToRead = Math.Min(bufferSize, (int) remainingBytes); bytesToRead = Math.Min(bufferSize, (int)remainingBytes);
if (totalBytesRead + bytesToRead > buffer.Length) if (totalBytesRead + bytesToRead > buffer.Length)
{ {
...@@ -148,8 +157,18 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -148,8 +157,18 @@ namespace Titanium.Web.Proxy.Helpers
public void Dispose() public void Dispose()
{ {
if (!disposed)
{
disposed = true;
buffers.Enqueue(staticBuffer);
}
} }
/// <summary>
/// Increase size of buffer and copy existing content to new buffer
/// </summary>
/// <param name="buffer"></param>
/// <param name="size"></param>
private void ResizeBuffer(ref byte[] buffer, long size) private void ResizeBuffer(ref byte[] buffer, long size)
{ {
var newBuffer = new byte[size]; var newBuffer = new byte[size];
......
...@@ -198,7 +198,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -198,7 +198,7 @@ namespace Titanium.Web.Proxy.Helpers
{ {
if (asyncResult is ReadAsyncResult) if (asyncResult is ReadAsyncResult)
{ {
return ((ReadAsyncResult) asyncResult).ReadBytes; return ((ReadAsyncResult)asyncResult).ReadBytes;
} }
return baseStream.EndRead(asyncResult); return baseStream.EndRead(asyncResult);
......
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Net.Sockets; using System.Net.Sockets;
namespace Titanium.Web.Proxy.Helpers namespace Titanium.Web.Proxy.Helpers
{ {
internal class NetworkHelper internal class NetworkHelper
{ {
private static int FindProcessIdFromLocalPort(int port, IpVersion ipVersion) private static int FindProcessIdFromLocalPort(int port, IpVersion ipVersion)
{ {
var tcpRow = TcpHelper.GetTcpRowByLocalPort(ipVersion, port); var tcpRow = TcpHelper.GetTcpRowByLocalPort(ipVersion, port);
return tcpRow?.ProcessId ?? 0; return tcpRow?.ProcessId ?? 0;
} }
internal static int GetProcessIdFromPort(int port, bool ipV6Enabled) internal static int GetProcessIdFromPort(int port, bool ipV6Enabled)
{ {
var processId = FindProcessIdFromLocalPort(port, IpVersion.Ipv4); var processId = FindProcessIdFromLocalPort(port, IpVersion.Ipv4);
if (processId > 0 && !ipV6Enabled) if (processId > 0 && !ipV6Enabled)
{ {
return processId; return processId;
} }
return FindProcessIdFromLocalPort(port, IpVersion.Ipv6); return FindProcessIdFromLocalPort(port, IpVersion.Ipv6);
} }
/// <summary> /// <summary>
/// Adapated from below link /// Adapated from below link
/// http://stackoverflow.com/questions/11834091/how-to-check-if-localhost /// http://stackoverflow.com/questions/11834091/how-to-check-if-localhost
/// </summary> /// </summary>
/// <param name="address"></param> /// <param name="address"></param>
/// <returns></returns> /// <returns></returns>
internal static bool IsLocalIpAddress(IPAddress address) internal static bool IsLocalIpAddress(IPAddress address)
{ {
// get local IP addresses // get local IP addresses
var localIPs = Dns.GetHostAddresses(Dns.GetHostName()); var localIPs = Dns.GetHostAddresses(Dns.GetHostName());
// test if any host IP equals to any local IP or to localhost // test if any host IP equals to any local IP or to localhost
return IPAddress.IsLoopback(address) || localIPs.Contains(address); return IPAddress.IsLoopback(address) || localIPs.Contains(address);
} }
internal static bool IsLocalIpAddress(string hostName) internal static bool IsLocalIpAddress(string hostName)
{ {
bool isLocalhost = false; bool isLocalhost = false;
IPHostEntry localhost = Dns.GetHostEntry("127.0.0.1"); IPHostEntry localhost = Dns.GetHostEntry("127.0.0.1");
if (hostName == localhost.HostName) if (hostName == localhost.HostName)
{ {
IPHostEntry hostEntry = Dns.GetHostEntry(hostName); IPHostEntry hostEntry = Dns.GetHostEntry(hostName);
isLocalhost = hostEntry.AddressList.Any(IPAddress.IsLoopback); isLocalhost = hostEntry.AddressList.Any(IPAddress.IsLoopback);
} }
if (!isLocalhost) if (!isLocalhost)
{ {
localhost = Dns.GetHostEntry(Dns.GetHostName()); localhost = Dns.GetHostEntry(Dns.GetHostName());
IPAddress ipAddress; IPAddress ipAddress;
if (IPAddress.TryParse(hostName, out ipAddress)) if (IPAddress.TryParse(hostName, out ipAddress))
isLocalhost = localhost.AddressList.Any(x => x.Equals(ipAddress)); isLocalhost = localhost.AddressList.Any(x => x.Equals(ipAddress));
if (!isLocalhost) if (!isLocalhost)
{ {
try try
{ {
var hostEntry = Dns.GetHostEntry(hostName); var hostEntry = Dns.GetHostEntry(hostName);
isLocalhost = localhost.AddressList.Any(x => hostEntry.AddressList.Any(x.Equals)); isLocalhost = localhost.AddressList.Any(x => hostEntry.AddressList.Any(x.Equals));
} }
catch (SocketException) catch (SocketException)
{ {
} }
} }
} }
return isLocalhost; return isLocalhost;
} }
} }
} }
using System; using System;
using System.Runtime.InteropServices;
using Microsoft.Win32;
using System.Text.RegularExpressions;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using Microsoft.Win32;
// Helper classes for setting system proxy settings // Helper classes for setting system proxy settings
namespace Titanium.Web.Proxy.Helpers namespace Titanium.Web.Proxy.Helpers
...@@ -84,7 +84,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -84,7 +84,7 @@ namespace Titanium.Web.Proxy.Helpers
var exisitingContent = reg.GetValue("ProxyServer") as string; var exisitingContent = reg.GetValue("ProxyServer") as string;
var existingSystemProxyValues = GetSystemProxyValues(exisitingContent); var existingSystemProxyValues = GetSystemProxyValues(exisitingContent);
existingSystemProxyValues.RemoveAll(x => protocolType == ProxyProtocolType.Https ? x.IsHttps : !x.IsHttps); existingSystemProxyValues.RemoveAll(x => protocolType == ProxyProtocolType.Https ? x.IsHttps : !x.IsHttps);
existingSystemProxyValues.Add(new HttpSystemProxyValue() existingSystemProxyValues.Add(new HttpSystemProxyValue
{ {
HostName = hostname, HostName = hostname,
IsHttps = protocolType == ProxyProtocolType.Https, IsHttps = protocolType == ProxyProtocolType.Https,
......
...@@ -3,10 +3,9 @@ using System.Collections.Generic; ...@@ -3,10 +3,9 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net.NetworkInformation; using System.Net.NetworkInformation;
using System.Net.Security;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Security.Authentication;
using System.Text; using System.Text;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Titanium.Web.Proxy.Extensions; using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Models; using Titanium.Web.Proxy.Models;
...@@ -15,8 +14,6 @@ using Titanium.Web.Proxy.Shared; ...@@ -15,8 +14,6 @@ using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Helpers namespace Titanium.Web.Proxy.Helpers
{ {
using System.Net;
internal enum IpVersion internal enum IpVersion
{ {
Ipv4 = 1, Ipv4 = 1,
...@@ -232,7 +229,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -232,7 +229,7 @@ namespace Titanium.Web.Proxy.Helpers
finally finally
{ {
tcpConnection.Dispose(); tcpConnection.Dispose();
server.ServerConnectionCount--; Interlocked.Decrement(ref server.serverConnectionCount);
} }
} }
} }
......
...@@ -8,8 +8,8 @@ namespace Titanium.Web.Proxy.Http ...@@ -8,8 +8,8 @@ namespace Titanium.Web.Proxy.Http
{ {
internal static class HeaderParser internal static class HeaderParser
{ {
internal static async Task ReadHeaders(CustomBinaryReader reader, internal static async Task ReadHeaders(CustomBinaryReader reader,
Dictionary<string, List<HttpHeader>> nonUniqueResponseHeaders, Dictionary<string, List<HttpHeader>> nonUniqueResponseHeaders,
Dictionary<string, HttpHeader> headers) Dictionary<string, HttpHeader> headers)
{ {
string tmpLine; string tmpLine;
......
...@@ -11,7 +11,7 @@ namespace Titanium.Web.Proxy.Http ...@@ -11,7 +11,7 @@ namespace Titanium.Web.Proxy.Http
/// <summary> /// <summary>
/// Used to communicate with the server over HTTP(S) /// Used to communicate with the server over HTTP(S)
/// </summary> /// </summary>
public class HttpWebClient public class HttpWebClient : IDisposable
{ {
/// <summary> /// <summary>
/// Connection to server /// Connection to server
...@@ -79,7 +79,7 @@ namespace Titanium.Web.Proxy.Http ...@@ -79,7 +79,7 @@ namespace Titanium.Web.Proxy.Http
var requestLines = new StringBuilder(); var requestLines = new StringBuilder();
//prepare the request & headers //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}"); requestLines.AppendLine($"{Request.Method} {Request.RequestUri.AbsoluteUri} HTTP/{Request.HttpVersion.Major}.{Request.HttpVersion.Minor}");
} }
...@@ -93,7 +93,7 @@ namespace Titanium.Web.Proxy.Http ...@@ -93,7 +93,7 @@ namespace Titanium.Web.Proxy.Http
{ {
requestLines.AppendLine("Proxy-Connection: keep-alive"); requestLines.AppendLine("Proxy-Connection: keep-alive");
requestLines.AppendLine("Proxy-Authorization" + ": Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes( 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 //write request headers
foreach (var headerItem in Request.RequestHeaders) foreach (var headerItem in Request.RequestHeaders)
...@@ -208,5 +208,16 @@ namespace Titanium.Web.Proxy.Http ...@@ -208,5 +208,16 @@ namespace Titanium.Web.Proxy.Http
//Read the response headers in to unique and non-unique header collections //Read the response headers in to unique and non-unique header collections
await HeaderParser.ReadHeaders(ServerConnection.StreamReader, Response.NonUniqueResponseHeaders, Response.ResponseHeaders); await HeaderParser.ReadHeaders(ServerConnection.StreamReader, Response.NonUniqueResponseHeaders, Response.ResponseHeaders);
} }
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
ConnectHeaders = null;
Request.Dispose();
Response.Dispose();
}
} }
} }
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text; using System.Text;
using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Extensions; using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Models;
namespace Titanium.Web.Proxy.Http namespace Titanium.Web.Proxy.Http
{ {
/// <summary> /// <summary>
/// A HTTP(S) request object /// A HTTP(S) request object
/// </summary> /// </summary>
public class Request public class Request : IDisposable
{ {
/// <summary> /// <summary>
/// Request Method /// Request Method
...@@ -241,7 +241,7 @@ namespace Titanium.Web.Proxy.Http ...@@ -241,7 +241,7 @@ namespace Titanium.Web.Proxy.Http
/// request body as string /// request body as string
/// </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; }
...@@ -294,5 +294,20 @@ namespace Titanium.Web.Proxy.Http ...@@ -294,5 +294,20 @@ namespace Titanium.Web.Proxy.Http
RequestHeaders = new Dictionary<string, HttpHeader>(StringComparer.OrdinalIgnoreCase); RequestHeaders = new Dictionary<string, HttpHeader>(StringComparer.OrdinalIgnoreCase);
NonUniqueRequestHeaders = new Dictionary<string, List<HttpHeader>>(StringComparer.OrdinalIgnoreCase); NonUniqueRequestHeaders = new Dictionary<string, List<HttpHeader>>(StringComparer.OrdinalIgnoreCase);
} }
/// <summary>
/// Dispose off
/// </summary>
public void Dispose()
{
//not really needed since GC will collect it
//but just to be on safe side
RequestHeaders = null;
NonUniqueRequestHeaders = null;
RequestBody = null;
RequestBody = null;
}
} }
} }
using System.Collections.Generic; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Text; using System.Text;
using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Extensions; using Titanium.Web.Proxy.Extensions;
using System; using Titanium.Web.Proxy.Models;
namespace Titanium.Web.Proxy.Http namespace Titanium.Web.Proxy.Http
{ {
/// <summary> /// <summary>
/// Http(s) response object /// Http(s) response object
/// </summary> /// </summary>
public class Response public class Response : IDisposable
{ {
/// <summary> /// <summary>
/// Response Status Code. /// Response Status Code.
...@@ -234,5 +234,20 @@ namespace Titanium.Web.Proxy.Http ...@@ -234,5 +234,20 @@ namespace Titanium.Web.Proxy.Http
ResponseHeaders = new Dictionary<string, HttpHeader>(StringComparer.OrdinalIgnoreCase); ResponseHeaders = new Dictionary<string, HttpHeader>(StringComparer.OrdinalIgnoreCase);
NonUniqueResponseHeaders = new Dictionary<string, List<HttpHeader>>(StringComparer.OrdinalIgnoreCase); NonUniqueResponseHeaders = new Dictionary<string, List<HttpHeader>>(StringComparer.OrdinalIgnoreCase);
} }
/// <summary>
/// Dispose off
/// </summary>
public void Dispose()
{
//not really needed since GC will collect it
//but just to be on safe side
ResponseHeaders = null;
NonUniqueResponseHeaders = null;
ResponseBody = null;
ResponseBodyString = null;
}
} }
} }
...@@ -13,7 +13,7 @@ namespace Titanium.Web.Proxy.Http.Responses ...@@ -13,7 +13,7 @@ namespace Titanium.Web.Proxy.Http.Responses
/// <param name="status"></param> /// <param name="status"></param>
public GenericResponse(HttpStatusCode status) public GenericResponse(HttpStatusCode status)
{ {
ResponseStatusCode = ((int) status).ToString(); ResponseStatusCode = ((int)status).ToString();
ResponseStatusDescription = status.ToString(); ResponseStatusDescription = status.ToString();
} }
......
...@@ -21,7 +21,7 @@ namespace Titanium.Web.Proxy.Models ...@@ -21,7 +21,7 @@ namespace Titanium.Web.Proxy.Models
/// <summary> /// <summary>
/// Bypass this proxy for connections to localhost? /// Bypass this proxy for connections to localhost?
/// </summary> /// </summary>
public bool BypassForLocalhost { get; set; } public bool BypassLocalhost { get; set; }
/// <summary> /// <summary>
/// Username. /// Username.
......
...@@ -110,7 +110,7 @@ namespace Titanium.Web.Proxy.Network.Certificate ...@@ -110,7 +110,7 @@ namespace Titanium.Web.Proxy.Network.Certificate
// Corresponding private key // Corresponding private key
var privateKeyInfo = PrivateKeyInfoFactory.CreatePrivateKeyInfo(subjectKeyPair.Private); 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) if (seq.Count != 9)
{ {
......
...@@ -89,7 +89,7 @@ namespace Titanium.Web.Proxy.Network.Certificate ...@@ -89,7 +89,7 @@ namespace Titanium.Web.Proxy.Network.Certificate
} }
var x500CertDN = Activator.CreateInstance(typeX500DN); 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); typeX500DN.InvokeMember("Encode", BindingFlags.InvokeMethod, null, x500CertDN, typeValue);
var x500RootCertDN = Activator.CreateInstance(typeX500DN); var x500RootCertDN = Activator.CreateInstance(typeX500DN);
...@@ -110,16 +110,16 @@ namespace Titanium.Web.Proxy.Network.Certificate ...@@ -110,16 +110,16 @@ namespace Titanium.Web.Proxy.Network.Certificate
if (sharedPrivateKey == null) if (sharedPrivateKey == null)
{ {
sharedPrivateKey = Activator.CreateInstance(typeX509PrivateKey); sharedPrivateKey = Activator.CreateInstance(typeX509PrivateKey);
typeValue = new object[] {sProviderName}; typeValue = new object[] { sProviderName };
typeX509PrivateKey.InvokeMember("ProviderName", BindingFlags.PutDispProperty, null, sharedPrivateKey, typeValue); typeX509PrivateKey.InvokeMember("ProviderName", BindingFlags.PutDispProperty, null, sharedPrivateKey, typeValue);
typeValue[0] = 2; typeValue[0] = 2;
typeX509PrivateKey.InvokeMember("ExportPolicy", BindingFlags.PutDispProperty, null, sharedPrivateKey, typeValue); 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); typeX509PrivateKey.InvokeMember("KeySpec", BindingFlags.PutDispProperty, null, sharedPrivateKey, typeValue);
if (!isRoot) if (!isRoot)
{ {
typeValue = new object[] {176}; typeValue = new object[] { 176 };
typeX509PrivateKey.InvokeMember("KeyUsage", BindingFlags.PutDispProperty, null, sharedPrivateKey, typeValue); typeX509PrivateKey.InvokeMember("KeyUsage", BindingFlags.PutDispProperty, null, sharedPrivateKey, typeValue);
} }
...@@ -149,9 +149,9 @@ namespace Titanium.Web.Proxy.Network.Certificate ...@@ -149,9 +149,9 @@ namespace Titanium.Web.Proxy.Network.Certificate
var requestCert = Activator.CreateInstance(typeRequestCert); 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); typeRequestCert.InvokeMember("InitializeFromPrivateKey", BindingFlags.InvokeMethod, null, requestCert, typeValue);
typeValue = new[] {x500CertDN}; typeValue = new[] { x500CertDN };
typeRequestCert.InvokeMember("Subject", BindingFlags.PutDispProperty, null, requestCert, typeValue); typeRequestCert.InvokeMember("Subject", BindingFlags.PutDispProperty, null, requestCert, typeValue);
typeValue[0] = x500RootCertDN; typeValue[0] = x500RootCertDN;
typeRequestCert.InvokeMember("Issuer", BindingFlags.PutDispProperty, null, requestCert, typeValue); typeRequestCert.InvokeMember("Issuer", BindingFlags.PutDispProperty, null, requestCert, typeValue);
...@@ -186,14 +186,14 @@ namespace Titanium.Web.Proxy.Network.Certificate ...@@ -186,14 +186,14 @@ namespace Titanium.Web.Proxy.Network.Certificate
var extNames = Activator.CreateInstance(typeExtNames); var extNames = Activator.CreateInstance(typeExtNames);
var altDnsNames = Activator.CreateInstance(typeCAlternativeName); var altDnsNames = Activator.CreateInstance(typeCAlternativeName);
typeValue = new object[] {3, subject}; typeValue = new object[] { 3, subject };
typeCAlternativeName.InvokeMember("InitializeFromString", BindingFlags.InvokeMethod, null, altDnsNames, typeValue); typeCAlternativeName.InvokeMember("InitializeFromString", BindingFlags.InvokeMethod, null, altDnsNames, typeValue);
typeValue = new[] {altDnsNames}; typeValue = new[] { altDnsNames };
typeAltNamesCollection.InvokeMember("Add", BindingFlags.InvokeMethod, null, altNameCollection, typeValue); typeAltNamesCollection.InvokeMember("Add", BindingFlags.InvokeMethod, null, altNameCollection, typeValue);
typeValue = new[] {altNameCollection}; typeValue = new[] { altNameCollection };
typeExtNames.InvokeMember("InitializeEncode", BindingFlags.InvokeMethod, null, extNames, typeValue); typeExtNames.InvokeMember("InitializeEncode", BindingFlags.InvokeMethod, null, extNames, typeValue);
typeValue[0] = extNames; typeValue[0] = extNames;
...@@ -204,27 +204,27 @@ namespace Titanium.Web.Proxy.Network.Certificate ...@@ -204,27 +204,27 @@ namespace Titanium.Web.Proxy.Network.Certificate
{ {
var signerCertificate = Activator.CreateInstance(typeSignerCertificate); 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); typeSignerCertificate.InvokeMember("Initialize", BindingFlags.InvokeMethod, null, signerCertificate, typeValue);
typeValue = new[] {signerCertificate}; typeValue = new[] { signerCertificate };
typeRequestCert.InvokeMember("SignerCertificate", BindingFlags.PutDispProperty, null, requestCert, typeValue); typeRequestCert.InvokeMember("SignerCertificate", BindingFlags.PutDispProperty, null, requestCert, typeValue);
} }
else else
{ {
var basicConstraints = Activator.CreateInstance(typeBasicConstraints); var basicConstraints = Activator.CreateInstance(typeBasicConstraints);
typeValue = new object[] {"true", "0"}; typeValue = new object[] { "true", "0" };
typeBasicConstraints.InvokeMember("InitializeEncode", BindingFlags.InvokeMethod, null, basicConstraints, typeValue); typeBasicConstraints.InvokeMember("InitializeEncode", BindingFlags.InvokeMethod, null, basicConstraints, typeValue);
typeValue = new[] {basicConstraints}; typeValue = new[] { basicConstraints };
typeX509Extensions.InvokeMember("Add", BindingFlags.InvokeMethod, null, certificate, typeValue); typeX509Extensions.InvokeMember("Add", BindingFlags.InvokeMethod, null, certificate, typeValue);
} }
oid = Activator.CreateInstance(typeOID); 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); typeOID.InvokeMember("InitializeFromAlgorithmName", BindingFlags.InvokeMethod, null, oid, typeValue);
typeValue = new[] {oid}; typeValue = new[] { oid };
typeRequestCert.InvokeMember("HashAlgorithm", BindingFlags.PutDispProperty, null, requestCert, typeValue); typeRequestCert.InvokeMember("HashAlgorithm", BindingFlags.PutDispProperty, null, requestCert, typeValue);
typeRequestCert.InvokeMember("Encode", BindingFlags.InvokeMethod, null, requestCert, null); typeRequestCert.InvokeMember("Encode", BindingFlags.InvokeMethod, null, requestCert, null);
...@@ -243,15 +243,15 @@ namespace Titanium.Web.Proxy.Network.Certificate ...@@ -243,15 +243,15 @@ namespace Titanium.Web.Proxy.Network.Certificate
typeValue[0] = 0; typeValue[0] = 0;
var createCertRequest = typeX509Enrollment.InvokeMember("CreateRequest", BindingFlags.InvokeMethod, null, x509Enrollment, typeValue); var createCertRequest = typeX509Enrollment.InvokeMember("CreateRequest", BindingFlags.InvokeMethod, null, x509Enrollment, typeValue);
typeValue = new[] {2, createCertRequest, 0, string.Empty}; typeValue = new[] { 2, createCertRequest, 0, string.Empty };
typeX509Enrollment.InvokeMember("InstallResponse", BindingFlags.InvokeMethod, null, x509Enrollment, typeValue); typeX509Enrollment.InvokeMember("InstallResponse", BindingFlags.InvokeMethod, null, x509Enrollment, typeValue);
typeValue = new object[] {null, 0, 1}; typeValue = new object[] { null, 0, 1 };
try 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); return new X509Certificate2(Convert.FromBase64String(empty), string.Empty, X509KeyStorageFlags.Exportable);
} }
catch (Exception) catch (Exception)
...@@ -293,8 +293,7 @@ namespace Titanium.Web.Proxy.Network.Certificate ...@@ -293,8 +293,7 @@ namespace Titanium.Web.Proxy.Network.Certificate
var graceTime = DateTime.Now.AddDays(GraceDays); var graceTime = DateTime.Now.AddDays(GraceDays);
var now = DateTime.Now; var now = DateTime.Now;
rCert = !isRoot ? MakeCertificate(false, sSubjectCN, fullSubject, keyLength, HashAlgo, graceTime, now.AddDays(ValidDays), signingCert) : rCert = MakeCertificate(isRoot, sSubjectCN, fullSubject, keyLength, HashAlgo, graceTime, now.AddDays(ValidDays), isRoot ? null : signingCert);
MakeCertificate(true, sSubjectCN, fullSubject, keyLength, HashAlgo, graceTime, now.AddDays(ValidDays), null);
return rCert; return rCert;
} }
} }
......
using System; using System;
using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks; 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.Helpers;
using Titanium.Web.Proxy.Network.Certificate;
namespace Titanium.Web.Proxy.Network namespace Titanium.Web.Proxy.Network
{ {
...@@ -29,7 +31,7 @@ namespace Titanium.Web.Proxy.Network ...@@ -29,7 +31,7 @@ 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>
public class CertificateManager : IDisposable public sealed class CertificateManager : IDisposable
{ {
internal CertificateEngine Engine internal CertificateEngine Engine
{ {
...@@ -51,7 +53,7 @@ namespace Titanium.Web.Proxy.Network ...@@ -51,7 +53,7 @@ namespace Titanium.Web.Proxy.Network
if (certEngine == null) if (certEngine == null)
{ {
certEngine = engine == CertificateEngine.BouncyCastle certEngine = engine == CertificateEngine.BouncyCastle
? (ICertificateMaker) new BCCertificateMaker() ? (ICertificateMaker)new BCCertificateMaker()
: new WinCertificateMaker(); : new WinCertificateMaker();
} }
} }
...@@ -132,12 +134,12 @@ namespace Titanium.Web.Proxy.Network ...@@ -132,12 +134,12 @@ namespace Titanium.Web.Proxy.Network
private string GetRootCertificatePath() private string GetRootCertificatePath()
{ {
var assemblyLocation = System.Reflection.Assembly.GetExecutingAssembly().Location; var assemblyLocation = Assembly.GetExecutingAssembly().Location;
// dynamically loaded assemblies returns string.Empty location // dynamically loaded assemblies returns string.Empty location
if (assemblyLocation == string.Empty) if (assemblyLocation == string.Empty)
{ {
assemblyLocation = System.Reflection.Assembly.GetEntryAssembly().Location; assemblyLocation = Assembly.GetEntryAssembly().Location;
} }
var path = Path.GetDirectoryName(assemblyLocation); var path = Path.GetDirectoryName(assemblyLocation);
...@@ -146,7 +148,7 @@ namespace Titanium.Web.Proxy.Network ...@@ -146,7 +148,7 @@ namespace Titanium.Web.Proxy.Network
return fileName; return fileName;
} }
internal X509Certificate2 LoadRootCertificate() private X509Certificate2 LoadRootCertificate()
{ {
var fileName = GetRootCertificatePath(); var fileName = GetRootCertificatePath();
if (!File.Exists(fileName)) return null; if (!File.Exists(fileName)) return null;
...@@ -217,6 +219,51 @@ namespace Titanium.Web.Proxy.Network ...@@ -217,6 +219,51 @@ namespace Titanium.Web.Proxy.Network
TrustRootCertificate(StoreLocation.LocalMachine); TrustRootCertificate(StoreLocation.LocalMachine);
} }
/// <summary>
/// Puts the certificate to the local machine's certificate store.
/// Needs elevated permission. Works only on Windows.
/// </summary>
/// <returns></returns>
public bool TrustRootCertificateAsAdministrator()
{
if (RunTime.IsRunningOnMono())
{
return false;
}
var fileName = Path.GetTempFileName();
File.WriteAllBytes(fileName, RootCertificate.Export(X509ContentType.Pkcs12));
var info = new ProcessStartInfo
{
FileName = "certutil.exe",
Arguments = "-importPFX -p \"\" -f \"" + fileName + "\"",
CreateNoWindow = true,
UseShellExecute = true,
Verb = "runas",
ErrorDialog = false,
};
try
{
var process = Process.Start(info);
if (process == null)
{
return false;
}
process.WaitForExit();
File.Delete(fileName);
}
catch
{
return false;
}
return true;
}
/// <summary> /// <summary>
/// Removes the trusted certificates. /// Removes the trusted certificates.
/// </summary> /// </summary>
...@@ -229,13 +276,49 @@ namespace Titanium.Web.Proxy.Network ...@@ -229,13 +276,49 @@ namespace Titanium.Web.Proxy.Network
RemoveTrustedRootCertificates(StoreLocation.LocalMachine); RemoveTrustedRootCertificates(StoreLocation.LocalMachine);
} }
/// <summary>
/// Determines whether the root certificate is trusted.
/// </summary>
public bool IsRootCertificateTrusted()
{
return FindRootCertificate(StoreLocation.CurrentUser) || IsRootCertificateMachineTrusted();
}
/// <summary>
/// Determines whether the root certificate is machine trusted.
/// </summary>
public bool IsRootCertificateMachineTrusted()
{
return FindRootCertificate(StoreLocation.LocalMachine);
}
private bool FindRootCertificate(StoreLocation storeLocation)
{
string value = $"{RootCertificate.Issuer}";
return FindCertificates(StoreName.Root, storeLocation, value).Count > 0;
}
private X509Certificate2Collection FindCertificates(StoreName storeName, StoreLocation storeLocation, string findValue)
{
X509Store x509Store = new X509Store(storeName, storeLocation);
try
{
x509Store.Open(OpenFlags.OpenExistingOnly);
return x509Store.Certificates.Find(X509FindType.FindBySubjectDistinguishedName, findValue, false);
}
finally
{
x509Store.Close();
}
}
/// <summary> /// <summary>
/// Create an SSL certificate /// Create an SSL certificate
/// </summary> /// </summary>
/// <param name="certificateName"></param> /// <param name="certificateName"></param>
/// <param name="isRootCertificate"></param> /// <param name="isRootCertificate"></param>
/// <returns></returns> /// <returns></returns>
internal virtual X509Certificate2 CreateCertificate(string certificateName, bool isRootCertificate) internal X509Certificate2 CreateCertificate(string certificateName, bool isRootCertificate)
{ {
if (certificateCache.ContainsKey(certificateName)) if (certificateCache.ContainsKey(certificateName))
{ {
...@@ -264,7 +347,7 @@ namespace Titanium.Web.Proxy.Network ...@@ -264,7 +347,7 @@ namespace Titanium.Web.Proxy.Network
} }
if (certificate != null && !certificateCache.ContainsKey(certificateName)) if (certificate != null && !certificateCache.ContainsKey(certificateName))
{ {
certificateCache.Add(certificateName, new CachedCertificate {Certificate = certificate}); certificateCache.Add(certificateName, new CachedCertificate { Certificate = certificate });
} }
} }
else else
...@@ -316,7 +399,7 @@ namespace Titanium.Web.Proxy.Network ...@@ -316,7 +399,7 @@ namespace Titanium.Web.Proxy.Network
/// </summary> /// </summary>
/// <param name="storeLocation"></param> /// <param name="storeLocation"></param>
/// <returns></returns> /// <returns></returns>
internal void TrustRootCertificate(StoreLocation storeLocation) private void TrustRootCertificate(StoreLocation storeLocation)
{ {
if (RootCertificate == null) if (RootCertificate == null)
{ {
...@@ -327,7 +410,7 @@ namespace Titanium.Web.Proxy.Network ...@@ -327,7 +410,7 @@ namespace Titanium.Web.Proxy.Network
return; return;
} }
X509Store x509RootStore = new X509Store(StoreName.Root, storeLocation); var x509RootStore = new X509Store(StoreName.Root, storeLocation);
var x509PersonalStore = new X509Store(StoreName.My, storeLocation); var x509PersonalStore = new X509Store(StoreName.My, storeLocation);
try try
...@@ -356,7 +439,7 @@ namespace Titanium.Web.Proxy.Network ...@@ -356,7 +439,7 @@ namespace Titanium.Web.Proxy.Network
/// </summary> /// </summary>
/// <param name="storeLocation"></param> /// <param name="storeLocation"></param>
/// <returns></returns> /// <returns></returns>
internal void RemoveTrustedRootCertificates(StoreLocation storeLocation) private void RemoveTrustedRootCertificates(StoreLocation storeLocation)
{ {
if (RootCertificate == null) if (RootCertificate == null)
{ {
...@@ -367,7 +450,7 @@ namespace Titanium.Web.Proxy.Network ...@@ -367,7 +450,7 @@ namespace Titanium.Web.Proxy.Network
return; return;
} }
X509Store x509RootStore = new X509Store(StoreName.Root, storeLocation); var x509RootStore = new X509Store(StoreName.Root, storeLocation);
var x509PersonalStore = new X509Store(StoreName.My, storeLocation); var x509PersonalStore = new X509Store(StoreName.My, storeLocation);
try try
...@@ -381,7 +464,7 @@ namespace Titanium.Web.Proxy.Network ...@@ -381,7 +464,7 @@ namespace Titanium.Web.Proxy.Network
catch (Exception e) catch (Exception e)
{ {
exceptionFunc( exceptionFunc(
new Exception("Failed to make system trust root certificate " new Exception("Failed to remove root certificate trust "
+ $" for {storeLocation} store location. You may need admin rights.", e)); + $" for {storeLocation} store location. You may need admin rights.", e));
} }
finally finally
...@@ -391,6 +474,9 @@ namespace Titanium.Web.Proxy.Network ...@@ -391,6 +474,9 @@ namespace Titanium.Web.Proxy.Network
} }
} }
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose() public void Dispose()
{ {
} }
......
...@@ -58,7 +58,21 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -58,7 +58,21 @@ namespace Titanium.Web.Proxy.Network.Tcp
StreamReader?.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
{
}
} }
} }
} }
using System; using System;
using System.Net.Sockets; using System.IO;
using System.Text; using System.Linq;
using System.Threading.Tasks; using System.Net.Security;
using System.IO; using System.Net.Sockets;
using System.Net.Security; using System.Text;
using Titanium.Web.Proxy.Helpers; using System.Threading;
using Titanium.Web.Proxy.Models; using System.Threading.Tasks;
using System.Linq; using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Extensions; using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Shared; using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Network.Tcp
{ namespace Titanium.Web.Proxy.Network.Tcp
/// <summary> {
/// A class that manages Tcp Connection to server used by this proxy server /// <summary>
/// </summary> /// A class that manages Tcp Connection to server used by this proxy server
internal class TcpConnectionFactory /// </summary>
{ internal class TcpConnectionFactory
{
/// <summary> /// <summary>
/// Creates a TCP connection to server /// Creates a TCP connection to server
/// </summary> /// </summary>
/// <param name="server"></param> /// <param name="server"></param>
/// <param name="remoteHostName"></param> /// <param name="remoteHostName"></param>
/// <param name="remotePort"></param> /// <param name="remotePort"></param>
/// <param name="httpVersion"></param> /// <param name="httpVersion"></param>
/// <param name="isHttps"></param> /// <param name="isHttps"></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>
/// <returns></returns> /// <returns></returns>
internal async Task<TcpConnection> CreateClient(ProxyServer server, internal async Task<TcpConnection> CreateClient(ProxyServer server,
string remoteHostName, int remotePort, Version httpVersion, string remoteHostName, int remotePort, Version httpVersion,
bool isHttps, bool isHttps,
ExternalProxy externalHttpProxy, ExternalProxy externalHttpsProxy, ExternalProxy externalHttpProxy, ExternalProxy externalHttpsProxy,
Stream clientStream) Stream clientStream)
{ {
TcpClient client; TcpClient client;
CustomBufferedStream stream; CustomBufferedStream stream;
bool isLocalhost = (externalHttpsProxy == null && externalHttpProxy == null) ? false : NetworkHelper.IsLocalIpAddress(remoteHostName);
bool useHttpProxy = false;
bool useHttpsProxy = externalHttpsProxy != null && externalHttpsProxy.HostName != remoteHostName && (externalHttpsProxy.BypassForLocalhost && !isLocalhost);
bool useHttpProxy = externalHttpProxy != null && externalHttpProxy.HostName != remoteHostName && (externalHttpProxy.BypassForLocalhost && !isLocalhost); //check if external proxy is set for HTTP
if (!isHttps && externalHttpProxy != null
if (isHttps) && externalHttpProxy.HostName != remoteHostName)
{ {
SslStream sslStream = null; useHttpProxy = true;
//If this proxy uses another external proxy then create a tunnel request for HTTPS connections //check if we need to ByPass
if (useHttpsProxy) if (externalHttpProxy.BypassLocalhost
{ && NetworkHelper.IsLocalIpAddress(remoteHostName))
client = new TcpClient(server.UpStreamEndPoint); {
await client.ConnectAsync(externalHttpsProxy.HostName, externalHttpsProxy.Port); useHttpProxy = false;
stream = new CustomBufferedStream(client.GetStream(), server.BufferSize); }
}
using (var writer = new StreamWriter(stream, Encoding.ASCII, server.BufferSize, true) {NewLine = ProxyConstants.NewLine})
{ bool useHttpsProxy = false;
await writer.WriteLineAsync($"CONNECT {remoteHostName}:{remotePort} HTTP/{httpVersion}"); //check if external proxy is set for HTTPS
await writer.WriteLineAsync($"Host: {remoteHostName}:{remotePort}"); if (isHttps && externalHttpsProxy != null
await writer.WriteLineAsync("Connection: Keep-Alive"); && externalHttpsProxy.HostName != remoteHostName)
{
if (!string.IsNullOrEmpty(externalHttpsProxy.UserName) && externalHttpsProxy.Password != null) useHttpsProxy = true;
{
await writer.WriteLineAsync("Proxy-Connection: keep-alive"); //check if we need to ByPass
await writer.WriteLineAsync("Proxy-Authorization" + ": Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes(externalHttpsProxy.UserName + ":" + externalHttpsProxy.Password))); if (externalHttpsProxy.BypassLocalhost
} && NetworkHelper.IsLocalIpAddress(remoteHostName))
await writer.WriteLineAsync(); {
await writer.FlushAsync(); useHttpsProxy = false;
writer.Close(); }
} }
using (var reader = new CustomBinaryReader(stream, server.BufferSize)) if (isHttps)
{ {
var result = await reader.ReadLineAsync(); SslStream sslStream = null;
if (!new[] {"200 OK", "connection established"}.Any(s => result.ContainsIgnoreCase(s))) //If this proxy uses another external proxy then create a tunnel request for HTTPS connections
{ if (useHttpsProxy)
throw new Exception("Upstream proxy failed to create a secure tunnel"); {
} client = new TcpClient(server.UpStreamEndPoint);
await client.ConnectAsync(externalHttpsProxy.HostName, externalHttpsProxy.Port);
await reader.ReadAndIgnoreAllLinesAsync(); stream = new CustomBufferedStream(client.GetStream(), server.BufferSize);
}
} using (var writer = new StreamWriter(stream, Encoding.ASCII, server.BufferSize, true) { NewLine = ProxyConstants.NewLine })
else {
{ await writer.WriteLineAsync($"CONNECT {remoteHostName}:{remotePort} HTTP/{httpVersion}");
client = new TcpClient(server.UpStreamEndPoint); await writer.WriteLineAsync($"Host: {remoteHostName}:{remotePort}");
await client.ConnectAsync(remoteHostName, remotePort); await writer.WriteLineAsync("Connection: Keep-Alive");
stream = new CustomBufferedStream(client.GetStream(), server.BufferSize);
} if (!string.IsNullOrEmpty(externalHttpsProxy.UserName) && externalHttpsProxy.Password != null)
{
try await writer.WriteLineAsync("Proxy-Connection: keep-alive");
{ await writer.WriteLineAsync("Proxy-Authorization" + ": Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes(externalHttpsProxy.UserName + ":" + externalHttpsProxy.Password)));
sslStream = new SslStream(stream, true, server.ValidateServerCertificate, }
server.SelectClientCertificate); await writer.WriteLineAsync();
await writer.FlushAsync();
await sslStream.AuthenticateAsClientAsync(remoteHostName, null, server.SupportedSslProtocols, false); writer.Close();
}
stream = new CustomBufferedStream(sslStream, server.BufferSize);
} using (var reader = new CustomBinaryReader(stream, server.BufferSize))
catch {
{ var result = await reader.ReadLineAsync();
sslStream?.Dispose();
if (!new[] { "200 OK", "connection established" }.Any(s => result.ContainsIgnoreCase(s)))
throw; {
} throw new Exception("Upstream proxy failed to create a secure tunnel");
} }
else
{ await reader.ReadAndIgnoreAllLinesAsync();
if (useHttpProxy) }
{ }
client = new TcpClient(server.UpStreamEndPoint); else
await client.ConnectAsync(externalHttpProxy.HostName, externalHttpProxy.Port); {
stream = new CustomBufferedStream(client.GetStream(), server.BufferSize); client = new TcpClient(server.UpStreamEndPoint);
} await client.ConnectAsync(remoteHostName, remotePort);
else stream = new CustomBufferedStream(client.GetStream(), server.BufferSize);
{ }
client = new TcpClient(server.UpStreamEndPoint);
await client.ConnectAsync(remoteHostName, remotePort); try
stream = new CustomBufferedStream(client.GetStream(), server.BufferSize); {
} sslStream = new SslStream(stream, true, server.ValidateServerCertificate,
} server.SelectClientCertificate);
client.ReceiveTimeout = server.ConnectionTimeOutSeconds * 1000; await sslStream.AuthenticateAsClientAsync(remoteHostName, null, server.SupportedSslProtocols, server.CheckCertificateRevocation);
client.SendTimeout = server.ConnectionTimeOutSeconds * 1000;
stream = new CustomBufferedStream(sslStream, server.BufferSize);
client.LingerState = new LingerOption(true, 0); }
catch
server.ServerConnectionCount++; {
sslStream?.Close();
return new TcpConnection sslStream?.Dispose();
{
UpStreamHttpProxy = externalHttpProxy, throw;
UpStreamHttpsProxy = externalHttpsProxy, }
HostName = remoteHostName, }
Port = remotePort, else
IsHttps = isHttps, {
TcpClient = client, if (useHttpProxy)
StreamReader = new CustomBinaryReader(stream, server.BufferSize), {
Stream = stream, client = new TcpClient(server.UpStreamEndPoint);
Version = httpVersion await client.ConnectAsync(externalHttpProxy.HostName, externalHttpProxy.Port);
}; stream = new CustomBufferedStream(client.GetStream(), server.BufferSize);
} }
} else
} {
client = new TcpClient(server.UpStreamEndPoint);
await client.ConnectAsync(remoteHostName, remotePort);
stream = new CustomBufferedStream(client.GetStream(), server.BufferSize);
}
}
client.ReceiveTimeout = server.ConnectionTimeOutSeconds * 1000;
client.SendTimeout = server.ConnectionTimeOutSeconds * 1000;
Interlocked.Increment(ref server.serverConnectionCount);
return new TcpConnection
{
UpStreamHttpProxy = externalHttpProxy,
UpStreamHttpsProxy = externalHttpsProxy,
HostName = remoteHostName,
Port = remotePort,
IsHttps = isHttps,
TcpClient = client,
StreamReader = new CustomBinaryReader(stream, server.BufferSize),
Stream = stream,
Version = httpVersion
};
}
}
}
...@@ -70,8 +70,8 @@ namespace Titanium.Web.Proxy ...@@ -70,8 +70,8 @@ namespace Titanium.Web.Proxy
{ {
ResponseHeaders = new Dictionary<string, HttpHeader> ResponseHeaders = new Dictionary<string, HttpHeader>
{ {
{"Proxy-Authenticate", new HttpHeader("Proxy-Authenticate", "Basic realm=\"TitaniumProxy\"")}, { "Proxy-Authenticate", new HttpHeader("Proxy-Authenticate", "Basic realm=\"TitaniumProxy\"") },
{"Proxy-Connection", new HttpHeader("Proxy-Connection", "close")} { "Proxy-Connection", new HttpHeader("Proxy-Connection", "close") }
} }
}; };
await WriteResponseHeaders(clientStreamWriter, response); await WriteResponseHeaders(clientStreamWriter, response);
......
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Net; using System.Net;
using System.Net.Sockets; using System.Net.Sockets;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Titanium.Web.Proxy.EventArguments; using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Helpers; using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Models; using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Network; using Titanium.Web.Proxy.Network;
using System.Linq;
using System.Security.Authentication;
using Titanium.Web.Proxy.Network.Tcp; using Titanium.Web.Proxy.Network.Tcp;
using System.Security.Cryptography.X509Certificates;
namespace Titanium.Web.Proxy namespace Titanium.Web.Proxy
{ {
...@@ -34,8 +35,21 @@ namespace Titanium.Web.Proxy ...@@ -34,8 +35,21 @@ namespace Titanium.Web.Proxy
/// </summary> /// </summary>
private Action<Exception> exceptionFunc; private Action<Exception> exceptionFunc;
/// <summary>
/// Backing field for corresponding public property
/// </summary>
private bool trustRootCertificate; private bool trustRootCertificate;
/// <summary>
/// Backing field for corresponding public property
/// </summary>
private int clientConnectionCount;
/// <summary>
/// Backing field for corresponding public property
/// </summary>
internal int serverConnectionCount;
/// <summary> /// <summary>
/// A object that creates tcp connection to server /// A object that creates tcp connection to server
/// </summary> /// </summary>
...@@ -50,8 +64,7 @@ namespace Titanium.Web.Proxy ...@@ -50,8 +64,7 @@ namespace Titanium.Web.Proxy
/// <summary> /// <summary>
/// Set firefox to use default system proxy /// Set firefox to use default system proxy
/// </summary> /// </summary>
private FireFoxProxySettingsManager firefoxProxySettingsManager private FireFoxProxySettingsManager firefoxProxySettingsManager = new FireFoxProxySettingsManager();
= new FireFoxProxySettingsManager();
#endif #endif
/// <summary> /// <summary>
...@@ -125,6 +138,12 @@ namespace Titanium.Web.Proxy ...@@ -125,6 +138,12 @@ namespace Titanium.Web.Proxy
set { CertificateManager.Engine = value; } set { CertificateManager.Engine = value; }
} }
/// <summary>
/// Should we check for certificare revocation during SSL authentication to servers
/// Note: If enabled can reduce performance (Default disabled)
/// </summary>
public bool CheckCertificateRevocation { get; set; }
/// <summary> /// <summary>
/// Does this proxy uses the HTTP protocol 100 continue behaviour strictly? /// Does this proxy uses the HTTP protocol 100 continue behaviour strictly?
/// Broken 100 contunue implementations on server/client may cause problems if enabled /// Broken 100 contunue implementations on server/client may cause problems if enabled
...@@ -224,18 +243,18 @@ namespace Titanium.Web.Proxy ...@@ -224,18 +243,18 @@ namespace Titanium.Web.Proxy
/// List of supported Ssl versions /// List of supported Ssl versions
/// </summary> /// </summary>
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> /// <summary>
/// Total number of active client connections /// Total number of active client connections
/// </summary> /// </summary>
public int ClientConnectionCount { get; private set; } public int ClientConnectionCount => clientConnectionCount;
/// <summary> /// <summary>
/// Total number of active server connections /// Total number of active server connections
/// </summary> /// </summary>
public int ServerConnectionCount { get; internal set; } public int ServerConnectionCount => serverConnectionCount;
/// <summary> /// <summary>
/// Constructor /// Constructor
...@@ -381,8 +400,7 @@ namespace Titanium.Web.Proxy ...@@ -381,8 +400,7 @@ namespace Titanium.Web.Proxy
#if !DEBUG #if !DEBUG
firefoxProxySettingsManager.AddFirefox(); firefoxProxySettingsManager.AddFirefox();
#endif #endif
Console.WriteLine("Set endpoint at Ip {0} and port: {1} as System HTTPS Proxy", Console.WriteLine("Set endpoint at Ip {0} and port: {1} as System HTTPS Proxy", endPoint.IpAddress, endPoint.Port);
endPoint.IpAddress, endPoint.Port);
} }
/// <summary> /// <summary>
...@@ -596,13 +614,10 @@ namespace Titanium.Web.Proxy ...@@ -596,13 +614,10 @@ namespace Titanium.Web.Proxy
{ {
Task.Run(async () => Task.Run(async () =>
{ {
ClientConnectionCount++; Interlocked.Increment(ref clientConnectionCount);
//This line is important! tcpClient.ReceiveTimeout = ConnectionTimeOutSeconds * 1000;
//contributors please don't remove it without discussion tcpClient.SendTimeout = ConnectionTimeOutSeconds * 1000;
//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
{ {
...@@ -617,8 +632,23 @@ namespace Titanium.Web.Proxy ...@@ -617,8 +632,23 @@ namespace Titanium.Web.Proxy
} }
finally finally
{ {
ClientConnectionCount--; Interlocked.Decrement(ref clientConnectionCount);
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
{
}
} }
}); });
} }
...@@ -637,46 +667,5 @@ namespace Titanium.Web.Proxy ...@@ -637,46 +667,5 @@ namespace Titanium.Web.Proxy
endPoint.Listener.Server.Close(); endPoint.Listener.Server.Close();
endPoint.Listener.Server.Dispose(); endPoint.Listener.Server.Dispose();
} }
/// <summary>
/// Invocator for BeforeRequest event.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
protected virtual void OnBeforeRequest(object sender, SessionEventArgs e)
{
BeforeRequest?.Invoke(sender, e);
}
/// <summary>
/// Invocator for BeforeResponse event.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
/// <returns></returns>
protected virtual void OnBeforeResponse(object sender, SessionEventArgs e)
{
BeforeResponse?.Invoke(sender, e);
}
/// <summary>
/// Invocator for ServerCertificateValidationCallback event.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
protected virtual void OnServerCertificateValidationCallback(object sender, CertificateValidationEventArgs e)
{
ServerCertificateValidationCallback?.Invoke(sender, e);
}
/// <summary>
/// Invocator for ClientCertifcateSelectionCallback event.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
protected virtual void OnClientCertificateSelectionCallback(object sender, CertificateSelectionEventArgs e)
{
ClientCertificateSelectionCallback?.Invoke(sender, e);
}
} }
} }
...@@ -6,15 +6,15 @@ using System.Net; ...@@ -6,15 +6,15 @@ using System.Net;
using System.Net.Security; using System.Net.Security;
using System.Net.Sockets; using System.Net.Sockets;
using System.Security.Authentication; using System.Security.Authentication;
using Titanium.Web.Proxy.Exceptions; using System.Threading.Tasks;
using Titanium.Web.Proxy.EventArguments; using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Exceptions;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Helpers; using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Shared;
using Titanium.Web.Proxy.Http; using Titanium.Web.Proxy.Http;
using System.Threading.Tasks; using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Network.Tcp; using Titanium.Web.Proxy.Network.Tcp;
using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy namespace Titanium.Web.Proxy
{ {
...@@ -23,14 +23,18 @@ namespace Titanium.Web.Proxy ...@@ -23,14 +23,18 @@ namespace Titanium.Web.Proxy
/// </summary> /// </summary>
partial class ProxyServer partial class ProxyServer
{ {
//This is called when client is aware of proxy /// <summary>
//So for HTTPS requests client would send CONNECT header to negotiate a secure tcp tunnel via proxy /// This is called when client is aware of proxy
/// So for HTTPS requests client would send CONNECT header to negotiate a secure tcp tunnel via proxy
/// </summary>
/// <param name="endPoint"></param>
/// <param name="tcpClient"></param>
/// <returns></returns>
private async Task HandleClient(ExplicitProxyEndPoint endPoint, TcpClient tcpClient) private async Task HandleClient(ExplicitProxyEndPoint endPoint, TcpClient tcpClient)
{ {
CustomBufferedStream clientStream = new CustomBufferedStream(tcpClient.GetStream(), BufferSize); var disposed = false;
clientStream.ReadTimeout = ConnectionTimeOutSeconds * 1000; var clientStream = new CustomBufferedStream(tcpClient.GetStream(), BufferSize);
clientStream.WriteTimeout = ConnectionTimeOutSeconds * 1000;
var clientStreamReader = new CustomBinaryReader(clientStream, BufferSize); var clientStreamReader = new CustomBinaryReader(clientStream, BufferSize);
var clientStreamWriter = new StreamWriter(clientStream) { NewLine = ProxyConstants.NewLine }; var clientStreamWriter = new StreamWriter(clientStream) { NewLine = ProxyConstants.NewLine };
...@@ -44,7 +48,6 @@ namespace Titanium.Web.Proxy ...@@ -44,7 +48,6 @@ namespace Titanium.Web.Proxy
if (string.IsNullOrEmpty(httpCmd)) if (string.IsNullOrEmpty(httpCmd))
{ {
Dispose(clientStream, clientStreamReader, clientStreamWriter, null);
return; return;
} }
...@@ -99,7 +102,6 @@ namespace Titanium.Web.Proxy ...@@ -99,7 +102,6 @@ namespace Titanium.Web.Proxy
if (await CheckAuthorization(clientStreamWriter, connectRequestHeaders) == false) if (await CheckAuthorization(clientStreamWriter, connectRequestHeaders) == false)
{ {
Dispose(clientStream, clientStreamReader, clientStreamWriter, null);
return; return;
} }
...@@ -111,22 +113,23 @@ namespace Titanium.Web.Proxy ...@@ -111,22 +113,23 @@ namespace Titanium.Web.Proxy
{ {
sslStream = new SslStream(clientStream, true); 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 //Successfully managed to authenticate the client using the fake certificate
await sslStream.AuthenticateAsServerAsync(certificate, false, await sslStream.AuthenticateAsServerAsync(certificate, false,
SupportedSslProtocols, false); SupportedSslProtocols, false);
//HTTPS server created - we can now decrypt the client's traffic //HTTPS server created - we can now decrypt the client's traffic
clientStream = new CustomBufferedStream(sslStream, BufferSize); clientStream = new CustomBufferedStream(sslStream, BufferSize);
clientStreamReader.Dispose();
clientStreamReader = new CustomBinaryReader(clientStream, BufferSize); clientStreamReader = new CustomBinaryReader(clientStream, BufferSize);
clientStreamWriter = new StreamWriter(clientStream) { NewLine = ProxyConstants.NewLine }; clientStreamWriter = new StreamWriter(clientStream) { NewLine = ProxyConstants.NewLine };
} }
catch catch
{ {
sslStream?.Dispose(); sslStream?.Dispose();
Dispose(clientStream, clientStreamReader, clientStreamWriter, null);
return; return;
} }
...@@ -138,6 +141,7 @@ namespace Titanium.Web.Proxy ...@@ -138,6 +141,7 @@ namespace Titanium.Web.Proxy
{ {
//Siphon out CONNECT request headers //Siphon out CONNECT request headers
await clientStreamReader.ReadAndIgnoreAllLinesAsync(); await clientStreamReader.ReadAndIgnoreAllLinesAsync();
//write back successfull CONNECT response //write back successfull CONNECT response
await WriteConnectResponse(clientStreamWriter, version); await WriteConnectResponse(clientStreamWriter, version);
...@@ -147,243 +151,82 @@ namespace Titanium.Web.Proxy ...@@ -147,243 +151,82 @@ namespace Titanium.Web.Proxy
false, false,
clientStream, tcpConnectionFactory); clientStream, tcpConnectionFactory);
Dispose(clientStream, clientStreamReader, clientStreamWriter, null);
return; return;
} }
//Now create the request //Now create the request
await HandleHttpSessionRequest(tcpClient, httpCmd, clientStream, clientStreamReader, clientStreamWriter, disposed = await HandleHttpSessionRequest(tcpClient, httpCmd, clientStream, clientStreamReader, clientStreamWriter,
httpRemoteUri.Scheme == Uri.UriSchemeHttps ? httpRemoteUri.Host : null, endPoint, connectRequestHeaders); httpRemoteUri.Scheme == Uri.UriSchemeHttps ? httpRemoteUri.Host : null, endPoint,
connectRequestHeaders);
}
catch (Exception e)
{
ExceptionFunc(new Exception("Error whilst authorizing request", e));
} }
catch (Exception) finally
{ {
Dispose(clientStream, if (!disposed)
clientStreamReader, {
clientStreamWriter, null); Dispose(clientStream, clientStreamReader, clientStreamWriter, null);
}
} }
} }
//This is called when this proxy acts as a reverse proxy (like a real http server) /// <summary>
//So for HTTPS requests we would start SSL negotiation right away without expecting a CONNECT request from client /// This is called when this proxy acts as a reverse proxy (like a real http server)
/// So for HTTPS requests we would start SSL negotiation right away without expecting a CONNECT request from client
/// </summary>
/// <param name="endPoint"></param>
/// <param name="tcpClient"></param>
/// <returns></returns>
private async Task HandleClient(TransparentProxyEndPoint endPoint, TcpClient tcpClient) 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;
CustomBinaryReader clientStreamReader = null; CustomBinaryReader clientStreamReader = null;
StreamWriter clientStreamWriter = 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 //implement in future once SNI supported by SSL stream, for now use the same certificate
var certificate = CertificateManager.CreateCertificate(endPoint.GenericCertificateName, false); var certificate = CertificateManager.CreateCertificate(endPoint.GenericCertificateName, false);
try
{
//Successfully managed to authenticate the client using the fake certificate //Successfully managed to authenticate the client using the fake certificate
await sslStream.AuthenticateAsServerAsync(certificate, false, await sslStream.AuthenticateAsServerAsync(certificate, false,
SslProtocols.Tls, 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 //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); clientStreamReader = new CustomBinaryReader(clientStream, BufferSize);
clientStreamWriter = new StreamWriter(clientStream) { NewLine = ProxyConstants.NewLine }; clientStreamWriter = new StreamWriter(clientStream) { NewLine = ProxyConstants.NewLine };
}
//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);
}
/// <summary> //now read the request line
/// Create a Server Connection var httpCmd = await clientStreamReader.ReadLineAsync();
/// </summary>
/// <param name="args"></param>
/// <returns></returns>
private async Task<TcpConnection> GetServerConnection(
SessionEventArgs args)
{
ExternalProxy customUpStreamHttpProxy = null;
ExternalProxy customUpStreamHttpsProxy = null;
if (args.WebSession.Request.RequestUri.Scheme == "http") //Now create the request
{ disposed = await HandleHttpSessionRequest(tcpClient, httpCmd, clientStream, clientStreamReader, clientStreamWriter,
if (GetCustomUpStreamHttpProxyFunc != null) endPoint.EnableSsl ? endPoint.GenericCertificateName : null, endPoint, null);
{
customUpStreamHttpProxy = await GetCustomUpStreamHttpProxyFunc(args);
}
}
else
{
if (GetCustomUpStreamHttpsProxyFunc != null)
{
customUpStreamHttpsProxy = await GetCustomUpStreamHttpsProxyFunc(args);
}
} }
finally
args.CustomUpStreamHttpProxyUsed = customUpStreamHttpProxy;
args.CustomUpStreamHttpsProxyUsed = customUpStreamHttpsProxy;
return await tcpConnectionFactory.CreateClient(this,
args.WebSession.Request.RequestUri.Host,
args.WebSession.Request.RequestUri.Port,
args.WebSession.Request.HttpVersion,
args.IsHttps,
customUpStreamHttpProxy ?? UpStreamHttpProxy,
customUpStreamHttpsProxy ?? UpStreamHttpsProxy,
args.ProxyClient.ClientStream);
}
private async Task<bool> HandleHttpSessionRequestInternal(TcpConnection connection,
SessionEventArgs args, bool closeConnection)
{
try
{ {
args.WebSession.Request.RequestLocked = true; if (!disposed)
//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;
}
//if expect continue is enabled then send the headers first
//and see if server would return 100 conitinue
if (args.WebSession.Request.ExpectContinue)
{
args.WebSession.SetConnection(connection);
await args.WebSession.SendRequest(Enable100ContinueBehaviour);
}
//If 100 continue was the response inform that to the client
if (Enable100ContinueBehaviour)
{
if (args.WebSession.Request.Is100Continue)
{
await WriteResponseStatus(args.WebSession.Response.HttpVersion, "100",
"Continue", args.ProxyClient.ClientStreamWriter);
await args.ProxyClient.ClientStreamWriter.WriteLineAsync();
}
else if (args.WebSession.Request.ExpectationFailed)
{
await WriteResponseStatus(args.WebSession.Response.HttpVersion, "417",
"Expectation Failed", args.ProxyClient.ClientStreamWriter);
await args.ProxyClient.ClientStreamWriter.WriteLineAsync();
}
}
//If expect continue is not enabled then set the connectio and send request headers
if (!args.WebSession.Request.ExpectContinue)
{ {
args.WebSession.SetConnection(connection); Dispose(clientStream, clientStreamReader, clientStreamWriter, null);
await args.WebSession.SendRequest(Enable100ContinueBehaviour);
}
//If request was modified by user
if (args.WebSession.Request.RequestBodyRead)
{
if (args.WebSession.Request.ContentEncoding != null)
{
args.WebSession.Request.RequestBody = await GetCompressedResponseBody(args.WebSession.Request.ContentEncoding, args.WebSession.Request.RequestBody);
}
//chunked send is not supported as of now
args.WebSession.Request.ContentLength = args.WebSession.Request.RequestBody.Length;
var newStream = args.WebSession.ServerConnection.Stream;
await newStream.WriteAsync(args.WebSession.Request.RequestBody, 0, args.WebSession.Request.RequestBody.Length);
}
else
{
if (!args.WebSession.Request.ExpectationFailed)
{
//If its a post/put/patch request, then read the client html body and send it to server
var method = args.WebSession.Request.Method.ToUpper();
if (method == "POST" || method == "PUT" || method == "PATCH")
{
await SendClientRequestBody(args);
}
}
}
//If not expectation failed response was returned by server then parse response
if (!args.WebSession.Request.ExpectationFailed)
{
var result = await HandleHttpSessionResponse(args);
//already disposed inside above method
if (result == false)
{
return false;
}
}
//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 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;
}
if (closeConnection)
{
//dispose
Dispose(args.ProxyClient.ClientStream,
args.ProxyClient.ClientStreamReader,
args.ProxyClient.ClientStreamWriter,
args.WebSession.ServerConnection);
return false;
}
return true;
} }
/// <summary> /// <summary>
/// This is the core request handler method for a particular connection from client /// This is the core request handler method for a particular connection from client
/// Will create new session (request/response) sequence until
/// client/server abruptly terminates connection or by normal HTTP termination
/// </summary> /// </summary>
/// <param name="client"></param> /// <param name="client"></param>
/// <param name="httpCmd"></param> /// <param name="httpCmd"></param>
...@@ -393,13 +236,13 @@ namespace Titanium.Web.Proxy ...@@ -393,13 +236,13 @@ namespace Titanium.Web.Proxy
/// <param name="httpsHostName"></param> /// <param name="httpsHostName"></param>
/// <param name="endPoint"></param> /// <param name="endPoint"></param>
/// <param name="connectHeaders"></param> /// <param name="connectHeaders"></param>
/// <param name="customUpStreamHttpProxy"></param>
/// <param name="customUpStreamHttpsProxy"></param>
/// <returns></returns> /// <returns></returns>
private async Task HandleHttpSessionRequest(TcpClient client, string httpCmd, Stream clientStream, private async Task<bool> HandleHttpSessionRequest(TcpClient client, string httpCmd, Stream clientStream,
CustomBinaryReader clientStreamReader, StreamWriter clientStreamWriter, string httpsHostName, CustomBinaryReader clientStreamReader, StreamWriter clientStreamWriter, string httpsHostName,
ProxyEndPoint endPoint, List<HttpHeader> connectHeaders) ProxyEndPoint endPoint, List<HttpHeader> connectHeaders)
{ {
bool disposed = false;
TcpConnection connection = null; TcpConnection connection = null;
//Loop through each subsequest request on this particular client connection //Loop through each subsequest request on this particular client connection
...@@ -408,19 +251,14 @@ namespace Titanium.Web.Proxy ...@@ -408,19 +251,14 @@ namespace Titanium.Web.Proxy
{ {
if (string.IsNullOrEmpty(httpCmd)) if (string.IsNullOrEmpty(httpCmd))
{ {
Dispose(clientStream,
clientStreamReader,
clientStreamWriter,
connection);
break; break;
} }
var args = new SessionEventArgs(BufferSize, HandleHttpSessionResponse) var args = new SessionEventArgs(BufferSize, HandleHttpSessionResponse)
{ {
ProxyClient = { TcpClient = client }, ProxyClient = { TcpClient = client },
WebSession = { ConnectHeaders = connectHeaders } WebSession = { ConnectHeaders = connectHeaders }
}; };
args.WebSession.ProcessId = new Lazy<int>(() => args.WebSession.ProcessId = new Lazy<int>(() =>
{ {
...@@ -458,7 +296,8 @@ namespace Titanium.Web.Proxy ...@@ -458,7 +296,8 @@ namespace Titanium.Web.Proxy
//Read the request headers in to unique and non-unique header collections //Read the request headers in to unique and non-unique header collections
await HeaderParser.ReadHeaders(clientStreamReader, args.WebSession.Request.NonUniqueRequestHeaders, args.WebSession.Request.RequestHeaders); 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])); : string.Concat("https://", args.WebSession.Request.Host ?? httpsHostName, httpCmdSplit[1]));
args.WebSession.Request.RequestUri = httpRemoteUri; args.WebSession.Request.RequestUri = httpRemoteUri;
...@@ -469,15 +308,11 @@ namespace Titanium.Web.Proxy ...@@ -469,15 +308,11 @@ namespace Titanium.Web.Proxy
args.ProxyClient.ClientStreamReader = clientStreamReader; args.ProxyClient.ClientStreamReader = clientStreamReader;
args.ProxyClient.ClientStreamWriter = clientStreamWriter; args.ProxyClient.ClientStreamWriter = clientStreamWriter;
if (httpsHostName == null && if (httpsHostName == null &&
await CheckAuthorization(clientStreamWriter, await CheckAuthorization(clientStreamWriter,
args.WebSession.Request.RequestHeaders.Values) == false) args.WebSession.Request.RequestHeaders.Values) == false)
{ {
Dispose(clientStream, args.Dispose();
clientStreamReader,
clientStreamWriter,
connection);
break; break;
} }
...@@ -487,15 +322,7 @@ namespace Titanium.Web.Proxy ...@@ -487,15 +322,7 @@ namespace Titanium.Web.Proxy
//If user requested interception do it //If user requested interception do it
if (BeforeRequest != null) if (BeforeRequest != null)
{ {
var invocationList = BeforeRequest.GetInvocationList(); await BeforeRequest.InvokeParallelAsync(this, args);
var handlerTasks = new Task[invocationList.Length];
for (var i = 0; i < invocationList.Length; i++)
{
handlerTasks[i] = ((Func<object, SessionEventArgs, Task>)invocationList[i])(this, args);
}
await Task.WhenAll(handlerTasks);
} }
//if upgrading to websocket then relay the requet without reading the contents //if upgrading to websocket then relay the requet without reading the contents
...@@ -506,11 +333,7 @@ namespace Titanium.Web.Proxy ...@@ -506,11 +333,7 @@ namespace Titanium.Web.Proxy
httpCmd, httpVersion, args.WebSession.Request.RequestHeaders, args.IsHttps, httpCmd, httpVersion, args.WebSession.Request.RequestHeaders, args.IsHttps,
clientStream, tcpConnectionFactory); clientStream, tcpConnectionFactory);
Dispose(clientStream, args.Dispose();
clientStreamReader,
clientStreamWriter,
connection);
break; break;
} }
...@@ -520,52 +343,210 @@ namespace Titanium.Web.Proxy ...@@ -520,52 +343,210 @@ namespace Titanium.Web.Proxy
} }
//construct the web request that we are going to issue on behalf of the client. //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 //already disposed inside above method
args.Dispose();
break; break;
} }
if (args.WebSession.Request.CancelRequest) if (args.WebSession.Request.CancelRequest)
{ {
Dispose(clientStream, args.Dispose();
clientStreamReader,
clientStreamWriter,
connection);
break; break;
} }
//if connection is closing exit //if connection is closing exit
if (args.WebSession.Response.ResponseKeepAlive == false) if (args.WebSession.Response.ResponseKeepAlive == false)
{ {
Dispose(clientStream, args.Dispose();
clientStreamReader,
clientStreamWriter,
connection);
break; break;
} }
args.Dispose();
// read the next request // read the next request
httpCmd = await clientStreamReader.ReadLineAsync(); httpCmd = await clientStreamReader.ReadLineAsync();
} }
catch (Exception e) catch (Exception e)
{ {
ExceptionFunc(new ProxyHttpException("Error occured whilst handling session request", e, args)); ExceptionFunc(new ProxyHttpException("Error occured whilst handling session request", e, args));
Dispose(clientStream,
clientStreamReader,
clientStreamWriter,
connection);
break; break;
} }
} }
if (!disposed)
{
Dispose(clientStream, clientStreamReader, clientStreamWriter, connection);
}
return true;
}
/// <summary>
/// Handle a specific session (request/response sequence)
/// </summary>
/// <param name="connection"></param>
/// <param name="args"></param>
/// <param name="closeConnection"></param>
/// <returns></returns>
private async Task<bool> HandleHttpSessionRequestInternal(TcpConnection connection,
SessionEventArgs args, bool closeConnection)
{
bool disposed = false;
bool keepAlive = false;
try
{
args.WebSession.Request.RequestLocked = true;
//If request was cancelled by user then dispose the client
if (args.WebSession.Request.CancelRequest)
{
return true;
}
//if expect continue is enabled then send the headers first
//and see if server would return 100 conitinue
if (args.WebSession.Request.ExpectContinue)
{
args.WebSession.SetConnection(connection);
await args.WebSession.SendRequest(Enable100ContinueBehaviour);
}
//If 100 continue was the response inform that to the client
if (Enable100ContinueBehaviour)
{
if (args.WebSession.Request.Is100Continue)
{
await WriteResponseStatus(args.WebSession.Response.HttpVersion, "100",
"Continue", args.ProxyClient.ClientStreamWriter);
await args.ProxyClient.ClientStreamWriter.WriteLineAsync();
}
else if (args.WebSession.Request.ExpectationFailed)
{
await WriteResponseStatus(args.WebSession.Response.HttpVersion, "417",
"Expectation Failed", args.ProxyClient.ClientStreamWriter);
await args.ProxyClient.ClientStreamWriter.WriteLineAsync();
}
}
//If expect continue is not enabled then set the connectio and send request headers
if (!args.WebSession.Request.ExpectContinue)
{
args.WebSession.SetConnection(connection);
await args.WebSession.SendRequest(Enable100ContinueBehaviour);
}
//If request was modified by user
if (args.WebSession.Request.RequestBodyRead)
{
if (args.WebSession.Request.ContentEncoding != null)
{
args.WebSession.Request.RequestBody = await GetCompressedResponseBody(args.WebSession.Request.ContentEncoding, args.WebSession.Request.RequestBody);
}
//chunked send is not supported as of now
args.WebSession.Request.ContentLength = args.WebSession.Request.RequestBody.Length;
var newStream = args.WebSession.ServerConnection.Stream;
await newStream.WriteAsync(args.WebSession.Request.RequestBody, 0, args.WebSession.Request.RequestBody.Length);
}
else
{
if (!args.WebSession.Request.ExpectationFailed)
{
//If its a post/put/patch request, then read the client html body and send it to server
var method = args.WebSession.Request.Method.ToUpper();
if (method == "POST" || method == "PUT" || method == "PATCH")
{
await SendClientRequestBody(args);
}
}
}
//If not expectation failed response was returned by server then parse response
if (!args.WebSession.Request.ExpectationFailed)
{
disposed = await HandleHttpSessionResponse(args);
//already disposed inside above method
if (disposed)
{
return true;
}
}
//if connection is closing exit
if (args.WebSession.Response.ResponseKeepAlive == false)
{
return true;
}
if (!closeConnection)
{
keepAlive = true;
return false;
}
}
catch (Exception e)
{
ExceptionFunc(new ProxyHttpException("Error occured whilst handling session request (internal)", e, args));
return true;
}
finally
{
if (!disposed && !keepAlive)
{
//dispose
Dispose(args.ProxyClient.ClientStream, args.ProxyClient.ClientStreamReader, args.ProxyClient.ClientStreamWriter, args.WebSession.ServerConnection);
}
}
return true;
} }
/// <summary>
/// Create a Server Connection
/// </summary>
/// <param name="args"></param>
/// <returns></returns>
private async Task<TcpConnection> GetServerConnection(
SessionEventArgs args)
{
ExternalProxy customUpStreamHttpProxy = null;
ExternalProxy customUpStreamHttpsProxy = null;
if (args.WebSession.Request.RequestUri.Scheme == "http")
{
if (GetCustomUpStreamHttpProxyFunc != null)
{
customUpStreamHttpProxy = await GetCustomUpStreamHttpProxyFunc(args);
}
}
else
{
if (GetCustomUpStreamHttpsProxyFunc != null)
{
customUpStreamHttpsProxy = await GetCustomUpStreamHttpsProxyFunc(args);
}
}
args.CustomUpStreamHttpProxyUsed = customUpStreamHttpProxy;
args.CustomUpStreamHttpsProxyUsed = customUpStreamHttpsProxy;
return await tcpConnectionFactory.CreateClient(this,
args.WebSession.Request.RequestUri.Host,
args.WebSession.Request.RequestUri.Port,
args.WebSession.Request.HttpVersion,
args.IsHttps,
customUpStreamHttpProxy ?? UpStreamHttpProxy,
customUpStreamHttpsProxy ?? UpStreamHttpsProxy,
args.ProxyClient.ClientStream);
}
/// <summary> /// <summary>
/// Write successfull CONNECT response to client /// Write successfull CONNECT response to client
/// </summary> /// </summary>
......
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using Titanium.Web.Proxy.EventArguments; using System.Threading;
using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Compression;
using System.Threading.Tasks; using System.Threading.Tasks;
using Titanium.Web.Proxy.Compression;
using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Exceptions; using Titanium.Web.Proxy.Exceptions;
using Titanium.Web.Proxy.Extensions; using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Helpers; using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Network.Tcp; using Titanium.Web.Proxy.Network.Tcp;
namespace Titanium.Web.Proxy namespace Titanium.Web.Proxy
...@@ -22,7 +23,7 @@ namespace Titanium.Web.Proxy ...@@ -22,7 +23,7 @@ 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>true if no errors</returns> /// <returns>true if client/server connection was terminated (and disposed) </returns>
private async Task<bool> HandleHttpSessionResponse(SessionEventArgs args) private async Task<bool> HandleHttpSessionResponse(SessionEventArgs args)
{ {
try try
...@@ -40,28 +41,20 @@ namespace Titanium.Web.Proxy ...@@ -40,28 +41,20 @@ namespace Titanium.Web.Proxy
//If user requested call back then do it //If user requested call back then do it
if (BeforeResponse != null && !args.WebSession.Response.ResponseLocked) if (BeforeResponse != null && !args.WebSession.Response.ResponseLocked)
{ {
Delegate[] invocationList = BeforeResponse.GetInvocationList(); await BeforeResponse.InvokeParallelAsync(this, args);
Task[] handlerTasks = new Task[invocationList.Length];
for (int i = 0; i < invocationList.Length; i++)
{
handlerTasks[i] = ((Func<object, SessionEventArgs, Task>)invocationList[i])(this, args);
}
await Task.WhenAll(handlerTasks);
} }
if (args.ReRequest) if (args.ReRequest)
{ {
if(args.WebSession.ServerConnection != null) if (args.WebSession.ServerConnection != null)
{ {
args.WebSession.ServerConnection.Dispose(); args.WebSession.ServerConnection.Dispose();
ServerConnectionCount--; Interlocked.Decrement(ref serverConnectionCount);
} }
var connection = await GetServerConnection(args); var connection = await GetServerConnection(args);
var result = await HandleHttpSessionRequestInternal(null, args, true); var disposed = await HandleHttpSessionRequestInternal(null, args, true);
return result; return disposed;
} }
args.WebSession.Response.ResponseLocked = true; args.WebSession.Response.ResponseLocked = true;
...@@ -137,12 +130,10 @@ namespace Titanium.Web.Proxy ...@@ -137,12 +130,10 @@ namespace Titanium.Web.Proxy
Dispose(args.ProxyClient.ClientStream, args.ProxyClient.ClientStreamReader, Dispose(args.ProxyClient.ClientStream, args.ProxyClient.ClientStreamReader,
args.ProxyClient.ClientStreamWriter, args.WebSession.ServerConnection); args.ProxyClient.ClientStreamWriter, args.WebSession.ServerConnection);
return false; return true;
} }
args.Dispose(); return false;
return true;
} }
/// <summary> /// <summary>
...@@ -240,15 +231,17 @@ namespace Titanium.Web.Proxy ...@@ -240,15 +231,17 @@ namespace Titanium.Web.Proxy
StreamWriter clientStreamWriter, StreamWriter clientStreamWriter,
TcpConnection serverConnection) TcpConnection serverConnection)
{ {
ServerConnectionCount--;
clientStream?.Close(); clientStream?.Close();
clientStream?.Dispose(); clientStream?.Dispose();
clientStreamReader?.Dispose(); clientStreamReader?.Dispose();
clientStreamWriter?.Dispose(); clientStreamWriter?.Dispose();
serverConnection?.Dispose(); if (serverConnection != null)
{
serverConnection.Dispose();
Interlocked.Decrement(ref serverConnectionCount);
}
} }
} }
} }
...@@ -7,9 +7,9 @@ namespace Titanium.Web.Proxy.Shared ...@@ -7,9 +7,9 @@ namespace Titanium.Web.Proxy.Shared
/// </summary> /// </summary>
internal class ProxyConstants internal class ProxyConstants
{ {
internal static readonly char[] SpaceSplit = {' '}; internal static readonly char[] SpaceSplit = { ' ' };
internal static readonly char[] ColonSplit = {':'}; internal static readonly char[] ColonSplit = { ':' };
internal static readonly char[] SemiColonSplit = {';'}; internal static readonly char[] SemiColonSplit = { ';' };
internal static readonly byte[] NewLineBytes = Encoding.ASCII.GetBytes(NewLine); internal static readonly byte[] NewLineBytes = Encoding.ASCII.GetBytes(NewLine);
......
...@@ -73,6 +73,7 @@ ...@@ -73,6 +73,7 @@
<Compile Include="EventArguments\CertificateSelectionEventArgs.cs" /> <Compile Include="EventArguments\CertificateSelectionEventArgs.cs" />
<Compile Include="EventArguments\CertificateValidationEventArgs.cs" /> <Compile Include="EventArguments\CertificateValidationEventArgs.cs" />
<Compile Include="Extensions\ByteArrayExtensions.cs" /> <Compile Include="Extensions\ByteArrayExtensions.cs" />
<Compile Include="Extensions\FuncExtensions.cs" />
<Compile Include="Extensions\StringExtensions.cs" /> <Compile Include="Extensions\StringExtensions.cs" />
<Compile Include="Helpers\CustomBufferedStream.cs" /> <Compile Include="Helpers\CustomBufferedStream.cs" />
<Compile Include="Helpers\Network.cs" /> <Compile Include="Helpers\Network.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