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.Diagnostics;
using System.Runtime.InteropServices;
using Titanium.Web.Proxy.Examples.Basic.Helpers;
namespace Titanium.Web.Proxy.Examples.Basic
{
......@@ -9,11 +11,13 @@ namespace Titanium.Web.Proxy.Examples.Basic
public static void Main(string[] args)
{
//fix console hang due to QuickEdit mode
ConsoleHelper.DisableQuickEditMode();
//On Console exit make sure we also exit the proxy
NativeMethods.Handler = ConsoleEventCallback;
NativeMethods.SetConsoleCtrlHandler(NativeMethods.Handler, true);
//Start proxy controller
controller.StartProxy();
......@@ -50,5 +54,7 @@ namespace Titanium.Web.Proxy.Examples.Basic
// Pinvoke
internal delegate bool ConsoleEventDelegate(int eventType);
}
}
\ No newline at end of file
}
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Security;
using System.Threading.Tasks;
using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Models;
......@@ -47,18 +47,18 @@ namespace Titanium.Web.Proxy.Examples.Basic
//Exclude Https addresses you don't want to proxy
//Useful for clients that use certificate pinning
//for example google.com and dropbox.com
ExcludedHttpsHostNameRegex = new List<string>() { "dropbox.com" }
ExcludedHttpsHostNameRegex = new List<string> { "dropbox.com" }
//Include Https addresses you want to proxy (others will be excluded)
//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
//Use self-issued generic certificate on all https requests
//Optimizes performance by not creating a certificate for each https-enabled domain
//Useful when certificate trust is not required by proxy clients
// GenericCertificate = new X509Certificate2(Path.Combine(System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), "genericcert.pfx"), "password")
//GenericCertificate = new X509Certificate2(Path.Combine(System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), "genericcert.pfx"), "password")
};
//An explicit endpoint is where the client knows about the existence of a proxy
......@@ -107,7 +107,7 @@ namespace Titanium.Web.Proxy.Examples.Basic
//intecept & cancel redirect or update requests
public async Task OnRequest(object sender, SessionEventArgs e)
{
Console.WriteLine("Active Client Connections:" + ((ProxyServer) sender).ClientConnectionCount);
Console.WriteLine("Active Client Connections:" + ((ProxyServer)sender).ClientConnectionCount);
Console.WriteLine(e.WebSession.Request.Url);
//read request headers
......@@ -150,7 +150,7 @@ namespace Titanium.Web.Proxy.Examples.Basic
//Modify response
public async Task OnResponse(object sender, SessionEventArgs e)
{
Console.WriteLine("Active Server Connections:" + (sender as ProxyServer).ServerConnectionCount);
Console.WriteLine("Active Server Connections:" + ((ProxyServer)sender).ServerConnectionCount);
if (requestBodyHistory.ContainsKey(e.Id))
{
......@@ -189,7 +189,7 @@ namespace Titanium.Web.Proxy.Examples.Basic
public Task OnCertificateValidation(object sender, CertificateValidationEventArgs e)
{
//set IsValid to true/false based on Certificate Errors
if (e.SslPolicyErrors == System.Net.Security.SslPolicyErrors.None)
if (e.SslPolicyErrors == SslPolicyErrors.None)
{
e.IsValid = true;
}
......
......@@ -55,6 +55,7 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Helpers\ConsoleHelper.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ProxyTestController.cs" />
......
......@@ -17,8 +17,7 @@ Features
* Safely relays Web Socket requests over HTTP
* Support mutual SSL authentication
* Fully asynchronous proxy
* Supports proxy authentication
* Supports proxy authentication & automatic proxy detection
Usage
=====
......@@ -204,10 +203,8 @@ public Task OnCertificateSelection(object sender, CertificateSelectionEventArgs
```
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 HTTP 2.0
* Support upstream AutoProxy detection
* Support SOCKS protocol
* Implement Kerberos/NTLM authentication over HTTP protocols for windows domain
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
......
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Diagnostics;
using System.Net;
using System.Net.Http;
using System.Net.Security;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Models;
using System.Net.Http;
using System.Diagnostics;
namespace Titanium.Web.Proxy.IntegrationTests
{
......@@ -13,7 +14,6 @@ namespace Titanium.Web.Proxy.IntegrationTests
public class SslTests
{
[TestMethod]
public void TestSsl()
{
//expand this to stress test to find
......@@ -103,7 +103,7 @@ namespace Titanium.Web.Proxy.IntegrationTests
public Task OnCertificateValidation(object sender, CertificateValidationEventArgs e)
{
//set IsValid to true/false based on Certificate Errors
if (e.SslPolicyErrors == System.Net.Security.SslPolicyErrors.None)
if (e.SslPolicyErrors == SslPolicyErrors.None)
{
e.IsValid = true;
}
......
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Titanium.Web.Proxy.Network;
using System.Threading.Tasks;
using System.Collections.Generic;
namespace Titanium.Web.Proxy.UnitTests
{
......@@ -10,8 +10,7 @@ namespace Titanium.Web.Proxy.UnitTests
public class CertificateManagerTests
{
private static readonly string[] hostNames
= new string[] { "facebook.com", "youtube.com", "google.com",
"bing.com", "yahoo.com"};
= { "facebook.com", "youtube.com", "google.com", "bing.com", "yahoo.com" };
private readonly Random random = new Random();
......@@ -36,16 +35,13 @@ namespace Titanium.Web.Proxy.UnitTests
var certificate = mgr.CreateCertificate(host, false);
Assert.IsNotNull(certificate);
}));
}
}
await Task.WhenAll(tasks.ToArray());
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">
<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/=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/=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/=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>
\ No newline at end of file
<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>
<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.Net.Security;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Extensions;
namespace Titanium.Web.Proxy
{
......@@ -32,17 +32,8 @@ namespace Titanium.Web.Proxy
SslPolicyErrors = sslPolicyErrors
};
Delegate[] invocationList = ServerCertificateValidationCallback.GetInvocationList();
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();
//why is the sender null?
ServerCertificateValidationCallback.InvokeParallel(this, args);
return args.IsValid;
}
......@@ -108,17 +99,8 @@ namespace Titanium.Web.Proxy
ClientCertificate = clientCertificate
};
Delegate[] invocationList = ClientCertificateSelectionCallback.GetInvocationList();
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();
//why is the sender null?
ClientCertificateSelectionCallback.InvokeParallel(this, args);
return args.ClientCertificate;
}
......
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Text;
using Titanium.Web.Proxy.Exceptions;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Decompression;
using Titanium.Web.Proxy.Exceptions;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Http.Responses;
using Titanium.Web.Proxy.Extensions;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Network;
using System.Net;
using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Network;
namespace Titanium.Web.Proxy.EventArguments
{
......@@ -30,7 +30,7 @@ namespace Titanium.Web.Proxy.EventArguments
/// <summary>
/// Holds a reference to proxy response handler method
/// </summary>
private readonly Func<SessionEventArgs, Task> httpResponseHandler;
private Func<SessionEventArgs, Task> httpResponseHandler;
/// <summary>
/// Holds a reference to client
......@@ -56,7 +56,7 @@ namespace Titanium.Web.Proxy.EventArguments
/// <summary>
/// Client End Point.
/// </summary>
public IPEndPoint ClientEndPoint => (IPEndPoint) ProxyClient.TcpClient.Client.RemoteEndPoint;
public IPEndPoint ClientEndPoint => (IPEndPoint)ProxyClient.TcpClient.Client.RemoteEndPoint;
/// <summary>
/// A web session corresponding to a single request/response sequence
......@@ -158,7 +158,7 @@ namespace Titanium.Web.Proxy.EventArguments
await WebSession.ServerConnection.StreamReader.CopyBytesToStream(bufferSize, responseBodyStream,
WebSession.Response.ContentLength);
}
else if ((WebSession.Response.HttpVersion.Major == 1 && WebSession.Response.HttpVersion.Minor == 0) || WebSession.Response.ContentLength == -1)
else if (WebSession.Response.HttpVersion.Major == 1 && WebSession.Response.HttpVersion.Minor == 0 || WebSession.Response.ContentLength == -1)
{
await WebSession.ServerConnection.StreamReader.CopyBytesToStream(bufferSize, responseBodyStream, long.MaxValue);
}
......@@ -522,6 +522,11 @@ namespace Titanium.Web.Proxy.EventArguments
/// </summary>
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
if (contentLength < bufferSize)
{
bytesToRead = (int) contentLength;
bytesToRead = (int)contentLength;
}
var buffer = new byte[bufferSize];
......@@ -153,8 +153,8 @@ namespace Titanium.Web.Proxy.Extensions
break;
bytesRead = 0;
var remainingBytes = (contentLength - totalBytesRead);
bytesToRead = remainingBytes > (long) bufferSize ? bufferSize : (int) remainingBytes;
var remainingBytes = contentLength - totalBytesRead;
bytesToRead = remainingBytes > (long)bufferSize ? bufferSize : (int)remainingBytes;
}
}
else
......
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Text;
......@@ -18,10 +19,18 @@ namespace Titanium.Web.Proxy.Helpers
private readonly byte[] staticBuffer;
private readonly Encoding encoding;
private static readonly ConcurrentQueue<byte[]> buffers
= new ConcurrentQueue<byte[]>();
private volatile bool disposed;
internal CustomBinaryReader(CustomBufferedStream stream, int bufferSize)
{
this.stream = stream;
staticBuffer = new byte[bufferSize];
if (!buffers.TryDequeue(out staticBuffer) || staticBuffer.Length != bufferSize)
{
staticBuffer = new byte[bufferSize];
}
this.bufferSize = bufferSize;
......@@ -112,7 +121,7 @@ namespace Titanium.Web.Proxy.Helpers
var buffer = staticBuffer;
if (totalBytesToRead < bufferSize)
{
bytesToRead = (int) totalBytesToRead;
bytesToRead = (int)totalBytesToRead;
buffer = new byte[bytesToRead];
}
......@@ -127,7 +136,7 @@ namespace Titanium.Web.Proxy.Helpers
break;
var remainingBytes = totalBytesToRead - totalBytesRead;
bytesToRead = Math.Min(bufferSize, (int) remainingBytes);
bytesToRead = Math.Min(bufferSize, (int)remainingBytes);
if (totalBytesRead + bytesToRead > buffer.Length)
{
......@@ -148,8 +157,18 @@ namespace Titanium.Web.Proxy.Helpers
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)
{
var newBuffer = new byte[size];
......
......@@ -198,7 +198,7 @@ namespace Titanium.Web.Proxy.Helpers
{
if (asyncResult is ReadAsyncResult)
{
return ((ReadAsyncResult) asyncResult).ReadBytes;
return ((ReadAsyncResult)asyncResult).ReadBytes;
}
return baseStream.EndRead(asyncResult);
......
using System.Linq;
using System.Net;
using System.Net.Sockets;
namespace Titanium.Web.Proxy.Helpers
{
internal class NetworkHelper
{
private static int FindProcessIdFromLocalPort(int port, IpVersion ipVersion)
{
var tcpRow = TcpHelper.GetTcpRowByLocalPort(ipVersion, port);
return tcpRow?.ProcessId ?? 0;
}
internal static int GetProcessIdFromPort(int port, bool ipV6Enabled)
{
var processId = FindProcessIdFromLocalPort(port, IpVersion.Ipv4);
if (processId > 0 && !ipV6Enabled)
{
return processId;
}
return FindProcessIdFromLocalPort(port, IpVersion.Ipv6);
}
/// <summary>
/// Adapated from below link
/// http://stackoverflow.com/questions/11834091/how-to-check-if-localhost
/// </summary>
/// <param name="address"></param>
/// <returns></returns>
internal static bool IsLocalIpAddress(IPAddress address)
{
// get local IP addresses
var localIPs = Dns.GetHostAddresses(Dns.GetHostName());
// test if any host IP equals to any local IP or to localhost
return IPAddress.IsLoopback(address) || localIPs.Contains(address);
}
internal static bool IsLocalIpAddress(string hostName)
{
bool isLocalhost = false;
IPHostEntry localhost = Dns.GetHostEntry("127.0.0.1");
if (hostName == localhost.HostName)
{
IPHostEntry hostEntry = Dns.GetHostEntry(hostName);
isLocalhost = hostEntry.AddressList.Any(IPAddress.IsLoopback);
}
if (!isLocalhost)
{
localhost = Dns.GetHostEntry(Dns.GetHostName());
IPAddress ipAddress;
if (IPAddress.TryParse(hostName, out ipAddress))
isLocalhost = localhost.AddressList.Any(x => x.Equals(ipAddress));
if (!isLocalhost)
{
try
{
var hostEntry = Dns.GetHostEntry(hostName);
isLocalhost = localhost.AddressList.Any(x => hostEntry.AddressList.Any(x.Equals));
}
catch (SocketException)
{
}
}
}
return isLocalhost;
}
}
}
using System.Linq;
using System.Net;
using System.Net.Sockets;
namespace Titanium.Web.Proxy.Helpers
{
internal class NetworkHelper
{
private static int FindProcessIdFromLocalPort(int port, IpVersion ipVersion)
{
var tcpRow = TcpHelper.GetTcpRowByLocalPort(ipVersion, port);
return tcpRow?.ProcessId ?? 0;
}
internal static int GetProcessIdFromPort(int port, bool ipV6Enabled)
{
var processId = FindProcessIdFromLocalPort(port, IpVersion.Ipv4);
if (processId > 0 && !ipV6Enabled)
{
return processId;
}
return FindProcessIdFromLocalPort(port, IpVersion.Ipv6);
}
/// <summary>
/// Adapated from below link
/// http://stackoverflow.com/questions/11834091/how-to-check-if-localhost
/// </summary>
/// <param name="address"></param>
/// <returns></returns>
internal static bool IsLocalIpAddress(IPAddress address)
{
// get local IP addresses
var localIPs = Dns.GetHostAddresses(Dns.GetHostName());
// test if any host IP equals to any local IP or to localhost
return IPAddress.IsLoopback(address) || localIPs.Contains(address);
}
internal static bool IsLocalIpAddress(string hostName)
{
bool isLocalhost = false;
IPHostEntry localhost = Dns.GetHostEntry("127.0.0.1");
if (hostName == localhost.HostName)
{
IPHostEntry hostEntry = Dns.GetHostEntry(hostName);
isLocalhost = hostEntry.AddressList.Any(IPAddress.IsLoopback);
}
if (!isLocalhost)
{
localhost = Dns.GetHostEntry(Dns.GetHostName());
IPAddress ipAddress;
if (IPAddress.TryParse(hostName, out ipAddress))
isLocalhost = localhost.AddressList.Any(x => x.Equals(ipAddress));
if (!isLocalhost)
{
try
{
var hostEntry = Dns.GetHostEntry(hostName);
isLocalhost = localhost.AddressList.Any(x => hostEntry.AddressList.Any(x.Equals));
}
catch (SocketException)
{
}
}
}
return isLocalhost;
}
}
}
using System;
using System.Runtime.InteropServices;
using Microsoft.Win32;
using System.Text.RegularExpressions;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using Microsoft.Win32;
// Helper classes for setting system proxy settings
namespace Titanium.Web.Proxy.Helpers
......@@ -84,7 +84,7 @@ namespace Titanium.Web.Proxy.Helpers
var exisitingContent = reg.GetValue("ProxyServer") as string;
var existingSystemProxyValues = GetSystemProxyValues(exisitingContent);
existingSystemProxyValues.RemoveAll(x => protocolType == ProxyProtocolType.Https ? x.IsHttps : !x.IsHttps);
existingSystemProxyValues.Add(new HttpSystemProxyValue()
existingSystemProxyValues.Add(new HttpSystemProxyValue
{
HostName = hostname,
IsHttps = protocolType == ProxyProtocolType.Https,
......
......@@ -3,10 +3,9 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.NetworkInformation;
using System.Net.Security;
using System.Runtime.InteropServices;
using System.Security.Authentication;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Models;
......@@ -15,8 +14,6 @@ using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Helpers
{
using System.Net;
internal enum IpVersion
{
Ipv4 = 1,
......@@ -232,7 +229,7 @@ namespace Titanium.Web.Proxy.Helpers
finally
{
tcpConnection.Dispose();
server.ServerConnectionCount--;
Interlocked.Decrement(ref server.serverConnectionCount);
}
}
}
......
......@@ -8,8 +8,8 @@ namespace Titanium.Web.Proxy.Http
{
internal static class HeaderParser
{
internal static async Task ReadHeaders(CustomBinaryReader reader,
Dictionary<string, List<HttpHeader>> nonUniqueResponseHeaders,
internal static async Task ReadHeaders(CustomBinaryReader reader,
Dictionary<string, List<HttpHeader>> nonUniqueResponseHeaders,
Dictionary<string, HttpHeader> headers)
{
string tmpLine;
......
......@@ -11,7 +11,7 @@ namespace Titanium.Web.Proxy.Http
/// <summary>
/// Used to communicate with the server over HTTP(S)
/// </summary>
public class HttpWebClient
public class HttpWebClient : IDisposable
{
/// <summary>
/// Connection to server
......@@ -79,7 +79,7 @@ namespace Titanium.Web.Proxy.Http
var requestLines = new StringBuilder();
//prepare the request & headers
if ((ServerConnection.UpStreamHttpProxy != null && ServerConnection.IsHttps == false) || (ServerConnection.UpStreamHttpsProxy != null && ServerConnection.IsHttps))
if (ServerConnection.UpStreamHttpProxy != null && ServerConnection.IsHttps == false || ServerConnection.UpStreamHttpsProxy != null && ServerConnection.IsHttps)
{
requestLines.AppendLine($"{Request.Method} {Request.RequestUri.AbsoluteUri} HTTP/{Request.HttpVersion.Major}.{Request.HttpVersion.Minor}");
}
......@@ -93,7 +93,7 @@ namespace Titanium.Web.Proxy.Http
{
requestLines.AppendLine("Proxy-Connection: keep-alive");
requestLines.AppendLine("Proxy-Authorization" + ": Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes(
$"{ServerConnection.UpStreamHttpProxy.UserName}:{ServerConnection.UpStreamHttpProxy.Password}")));
$"{ServerConnection.UpStreamHttpProxy.UserName}:{ServerConnection.UpStreamHttpProxy.Password}")));
}
//write request headers
foreach (var headerItem in Request.RequestHeaders)
......@@ -208,5 +208,16 @@ namespace Titanium.Web.Proxy.Http
//Read the response headers in to unique and non-unique header collections
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.Collections.Generic;
using System.Text;
using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Models;
namespace Titanium.Web.Proxy.Http
{
/// <summary>
/// A HTTP(S) request object
/// </summary>
public class Request
public class Request : IDisposable
{
/// <summary>
/// Request Method
......@@ -241,7 +241,7 @@ namespace Titanium.Web.Proxy.Http
/// request body as string
/// </summary>
internal string RequestBodyString { get; set; }
internal bool RequestBodyRead { get; set; }
internal bool RequestLocked { get; set; }
......@@ -294,5 +294,20 @@ namespace Titanium.Web.Proxy.Http
RequestHeaders = new Dictionary<string, 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.Text;
using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Extensions;
using System;
using Titanium.Web.Proxy.Models;
namespace Titanium.Web.Proxy.Http
{
/// <summary>
/// Http(s) response object
/// </summary>
public class Response
public class Response : IDisposable
{
/// <summary>
/// Response Status Code.
......@@ -234,5 +234,20 @@ namespace Titanium.Web.Proxy.Http
ResponseHeaders = new Dictionary<string, 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
/// <param name="status"></param>
public GenericResponse(HttpStatusCode status)
{
ResponseStatusCode = ((int) status).ToString();
ResponseStatusCode = ((int)status).ToString();
ResponseStatusDescription = status.ToString();
}
......
......@@ -21,7 +21,7 @@ namespace Titanium.Web.Proxy.Models
/// <summary>
/// Bypass this proxy for connections to localhost?
/// </summary>
public bool BypassForLocalhost { get; set; }
public bool BypassLocalhost { get; set; }
/// <summary>
/// Username.
......
......@@ -110,7 +110,7 @@ namespace Titanium.Web.Proxy.Network.Certificate
// Corresponding private key
var privateKeyInfo = PrivateKeyInfoFactory.CreatePrivateKeyInfo(subjectKeyPair.Private);
var seq = (Asn1Sequence) Asn1Object.FromByteArray(privateKeyInfo.ParsePrivateKey().GetDerEncoded());
var seq = (Asn1Sequence)Asn1Object.FromByteArray(privateKeyInfo.ParsePrivateKey().GetDerEncoded());
if (seq.Count != 9)
{
......
......@@ -89,7 +89,7 @@ namespace Titanium.Web.Proxy.Network.Certificate
}
var x500CertDN = Activator.CreateInstance(typeX500DN);
var typeValue = new object[] {fullSubject, 0};
var typeValue = new object[] { fullSubject, 0 };
typeX500DN.InvokeMember("Encode", BindingFlags.InvokeMethod, null, x500CertDN, typeValue);
var x500RootCertDN = Activator.CreateInstance(typeX500DN);
......@@ -110,16 +110,16 @@ namespace Titanium.Web.Proxy.Network.Certificate
if (sharedPrivateKey == null)
{
sharedPrivateKey = Activator.CreateInstance(typeX509PrivateKey);
typeValue = new object[] {sProviderName};
typeValue = new object[] { sProviderName };
typeX509PrivateKey.InvokeMember("ProviderName", BindingFlags.PutDispProperty, null, sharedPrivateKey, typeValue);
typeValue[0] = 2;
typeX509PrivateKey.InvokeMember("ExportPolicy", BindingFlags.PutDispProperty, null, sharedPrivateKey, typeValue);
typeValue = new object[] {(isRoot ? 2 : 1)};
typeValue = new object[] { isRoot ? 2 : 1 };
typeX509PrivateKey.InvokeMember("KeySpec", BindingFlags.PutDispProperty, null, sharedPrivateKey, typeValue);
if (!isRoot)
{
typeValue = new object[] {176};
typeValue = new object[] { 176 };
typeX509PrivateKey.InvokeMember("KeyUsage", BindingFlags.PutDispProperty, null, sharedPrivateKey, typeValue);
}
......@@ -149,9 +149,9 @@ namespace Titanium.Web.Proxy.Network.Certificate
var requestCert = Activator.CreateInstance(typeRequestCert);
typeValue = new[] {1, sharedPrivateKey, string.Empty};
typeValue = new[] { 1, sharedPrivateKey, string.Empty };
typeRequestCert.InvokeMember("InitializeFromPrivateKey", BindingFlags.InvokeMethod, null, requestCert, typeValue);
typeValue = new[] {x500CertDN};
typeValue = new[] { x500CertDN };
typeRequestCert.InvokeMember("Subject", BindingFlags.PutDispProperty, null, requestCert, typeValue);
typeValue[0] = x500RootCertDN;
typeRequestCert.InvokeMember("Issuer", BindingFlags.PutDispProperty, null, requestCert, typeValue);
......@@ -186,14 +186,14 @@ namespace Titanium.Web.Proxy.Network.Certificate
var extNames = Activator.CreateInstance(typeExtNames);
var altDnsNames = Activator.CreateInstance(typeCAlternativeName);
typeValue = new object[] {3, subject};
typeValue = new object[] { 3, subject };
typeCAlternativeName.InvokeMember("InitializeFromString", BindingFlags.InvokeMethod, null, altDnsNames, typeValue);
typeValue = new[] {altDnsNames};
typeValue = new[] { altDnsNames };
typeAltNamesCollection.InvokeMember("Add", BindingFlags.InvokeMethod, null, altNameCollection, typeValue);
typeValue = new[] {altNameCollection};
typeValue = new[] { altNameCollection };
typeExtNames.InvokeMember("InitializeEncode", BindingFlags.InvokeMethod, null, extNames, typeValue);
typeValue[0] = extNames;
......@@ -204,27 +204,27 @@ namespace Titanium.Web.Proxy.Network.Certificate
{
var signerCertificate = Activator.CreateInstance(typeSignerCertificate);
typeValue = new object[] {0, 0, 12, signingCertificate.Thumbprint};
typeValue = new object[] { 0, 0, 12, signingCertificate.Thumbprint };
typeSignerCertificate.InvokeMember("Initialize", BindingFlags.InvokeMethod, null, signerCertificate, typeValue);
typeValue = new[] {signerCertificate};
typeValue = new[] { signerCertificate };
typeRequestCert.InvokeMember("SignerCertificate", BindingFlags.PutDispProperty, null, requestCert, typeValue);
}
else
{
var basicConstraints = Activator.CreateInstance(typeBasicConstraints);
typeValue = new object[] {"true", "0"};
typeValue = new object[] { "true", "0" };
typeBasicConstraints.InvokeMember("InitializeEncode", BindingFlags.InvokeMethod, null, basicConstraints, typeValue);
typeValue = new[] {basicConstraints};
typeValue = new[] { basicConstraints };
typeX509Extensions.InvokeMember("Add", BindingFlags.InvokeMethod, null, certificate, typeValue);
}
oid = Activator.CreateInstance(typeOID);
typeValue = new object[] {1, 0, 0, hashAlg};
typeValue = new object[] { 1, 0, 0, hashAlg };
typeOID.InvokeMember("InitializeFromAlgorithmName", BindingFlags.InvokeMethod, null, oid, typeValue);
typeValue = new[] {oid};
typeValue = new[] { oid };
typeRequestCert.InvokeMember("HashAlgorithm", BindingFlags.PutDispProperty, null, requestCert, typeValue);
typeRequestCert.InvokeMember("Encode", BindingFlags.InvokeMethod, null, requestCert, null);
......@@ -243,15 +243,15 @@ namespace Titanium.Web.Proxy.Network.Certificate
typeValue[0] = 0;
var createCertRequest = typeX509Enrollment.InvokeMember("CreateRequest", BindingFlags.InvokeMethod, null, x509Enrollment, typeValue);
typeValue = new[] {2, createCertRequest, 0, string.Empty};
var createCertRequest = typeX509Enrollment.InvokeMember("CreateRequest", BindingFlags.InvokeMethod, null, x509Enrollment, typeValue);
typeValue = new[] { 2, createCertRequest, 0, string.Empty };
typeX509Enrollment.InvokeMember("InstallResponse", BindingFlags.InvokeMethod, null, x509Enrollment, typeValue);
typeValue = new object[] {null, 0, 1};
typeValue = new object[] { null, 0, 1 };
try
{
var empty = (string) typeX509Enrollment.InvokeMember("CreatePFX", BindingFlags.InvokeMethod, null, x509Enrollment, typeValue);
var empty = (string)typeX509Enrollment.InvokeMember("CreatePFX", BindingFlags.InvokeMethod, null, x509Enrollment, typeValue);
return new X509Certificate2(Convert.FromBase64String(empty), string.Empty, X509KeyStorageFlags.Exportable);
}
catch (Exception)
......@@ -293,8 +293,7 @@ namespace Titanium.Web.Proxy.Network.Certificate
var graceTime = DateTime.Now.AddDays(GraceDays);
var now = DateTime.Now;
rCert = !isRoot ? MakeCertificate(false, sSubjectCN, fullSubject, keyLength, HashAlgo, graceTime, now.AddDays(ValidDays), signingCert) :
MakeCertificate(true, sSubjectCN, fullSubject, keyLength, HashAlgo, graceTime, now.AddDays(ValidDays), null);
rCert = MakeCertificate(isRoot, sSubjectCN, fullSubject, keyLength, HashAlgo, graceTime, now.AddDays(ValidDays), isRoot ? null : signingCert);
return rCert;
}
}
......
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
using System.Linq;
using System.Collections.Concurrent;
using System.IO;
using Titanium.Web.Proxy.Network.Certificate;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Network.Certificate;
namespace Titanium.Web.Proxy.Network
{
......@@ -29,7 +31,7 @@ namespace Titanium.Web.Proxy.Network
/// <summary>
/// A class to manage SSL certificates used by this proxy server
/// </summary>
public class CertificateManager : IDisposable
public sealed class CertificateManager : IDisposable
{
internal CertificateEngine Engine
{
......@@ -51,7 +53,7 @@ namespace Titanium.Web.Proxy.Network
if (certEngine == null)
{
certEngine = engine == CertificateEngine.BouncyCastle
? (ICertificateMaker) new BCCertificateMaker()
? (ICertificateMaker)new BCCertificateMaker()
: new WinCertificateMaker();
}
}
......@@ -132,12 +134,12 @@ namespace Titanium.Web.Proxy.Network
private string GetRootCertificatePath()
{
var assemblyLocation = System.Reflection.Assembly.GetExecutingAssembly().Location;
var assemblyLocation = Assembly.GetExecutingAssembly().Location;
// dynamically loaded assemblies returns string.Empty location
if (assemblyLocation == string.Empty)
{
assemblyLocation = System.Reflection.Assembly.GetEntryAssembly().Location;
assemblyLocation = Assembly.GetEntryAssembly().Location;
}
var path = Path.GetDirectoryName(assemblyLocation);
......@@ -146,7 +148,7 @@ namespace Titanium.Web.Proxy.Network
return fileName;
}
internal X509Certificate2 LoadRootCertificate()
private X509Certificate2 LoadRootCertificate()
{
var fileName = GetRootCertificatePath();
if (!File.Exists(fileName)) return null;
......@@ -217,6 +219,51 @@ namespace Titanium.Web.Proxy.Network
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>
/// Removes the trusted certificates.
/// </summary>
......@@ -229,13 +276,49 @@ namespace Titanium.Web.Proxy.Network
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>
/// Create an SSL certificate
/// </summary>
/// <param name="certificateName"></param>
/// <param name="isRootCertificate"></param>
/// <returns></returns>
internal virtual X509Certificate2 CreateCertificate(string certificateName, bool isRootCertificate)
internal X509Certificate2 CreateCertificate(string certificateName, bool isRootCertificate)
{
if (certificateCache.ContainsKey(certificateName))
{
......@@ -264,7 +347,7 @@ namespace Titanium.Web.Proxy.Network
}
if (certificate != null && !certificateCache.ContainsKey(certificateName))
{
certificateCache.Add(certificateName, new CachedCertificate {Certificate = certificate});
certificateCache.Add(certificateName, new CachedCertificate { Certificate = certificate });
}
}
else
......@@ -316,7 +399,7 @@ namespace Titanium.Web.Proxy.Network
/// </summary>
/// <param name="storeLocation"></param>
/// <returns></returns>
internal void TrustRootCertificate(StoreLocation storeLocation)
private void TrustRootCertificate(StoreLocation storeLocation)
{
if (RootCertificate == null)
{
......@@ -327,7 +410,7 @@ namespace Titanium.Web.Proxy.Network
return;
}
X509Store x509RootStore = new X509Store(StoreName.Root, storeLocation);
var x509RootStore = new X509Store(StoreName.Root, storeLocation);
var x509PersonalStore = new X509Store(StoreName.My, storeLocation);
try
......@@ -356,7 +439,7 @@ namespace Titanium.Web.Proxy.Network
/// </summary>
/// <param name="storeLocation"></param>
/// <returns></returns>
internal void RemoveTrustedRootCertificates(StoreLocation storeLocation)
private void RemoveTrustedRootCertificates(StoreLocation storeLocation)
{
if (RootCertificate == null)
{
......@@ -367,7 +450,7 @@ namespace Titanium.Web.Proxy.Network
return;
}
X509Store x509RootStore = new X509Store(StoreName.Root, storeLocation);
var x509RootStore = new X509Store(StoreName.Root, storeLocation);
var x509PersonalStore = new X509Store(StoreName.My, storeLocation);
try
......@@ -381,7 +464,7 @@ namespace Titanium.Web.Proxy.Network
catch (Exception e)
{
exceptionFunc(
new Exception("Failed to make system trust root certificate "
new Exception("Failed to remove root certificate trust "
+ $" for {storeLocation} store location. You may need admin rights.", e));
}
finally
......@@ -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()
{
}
......
......@@ -58,7 +58,21 @@ namespace Titanium.Web.Proxy.Network.Tcp
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.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.Net.Security;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Models;
using System.Linq;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Network.Tcp
{
/// <summary>
/// A class that manages Tcp Connection to server used by this proxy server
/// </summary>
internal class TcpConnectionFactory
{
/// <summary>
/// Creates a TCP connection to server
/// </summary>
/// <param name="server"></param>
/// <param name="remoteHostName"></param>
/// <param name="remotePort"></param>
/// <param name="httpVersion"></param>
/// <param name="isHttps"></param>
/// <param name="externalHttpProxy"></param>
/// <param name="externalHttpsProxy"></param>
/// <param name="clientStream"></param>
/// <returns></returns>
internal async Task<TcpConnection> CreateClient(ProxyServer server,
string remoteHostName, int remotePort, Version httpVersion,
bool isHttps,
ExternalProxy externalHttpProxy, ExternalProxy externalHttpsProxy,
Stream clientStream)
{
TcpClient client;
CustomBufferedStream stream;
bool isLocalhost = (externalHttpsProxy == null && externalHttpProxy == null) ? false : NetworkHelper.IsLocalIpAddress(remoteHostName);
bool useHttpsProxy = externalHttpsProxy != null && externalHttpsProxy.HostName != remoteHostName && (externalHttpsProxy.BypassForLocalhost && !isLocalhost);
bool useHttpProxy = externalHttpProxy != null && externalHttpProxy.HostName != remoteHostName && (externalHttpProxy.BypassForLocalhost && !isLocalhost);
if (isHttps)
{
SslStream sslStream = null;
//If this proxy uses another external proxy then create a tunnel request for HTTPS connections
if (useHttpsProxy)
{
client = new TcpClient(server.UpStreamEndPoint);
await client.ConnectAsync(externalHttpsProxy.HostName, externalHttpsProxy.Port);
stream = new CustomBufferedStream(client.GetStream(), server.BufferSize);
using (var writer = new StreamWriter(stream, Encoding.ASCII, server.BufferSize, true) {NewLine = ProxyConstants.NewLine})
{
await writer.WriteLineAsync($"CONNECT {remoteHostName}:{remotePort} HTTP/{httpVersion}");
await writer.WriteLineAsync($"Host: {remoteHostName}:{remotePort}");
await writer.WriteLineAsync("Connection: Keep-Alive");
if (!string.IsNullOrEmpty(externalHttpsProxy.UserName) && externalHttpsProxy.Password != null)
{
await writer.WriteLineAsync("Proxy-Connection: keep-alive");
await writer.WriteLineAsync("Proxy-Authorization" + ": Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes(externalHttpsProxy.UserName + ":" + externalHttpsProxy.Password)));
}
await writer.WriteLineAsync();
await writer.FlushAsync();
writer.Close();
}
using (var reader = new CustomBinaryReader(stream, server.BufferSize))
{
var result = await reader.ReadLineAsync();
if (!new[] {"200 OK", "connection established"}.Any(s => result.ContainsIgnoreCase(s)))
{
throw new Exception("Upstream proxy failed to create a secure tunnel");
}
await reader.ReadAndIgnoreAllLinesAsync();
}
}
else
{
client = new TcpClient(server.UpStreamEndPoint);
await client.ConnectAsync(remoteHostName, remotePort);
stream = new CustomBufferedStream(client.GetStream(), server.BufferSize);
}
try
{
sslStream = new SslStream(stream, true, server.ValidateServerCertificate,
server.SelectClientCertificate);
await sslStream.AuthenticateAsClientAsync(remoteHostName, null, server.SupportedSslProtocols, false);
stream = new CustomBufferedStream(sslStream, server.BufferSize);
}
catch
{
sslStream?.Dispose();
throw;
}
}
else
{
if (useHttpProxy)
{
client = new TcpClient(server.UpStreamEndPoint);
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;
client.LingerState = new LingerOption(true, 0);
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
};
}
}
}
using System;
using System.IO;
using System.Linq;
using System.Net.Security;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Network.Tcp
{
/// <summary>
/// A class that manages Tcp Connection to server used by this proxy server
/// </summary>
internal class TcpConnectionFactory
{
/// <summary>
/// Creates a TCP connection to server
/// </summary>
/// <param name="server"></param>
/// <param name="remoteHostName"></param>
/// <param name="remotePort"></param>
/// <param name="httpVersion"></param>
/// <param name="isHttps"></param>
/// <param name="externalHttpProxy"></param>
/// <param name="externalHttpsProxy"></param>
/// <param name="clientStream"></param>
/// <returns></returns>
internal async Task<TcpConnection> CreateClient(ProxyServer server,
string remoteHostName, int remotePort, Version httpVersion,
bool isHttps,
ExternalProxy externalHttpProxy, ExternalProxy externalHttpsProxy,
Stream clientStream)
{
TcpClient client;
CustomBufferedStream stream;
bool useHttpProxy = false;
//check if external proxy is set for HTTP
if (!isHttps && externalHttpProxy != null
&& externalHttpProxy.HostName != remoteHostName)
{
useHttpProxy = true;
//check if we need to ByPass
if (externalHttpProxy.BypassLocalhost
&& NetworkHelper.IsLocalIpAddress(remoteHostName))
{
useHttpProxy = false;
}
}
bool useHttpsProxy = false;
//check if external proxy is set for HTTPS
if (isHttps && externalHttpsProxy != null
&& externalHttpsProxy.HostName != remoteHostName)
{
useHttpsProxy = true;
//check if we need to ByPass
if (externalHttpsProxy.BypassLocalhost
&& NetworkHelper.IsLocalIpAddress(remoteHostName))
{
useHttpsProxy = false;
}
}
if (isHttps)
{
SslStream sslStream = null;
//If this proxy uses another external proxy then create a tunnel request for HTTPS connections
if (useHttpsProxy)
{
client = new TcpClient(server.UpStreamEndPoint);
await client.ConnectAsync(externalHttpsProxy.HostName, externalHttpsProxy.Port);
stream = new CustomBufferedStream(client.GetStream(), server.BufferSize);
using (var writer = new StreamWriter(stream, Encoding.ASCII, server.BufferSize, true) { NewLine = ProxyConstants.NewLine })
{
await writer.WriteLineAsync($"CONNECT {remoteHostName}:{remotePort} HTTP/{httpVersion}");
await writer.WriteLineAsync($"Host: {remoteHostName}:{remotePort}");
await writer.WriteLineAsync("Connection: Keep-Alive");
if (!string.IsNullOrEmpty(externalHttpsProxy.UserName) && externalHttpsProxy.Password != null)
{
await writer.WriteLineAsync("Proxy-Connection: keep-alive");
await writer.WriteLineAsync("Proxy-Authorization" + ": Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes(externalHttpsProxy.UserName + ":" + externalHttpsProxy.Password)));
}
await writer.WriteLineAsync();
await writer.FlushAsync();
writer.Close();
}
using (var reader = new CustomBinaryReader(stream, server.BufferSize))
{
var result = await reader.ReadLineAsync();
if (!new[] { "200 OK", "connection established" }.Any(s => result.ContainsIgnoreCase(s)))
{
throw new Exception("Upstream proxy failed to create a secure tunnel");
}
await reader.ReadAndIgnoreAllLinesAsync();
}
}
else
{
client = new TcpClient(server.UpStreamEndPoint);
await client.ConnectAsync(remoteHostName, remotePort);
stream = new CustomBufferedStream(client.GetStream(), server.BufferSize);
}
try
{
sslStream = new SslStream(stream, true, server.ValidateServerCertificate,
server.SelectClientCertificate);
await sslStream.AuthenticateAsClientAsync(remoteHostName, null, server.SupportedSslProtocols, server.CheckCertificateRevocation);
stream = new CustomBufferedStream(sslStream, server.BufferSize);
}
catch
{
sslStream?.Close();
sslStream?.Dispose();
throw;
}
}
else
{
if (useHttpProxy)
{
client = new TcpClient(server.UpStreamEndPoint);
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
{
ResponseHeaders = new Dictionary<string, HttpHeader>
{
{"Proxy-Authenticate", new HttpHeader("Proxy-Authenticate", "Basic realm=\"TitaniumProxy\"")},
{"Proxy-Connection", new HttpHeader("Proxy-Connection", "close")}
{ "Proxy-Authenticate", new HttpHeader("Proxy-Authenticate", "Basic realm=\"TitaniumProxy\"") },
{ "Proxy-Connection", new HttpHeader("Proxy-Connection", "close") }
}
};
await WriteResponseHeaders(clientStreamWriter, response);
......
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Network;
using System.Linq;
using System.Security.Authentication;
using Titanium.Web.Proxy.Network.Tcp;
using System.Security.Cryptography.X509Certificates;
namespace Titanium.Web.Proxy
{
......@@ -34,8 +35,21 @@ namespace Titanium.Web.Proxy
/// </summary>
private Action<Exception> exceptionFunc;
/// <summary>
/// Backing field for corresponding public property
/// </summary>
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>
/// A object that creates tcp connection to server
/// </summary>
......@@ -50,8 +64,7 @@ namespace Titanium.Web.Proxy
/// <summary>
/// Set firefox to use default system proxy
/// </summary>
private FireFoxProxySettingsManager firefoxProxySettingsManager
= new FireFoxProxySettingsManager();
private FireFoxProxySettingsManager firefoxProxySettingsManager = new FireFoxProxySettingsManager();
#endif
/// <summary>
......@@ -125,6 +138,12 @@ namespace Titanium.Web.Proxy
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>
/// Does this proxy uses the HTTP protocol 100 continue behaviour strictly?
/// Broken 100 contunue implementations on server/client may cause problems if enabled
......@@ -224,18 +243,18 @@ namespace Titanium.Web.Proxy
/// List of supported Ssl versions
/// </summary>
public SslProtocols SupportedSslProtocols { get; set; } = SslProtocols.Tls
| SslProtocols.Tls11 | SslProtocols.Tls12 | SslProtocols.Ssl3;
| SslProtocols.Tls11 | SslProtocols.Tls12 | SslProtocols.Ssl3;
/// <summary>
/// Total number of active client connections
/// </summary>
public int ClientConnectionCount { get; private set; }
public int ClientConnectionCount => clientConnectionCount;
/// <summary>
/// Total number of active server connections
/// </summary>
public int ServerConnectionCount { get; internal set; }
public int ServerConnectionCount => serverConnectionCount;
/// <summary>
/// Constructor
......@@ -381,8 +400,7 @@ namespace Titanium.Web.Proxy
#if !DEBUG
firefoxProxySettingsManager.AddFirefox();
#endif
Console.WriteLine("Set endpoint at Ip {0} and port: {1} as System HTTPS Proxy",
endPoint.IpAddress, endPoint.Port);
Console.WriteLine("Set endpoint at Ip {0} and port: {1} as System HTTPS Proxy", endPoint.IpAddress, endPoint.Port);
}
/// <summary>
......@@ -596,13 +614,10 @@ namespace Titanium.Web.Proxy
{
Task.Run(async () =>
{
ClientConnectionCount++;
Interlocked.Increment(ref clientConnectionCount);
//This line is important!
//contributors please don't remove it without discussion
//It helps to avoid eventual deterioration of performance due to TCP port exhaustion
//due to default TCP CLOSE_WAIT timeout for 4 minutes
tcpClient.LingerState = new LingerOption(true, 0);
tcpClient.ReceiveTimeout = ConnectionTimeOutSeconds * 1000;
tcpClient.SendTimeout = ConnectionTimeOutSeconds * 1000;
try
{
......@@ -617,8 +632,23 @@ namespace Titanium.Web.Proxy
}
finally
{
ClientConnectionCount--;
tcpClient?.Close();
Interlocked.Decrement(ref clientConnectionCount);
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
endPoint.Listener.Server.Close();
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;
using System.Net.Security;
using System.Net.Sockets;
using System.Security.Authentication;
using Titanium.Web.Proxy.Exceptions;
using System.Threading.Tasks;
using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Exceptions;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Shared;
using Titanium.Web.Proxy.Http;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Network.Tcp;
using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy
{
......@@ -23,14 +23,18 @@ namespace Titanium.Web.Proxy
/// </summary>
partial class ProxyServer
{
//This is called when client is aware of proxy
//So for HTTPS requests client would send CONNECT header to negotiate a secure tcp tunnel via proxy
/// <summary>
/// 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)
{
CustomBufferedStream clientStream = new CustomBufferedStream(tcpClient.GetStream(), BufferSize);
var disposed = false;
clientStream.ReadTimeout = ConnectionTimeOutSeconds * 1000;
clientStream.WriteTimeout = ConnectionTimeOutSeconds * 1000;
var clientStream = new CustomBufferedStream(tcpClient.GetStream(), BufferSize);
var clientStreamReader = new CustomBinaryReader(clientStream, BufferSize);
var clientStreamWriter = new StreamWriter(clientStream) { NewLine = ProxyConstants.NewLine };
......@@ -44,7 +48,6 @@ namespace Titanium.Web.Proxy
if (string.IsNullOrEmpty(httpCmd))
{
Dispose(clientStream, clientStreamReader, clientStreamWriter, null);
return;
}
......@@ -99,7 +102,6 @@ namespace Titanium.Web.Proxy
if (await CheckAuthorization(clientStreamWriter, connectRequestHeaders) == false)
{
Dispose(clientStream, clientStreamReader, clientStreamWriter, null);
return;
}
......@@ -111,22 +113,23 @@ namespace Titanium.Web.Proxy
{
sslStream = new SslStream(clientStream, true);
var certificate = endPoint.GenericCertificate ?? CertificateManager.CreateCertificate(httpRemoteUri.Host, false);
var certificate = endPoint.GenericCertificate ??
CertificateManager.CreateCertificate(httpRemoteUri.Host, false);
//Successfully managed to authenticate the client using the fake certificate
await sslStream.AuthenticateAsServerAsync(certificate, false,
SupportedSslProtocols, false);
//HTTPS server created - we can now decrypt the client's traffic
clientStream = new CustomBufferedStream(sslStream, BufferSize);
clientStreamReader.Dispose();
clientStreamReader = new CustomBinaryReader(clientStream, BufferSize);
clientStreamWriter = new StreamWriter(clientStream) { NewLine = ProxyConstants.NewLine };
}
catch
{
sslStream?.Dispose();
Dispose(clientStream, clientStreamReader, clientStreamWriter, null);
return;
}
......@@ -138,6 +141,7 @@ namespace Titanium.Web.Proxy
{
//Siphon out CONNECT request headers
await clientStreamReader.ReadAndIgnoreAllLinesAsync();
//write back successfull CONNECT response
await WriteConnectResponse(clientStreamWriter, version);
......@@ -147,243 +151,82 @@ namespace Titanium.Web.Proxy
false,
clientStream, tcpConnectionFactory);
Dispose(clientStream, clientStreamReader, clientStreamWriter, null);
return;
}
//Now create the request
await HandleHttpSessionRequest(tcpClient, httpCmd, clientStream, clientStreamReader, clientStreamWriter,
httpRemoteUri.Scheme == Uri.UriSchemeHttps ? httpRemoteUri.Host : null, endPoint, connectRequestHeaders);
disposed = await HandleHttpSessionRequest(tcpClient, httpCmd, clientStream, clientStreamReader, clientStreamWriter,
httpRemoteUri.Scheme == Uri.UriSchemeHttps ? httpRemoteUri.Host : null, endPoint,
connectRequestHeaders);
}
catch (Exception e)
{
ExceptionFunc(new Exception("Error whilst authorizing request", e));
}
catch (Exception)
finally
{
Dispose(clientStream,
clientStreamReader,
clientStreamWriter, null);
if (!disposed)
{
Dispose(clientStream, clientStreamReader, clientStreamWriter, null);
}
}
}
//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>
/// 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)
{
CustomBufferedStream clientStream = new CustomBufferedStream(tcpClient.GetStream(), BufferSize);
clientStream.ReadTimeout = ConnectionTimeOutSeconds * 1000;
clientStream.WriteTimeout = ConnectionTimeOutSeconds * 1000;
bool disposed = false;
var clientStream = new CustomBufferedStream(tcpClient.GetStream(), BufferSize);
CustomBinaryReader clientStreamReader = null;
StreamWriter clientStreamWriter = null;
if (endPoint.EnableSsl)
try
{
var sslStream = new SslStream(clientStream, true);
if (endPoint.EnableSsl)
{
var sslStream = new SslStream(clientStream, true);
clientStream = new CustomBufferedStream(sslStream, BufferSize);
//implement in future once SNI supported by SSL stream, for now use the same certificate
var certificate = CertificateManager.CreateCertificate(endPoint.GenericCertificateName, false);
//implement in future once SNI supported by SSL stream, for now use the same certificate
var certificate = CertificateManager.CreateCertificate(endPoint.GenericCertificateName, false);
try
{
//Successfully managed to authenticate the client using the fake certificate
await sslStream.AuthenticateAsServerAsync(certificate, false,
SslProtocols.Tls, false);
clientStream = new CustomBufferedStream(sslStream, BufferSize);
clientStreamReader = new CustomBinaryReader(clientStream, BufferSize);
clientStreamWriter = new StreamWriter(clientStream) { NewLine = ProxyConstants.NewLine };
//HTTPS server created - we can now decrypt the client's traffic
}
catch (Exception)
{
sslStream.Dispose();
Dispose(sslStream,
clientStreamReader,
clientStreamWriter, null);
return;
}
}
else
{
clientStreamReader = new CustomBinaryReader(clientStream, BufferSize);
clientStreamWriter = new StreamWriter(clientStream) { NewLine = ProxyConstants.NewLine };
}
//now read the request line
var httpCmd = await clientStreamReader.ReadLineAsync();
//Now create the request
await HandleHttpSessionRequest(tcpClient, httpCmd, clientStream, clientStreamReader, clientStreamWriter,
endPoint.EnableSsl ? endPoint.GenericCertificateName : null, endPoint, null);
}
/// <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;
//now read the request line
var httpCmd = await clientStreamReader.ReadLineAsync();
if (args.WebSession.Request.RequestUri.Scheme == "http")
{
if (GetCustomUpStreamHttpProxyFunc != null)
{
customUpStreamHttpProxy = await GetCustomUpStreamHttpProxyFunc(args);
}
}
else
{
if (GetCustomUpStreamHttpsProxyFunc != null)
{
customUpStreamHttpsProxy = await GetCustomUpStreamHttpsProxyFunc(args);
}
//Now create the request
disposed = await HandleHttpSessionRequest(tcpClient, httpCmd, clientStream, clientStreamReader, clientStreamWriter,
endPoint.EnableSsl ? endPoint.GenericCertificateName : null, endPoint, null);
}
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
finally
{
args.WebSession.Request.RequestLocked = true;
//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)
if (!disposed)
{
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)
{
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;
Dispose(clientStream, clientStreamReader, clientStreamWriter, null);
}
}
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>
/// 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>
/// <param name="client"></param>
/// <param name="httpCmd"></param>
......@@ -393,13 +236,13 @@ namespace Titanium.Web.Proxy
/// <param name="httpsHostName"></param>
/// <param name="endPoint"></param>
/// <param name="connectHeaders"></param>
/// <param name="customUpStreamHttpProxy"></param>
/// <param name="customUpStreamHttpsProxy"></param>
/// <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,
ProxyEndPoint endPoint, List<HttpHeader> connectHeaders)
{
bool disposed = false;
TcpConnection connection = null;
//Loop through each subsequest request on this particular client connection
......@@ -408,19 +251,14 @@ namespace Titanium.Web.Proxy
{
if (string.IsNullOrEmpty(httpCmd))
{
Dispose(clientStream,
clientStreamReader,
clientStreamWriter,
connection);
break;
}
var args = new SessionEventArgs(BufferSize, HandleHttpSessionResponse)
{
ProxyClient = { TcpClient = client },
WebSession = { ConnectHeaders = connectHeaders }
};
{
ProxyClient = { TcpClient = client },
WebSession = { ConnectHeaders = connectHeaders }
};
args.WebSession.ProcessId = new Lazy<int>(() =>
{
......@@ -458,7 +296,8 @@ namespace Titanium.Web.Proxy
//Read the request headers in to unique and non-unique header collections
await HeaderParser.ReadHeaders(clientStreamReader, args.WebSession.Request.NonUniqueRequestHeaders, args.WebSession.Request.RequestHeaders);
var httpRemoteUri = new Uri(httpsHostName == null ? httpCmdSplit[1]
var httpRemoteUri = new Uri(httpsHostName == null
? httpCmdSplit[1]
: string.Concat("https://", args.WebSession.Request.Host ?? httpsHostName, httpCmdSplit[1]));
args.WebSession.Request.RequestUri = httpRemoteUri;
......@@ -469,15 +308,11 @@ namespace Titanium.Web.Proxy
args.ProxyClient.ClientStreamReader = clientStreamReader;
args.ProxyClient.ClientStreamWriter = clientStreamWriter;
if (httpsHostName == null &&
await CheckAuthorization(clientStreamWriter,
if (httpsHostName == null &&
await CheckAuthorization(clientStreamWriter,
args.WebSession.Request.RequestHeaders.Values) == false)
{
Dispose(clientStream,
clientStreamReader,
clientStreamWriter,
connection);
args.Dispose();
break;
}
......@@ -487,15 +322,7 @@ namespace Titanium.Web.Proxy
//If user requested interception do it
if (BeforeRequest != null)
{
var invocationList = BeforeRequest.GetInvocationList();
var handlerTasks = new Task[invocationList.Length];
for (var i = 0; i < invocationList.Length; i++)
{
handlerTasks[i] = ((Func<object, SessionEventArgs, Task>)invocationList[i])(this, args);
}
await Task.WhenAll(handlerTasks);
await BeforeRequest.InvokeParallelAsync(this, args);
}
//if upgrading to websocket then relay the requet without reading the contents
......@@ -506,11 +333,7 @@ namespace Titanium.Web.Proxy
httpCmd, httpVersion, args.WebSession.Request.RequestHeaders, args.IsHttps,
clientStream, tcpConnectionFactory);
Dispose(clientStream,
clientStreamReader,
clientStreamWriter,
connection);
args.Dispose();
break;
}
......@@ -520,52 +343,210 @@ namespace Titanium.Web.Proxy
}
//construct the web request that we are going to issue on behalf of the client.
var result = await HandleHttpSessionRequestInternal(connection, args, false);
disposed = await HandleHttpSessionRequestInternal(connection, args, false);
if (result == false)
if (disposed)
{
//already disposed inside above method
args.Dispose();
break;
}
if (args.WebSession.Request.CancelRequest)
{
Dispose(clientStream,
clientStreamReader,
clientStreamWriter,
connection);
args.Dispose();
break;
}
//if connection is closing exit
if (args.WebSession.Response.ResponseKeepAlive == false)
{
Dispose(clientStream,
clientStreamReader,
clientStreamWriter,
connection);
args.Dispose();
break;
}
args.Dispose();
// read the next request
httpCmd = await clientStreamReader.ReadLineAsync();
}
catch (Exception e)
{
ExceptionFunc(new ProxyHttpException("Error occured whilst handling session request", e, args));
Dispose(clientStream,
clientStreamReader,
clientStreamWriter,
connection);
break;
}
}
if (!disposed)
{
Dispose(clientStream, clientStreamReader, clientStreamWriter, connection);
}
return true;
}
/// <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>
/// Write successfull CONNECT response to client
/// </summary>
......
using System;
using System.Collections.Generic;
using System.IO;
using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Compression;
using System.Threading;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Compression;
using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Exceptions;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Network.Tcp;
namespace Titanium.Web.Proxy
......@@ -22,7 +23,7 @@ namespace Titanium.Web.Proxy
/// Called asynchronously when a request was successfully and we received the response
/// </summary>
/// <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)
{
try
......@@ -40,28 +41,20 @@ namespace Titanium.Web.Proxy
//If user requested call back then do it
if (BeforeResponse != null && !args.WebSession.Response.ResponseLocked)
{
Delegate[] invocationList = BeforeResponse.GetInvocationList();
Task[] handlerTasks = new Task[invocationList.Length];
for (int i = 0; i < invocationList.Length; i++)
{
handlerTasks[i] = ((Func<object, SessionEventArgs, Task>)invocationList[i])(this, args);
}
await Task.WhenAll(handlerTasks);
await BeforeResponse.InvokeParallelAsync(this, args);
}
if (args.ReRequest)
{
if(args.WebSession.ServerConnection != null)
if (args.WebSession.ServerConnection != null)
{
args.WebSession.ServerConnection.Dispose();
ServerConnectionCount--;
Interlocked.Decrement(ref serverConnectionCount);
}
var connection = await GetServerConnection(args);
var result = await HandleHttpSessionRequestInternal(null, args, true);
return result;
var disposed = await HandleHttpSessionRequestInternal(null, args, true);
return disposed;
}
args.WebSession.Response.ResponseLocked = true;
......@@ -137,12 +130,10 @@ namespace Titanium.Web.Proxy
Dispose(args.ProxyClient.ClientStream, args.ProxyClient.ClientStreamReader,
args.ProxyClient.ClientStreamWriter, args.WebSession.ServerConnection);
return false;
return true;
}
args.Dispose();
return true;
return false;
}
/// <summary>
......@@ -240,15 +231,17 @@ namespace Titanium.Web.Proxy
StreamWriter clientStreamWriter,
TcpConnection serverConnection)
{
ServerConnectionCount--;
clientStream?.Close();
clientStream?.Dispose();
clientStreamReader?.Dispose();
clientStreamWriter?.Dispose();
serverConnection?.Dispose();
if (serverConnection != null)
{
serverConnection.Dispose();
Interlocked.Decrement(ref serverConnectionCount);
}
}
}
}
......@@ -7,9 +7,9 @@ namespace Titanium.Web.Proxy.Shared
/// </summary>
internal class ProxyConstants
{
internal static readonly char[] SpaceSplit = {' '};
internal static readonly char[] ColonSplit = {':'};
internal static readonly char[] SemiColonSplit = {';'};
internal static readonly char[] SpaceSplit = { ' ' };
internal static readonly char[] ColonSplit = { ':' };
internal static readonly char[] SemiColonSplit = { ';' };
internal static readonly byte[] NewLineBytes = Encoding.ASCII.GetBytes(NewLine);
......
......@@ -73,6 +73,7 @@
<Compile Include="EventArguments\CertificateSelectionEventArgs.cs" />
<Compile Include="EventArguments\CertificateValidationEventArgs.cs" />
<Compile Include="Extensions\ByteArrayExtensions.cs" />
<Compile Include="Extensions\FuncExtensions.cs" />
<Compile Include="Extensions\StringExtensions.cs" />
<Compile Include="Helpers\CustomBufferedStream.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