Commit 5a683862 authored by justcoding121's avatar justcoding121

Merge with Beta

Since Chrome HTTPS is broken in stable due to #199, lets merge with beta
asap
parent 45d3a905
...@@ -5,7 +5,7 @@ namespace Titanium.Web.Proxy.Examples.Basic ...@@ -5,7 +5,7 @@ namespace Titanium.Web.Proxy.Examples.Basic
{ {
public class Program public class Program
{ {
private static readonly ProxyTestController Controller = new ProxyTestController(); private static readonly ProxyTestController controller = new ProxyTestController();
public static void Main(string[] args) public static void Main(string[] args)
{ {
...@@ -15,13 +15,13 @@ namespace Titanium.Web.Proxy.Examples.Basic ...@@ -15,13 +15,13 @@ namespace Titanium.Web.Proxy.Examples.Basic
//Start proxy controller //Start proxy controller
Controller.StartProxy(); controller.StartProxy();
Console.WriteLine("Hit any key to exit.."); Console.WriteLine("Hit any key to exit..");
Console.WriteLine(); Console.WriteLine();
Console.Read(); Console.Read();
Controller.Stop(); controller.Stop();
} }
...@@ -30,7 +30,7 @@ namespace Titanium.Web.Proxy.Examples.Basic ...@@ -30,7 +30,7 @@ namespace Titanium.Web.Proxy.Examples.Basic
if (eventType != 2) return false; if (eventType != 2) return false;
try try
{ {
Controller.Stop(); controller.Stop();
} }
catch catch
{ {
......
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Net; using System.Net;
using System.Threading.Tasks; using System.Threading.Tasks;
using Titanium.Web.Proxy.EventArguments; using Titanium.Web.Proxy.EventArguments;
...@@ -9,16 +10,29 @@ namespace Titanium.Web.Proxy.Examples.Basic ...@@ -9,16 +10,29 @@ namespace Titanium.Web.Proxy.Examples.Basic
{ {
public class ProxyTestController public class ProxyTestController
{ {
private ProxyServer proxyServer; private readonly ProxyServer proxyServer;
//share requestBody outside handlers //share requestBody outside handlers
private Dictionary<Guid, string> requestBodyHistory; private readonly Dictionary<Guid, string> requestBodyHistory = new Dictionary<Guid, string>();
public ProxyTestController() public ProxyTestController()
{ {
proxyServer = new ProxyServer(); proxyServer = new ProxyServer();
//generate root certificate without storing it in file system
//proxyServer.CertificateEngine = Network.CertificateEngine.BouncyCastle;
//proxyServer.CertificateManager.CreateTrustedRootCertificate(false);
//proxyServer.CertificateManager.TrustRootCertificate();
proxyServer.ExceptionFunc = exception => Console.WriteLine(exception.Message);
proxyServer.TrustRootCertificate = true; proxyServer.TrustRootCertificate = true;
requestBodyHistory = new Dictionary<Guid, string>();
//optionally set the Certificate Engine
//Under Mono only BouncyCastle will be supported
//proxyServer.CertificateEngine = Network.CertificateEngine.BouncyCastle;
//optionally set the Root Certificate
//proxyServer.RootCertificate = new X509Certificate2("myCert.pfx", string.Empty, X509KeyStorageFlags.Exportable);
} }
public void StartProxy() public void StartProxy()
...@@ -28,21 +42,32 @@ namespace Titanium.Web.Proxy.Examples.Basic ...@@ -28,21 +42,32 @@ namespace Titanium.Web.Proxy.Examples.Basic
proxyServer.ServerCertificateValidationCallback += OnCertificateValidation; proxyServer.ServerCertificateValidationCallback += OnCertificateValidation;
proxyServer.ClientCertificateSelectionCallback += OnCertificateSelection; proxyServer.ClientCertificateSelectionCallback += OnCertificateSelection;
//Exclude Https addresses you don't want to proxy
//Usefull for clients that use certificate pinning
//for example dropbox.com
var explicitEndPoint = new ExplicitProxyEndPoint(IPAddress.Any, 8000, true) var explicitEndPoint = new ExplicitProxyEndPoint(IPAddress.Any, 8000, true)
{ {
// ExcludedHttpsHostNameRegex = new List<string>() { "google.com", "dropbox.com" } //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" }
//Include Https addresses you want to proxy (others will be excluded)
//for example 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")
}; };
//An explicit endpoint is where the client knows about the existance of a proxy //An explicit endpoint is where the client knows about the existence of a proxy
//So client sends request in a proxy friendly manner //So client sends request in a proxy friendly manner
proxyServer.AddEndPoint(explicitEndPoint); proxyServer.AddEndPoint(explicitEndPoint);
proxyServer.Start(); proxyServer.Start();
//Transparent endpoint is usefull for reverse proxying (client is not aware of the existance of proxy) //Transparent endpoint is useful for reverse proxying (client is not aware of the existence of proxy)
//A transparent endpoint usually requires a network router port forwarding HTTP(S) packets to this endpoint //A transparent endpoint usually requires a network router port forwarding HTTP(S) packets to this endpoint
//Currently do not support Server Name Indication (It is not currently supported by SslStream class) //Currently do not support Server Name Indication (It is not currently supported by SslStream class)
//That means that the transparent endpoint will always provide the same Generic Certificate to all HTTPS requests //That means that the transparent endpoint will always provide the same Generic Certificate to all HTTPS requests
...@@ -74,18 +99,22 @@ namespace Titanium.Web.Proxy.Examples.Basic ...@@ -74,18 +99,22 @@ namespace Titanium.Web.Proxy.Examples.Basic
proxyServer.ClientCertificateSelectionCallback -= OnCertificateSelection; proxyServer.ClientCertificateSelectionCallback -= OnCertificateSelection;
proxyServer.Stop(); proxyServer.Stop();
//remove the generated certificates
//proxyServer.CertificateManager.RemoveTrustedRootCertificates();
} }
//intecept & cancel, redirect or update requests //intecept & cancel redirect or update requests
public async Task OnRequest(object sender, SessionEventArgs e) public async Task OnRequest(object sender, SessionEventArgs e)
{ {
Console.WriteLine("Active Client Connections:" + ((ProxyServer) sender).ClientConnectionCount);
Console.WriteLine(e.WebSession.Request.Url); Console.WriteLine(e.WebSession.Request.Url);
////read request headers //read request headers
var requestHeaders = e.WebSession.Request.RequestHeaders; var requestHeaders = e.WebSession.Request.RequestHeaders;
var method = e.WebSession.Request.Method.ToUpper(); var method = e.WebSession.Request.Method.ToUpper();
if ((method == "POST" || method == "PUT" || method == "PATCH")) if (method == "POST" || method == "PUT" || method == "PATCH")
{ {
//Get/Set request body bytes //Get/Set request body bytes
byte[] bodyBytes = await e.GetRequestBody(); byte[] bodyBytes = await e.GetRequestBody();
...@@ -98,45 +127,49 @@ namespace Titanium.Web.Proxy.Examples.Basic ...@@ -98,45 +127,49 @@ namespace Titanium.Web.Proxy.Examples.Basic
requestBodyHistory[e.Id] = bodyString; requestBodyHistory[e.Id] = bodyString;
} }
//To cancel a request with a custom HTML content ////To cancel a request with a custom HTML content
//Filter URL ////Filter URL
if (e.WebSession.Request.RequestUri.AbsoluteUri.Contains("google.com")) //if (e.WebSession.Request.RequestUri.AbsoluteUri.Contains("google.com"))
{ //{
await e.Ok("<!DOCTYPE html>" + // await e.Ok("<!DOCTYPE html>" +
"<html><body><h1>" + // "<html><body><h1>" +
"Website Blocked" + // "Website Blocked" +
"</h1>" + // "</h1>" +
"<p>Blocked by titanium web proxy.</p>" + // "<p>Blocked by titanium web proxy.</p>" +
"</body>" + // "</body>" +
"</html>"); // "</html>");
} //}
//Redirect example
if (e.WebSession.Request.RequestUri.AbsoluteUri.Contains("wikipedia.org")) ////Redirect example
{ //if (e.WebSession.Request.RequestUri.AbsoluteUri.Contains("wikipedia.org"))
await e.Redirect("https://www.paypal.com"); //{
} // await e.Redirect("https://www.paypal.com");
//}
} }
//Modify response //Modify response
public async Task OnResponse(object sender, SessionEventArgs e) public async Task OnResponse(object sender, SessionEventArgs e)
{ {
if(requestBodyHistory.ContainsKey(e.Id)) Console.WriteLine("Active Server Connections:" + (sender as ProxyServer).ServerConnectionCount);
if (requestBodyHistory.ContainsKey(e.Id))
{ {
//access request body by looking up the shared dictionary using requestId //access request body by looking up the shared dictionary using requestId
var requestBody = requestBodyHistory[e.Id]; var requestBody = requestBodyHistory[e.Id];
} }
//read response headers //read response headers
var responseHeaders = e.WebSession.Response.ResponseHeaders; var responseHeaders = e.WebSession.Response.ResponseHeaders;
// print out process id of current session // print out process id of current session
Console.WriteLine($"PID: {e.WebSession.ProcessId.Value}"); //Console.WriteLine($"PID: {e.WebSession.ProcessId.Value}");
//if (!e.ProxySession.Request.Host.Equals("medeczane.sgk.gov.tr")) return; //if (!e.ProxySession.Request.Host.Equals("medeczane.sgk.gov.tr")) return;
if (e.WebSession.Request.Method == "GET" || e.WebSession.Request.Method == "POST") if (e.WebSession.Request.Method == "GET" || e.WebSession.Request.Method == "POST")
{ {
if (e.WebSession.Response.ResponseStatusCode == "200") if (e.WebSession.Response.ResponseStatusCode == "200")
{ {
if (e.WebSession.Response.ContentType!=null && e.WebSession.Response.ContentType.Trim().ToLower().Contains("text/html")) if (e.WebSession.Response.ContentType != null && e.WebSession.Response.ContentType.Trim().ToLower().Contains("text/html"))
{ {
byte[] bodyBytes = await e.GetResponseBody(); byte[] bodyBytes = await e.GetResponseBody();
await e.SetResponseBody(bodyBytes); await e.SetResponseBody(bodyBytes);
...@@ -176,4 +209,4 @@ namespace Titanium.Web.Proxy.Examples.Basic ...@@ -176,4 +209,4 @@ namespace Titanium.Web.Proxy.Examples.Basic
return Task.FromResult(0); return Task.FromResult(0);
} }
} }
} }
\ No newline at end of file
...@@ -25,7 +25,7 @@ ...@@ -25,7 +25,7 @@
<Prefer32Bit>false</Prefer32Bit> <Prefer32Bit>false</Prefer32Bit>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType> <DebugType>none</DebugType>
<Optimize>true</Optimize> <Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath> <OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE;NET45</DefineConstants> <DefineConstants>TRACE;NET45</DefineConstants>
...@@ -33,6 +33,7 @@ ...@@ -33,6 +33,7 @@
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion> <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<Prefer32Bit>false</Prefer32Bit> <Prefer32Bit>false</Prefer32Bit>
<DebugSymbols>false</DebugSymbols>
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>
<StartupObject /> <StartupObject />
......
Doneness: Doneness:
- [ ] Build is okay - I made sure that this change is building successfully. - [ ] Build is okay - I made sure that this change is building successfully.
- [ ] No Bugs - I made sure that this change is working properly as expected. It does'nt have any bugs that you are aware of. - [ ] No Bugs - I made sure that this change is working properly as expected. It doesn't have any bugs that you are aware of.
- [ ] Branching - If this is not a hotfix, I am making this request against develop branch - [ ] Branching - If this is not a hotfix, I am making this request against develop branch
Titanium Titanium
======== ========
A light weight http(s) proxy server written in C# A light weight HTTP(S) proxy server written in C#
![Build Status](https://ci.appveyor.com/api/projects/status/rvlxv8xgj0m7lkr4?svg=true) ![Build Status](https://ci.appveyor.com/api/projects/status/rvlxv8xgj0m7lkr4?svg=true)
...@@ -11,10 +11,10 @@ Kindly report only issues/bugs here . For programming help or questions use [Sta ...@@ -11,10 +11,10 @@ Kindly report only issues/bugs here . For programming help or questions use [Sta
Features Features
======== ========
* Supports Http(s) and most features of HTTP 1.1 * Supports HTTP(S) and most features of HTTP 1.1
* Support redirect/block/update requests * Support redirect/block/update requests
* Supports updating response * Supports updating response
* Safely relays WebSocket 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
...@@ -23,7 +23,7 @@ Features ...@@ -23,7 +23,7 @@ Features
Usage Usage
===== =====
Refer the HTTP Proxy Server library in your project, look up Test project to learn usage. Refer the HTTP Proxy Server library in your project, look up Test project to learn usage. ([Wiki & Contribution guidelines](https://github.com/justcoding121/Titanium-Web-Proxy/wiki))
Install by nuget: Install by nuget:
...@@ -38,150 +38,171 @@ For stable releases on [stable branch](https://github.com/justcoding121/Titanium ...@@ -38,150 +38,171 @@ For stable releases on [stable branch](https://github.com/justcoding121/Titanium
Setup HTTP proxy: Setup HTTP proxy:
```csharp ```csharp
var proxyServer = new ProxyServer(); var proxyServer = new ProxyServer();
//locally trust root certificate used by this proxy //locally trust root certificate used by this proxy
proxyServer.TrustRootCertificate = true; proxyServer.TrustRootCertificate = true;
proxyServer.BeforeRequest += OnRequest; //optionally set the Certificate Engine
proxyServer.BeforeResponse += OnResponse; //Under Mono only BouncyCastle will be supported
proxyServer.ServerCertificateValidationCallback += OnCertificateValidation; //proxyServer.CertificateEngine = Network.CertificateEngine.BouncyCastle;
proxyServer.ClientCertificateSelectionCallback += OnCertificateSelection;
proxyServer.BeforeRequest += OnRequest;
proxyServer.BeforeResponse += OnResponse;
//Exclude Https addresses you don't want to proxy proxyServer.ServerCertificateValidationCallback += OnCertificateValidation;
//Usefull for clients that use certificate pinning proxyServer.ClientCertificateSelectionCallback += OnCertificateSelection;
//for example dropbox.com
var explicitEndPoint = new ExplicitProxyEndPoint(IPAddress.Any, 8000, true)
{ var explicitEndPoint = new ExplicitProxyEndPoint(IPAddress.Any, 8000, true)
// ExcludedHttpsHostNameRegex = new List<string>() { "google.com", "dropbox.com" } {
}; //Exclude HTTPS addresses you don't want to proxy
//Useful for clients that use certificate pinning
//An explicit endpoint is where the client knows about the existance of a proxy //for example dropbox.com
//So client sends request in a proxy friendly manner // ExcludedHttpsHostNameRegex = new List<string>() { "google.com", "dropbox.com" }
proxyServer.AddEndPoint(explicitEndPoint);
proxyServer.Start(); //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
//Transparent endpoint is usefull for reverse proxying (client is not aware of the existance of proxy) // GenericCertificate = new X509Certificate2(Path.Combine(System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), "genericcert.pfx"), "password")
//A transparent endpoint usually requires a network router port forwarding HTTP(S) packets to this endpoint };
//Currently do not support Server Name Indication (It is not currently supported by SslStream class)
//That means that the transparent endpoint will always provide the same Generic Certificate to all HTTPS requests //An explicit endpoint is where the client knows about the existence of a proxy
//In this example only google.com will work for HTTPS requests //So client sends request in a proxy friendly manner
//Other sites will receive a certificate mismatch warning on browser proxyServer.AddEndPoint(explicitEndPoint);
var transparentEndPoint = new TransparentProxyEndPoint(IPAddress.Any, 8001, true) proxyServer.Start();
{
GenericCertificateName = "google.com" //Warning! Transparent endpoint is not tested end to end
}; //Transparent endpoint is useful for reverse proxy (client is not aware of the existence of proxy)
proxyServer.AddEndPoint(transparentEndPoint); //A transparent endpoint usually requires a network router port forwarding HTTP(S) packets to this endpoint
//Currently do not support Server Name Indication (It is not currently supported by SslStream class)
//proxyServer.UpStreamHttpProxy = new ExternalProxy() { HostName = "localhost", Port = 8888 }; //That means that the transparent endpoint will always provide the same Generic Certificate to all HTTPS requests
//proxyServer.UpStreamHttpsProxy = new ExternalProxy() { HostName = "localhost", Port = 8888 }; //In this example only google.com will work for HTTPS requests
//Other sites will receive a certificate mismatch warning on browser
foreach (var endPoint in proxyServer.ProxyEndPoints) var transparentEndPoint = new TransparentProxyEndPoint(IPAddress.Any, 8001, true)
Console.WriteLine("Listening on '{0}' endpoint at Ip {1} and port: {2} ", {
endPoint.GetType().Name, endPoint.IpAddress, endPoint.Port); GenericCertificateName = "google.com"
};
//Only explicit proxies can be set as system proxy! proxyServer.AddEndPoint(transparentEndPoint);
proxyServer.SetAsSystemHttpProxy(explicitEndPoint);
proxyServer.SetAsSystemHttpsProxy(explicitEndPoint); //proxyServer.UpStreamHttpProxy = new ExternalProxy() { HostName = "localhost", Port = 8888 };
//proxyServer.UpStreamHttpsProxy = new ExternalProxy() { HostName = "localhost", Port = 8888 };
//wait here (You can use something else as a wait function, I am using this as a demo)
Console.Read(); foreach (var endPoint in proxyServer.ProxyEndPoints)
Console.WriteLine("Listening on '{0}' endpoint at Ip {1} and port: {2} ",
//Unsubscribe & Quit endPoint.GetType().Name, endPoint.IpAddress, endPoint.Port);
proxyServer.BeforeRequest -= OnRequest;
proxyServer.BeforeResponse -= OnResponse; //Only explicit proxies can be set as system proxy!
proxyServer.ServerCertificateValidationCallback -= OnCertificateValidation; proxyServer.SetAsSystemHttpProxy(explicitEndPoint);
proxyServer.ClientCertificateSelectionCallback -= OnCertificateSelection; proxyServer.SetAsSystemHttpsProxy(explicitEndPoint);
proxyServer.Stop(); //wait here (You can use something else as a wait function, I am using this as a demo)
Console.Read();
//Unsubscribe & Quit
proxyServer.BeforeRequest -= OnRequest;
proxyServer.BeforeResponse -= OnResponse;
proxyServer.ServerCertificateValidationCallback -= OnCertificateValidation;
proxyServer.ClientCertificateSelectionCallback -= OnCertificateSelection;
proxyServer.Stop();
``` ```
Sample request and response event handlers Sample request and response event handlers
```csharp ```csharp
public async Task OnRequest(object sender, SessionEventArgs e)
{ //To access requestBody from OnResponse handler
Console.WriteLine(e.WebSession.Request.Url); private Dictionary<Guid, string> requestBodyHistory = new Dictionary<Guid, string>();
////read request headers public async Task OnRequest(object sender, SessionEventArgs e)
var requestHeaders = e.WebSession.Request.RequestHeaders; {
Console.WriteLine(e.WebSession.Request.Url);
var method = e.WebSession.Request.Method.ToUpper();
if ((method == "POST" || method == "PUT" || method == "PATCH")) ////read request headers
{ var requestHeaders = e.WebSession.Request.RequestHeaders;
//Get/Set request body bytes
byte[] bodyBytes = await e.GetRequestBody(); var method = e.WebSession.Request.Method.ToUpper();
await e.SetRequestBody(bodyBytes); if ((method == "POST" || method == "PUT" || method == "PATCH"))
{
//Get/Set request body as string //Get/Set request body bytes
string bodyString = await e.GetRequestBodyAsString(); byte[] bodyBytes = await e.GetRequestBody();
await e.SetRequestBodyString(bodyString); await e.SetRequestBody(bodyBytes);
} //Get/Set request body as string
string bodyString = await e.GetRequestBodyAsString();
//To cancel a request with a custom HTML content await e.SetRequestBodyString(bodyString);
//Filter URL
if (e.WebSession.Request.RequestUri.AbsoluteUri.Contains("google.com")) //store request Body/request headers etc with request Id as key
{ //so that you can find it from response handler using request Id
await e.Ok("<!DOCTYPE html>" + requestBodyHistory[e.Id] = bodyString;
"<html><body><h1>" + }
"Website Blocked" +
"</h1>" + //To cancel a request with a custom HTML content
"<p>Blocked by titanium web proxy.</p>" + //Filter URL
"</body>" + if (e.WebSession.Request.RequestUri.AbsoluteUri.Contains("google.com"))
"</html>"); {
} await e.Ok("<!DOCTYPE html>" +
//Redirect example "<html><body><h1>" +
if (e.WebSession.Request.RequestUri.AbsoluteUri.Contains("wikipedia.org")) "Website Blocked" +
{ "</h1>" +
await e.Redirect("https://www.paypal.com"); "<p>Blocked by titanium web proxy.</p>" +
} "</body>" +
} "</html>");
}
//Modify response //Redirect example
public async Task OnResponse(object sender, SessionEventArgs e) if (e.WebSession.Request.RequestUri.AbsoluteUri.Contains("wikipedia.org"))
{ {
//read response headers await e.Redirect("https://www.paypal.com");
var responseHeaders = e.WebSession.Response.ResponseHeaders; }
}
//if (!e.ProxySession.Request.Host.Equals("medeczane.sgk.gov.tr")) return;
if (e.WebSession.Request.Method == "GET" || e.WebSession.Request.Method == "POST") //Modify response
{ public async Task OnResponse(object sender, SessionEventArgs e)
if (e.WebSession.Response.ResponseStatusCode == "200") {
{ //read response headers
if (e.WebSession.Response.ContentType!=null && e.WebSession.Response.ContentType.Trim().ToLower().Contains("text/html")) var responseHeaders = e.WebSession.Response.ResponseHeaders;
{
byte[] bodyBytes = await e.GetResponseBody(); //if (!e.ProxySession.Request.Host.Equals("medeczane.sgk.gov.tr")) return;
await e.SetResponseBody(bodyBytes); if (e.WebSession.Request.Method == "GET" || e.WebSession.Request.Method == "POST")
{
string body = await e.GetResponseBodyAsString(); if (e.WebSession.Response.ResponseStatusCode == "200")
await e.SetResponseBodyString(body); {
} if (e.WebSession.Response.ContentType!=null && e.WebSession.Response.ContentType.Trim().ToLower().Contains("text/html"))
} {
} byte[] bodyBytes = await e.GetResponseBody();
} await e.SetResponseBody(bodyBytes);
/// Allows overriding default certificate validation logic string body = await e.GetResponseBodyAsString();
public Task OnCertificateValidation(object sender, CertificateValidationEventArgs e) await e.SetResponseBodyString(body);
{ }
//set IsValid to true/false based on Certificate Errors }
if (e.SslPolicyErrors == System.Net.Security.SslPolicyErrors.None) }
e.IsValid = true;
//access request body/request headers etc by looking up using requestId
return Task.FromResult(0); if(requestBodyHistory.ContainsKey(e.Id))
} {
var requestBody = requestBodyHistory[e.Id];
/// Allows overriding default client certificate selection logic during mutual authentication }
public Task OnCertificateSelection(object sender, CertificateSelectionEventArgs e) }
{
//set e.clientCertificate to override /// Allows overriding default certificate validation logic
public Task OnCertificateValidation(object sender, CertificateValidationEventArgs e)
return Task.FromResult(0); {
} //set IsValid to true/false based on Certificate Errors
if (e.SslPolicyErrors == System.Net.Security.SslPolicyErrors.None)
e.IsValid = true;
return Task.FromResult(0);
}
/// Allows overriding default client certificate selection logic during mutual authentication
public Task OnCertificateSelection(object sender, CertificateSelectionEventArgs e)
{
//set e.clientCertificate to override
return Task.FromResult(0);
}
``` ```
Future roadmap Future road map (Pull requests are welcome!)
============ ============
* Support Server Name Indication (SNI) for transparent endpoints * Support Server Name Indication (SNI) for transparent endpoints
* Support HTTP 2.0 * Support HTTP 2.0
......
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Titanium.Web.Proxy.IntegrationTests")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Titanium.Web.Proxy.IntegrationTests")]
[assembly: AssemblyCopyright("Copyright © 2017")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("32231301-b0fb-4f9e-98df-b3e8a88f4c16")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Net;
using System.Threading.Tasks;
using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Models;
using System.Net.Http;
using System.Diagnostics;
namespace Titanium.Web.Proxy.IntegrationTests
{
[TestClass]
public class SslTests
{
[TestMethod]
public void TestSsl()
{
//expand this to stress test to find
//why in long run proxy becomes unresponsive as per issue #184
var testUrl = "https://google.com";
int proxyPort = 8086;
var proxy = new ProxyTestController();
proxy.StartProxy(proxyPort);
using (var client = CreateHttpClient(testUrl, proxyPort))
{
var response = client.GetAsync(new Uri(testUrl)).Result;
}
}
private HttpClient CreateHttpClient(string url, int localProxyPort)
{
var handler = new HttpClientHandler
{
Proxy = new WebProxy($"http://localhost:{localProxyPort}", false),
UseProxy = true,
};
var client = new HttpClient(handler);
return client;
}
}
public class ProxyTestController
{
private readonly ProxyServer proxyServer;
public ProxyTestController()
{
proxyServer = new ProxyServer();
proxyServer.TrustRootCertificate = true;
}
public void StartProxy(int proxyPort)
{
proxyServer.BeforeRequest += OnRequest;
proxyServer.BeforeResponse += OnResponse;
proxyServer.ServerCertificateValidationCallback += OnCertificateValidation;
proxyServer.ClientCertificateSelectionCallback += OnCertificateSelection;
var explicitEndPoint = new ExplicitProxyEndPoint(IPAddress.Any, proxyPort, true);
//An explicit endpoint is where the client knows about the existance of a proxy
//So client sends request in a proxy friendly manner
proxyServer.AddEndPoint(explicitEndPoint);
proxyServer.Start();
foreach (var endPoint in proxyServer.ProxyEndPoints)
Console.WriteLine("Listening on '{0}' endpoint at Ip {1} and port: {2} ",
endPoint.GetType().Name, endPoint.IpAddress, endPoint.Port);
}
public void Stop()
{
proxyServer.BeforeRequest -= OnRequest;
proxyServer.BeforeResponse -= OnResponse;
proxyServer.ServerCertificateValidationCallback -= OnCertificateValidation;
proxyServer.ClientCertificateSelectionCallback -= OnCertificateSelection;
proxyServer.Stop();
}
//intecept & cancel, redirect or update requests
public async Task OnRequest(object sender, SessionEventArgs e)
{
Debug.WriteLine(e.WebSession.Request.Url);
await Task.FromResult(0);
}
//Modify response
public async Task OnResponse(object sender, SessionEventArgs e)
{
await Task.FromResult(0);
}
/// <summary>
/// Allows overriding default certificate validation logic
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
public Task OnCertificateValidation(object sender, CertificateValidationEventArgs e)
{
//set IsValid to true/false based on Certificate Errors
if (e.SslPolicyErrors == System.Net.Security.SslPolicyErrors.None)
{
e.IsValid = true;
}
return Task.FromResult(0);
}
/// <summary>
/// Allows overriding default client certificate selection logic during mutual authentication
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
public Task OnCertificateSelection(object sender, CertificateSelectionEventArgs e)
{
//set e.clientCertificate to override
return Task.FromResult(0);
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{32231301-B0FB-4F9E-98DF-B3E8A88F4C16}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Titanium.Web.Proxy.IntegrationTests</RootNamespace>
<AssemblyName>Titanium.Web.Proxy.IntegrationTests</AssemblyName>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
<ReferencePath>$(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages</ReferencePath>
<IsCodedUITest>False</IsCodedUITest>
<TestProjectType>UnitTest</TestProjectType>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Net.Http" />
</ItemGroup>
<Choose>
<When Condition="('$(VisualStudioVersion)' == '10.0' or '$(VisualStudioVersion)' == '') and '$(TargetFrameworkVersion)' == 'v3.5'">
<ItemGroup>
<Reference Include="Microsoft.VisualStudio.QualityTools.UnitTestFramework, Version=10.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" />
</ItemGroup>
</When>
<Otherwise>
<ItemGroup>
<Reference Include="Microsoft.VisualStudio.QualityTools.UnitTestFramework" />
</ItemGroup>
</Otherwise>
</Choose>
<ItemGroup>
<Compile Include="SslTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Titanium.Web.Proxy\Titanium.Web.Proxy.csproj">
<Project>{8d73a1be-868c-42d2-9ece-f32cc1a02906}</Project>
<Name>Titanium.Web.Proxy</Name>
</ProjectReference>
</ItemGroup>
<Choose>
<When Condition="'$(VisualStudioVersion)' == '10.0' And '$(IsCodedUITest)' == 'True'">
<ItemGroup>
<Reference Include="Microsoft.VisualStudio.QualityTools.CodedUITestFramework, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<Private>False</Private>
</Reference>
<Reference Include="Microsoft.VisualStudio.TestTools.UITest.Common, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<Private>False</Private>
</Reference>
<Reference Include="Microsoft.VisualStudio.TestTools.UITest.Extension, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<Private>False</Private>
</Reference>
<Reference Include="Microsoft.VisualStudio.TestTools.UITesting, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<Private>False</Private>
</Reference>
</ItemGroup>
</When>
</Choose>
<Import Project="$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets" Condition="Exists('$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets')" />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>
\ No newline at end of file
...@@ -9,7 +9,7 @@ namespace Titanium.Web.Proxy.UnitTests ...@@ -9,7 +9,7 @@ namespace Titanium.Web.Proxy.UnitTests
[TestClass] [TestClass]
public class CertificateManagerTests public class CertificateManagerTests
{ {
private readonly static string[] hostNames private static readonly string[] hostNames
= new string[] { "facebook.com", "youtube.com", "google.com", = new string[] { "facebook.com", "youtube.com", "google.com",
"bing.com", "yahoo.com"}; "bing.com", "yahoo.com"};
...@@ -20,8 +20,7 @@ namespace Titanium.Web.Proxy.UnitTests ...@@ -20,8 +20,7 @@ namespace Titanium.Web.Proxy.UnitTests
{ {
var tasks = new List<Task>(); var tasks = new List<Task>();
var mgr = new CertificateManager("Titanium", "Titanium Root Certificate Authority", var mgr = new CertificateManager(new Lazy<Action<Exception>>(() => (e => { })).Value);
new Lazy<Action<Exception>>(() => (e => { })).Value);
mgr.ClearIdleCertificates(1); mgr.ClearIdleCertificates(1);
......
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
......
...@@ -34,6 +34,12 @@ ...@@ -34,6 +34,12 @@
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
</PropertyGroup> </PropertyGroup>
<PropertyGroup>
<SignAssembly>true</SignAssembly>
</PropertyGroup>
<PropertyGroup>
<AssemblyOriginatorKeyFile>StrongNameKey.snk</AssemblyOriginatorKeyFile>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="System" /> <Reference Include="System" />
</ItemGroup> </ItemGroup>
...@@ -60,6 +66,9 @@ ...@@ -60,6 +66,9 @@
<Name>Titanium.Web.Proxy</Name> <Name>Titanium.Web.Proxy</Name>
</ProjectReference> </ProjectReference>
</ItemGroup> </ItemGroup>
<ItemGroup>
<None Include="StrongNameKey.snk" />
</ItemGroup>
<Choose> <Choose>
<When Condition="'$(VisualStudioVersion)' == '10.0' And '$(IsCodedUITest)' == 'True'"> <When Condition="'$(VisualStudioVersion)' == '10.0' And '$(IsCodedUITest)' == 'True'">
<ItemGroup> <ItemGroup>
......
 
Microsoft Visual Studio Solution File, Format Version 12.00 Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14 # Visual Studio 14
VisualStudioVersion = 14.0.25123.0 VisualStudioVersion = 14.0.25420.1
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Examples", "Examples", "{B6DBABDC-C985-4872-9C38-B4E5079CBC4B}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Examples", "Examples", "{B6DBABDC-C985-4872-9C38-B4E5079CBC4B}"
EndProject EndProject
...@@ -33,6 +33,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{BC1E0789 ...@@ -33,6 +33,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{BC1E0789
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Titanium.Web.Proxy.UnitTests", "Tests\Titanium.Web.Proxy.UnitTests\Titanium.Web.Proxy.UnitTests.csproj", "{B517E3D0-D03B-436F-AB03-34BA0D5321AF}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Titanium.Web.Proxy.UnitTests", "Tests\Titanium.Web.Proxy.UnitTests\Titanium.Web.Proxy.UnitTests.csproj", "{B517E3D0-D03B-436F-AB03-34BA0D5321AF}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Titanium.Web.Proxy.IntegrationTests", "Tests\Titanium.Web.Proxy.IntegrationTests\Titanium.Web.Proxy.IntegrationTests.csproj", "{32231301-B0FB-4F9E-98DF-B3E8A88F4C16}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
...@@ -51,6 +53,10 @@ Global ...@@ -51,6 +53,10 @@ Global
{B517E3D0-D03B-436F-AB03-34BA0D5321AF}.Debug|Any CPU.Build.0 = Debug|Any CPU {B517E3D0-D03B-436F-AB03-34BA0D5321AF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B517E3D0-D03B-436F-AB03-34BA0D5321AF}.Release|Any CPU.ActiveCfg = Release|Any CPU {B517E3D0-D03B-436F-AB03-34BA0D5321AF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B517E3D0-D03B-436F-AB03-34BA0D5321AF}.Release|Any CPU.Build.0 = Release|Any CPU {B517E3D0-D03B-436F-AB03-34BA0D5321AF}.Release|Any CPU.Build.0 = Release|Any CPU
{32231301-B0FB-4F9E-98DF-B3E8A88F4C16}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{32231301-B0FB-4F9E-98DF-B3E8A88F4C16}.Debug|Any CPU.Build.0 = Debug|Any CPU
{32231301-B0FB-4F9E-98DF-B3E8A88F4C16}.Release|Any CPU.ActiveCfg = Release|Any CPU
{32231301-B0FB-4F9E-98DF-B3E8A88F4C16}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
...@@ -58,6 +64,7 @@ Global ...@@ -58,6 +64,7 @@ Global
GlobalSection(NestedProjects) = preSolution GlobalSection(NestedProjects) = preSolution
{F3B7E553-1904-4E80-BDC7-212342B5C952} = {B6DBABDC-C985-4872-9C38-B4E5079CBC4B} {F3B7E553-1904-4E80-BDC7-212342B5C952} = {B6DBABDC-C985-4872-9C38-B4E5079CBC4B}
{B517E3D0-D03B-436F-AB03-34BA0D5321AF} = {BC1E0789-D348-49CF-8B67-5E99D50EDF64} {B517E3D0-D03B-436F-AB03-34BA0D5321AF} = {BC1E0789-D348-49CF-8B67-5E99D50EDF64}
{32231301-B0FB-4F9E-98DF-B3E8A88F4C16} = {BC1E0789-D348-49CF-8B67-5E99D50EDF64}
EndGlobalSection EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
EnterpriseLibraryConfigurationToolBinariesPath = .1.505.2\lib\NET35 EnterpriseLibraryConfigurationToolBinariesPath = .1.505.2\lib\NET35
......
<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:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=BC/@EntryIndexedValue">BC</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
using System; using System;
using System.Linq;
using System.Net.Security; using System.Net.Security;
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks; using System.Threading.Tasks;
...@@ -18,19 +17,20 @@ namespace Titanium.Web.Proxy ...@@ -18,19 +17,20 @@ namespace Titanium.Web.Proxy
/// <param name="sslPolicyErrors"></param> /// <param name="sslPolicyErrors"></param>
/// <returns></returns> /// <returns></returns>
internal bool ValidateServerCertificate( internal bool ValidateServerCertificate(
object sender, object sender,
X509Certificate certificate, X509Certificate certificate,
X509Chain chain, X509Chain chain,
SslPolicyErrors sslPolicyErrors) SslPolicyErrors sslPolicyErrors)
{ {
//if user callback is registered then do it //if user callback is registered then do it
if (ServerCertificateValidationCallback != null) if (ServerCertificateValidationCallback != null)
{ {
var args = new CertificateValidationEventArgs(); var args = new CertificateValidationEventArgs
{
args.Certificate = certificate; Certificate = certificate,
args.Chain = chain; Chain = chain,
args.SslPolicyErrors = sslPolicyErrors; SslPolicyErrors = sslPolicyErrors
};
Delegate[] invocationList = ServerCertificateValidationCallback.GetInvocationList(); Delegate[] invocationList = ServerCertificateValidationCallback.GetInvocationList();
...@@ -38,7 +38,7 @@ namespace Titanium.Web.Proxy ...@@ -38,7 +38,7 @@ namespace Titanium.Web.Proxy
for (int i = 0; i < invocationList.Length; i++) for (int i = 0; i < invocationList.Length; i++)
{ {
handlerTasks[i] = ((Func<object, CertificateValidationEventArgs, Task>)invocationList[i])(null, args); handlerTasks[i] = ((Func<object, CertificateValidationEventArgs, Task>) invocationList[i])(null, args);
} }
Task.WhenAll(handlerTasks).Wait(); Task.WhenAll(handlerTasks).Wait();
...@@ -73,7 +73,6 @@ namespace Titanium.Web.Proxy ...@@ -73,7 +73,6 @@ namespace Titanium.Web.Proxy
string[] acceptableIssuers) string[] acceptableIssuers)
{ {
X509Certificate clientCertificate = null; X509Certificate clientCertificate = null;
var customSslStream = sender as SslStream;
if (acceptableIssuers != null && if (acceptableIssuers != null &&
acceptableIssuers.Length > 0 && acceptableIssuers.Length > 0 &&
...@@ -100,20 +99,22 @@ namespace Titanium.Web.Proxy ...@@ -100,20 +99,22 @@ namespace Titanium.Web.Proxy
//If user call back is registered //If user call back is registered
if (ClientCertificateSelectionCallback != null) if (ClientCertificateSelectionCallback != null)
{ {
var args = new CertificateSelectionEventArgs(); var args = new CertificateSelectionEventArgs
{
TargetHost = targetHost,
LocalCertificates = localCertificates,
RemoteCertificate = remoteCertificate,
AcceptableIssuers = acceptableIssuers,
ClientCertificate = clientCertificate
};
args.TargetHost = targetHost;
args.LocalCertificates = localCertificates;
args.RemoteCertificate = remoteCertificate;
args.AcceptableIssuers = acceptableIssuers;
args.ClientCertificate = clientCertificate;
Delegate[] invocationList = ClientCertificateSelectionCallback.GetInvocationList(); Delegate[] invocationList = ClientCertificateSelectionCallback.GetInvocationList();
Task[] handlerTasks = new Task[invocationList.Length]; Task[] handlerTasks = new Task[invocationList.Length];
for (int i = 0; i < invocationList.Length; i++) for (int i = 0; i < invocationList.Length; i++)
{ {
handlerTasks[i] = ((Func<object, CertificateSelectionEventArgs, Task>)invocationList[i])(null, args); handlerTasks[i] = ((Func<object, CertificateSelectionEventArgs, Task>) invocationList[i])(null, args);
} }
Task.WhenAll(handlerTasks).Wait(); Task.WhenAll(handlerTasks).Wait();
......
...@@ -13,8 +13,6 @@ ...@@ -13,8 +13,6 @@
return new GZipCompression(); return new GZipCompression();
case "deflate": case "deflate":
return new DeflateCompression(); return new DeflateCompression();
case "zlib":
return new ZlibCompression();
default: default:
return null; return null;
} }
......
...@@ -15,7 +15,7 @@ namespace Titanium.Web.Proxy.Compression ...@@ -15,7 +15,7 @@ namespace Titanium.Web.Proxy.Compression
{ {
using (var zip = new DeflateStream(ms, CompressionMode.Compress, true)) using (var zip = new DeflateStream(ms, CompressionMode.Compress, true))
{ {
await zip.WriteAsync(responseBody, 0, responseBody.Length); await zip.WriteAsync(responseBody, 0, responseBody.Length);
} }
return ms.ToArray(); return ms.ToArray();
......
using Ionic.Zlib; using System.IO;
using System.IO; using System.IO.Compression;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Compression namespace Titanium.Web.Proxy.Compression
...@@ -15,7 +15,7 @@ namespace Titanium.Web.Proxy.Compression ...@@ -15,7 +15,7 @@ namespace Titanium.Web.Proxy.Compression
{ {
using (var zip = new GZipStream(ms, CompressionMode.Compress, true)) using (var zip = new GZipStream(ms, CompressionMode.Compress, true))
{ {
await zip.WriteAsync(responseBody, 0, responseBody.Length); await zip.WriteAsync(responseBody, 0, responseBody.Length);
} }
return ms.ToArray(); return ms.ToArray();
......
using Ionic.Zlib;
using System.IO;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Compression
{
/// <summary>
/// concrete implementation of zlib compression
/// </summary>
internal class ZlibCompression : ICompression
{
public async Task<byte[]> Compress(byte[] responseBody)
{
using (var ms = new MemoryStream())
{
using (var zip = new ZlibStream(ms, CompressionMode.Compress, true))
{
await zip.WriteAsync(responseBody, 0, responseBody.Length);
}
return ms.ToArray();
}
}
}
}
...@@ -7,14 +7,12 @@ ...@@ -7,14 +7,12 @@
{ {
internal IDecompression Create(string type) internal IDecompression Create(string type)
{ {
switch(type) switch (type)
{ {
case "gzip": case "gzip":
return new GZipDecompression(); return new GZipDecompression();
case "deflate": case "deflate":
return new DeflateDecompression(); return new DeflateDecompression();
case "zlib":
return new ZlibDecompression();
default: default:
return new DefaultDecompression(); return new DefaultDecompression();
} }
......
...@@ -2,7 +2,6 @@ ...@@ -2,7 +2,6 @@
namespace Titanium.Web.Proxy.Decompression namespace Titanium.Web.Proxy.Decompression
{ {
/// <summary> /// <summary>
/// When no compression is specified just return the byte array /// When no compression is specified just return the byte array
/// </summary> /// </summary>
......
using Ionic.Zlib; using System.IO;
using System.IO; using System.IO.Compression;
using System.Threading.Tasks; using System.Threading.Tasks;
using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Decompression namespace Titanium.Web.Proxy.Decompression
{ {
...@@ -12,8 +11,7 @@ namespace Titanium.Web.Proxy.Decompression ...@@ -12,8 +11,7 @@ namespace Titanium.Web.Proxy.Decompression
{ {
public async Task<byte[]> Decompress(byte[] compressedArray, int bufferSize) public async Task<byte[]> Decompress(byte[] compressedArray, int bufferSize)
{ {
var stream = new MemoryStream(compressedArray); using (var stream = new MemoryStream(compressedArray))
using (var decompressor = new DeflateStream(stream, CompressionMode.Decompress)) using (var decompressor = new DeflateStream(stream, CompressionMode.Decompress))
{ {
var buffer = new byte[bufferSize]; var buffer = new byte[bufferSize];
...@@ -23,7 +21,7 @@ namespace Titanium.Web.Proxy.Decompression ...@@ -23,7 +21,7 @@ namespace Titanium.Web.Proxy.Decompression
int read; int read;
while ((read = await decompressor.ReadAsync(buffer, 0, buffer.Length)) > 0) while ((read = await decompressor.ReadAsync(buffer, 0, buffer.Length)) > 0)
{ {
await output.WriteAsync(buffer, 0, read); output.Write(buffer, 0, read);
} }
return output.ToArray(); return output.ToArray();
......
using System.IO; using System.IO;
using System.IO.Compression; using System.IO.Compression;
using System.Threading.Tasks; using System.Threading.Tasks;
using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Decompression namespace Titanium.Web.Proxy.Decompression
{ {
...@@ -20,8 +19,9 @@ namespace Titanium.Web.Proxy.Decompression ...@@ -20,8 +19,9 @@ namespace Titanium.Web.Proxy.Decompression
int read; int read;
while ((read = await decompressor.ReadAsync(buffer, 0, buffer.Length)) > 0) while ((read = await decompressor.ReadAsync(buffer, 0, buffer.Length)) > 0)
{ {
await output.WriteAsync(buffer, 0, read); output.Write(buffer, 0, read);
} }
return output.ToArray(); return output.ToArray();
} }
} }
......
...@@ -7,6 +7,6 @@ namespace Titanium.Web.Proxy.Decompression ...@@ -7,6 +7,6 @@ namespace Titanium.Web.Proxy.Decompression
/// </summary> /// </summary>
internal interface IDecompression internal interface IDecompression
{ {
Task<byte[]> Decompress(byte[] compressedArray, int bufferSize); Task<byte[]> Decompress(byte[] compressedArray, int bufferSize);
} }
} }
using Ionic.Zlib;
using System.IO;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Decompression
{
/// <summary>
/// concrete implemetation of zlib de-compression
/// </summary>
internal class ZlibDecompression : IDecompression
{
public async Task<byte[]> Decompress(byte[] compressedArray, int bufferSize)
{
var memoryStream = new MemoryStream(compressedArray);
using (var decompressor = new ZlibStream(memoryStream, CompressionMode.Decompress))
{
var buffer = new byte[bufferSize];
using (var output = new MemoryStream())
{
int read;
while ((read = await decompressor.ReadAsync(buffer, 0, buffer.Length)) > 0)
{
await output.WriteAsync(buffer, 0, read);
}
return output.ToArray();
}
}
}
}
}
...@@ -8,13 +8,34 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -8,13 +8,34 @@ namespace Titanium.Web.Proxy.EventArguments
/// </summary> /// </summary>
public class CertificateSelectionEventArgs : EventArgs public class CertificateSelectionEventArgs : EventArgs
{ {
/// <summary>
/// Sender object.
/// </summary>
public object Sender { get; internal set; } public object Sender { get; internal set; }
/// <summary>
/// Target host.
/// </summary>
public string TargetHost { get; internal set; } public string TargetHost { get; internal set; }
/// <summary>
/// Local certificates.
/// </summary>
public X509CertificateCollection LocalCertificates { get; internal set; } public X509CertificateCollection LocalCertificates { get; internal set; }
/// <summary>
/// Remote certificate.
/// </summary>
public X509Certificate RemoteCertificate { get; internal set; } public X509Certificate RemoteCertificate { get; internal set; }
/// <summary>
/// Acceptable issuers.
/// </summary>
public string[] AcceptableIssuers { get; internal set; } public string[] AcceptableIssuers { get; internal set; }
/// <summary>
/// Client Certificate.
/// </summary>
public X509Certificate ClientCertificate { get; set; } public X509Certificate ClientCertificate { get; set; }
} }
} }
...@@ -7,17 +7,26 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -7,17 +7,26 @@ namespace Titanium.Web.Proxy.EventArguments
/// <summary> /// <summary>
/// An argument passed on to the user for validating the server certificate during SSL authentication /// An argument passed on to the user for validating the server certificate during SSL authentication
/// </summary> /// </summary>
public class CertificateValidationEventArgs : EventArgs, IDisposable public class CertificateValidationEventArgs : EventArgs
{ {
/// <summary>
/// Certificate
/// </summary>
public X509Certificate Certificate { get; internal set; } public X509Certificate Certificate { get; internal set; }
/// <summary>
/// Certificate chain
/// </summary>
public X509Chain Chain { get; internal set; } public X509Chain Chain { get; internal set; }
/// <summary>
/// SSL policy errors.
/// </summary>
public SslPolicyErrors SslPolicyErrors { get; internal set; } public SslPolicyErrors SslPolicyErrors { get; internal set; }
/// <summary>
/// is a valid certificate?
/// </summary>
public bool IsValid { get; set; } public bool IsValid { get; set; }
public void Dispose()
{
}
} }
} }
...@@ -22,7 +22,6 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -22,7 +22,6 @@ namespace Titanium.Web.Proxy.EventArguments
/// </summary> /// </summary>
public class SessionEventArgs : EventArgs, IDisposable public class SessionEventArgs : EventArgs, IDisposable
{ {
/// <summary> /// <summary>
/// Size of Buffers used by this object /// Size of Buffers used by this object
/// </summary> /// </summary>
...@@ -44,20 +43,20 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -44,20 +43,20 @@ namespace Titanium.Web.Proxy.EventArguments
/// </summary> /// </summary>
public Guid Id => WebSession.RequestId; public Guid Id => WebSession.RequestId;
//Should we send a rerequest /// <summary>
public bool ReRequest /// Should we send a rerequest
{ /// </summary>
get; public bool ReRequest { get; set; }
set;
}
/// <summary> /// <summary>
/// Does this session uses SSL /// Does this session uses SSL
/// </summary> /// </summary>
public bool IsHttps => WebSession.Request.RequestUri.Scheme == Uri.UriSchemeHttps; public bool IsHttps => WebSession.Request.RequestUri.Scheme == Uri.UriSchemeHttps;
/// <summary>
public IPEndPoint ClientEndPoint => (IPEndPoint)ProxyClient.TcpClient.Client.RemoteEndPoint; /// Client End Point.
/// </summary>
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
...@@ -65,8 +64,14 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -65,8 +64,14 @@ namespace Titanium.Web.Proxy.EventArguments
/// </summary> /// </summary>
public HttpWebClient WebSession { get; set; } public HttpWebClient WebSession { get; set; }
/// <summary>
/// Are we using a custom upstream HTTP proxy?
/// </summary>
public ExternalProxy CustomUpStreamHttpProxyUsed { get; set; } public ExternalProxy CustomUpStreamHttpProxyUsed { get; set; }
/// <summary>
/// Are we using a custom upstream HTTPS proxy?
/// </summary>
public ExternalProxy CustomUpStreamHttpsProxyUsed { get; set; } public ExternalProxy CustomUpStreamHttpsProxyUsed { get; set; }
/// <summary> /// <summary>
...@@ -88,7 +93,7 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -88,7 +93,7 @@ namespace Titanium.Web.Proxy.EventArguments
{ {
//GET request don't have a request body to read //GET request don't have a request body to read
var method = WebSession.Request.Method.ToUpper(); var method = WebSession.Request.Method.ToUpper();
if ((method != "POST" && method != "PUT" && method != "PATCH")) if (method != "POST" && method != "PUT" && method != "PATCH")
{ {
throw new BodyNotFoundException("Request don't have a body. " + throw new BodyNotFoundException("Request don't have a body. " +
"Please verify that this request is a Http POST/PUT/PATCH and request " + "Please verify that this request is a Http POST/PUT/PATCH and request " +
...@@ -98,14 +103,13 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -98,14 +103,13 @@ namespace Titanium.Web.Proxy.EventArguments
//Caching check //Caching check
if (WebSession.Request.RequestBody == null) if (WebSession.Request.RequestBody == null)
{ {
//If chunked then its easy just read the whole body with the content length mentioned in the request header //If chunked then its easy just read the whole body with the content length mentioned in the request header
using (var requestBodyStream = new MemoryStream()) using (var requestBodyStream = new MemoryStream())
{ {
//For chunked request we need to read data as they arrive, until we reach a chunk end symbol //For chunked request we need to read data as they arrive, until we reach a chunk end symbol
if (WebSession.Request.IsChunked) if (WebSession.Request.IsChunked)
{ {
await this.ProxyClient.ClientStreamReader.CopyBytesToStreamChunked(bufferSize, requestBodyStream); await ProxyClient.ClientStreamReader.CopyBytesToStreamChunked(requestBodyStream);
} }
else else
{ {
...@@ -113,9 +117,8 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -113,9 +117,8 @@ namespace Titanium.Web.Proxy.EventArguments
if (WebSession.Request.ContentLength > 0) if (WebSession.Request.ContentLength > 0)
{ {
//If not chunked then its easy just read the amount of bytes mentioned in content length header of response //If not chunked then its easy just read the amount of bytes mentioned in content length header of response
await this.ProxyClient.ClientStreamReader.CopyBytesToStream(bufferSize, requestBodyStream, await ProxyClient.ClientStreamReader.CopyBytesToStream(bufferSize, requestBodyStream,
WebSession.Request.ContentLength); WebSession.Request.ContentLength);
} }
else if (WebSession.Request.HttpVersion.Major == 1 && WebSession.Request.HttpVersion.Minor == 0) else if (WebSession.Request.HttpVersion.Major == 1 && WebSession.Request.HttpVersion.Minor == 0)
{ {
...@@ -130,7 +133,6 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -130,7 +133,6 @@ namespace Titanium.Web.Proxy.EventArguments
//So that next time we can deliver body from cache //So that next time we can deliver body from cache
WebSession.Request.RequestBodyRead = true; WebSession.Request.RequestBodyRead = true;
} }
} }
/// <summary> /// <summary>
...@@ -146,7 +148,7 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -146,7 +148,7 @@ namespace Titanium.Web.Proxy.EventArguments
//If chuncked the read chunk by chunk until we hit chunk end symbol //If chuncked the read chunk by chunk until we hit chunk end symbol
if (WebSession.Response.IsChunked) if (WebSession.Response.IsChunked)
{ {
await WebSession.ServerConnection.StreamReader.CopyBytesToStreamChunked(bufferSize, responseBodyStream); await WebSession.ServerConnection.StreamReader.CopyBytesToStreamChunked(responseBodyStream);
} }
else else
{ {
...@@ -155,7 +157,6 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -155,7 +157,6 @@ namespace Titanium.Web.Proxy.EventArguments
//If not chunked then its easy just read the amount of bytes mentioned in content length header of response //If not chunked then its easy just read the amount of bytes mentioned in content length header of response
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)
{ {
...@@ -165,7 +166,6 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -165,7 +166,6 @@ namespace Titanium.Web.Proxy.EventArguments
WebSession.Response.ResponseBody = await GetDecompressedResponseBody(WebSession.Response.ContentEncoding, WebSession.Response.ResponseBody = await GetDecompressedResponseBody(WebSession.Response.ContentEncoding,
responseBodyStream.ToArray()); responseBodyStream.ToArray());
} }
//set this to true for caching //set this to true for caching
WebSession.Response.ResponseBodyRead = true; WebSession.Response.ResponseBodyRead = true;
...@@ -189,6 +189,7 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -189,6 +189,7 @@ namespace Titanium.Web.Proxy.EventArguments
} }
return WebSession.Request.RequestBody; return WebSession.Request.RequestBody;
} }
/// <summary> /// <summary>
/// Gets the request body as string /// Gets the request body as string
/// </summary> /// </summary>
...@@ -255,7 +256,6 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -255,7 +256,6 @@ namespace Titanium.Web.Proxy.EventArguments
} }
await SetRequestBody(WebSession.Request.Encoding.GetBytes(body)); await SetRequestBody(WebSession.Request.Encoding.GetBytes(body));
} }
/// <summary> /// <summary>
...@@ -287,7 +287,7 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -287,7 +287,7 @@ namespace Titanium.Web.Proxy.EventArguments
await GetResponseBody(); await GetResponseBody();
return WebSession.Response.ResponseBodyString ?? return WebSession.Response.ResponseBodyString ??
(WebSession.Response.ResponseBodyString = WebSession.Response.Encoding.GetString(WebSession.Response.ResponseBody)); (WebSession.Response.ResponseBodyString = WebSession.Response.Encoding.GetString(WebSession.Response.ResponseBody));
} }
/// <summary> /// <summary>
...@@ -406,11 +406,79 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -406,11 +406,79 @@ namespace Titanium.Web.Proxy.EventArguments
public async Task Ok(byte[] result, Dictionary<string, HttpHeader> headers) public async Task Ok(byte[] result, Dictionary<string, HttpHeader> headers)
{ {
var response = new OkResponse(); var response = new OkResponse();
if (headers != null && headers.Count > 0)
{
response.ResponseHeaders = headers;
}
response.HttpVersion = WebSession.Request.HttpVersion;
response.ResponseBody = result;
await Respond(response);
WebSession.Request.CancelRequest = true;
}
/// <summary>
/// Before request is made to server 
/// Respond with the specified HTML string to client
/// and ignore the request 
/// </summary>
/// <param name="html"></param>
/// <param name="status"></param>
public async Task GenericResponse(string html, HttpStatusCode status)
{
await GenericResponse(html, null, status);
}
/// <summary>
/// Before request is made to server 
/// Respond with the specified HTML string to client
/// and the specified status
/// and ignore the request 
/// </summary>
/// <param name="html"></param>
/// <param name="headers"></param>
/// <param name="status"></param>
public async Task GenericResponse(string html, Dictionary<string, HttpHeader> headers, HttpStatusCode status)
{
if (WebSession.Request.RequestLocked)
{
throw new Exception("You cannot call this function after request is made to server.");
}
if (html == null)
{
html = string.Empty;
}
var result = Encoding.Default.GetBytes(html);
await GenericResponse(result, headers, status);
}
/// <summary>
/// Before request is made to server
/// Respond with the specified byte[] to client
/// and the specified status
/// and ignore the request
/// </summary>
/// <param name="result"></param>
/// <param name="headers"></param>
/// <param name="status"></param>
/// <returns></returns>
public async Task GenericResponse(byte[] result, Dictionary<string, HttpHeader> headers, HttpStatusCode status)
{
var response = new GenericResponse(status);
if (headers != null && headers.Count > 0) if (headers != null && headers.Count > 0)
{ {
response.ResponseHeaders = headers; response.ResponseHeaders = headers;
} }
response.HttpVersion = WebSession.Request.HttpVersion; response.HttpVersion = WebSession.Request.HttpVersion;
response.ResponseBody = result; response.ResponseBody = result;
await Respond(response); await Respond(response);
...@@ -418,12 +486,17 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -418,12 +486,17 @@ namespace Titanium.Web.Proxy.EventArguments
WebSession.Request.CancelRequest = true; WebSession.Request.CancelRequest = true;
} }
/// <summary>
/// Redirect to URL.
/// </summary>
/// <param name="url"></param>
/// <returns></returns>
public async Task Redirect(string url) public async Task Redirect(string url)
{ {
var response = new RedirectResponse(); var response = new RedirectResponse();
response.HttpVersion = WebSession.Request.HttpVersion; response.HttpVersion = WebSession.Request.HttpVersion;
response.ResponseHeaders.Add("Location", new Models.HttpHeader("Location", url)); response.ResponseHeaders.Add("Location", new HttpHeader("Location", url));
response.ResponseBody = Encoding.ASCII.GetBytes(string.Empty); response.ResponseBody = Encoding.ASCII.GetBytes(string.Empty);
await Respond(response); await Respond(response);
...@@ -449,7 +522,6 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -449,7 +522,6 @@ namespace Titanium.Web.Proxy.EventArguments
/// </summary> /// </summary>
public void Dispose() public void Dispose()
{ {
} }
} }
} }
\ No newline at end of file
using System; namespace Titanium.Web.Proxy.Exceptions
namespace Titanium.Web.Proxy.Exceptions
{ {
/// <summary> /// <summary>
/// An expception thrown when body is unexpectedly empty /// An expception thrown when body is unexpectedly empty
/// </summary> /// </summary>
public class BodyNotFoundException : ProxyException public class BodyNotFoundException : ProxyException
{ {
/// <summary>
/// Constructor.
/// </summary>
/// <param name="message"></param>
public BodyNotFoundException(string message) public BodyNotFoundException(string message)
: base(message) : base(message)
{ {
} }
} }
} }
\ No newline at end of file
...@@ -25,4 +25,4 @@ namespace Titanium.Web.Proxy.Exceptions ...@@ -25,4 +25,4 @@ namespace Titanium.Web.Proxy.Exceptions
/// </summary> /// </summary>
public IEnumerable<HttpHeader> Headers { get; } public IEnumerable<HttpHeader> Headers { get; }
} }
} }
\ No newline at end of file
...@@ -24,4 +24,4 @@ namespace Titanium.Web.Proxy.Exceptions ...@@ -24,4 +24,4 @@ namespace Titanium.Web.Proxy.Exceptions
{ {
} }
} }
} }
\ No newline at end of file
...@@ -27,4 +27,4 @@ namespace Titanium.Web.Proxy.Exceptions ...@@ -27,4 +27,4 @@ namespace Titanium.Web.Proxy.Exceptions
/// </remarks> /// </remarks>
public SessionEventArgs SessionEventArgs { get; } public SessionEventArgs SessionEventArgs { get; }
} }
} }
\ No newline at end of file
...@@ -2,7 +2,10 @@ ...@@ -2,7 +2,10 @@
namespace Titanium.Web.Proxy.Extensions namespace Titanium.Web.Proxy.Extensions
{ {
public static class ByteArrayExtensions /// <summary>
/// Extension methods for Byte Arrays.
/// </summary>
internal static class ByteArrayExtensions
{ {
/// <summary> /// <summary>
/// Get the sub array from byte of data /// Get the sub array from byte of data
...@@ -12,12 +15,11 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -12,12 +15,11 @@ namespace Titanium.Web.Proxy.Extensions
/// <param name="index"></param> /// <param name="index"></param>
/// <param name="length"></param> /// <param name="length"></param>
/// <returns></returns> /// <returns></returns>
public static T[] SubArray<T>(this T[] data, int index, int length) internal static T[] SubArray<T>(this T[] data, int index, int length)
{ {
T[] result = new T[length]; var result = new T[length];
Array.Copy(data, index, result, 0, length); Array.Copy(data, index, result, 0, length);
return result; return result;
} }
} }
} }
using System.Text; using System;
using System.Text;
using Titanium.Web.Proxy.Http; using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Shared; using Titanium.Web.Proxy.Shared;
...@@ -29,7 +30,7 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -29,7 +30,7 @@ namespace Titanium.Web.Proxy.Extensions
foreach (var contentType in contentTypes) foreach (var contentType in contentTypes)
{ {
var encodingSplit = contentType.Split('='); var encodingSplit = contentType.Split('=');
if (encodingSplit.Length == 2 && encodingSplit[0].ToLower().Trim() == "charset") if (encodingSplit.Length == 2 && encodingSplit[0].Trim().Equals("charset", StringComparison.CurrentCultureIgnoreCase))
{ {
return Encoding.GetEncoding(encodingSplit[1]); return Encoding.GetEncoding(encodingSplit[1]);
} }
...@@ -45,4 +46,4 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -45,4 +46,4 @@ namespace Titanium.Web.Proxy.Extensions
return Encoding.GetEncoding("ISO-8859-1"); return Encoding.GetEncoding("ISO-8859-1");
} }
} }
} }
\ No newline at end of file
using System.Text; using System;
using System.Text;
using Titanium.Web.Proxy.Http; using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Shared; using Titanium.Web.Proxy.Shared;
...@@ -26,7 +27,7 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -26,7 +27,7 @@ namespace Titanium.Web.Proxy.Extensions
foreach (var contentType in contentTypes) foreach (var contentType in contentTypes)
{ {
var encodingSplit = contentType.Split('='); var encodingSplit = contentType.Split('=');
if (encodingSplit.Length == 2 && encodingSplit[0].ToLower().Trim() == "charset") if (encodingSplit.Length == 2 && encodingSplit[0].Trim().Equals("charset", StringComparison.CurrentCultureIgnoreCase))
{ {
return Encoding.GetEncoding(encodingSplit[1]); return Encoding.GetEncoding(encodingSplit[1]);
} }
...@@ -42,4 +43,4 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -42,4 +43,4 @@ namespace Titanium.Web.Proxy.Extensions
return Encoding.GetEncoding("ISO-8859-1"); return Encoding.GetEncoding("ISO-8859-1");
} }
} }
} }
\ No newline at end of file
...@@ -30,15 +30,15 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -30,15 +30,15 @@ namespace Titanium.Web.Proxy.Extensions
await input.CopyToAsync(output); await input.CopyToAsync(output);
} }
/// <summary> /// <summary>
/// copies the specified bytes to the stream from the input stream /// copies the specified bytes to the stream from the input stream
/// </summary> /// </summary>
/// <param name="streamReader"></param> /// <param name="streamReader"></param>
/// <param name="bufferSize"></param> /// <param name="bufferSize"></param>
/// <param name="stream"></param> /// <param name="stream"></param>
/// <param name="totalBytesToRead"></param> /// <param name="totalBytesToRead"></param>
/// <returns></returns> /// <returns></returns>
internal static async Task CopyBytesToStream(this CustomBinaryReader streamReader, int bufferSize, Stream stream, long totalBytesToRead) internal static async Task CopyBytesToStream(this CustomBinaryReader streamReader, int bufferSize, Stream stream, long totalBytesToRead)
{ {
var totalbytesRead = 0; var totalbytesRead = 0;
...@@ -46,7 +46,7 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -46,7 +46,7 @@ namespace Titanium.Web.Proxy.Extensions
while (totalbytesRead < totalBytesToRead) while (totalbytesRead < totalBytesToRead)
{ {
var buffer = await streamReader.ReadBytesAsync(bufferSize, bytesToRead); var buffer = await streamReader.ReadBytesAsync(bytesToRead);
if (buffer.Length == 0) if (buffer.Length == 0)
{ {
...@@ -65,14 +65,13 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -65,14 +65,13 @@ namespace Titanium.Web.Proxy.Extensions
} }
} }
/// <summary> /// <summary>
/// Copies the stream chunked /// Copies the stream chunked
/// </summary> /// </summary>
/// <param name="clientStreamReader"></param> /// <param name="clientStreamReader"></param>
/// <param name="bufferSize"></param> /// <param name="stream"></param>
/// <param name="stream"></param> /// <returns></returns>
/// <returns></returns> internal static async Task CopyBytesToStreamChunked(this CustomBinaryReader clientStreamReader, Stream stream)
internal static async Task CopyBytesToStreamChunked(this CustomBinaryReader clientStreamReader, int bufferSize, Stream stream)
{ {
while (true) while (true)
{ {
...@@ -81,7 +80,7 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -81,7 +80,7 @@ namespace Titanium.Web.Proxy.Extensions
if (chunkSize != 0) if (chunkSize != 0)
{ {
var buffer = await clientStreamReader.ReadBytesAsync(bufferSize, chunkSize); var buffer = await clientStreamReader.ReadBytesAsync(chunkSize);
await stream.WriteAsync(buffer, 0, buffer.Length); await stream.WriteAsync(buffer, 0, buffer.Length);
//chunk trail //chunk trail
await clientStreamReader.ReadLineAsync(); await clientStreamReader.ReadLineAsync();
...@@ -93,6 +92,7 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -93,6 +92,7 @@ namespace Titanium.Web.Proxy.Extensions
} }
} }
} }
/// <summary> /// <summary>
/// Writes the byte array body to the given stream; optionally chunked /// Writes the byte array body to the given stream; optionally chunked
/// </summary> /// </summary>
...@@ -112,17 +112,17 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -112,17 +112,17 @@ namespace Titanium.Web.Proxy.Extensions
} }
} }
/// <summary> /// <summary>
/// Copies the specified content length number of bytes to the output stream from the given inputs stream /// Copies the specified content length number of bytes to the output stream from the given inputs stream
/// optionally chunked /// optionally chunked
/// </summary> /// </summary>
/// <param name="inStreamReader"></param> /// <param name="inStreamReader"></param>
/// <param name="bufferSize"></param> /// <param name="bufferSize"></param>
/// <param name="outStream"></param> /// <param name="outStream"></param>
/// <param name="isChunked"></param> /// <param name="isChunked"></param>
/// <param name="contentLength"></param> /// <param name="contentLength"></param>
/// <returns></returns> /// <returns></returns>
internal static async Task WriteResponseBody(this CustomBinaryReader inStreamReader, int bufferSize, Stream outStream, bool isChunked, long contentLength) internal static async Task WriteResponseBody(this CustomBinaryReader inStreamReader, int bufferSize, Stream outStream, bool isChunked, long contentLength)
{ {
if (!isChunked) if (!isChunked)
{ {
...@@ -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];
...@@ -154,23 +154,22 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -154,23 +154,22 @@ namespace Titanium.Web.Proxy.Extensions
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
{ {
await WriteResponseBodyChunked(inStreamReader, bufferSize, outStream); await WriteResponseBodyChunked(inStreamReader, outStream);
} }
} }
/// <summary> /// <summary>
/// Copies the streams chunked /// Copies the streams chunked
/// </summary> /// </summary>
/// <param name="inStreamReader"></param> /// <param name="inStreamReader"></param>
/// <param name="bufferSize"></param> /// <param name="outStream"></param>
/// <param name="outStream"></param> /// <returns></returns>
/// <returns></returns> internal static async Task WriteResponseBodyChunked(this CustomBinaryReader inStreamReader, Stream outStream)
internal static async Task WriteResponseBodyChunked(this CustomBinaryReader inStreamReader, int bufferSize, Stream outStream)
{ {
while (true) while (true)
{ {
...@@ -179,7 +178,7 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -179,7 +178,7 @@ namespace Titanium.Web.Proxy.Extensions
if (chunkSize != 0) if (chunkSize != 0)
{ {
var buffer = await inStreamReader.ReadBytesAsync(bufferSize, chunkSize); var buffer = await inStreamReader.ReadBytesAsync(chunkSize);
var chunkHeadBytes = Encoding.ASCII.GetBytes(chunkSize.ToString("x2")); var chunkHeadBytes = Encoding.ASCII.GetBytes(chunkSize.ToString("x2"));
...@@ -199,6 +198,7 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -199,6 +198,7 @@ namespace Titanium.Web.Proxy.Extensions
} }
} }
} }
/// <summary> /// <summary>
/// Copies the given input bytes to output stream chunked /// Copies the given input bytes to output stream chunked
/// </summary> /// </summary>
...@@ -216,6 +216,5 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -216,6 +216,5 @@ namespace Titanium.Web.Proxy.Extensions
await outStream.WriteAsync(ProxyConstants.ChunkEnd, 0, ProxyConstants.ChunkEnd.Length); await outStream.WriteAsync(ProxyConstants.ChunkEnd, 0, ProxyConstants.ChunkEnd.Length);
} }
} }
} }
\ No newline at end of file
using System.Globalization;
namespace Titanium.Web.Proxy.Extensions
{
internal static class StringExtensions
{
internal static bool ContainsIgnoreCase(this string str, string value)
{
return CultureInfo.CurrentCulture.CompareInfo.IndexOf(str, value, CompareOptions.IgnoreCase) >= 0;
}
}
}
using System.Net.Sockets; using System.Net.Sockets;
using Titanium.Web.Proxy.Helpers;
namespace Titanium.Web.Proxy.Extensions namespace Titanium.Web.Proxy.Extensions
{ {
...@@ -12,11 +13,11 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -12,11 +13,11 @@ namespace Titanium.Web.Proxy.Extensions
internal static bool IsConnected(this Socket client) internal static bool IsConnected(this Socket client)
{ {
// This is how you can determine whether a socket is still connected. // This is how you can determine whether a socket is still connected.
bool blockingState = client.Blocking; var blockingState = client.Blocking;
try try
{ {
byte[] tmp = new byte[1]; var tmp = new byte[1];
client.Blocking = false; client.Blocking = false;
client.Send(tmp, 0, 0); client.Send(tmp, 0, 0);
...@@ -25,20 +26,32 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -25,20 +26,32 @@ namespace Titanium.Web.Proxy.Extensions
catch (SocketException e) catch (SocketException e)
{ {
// 10035 == WSAEWOULDBLOCK // 10035 == WSAEWOULDBLOCK
if (e.NativeErrorCode.Equals(10035)) return e.NativeErrorCode.Equals(10035);
{
return true;
}
else
{
return false;
}
} }
finally finally
{ {
client.Blocking = blockingState; client.Blocking = blockingState;
} }
} }
}
/// <summary>
/// Gets the local port from a native TCP row object.
/// </summary>
/// <param name="tcpRow">The TCP row.</param>
/// <returns>The local port</returns>
internal static int GetLocalPort(this NativeMethods.TcpRow tcpRow)
{
return (tcpRow.localPort1 << 8) + tcpRow.localPort2 + (tcpRow.localPort3 << 24) + (tcpRow.localPort4 << 16);
}
/// <summary>
/// Gets the remote port from a native TCP row object.
/// </summary>
/// <param name="tcpRow">The TCP row.</param>
/// <returns>The remote port</returns>
internal static int GetRemotePort(this NativeMethods.TcpRow tcpRow)
{
return (tcpRow.remotePort1 << 8) + tcpRow.remotePort2 + (tcpRow.remotePort3 << 24) + (tcpRow.remotePort4 << 16);
}
}
} }
...@@ -3,12 +3,9 @@ using System.Collections.Generic; ...@@ -3,12 +3,9 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Helpers namespace Titanium.Web.Proxy.Helpers
{ {
/// <summary> /// <summary>
/// A custom binary reader that would allo us to read string line by line /// A custom binary reader that would allo us to read string line by line
/// using the specified encoding /// using the specified encoding
...@@ -16,15 +13,20 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -16,15 +13,20 @@ namespace Titanium.Web.Proxy.Helpers
/// </summary> /// </summary>
internal class CustomBinaryReader : IDisposable internal class CustomBinaryReader : IDisposable
{ {
private Stream stream; private readonly CustomBufferedStream stream;
private Encoding encoding; private readonly int bufferSize;
private readonly byte[] staticBuffer;
private readonly Encoding encoding;
internal CustomBinaryReader(Stream stream) internal CustomBinaryReader(CustomBufferedStream stream, int bufferSize)
{ {
this.stream = stream; this.stream = stream;
staticBuffer = new byte[bufferSize];
this.bufferSize = bufferSize;
//default to UTF-8 //default to UTF-8
this.encoding = Encoding.UTF8; encoding = Encoding.UTF8;
} }
internal Stream BaseStream => stream; internal Stream BaseStream => stream;
...@@ -35,33 +37,41 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -35,33 +37,41 @@ namespace Titanium.Web.Proxy.Helpers
/// <returns></returns> /// <returns></returns>
internal async Task<string> ReadLineAsync() internal async Task<string> ReadLineAsync()
{ {
using (var readBuffer = new MemoryStream()) var lastChar = default(byte);
int bufferDataLength = 0;
// try to use the thread static buffer, usually it is enough
var buffer = staticBuffer;
while (stream.DataAvailable || await stream.FillBufferAsync())
{ {
var lastChar = default(char); var newChar = stream.ReadByteFromBuffer();
var buffer = new byte[1]; buffer[bufferDataLength] = newChar;
while ((await this.stream.ReadAsync(buffer, 0, 1)) > 0) //if new line
{ if (lastChar == '\r' && newChar == '\n')
//if new line {
if (lastChar == '\r' && buffer[0] == '\n') return encoding.GetString(buffer, 0, bufferDataLength - 1);
{ }
var result = readBuffer.ToArray(); //end of stream
return encoding.GetString(result.SubArray(0, result.Length - 1)); if (newChar == '\0')
} {
//end of stream return encoding.GetString(buffer, 0, bufferDataLength);
if (buffer[0] == '\0') }
{
return encoding.GetString(readBuffer.ToArray()); bufferDataLength++;
}
//store last char for new line comparison
await readBuffer.WriteAsync(buffer,0,1); lastChar = newChar;
//store last char for new line comparison if (bufferDataLength == buffer.Length)
lastChar = (char)buffer[0]; {
} ResizeBuffer(ref buffer, bufferDataLength * 2);
}
return encoding.GetString(readBuffer.ToArray());
} }
return encoding.GetString(buffer, 0, bufferDataLength);
} }
/// <summary> /// <summary>
...@@ -79,46 +89,72 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -79,46 +89,72 @@ namespace Titanium.Web.Proxy.Helpers
return requestLines; return requestLines;
} }
/// <summary>
/// Read until the last new line, ignores the result
/// </summary>
/// <returns></returns>
internal async Task ReadAndIgnoreAllLinesAsync()
{
while (!string.IsNullOrEmpty(await ReadLineAsync()))
{
}
}
/// <summary> /// <summary>
/// Read the specified number of raw bytes from the base stream /// Read the specified number of raw bytes from the base stream
/// </summary> /// </summary>
/// <param name="totalBytesToRead"></param> /// <param name="totalBytesToRead"></param>
/// <returns></returns> /// <returns></returns>
internal async Task<byte[]> ReadBytesAsync(int bufferSize, long totalBytesToRead) internal async Task<byte[]> ReadBytesAsync(long totalBytesToRead)
{ {
int bytesToRead = bufferSize; int bytesToRead = bufferSize;
var buffer = staticBuffer;
if (totalBytesToRead < bufferSize) if (totalBytesToRead < bufferSize)
bytesToRead = (int)totalBytesToRead; {
bytesToRead = (int) totalBytesToRead;
var buffer = new byte[bufferSize]; buffer = new byte[bytesToRead];
}
var bytesRead = 0; int bytesRead;
var totalBytesRead = 0; var totalBytesRead = 0;
using (var outStream = new MemoryStream()) while ((bytesRead = await stream.ReadAsync(buffer, totalBytesRead, bytesToRead)) > 0)
{ {
while ((bytesRead += await this.stream.ReadAsync(buffer, 0, bytesToRead)) > 0) totalBytesRead += bytesRead;
{
await outStream.WriteAsync(buffer, 0, bytesRead);
totalBytesRead += bytesRead;
if (totalBytesRead == totalBytesToRead) if (totalBytesRead == totalBytesToRead)
break; break;
bytesRead = 0; var remainingBytes = totalBytesToRead - totalBytesRead;
var remainingBytes = (totalBytesToRead - totalBytesRead); bytesToRead = Math.Min(bufferSize, (int) remainingBytes);
bytesToRead = remainingBytes > (long)bufferSize ? bufferSize : (int)remainingBytes;
if (totalBytesRead + bytesToRead > buffer.Length)
{
ResizeBuffer(ref buffer, Math.Min(totalBytesToRead, buffer.Length * 2));
} }
}
return outStream.ToArray(); if (totalBytesRead != buffer.Length)
{
//Normally this should not happen. Resize the buffer anyway
var newBuffer = new byte[totalBytesRead];
Buffer.BlockCopy(buffer, 0, newBuffer, 0, totalBytesRead);
buffer = newBuffer;
} }
return buffer;
} }
public void Dispose() public void Dispose()
{ {
}
private void ResizeBuffer(ref byte[] buffer, long size)
{
var newBuffer = new byte[size];
Buffer.BlockCopy(buffer, 0, newBuffer, 0, buffer.Length);
buffer = newBuffer;
} }
} }
} }
\ No newline at end of file
using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.Remoting;
using System.Threading;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Helpers
{
/// <summary>
/// A custom network stream inherited from stream
/// with an underlying buffer
/// </summary>
/// <seealso cref="System.IO.Stream" />
internal class CustomBufferedStream : Stream
{
private readonly Stream baseStream;
private readonly byte[] streamBuffer;
private int bufferLength;
private int bufferPos;
/// <summary>
/// Initializes a new instance of the <see cref="CustomBufferedStream"/> class.
/// </summary>
/// <param name="baseStream">The base stream.</param>
/// <param name="bufferSize">Size of the buffer.</param>
public CustomBufferedStream(Stream baseStream, int bufferSize)
{
this.baseStream = baseStream;
streamBuffer = new byte[bufferSize];
}
/// <summary>
/// When overridden in a derived class, clears all buffers for this stream and causes any buffered data to be written to the underlying device.
/// </summary>
public override void Flush()
{
baseStream.Flush();
}
/// <summary>
/// When overridden in a derived class, sets the position within the current stream.
/// </summary>
/// <param name="offset">A byte offset relative to the <paramref name="origin" /> parameter.</param>
/// <param name="origin">A value of type <see cref="T:System.IO.SeekOrigin" /> indicating the reference point used to obtain the new position.</param>
/// <returns>
/// The new position within the current stream.
/// </returns>
public override long Seek(long offset, SeekOrigin origin)
{
bufferLength = 0;
bufferPos = 0;
return baseStream.Seek(offset, origin);
}
/// <summary>
/// When overridden in a derived class, sets the length of the current stream.
/// </summary>
/// <param name="value">The desired length of the current stream in bytes.</param>
public override void SetLength(long value)
{
baseStream.SetLength(value);
}
/// <summary>
/// When overridden in a derived class, reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read.
/// </summary>
/// <param name="buffer">An array of bytes. When this method returns, the buffer contains the specified byte array with the values between <paramref name="offset" /> and (<paramref name="offset" /> + <paramref name="count" /> - 1) replaced by the bytes read from the current source.</param>
/// <param name="offset">The zero-based byte offset in <paramref name="buffer" /> at which to begin storing the data read from the current stream.</param>
/// <param name="count">The maximum number of bytes to be read from the current stream.</param>
/// <returns>
/// The total number of bytes read into the buffer. This can be less than the number of bytes requested if that many bytes are not currently available, or zero (0) if the end of the stream has been reached.
/// </returns>
public override int Read(byte[] buffer, int offset, int count)
{
if (bufferLength == 0)
{
FillBuffer();
}
int available = Math.Min(bufferLength, count);
if (available > 0)
{
Buffer.BlockCopy(streamBuffer, bufferPos, buffer, offset, available);
bufferPos += available;
bufferLength -= available;
}
return available;
}
/// <summary>
/// When overridden in a derived class, writes a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written.
/// </summary>
/// <param name="buffer">An array of bytes. This method copies <paramref name="count" /> bytes from <paramref name="buffer" /> to the current stream.</param>
/// <param name="offset">The zero-based byte offset in <paramref name="buffer" /> at which to begin copying bytes to the current stream.</param>
/// <param name="count">The number of bytes to be written to the current stream.</param>
public override void Write(byte[] buffer, int offset, int count)
{
baseStream.Write(buffer, offset, count);
}
/// <summary>
/// Begins an asynchronous read operation. (Consider using <see cref="M:System.IO.Stream.ReadAsync(System.Byte[],System.Int32,System.Int32)" /> instead; see the Remarks section.)
/// </summary>
/// <param name="buffer">The buffer to read the data into.</param>
/// <param name="offset">The byte offset in <paramref name="buffer" /> at which to begin writing data read from the stream.</param>
/// <param name="count">The maximum number of bytes to read.</param>
/// <param name="callback">An optional asynchronous callback, to be called when the read is complete.</param>
/// <param name="state">A user-provided object that distinguishes this particular asynchronous read request from other requests.</param>
/// <returns>
/// An <see cref="T:System.IAsyncResult" /> that represents the asynchronous read, which could still be pending.
/// </returns>
[DebuggerStepThrough]
public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
{
if (bufferLength > 0)
{
int available = Math.Min(bufferLength, count);
Buffer.BlockCopy(streamBuffer, bufferPos, buffer, offset, available);
bufferPos += available;
bufferLength -= available;
return new ReadAsyncResult(available);
}
return baseStream.BeginRead(buffer, offset, count, callback, state);
}
/// <summary>
/// Begins an asynchronous write operation. (Consider using <see cref="M:System.IO.Stream.WriteAsync(System.Byte[],System.Int32,System.Int32)" /> instead; see the Remarks section.)
/// </summary>
/// <param name="buffer">The buffer to write data from.</param>
/// <param name="offset">The byte offset in <paramref name="buffer" /> from which to begin writing.</param>
/// <param name="count">The maximum number of bytes to write.</param>
/// <param name="callback">An optional asynchronous callback, to be called when the write is complete.</param>
/// <param name="state">A user-provided object that distinguishes this particular asynchronous write request from other requests.</param>
/// <returns>
/// An IAsyncResult that represents the asynchronous write, which could still be pending.
/// </returns>
[DebuggerStepThrough]
public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
{
return baseStream.BeginWrite(buffer, offset, count, callback, state);
}
/// <summary>
/// Closes the current stream and releases any resources (such as sockets and file handles) associated with the current stream. Instead of calling this method, ensure that the stream is properly disposed.
/// </summary>
public override void Close()
{
baseStream.Close();
}
/// <summary>
/// Asynchronously reads the bytes from the current stream and writes them to another stream, using a specified buffer size and cancellation token.
/// </summary>
/// <param name="destination">The stream to which the contents of the current stream will be copied.</param>
/// <param name="bufferSize">The size, in bytes, of the buffer. This value must be greater than zero. The default size is 81920.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is <see cref="P:System.Threading.CancellationToken.None" />.</param>
/// <returns>
/// A task that represents the asynchronous copy operation.
/// </returns>
public override async Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken)
{
if (bufferLength > 0)
{
await destination.WriteAsync(streamBuffer, bufferPos, bufferLength, cancellationToken);
bufferLength = 0;
}
await baseStream.CopyToAsync(destination, bufferSize, cancellationToken);
}
/// <summary>
/// Creates an object that contains all the relevant information required to generate a proxy used to communicate with a remote object.
/// </summary>
/// <param name="requestedType">The <see cref="T:System.Type" /> of the object that the new <see cref="T:System.Runtime.Remoting.ObjRef" /> will reference.</param>
/// <returns>
/// Information required to generate a proxy.
/// </returns>
public override ObjRef CreateObjRef(Type requestedType)
{
return baseStream.CreateObjRef(requestedType);
}
/// <summary>
/// Waits for the pending asynchronous read to complete. (Consider using <see cref="M:System.IO.Stream.ReadAsync(System.Byte[],System.Int32,System.Int32)" /> instead; see the Remarks section.)
/// </summary>
/// <param name="asyncResult">The reference to the pending asynchronous request to finish.</param>
/// <returns>
/// The number of bytes read from the stream, between zero (0) and the number of bytes you requested. Streams return zero (0) only at the end of the stream, otherwise, they should block until at least one byte is available.
/// </returns>
[DebuggerStepThrough]
public override int EndRead(IAsyncResult asyncResult)
{
if (asyncResult is ReadAsyncResult)
{
return ((ReadAsyncResult) asyncResult).ReadBytes;
}
return baseStream.EndRead(asyncResult);
}
/// <summary>
/// Ends an asynchronous write operation. (Consider using <see cref="M:System.IO.Stream.WriteAsync(System.Byte[],System.Int32,System.Int32)" /> instead; see the Remarks section.)
/// </summary>
/// <param name="asyncResult">A reference to the outstanding asynchronous I/O request.</param>
[DebuggerStepThrough]
public override void EndWrite(IAsyncResult asyncResult)
{
baseStream.EndWrite(asyncResult);
}
/// <summary>
/// Asynchronously clears all buffers for this stream, causes any buffered data to be written to the underlying device, and monitors cancellation requests.
/// </summary>
/// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is <see cref="P:System.Threading.CancellationToken.None" />.</param>
/// <returns>
/// A task that represents the asynchronous flush operation.
/// </returns>
public override Task FlushAsync(CancellationToken cancellationToken)
{
return baseStream.FlushAsync(cancellationToken);
}
/// <summary>
/// Obtains a lifetime service object to control the lifetime policy for this instance.
/// </summary>
/// <returns>
/// An object of type <see cref="T:System.Runtime.Remoting.Lifetime.ILease" /> used to control the lifetime policy for this instance. This is the current lifetime service object for this instance if one exists; otherwise, a new lifetime service object initialized to the value of the <see cref="P:System.Runtime.Remoting.Lifetime.LifetimeServices.LeaseManagerPollTime" /> property.
/// </returns>
public override object InitializeLifetimeService()
{
return baseStream.InitializeLifetimeService();
}
/// <summary>
/// Asynchronously reads a sequence of bytes from the current stream, advances the position within the stream by the number of bytes read, and monitors cancellation requests.
/// </summary>
/// <param name="buffer">The buffer to write the data into.</param>
/// <param name="offset">The byte offset in <paramref name="buffer" /> at which to begin writing data from the stream.</param>
/// <param name="count">The maximum number of bytes to read.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is <see cref="P:System.Threading.CancellationToken.None" />.</param>
/// <returns>
/// A task that represents the asynchronous read operation. The value of the <paramref name="TResult" /> parameter contains the total number of bytes read into the buffer. The result value can be less than the number of bytes requested if the number of bytes currently available is less than the requested number, or it can be 0 (zero) if the end of the stream has been reached.
/// </returns>
public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
if (bufferLength == 0)
{
await FillBufferAsync(cancellationToken);
}
int available = Math.Min(bufferLength, count);
if (available > 0)
{
Buffer.BlockCopy(streamBuffer, bufferPos, buffer, offset, available);
bufferPos += available;
bufferLength -= available;
}
return available;
}
/// <summary>
/// Reads a byte from the stream and advances the position within the stream by one byte, or returns -1 if at the end of the stream.
/// </summary>
/// <returns>
/// The unsigned byte cast to an Int32, or -1 if at the end of the stream.
/// </returns>
public override int ReadByte()
{
if (bufferLength == 0)
{
FillBuffer();
}
if (bufferLength == 0)
{
return -1;
}
bufferLength--;
return streamBuffer[bufferPos++];
}
public byte ReadByteFromBuffer()
{
if (bufferLength == 0)
{
throw new Exception("Buffer is empty");
}
bufferLength--;
return streamBuffer[bufferPos++];
}
/// <summary>
/// Asynchronously writes a sequence of bytes to the current stream, advances the current position within this stream by the number of bytes written, and monitors cancellation requests.
/// </summary>
/// <param name="buffer">The buffer to write data from.</param>
/// <param name="offset">The zero-based byte offset in <paramref name="buffer" /> from which to begin copying bytes to the stream.</param>
/// <param name="count">The maximum number of bytes to write.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is <see cref="P:System.Threading.CancellationToken.None" />.</param>
/// <returns>
/// A task that represents the asynchronous write operation.
/// </returns>
[DebuggerStepThrough]
public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
return baseStream.WriteAsync(buffer, offset, count, cancellationToken);
}
/// <summary>
/// Writes a byte to the current position in the stream and advances the position within the stream by one byte.
/// </summary>
/// <param name="value">The byte to write to the stream.</param>
public override void WriteByte(byte value)
{
baseStream.WriteByte(value);
}
/// <summary>
/// Releases the unmanaged resources used by the <see cref="T:System.IO.Stream" /> and optionally releases the managed resources.
/// </summary>
/// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param>
protected override void Dispose(bool disposing)
{
baseStream.Dispose();
}
/// <summary>
/// When overridden in a derived class, gets a value indicating whether the current stream supports reading.
/// </summary>
public override bool CanRead => baseStream.CanRead;
/// <summary>
/// When overridden in a derived class, gets a value indicating whether the current stream supports seeking.
/// </summary>
public override bool CanSeek => baseStream.CanSeek;
/// <summary>
/// When overridden in a derived class, gets a value indicating whether the current stream supports writing.
/// </summary>
public override bool CanWrite => baseStream.CanWrite;
/// <summary>
/// Gets a value that determines whether the current stream can time out.
/// </summary>
public override bool CanTimeout => baseStream.CanTimeout;
/// <summary>
/// When overridden in a derived class, gets the length in bytes of the stream.
/// </summary>
public override long Length => baseStream.Length;
public bool DataAvailable => bufferLength > 0;
/// <summary>
/// When overridden in a derived class, gets or sets the position within the current stream.
/// </summary>
public override long Position
{
get { return baseStream.Position; }
set { baseStream.Position = value; }
}
/// <summary>
/// Gets or sets a value, in miliseconds, that determines how long the stream will attempt to read before timing out.
/// </summary>
public override int ReadTimeout
{
get { return baseStream.ReadTimeout; }
set { baseStream.ReadTimeout = value; }
}
/// <summary>
/// Gets or sets a value, in miliseconds, that determines how long the stream will attempt to write before timing out.
/// </summary>
public override int WriteTimeout
{
get { return baseStream.WriteTimeout; }
set { baseStream.WriteTimeout = value; }
}
/// <summary>
/// Fills the buffer.
/// </summary>
public bool FillBuffer()
{
bufferLength = baseStream.Read(streamBuffer, 0, streamBuffer.Length);
bufferPos = 0;
return bufferLength > 0;
}
/// <summary>
/// Fills the buffer asynchronous.
/// </summary>
/// <returns></returns>
public Task<bool> FillBufferAsync()
{
return FillBufferAsync(CancellationToken.None);
}
/// <summary>
/// Fills the buffer asynchronous.
/// </summary>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns></returns>
public async Task<bool> FillBufferAsync(CancellationToken cancellationToken)
{
bufferLength = await baseStream.ReadAsync(streamBuffer, 0, streamBuffer.Length, cancellationToken);
bufferPos = 0;
return bufferLength > 0;
}
private class ReadAsyncResult : IAsyncResult
{
public int ReadBytes { get; }
public bool IsCompleted => true;
public WaitHandle AsyncWaitHandle => null;
public object AsyncState => null;
public bool CompletedSynchronously => true;
public ReadAsyncResult(int readBytes)
{
ReadBytes = readBytes;
}
}
}
}
...@@ -6,9 +6,12 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -6,9 +6,12 @@ namespace Titanium.Web.Proxy.Helpers
/// <summary> /// <summary>
/// A helper class to set proxy settings for firefox /// A helper class to set proxy settings for firefox
/// </summary> /// </summary>
public class FireFoxProxySettingsManager internal class FireFoxProxySettingsManager
{ {
public void AddFirefox() /// <summary>
/// Add Firefox settings.
/// </summary>
internal void AddFirefox()
{ {
try try
{ {
...@@ -16,21 +19,17 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -16,21 +19,17 @@ namespace Titanium.Web.Proxy.Helpers
new DirectoryInfo(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + new DirectoryInfo(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) +
"\\Mozilla\\Firefox\\Profiles\\").GetDirectories("*.default"); "\\Mozilla\\Firefox\\Profiles\\").GetDirectories("*.default");
var myFfPrefFile = myProfileDirectory[0].FullName + "\\prefs.js"; var myFfPrefFile = myProfileDirectory[0].FullName + "\\prefs.js";
if (File.Exists(myFfPrefFile)) if (!File.Exists(myFfPrefFile)) return;
{ // We have a pref file so let''s make sure it has the proxy setting
// We have a pref file so let''s make sure it has the proxy setting var myReader = new StreamReader(myFfPrefFile);
var myReader = new StreamReader(myFfPrefFile); var myPrefContents = myReader.ReadToEnd();
var myPrefContents = myReader.ReadToEnd(); myReader.Close();
myReader.Close(); if (!myPrefContents.Contains("user_pref(\"network.proxy.type\", 0);")) return;
if (myPrefContents.Contains("user_pref(\"network.proxy.type\", 0);")) // Add the proxy enable line and write it back to the file
{ myPrefContents = myPrefContents.Replace("user_pref(\"network.proxy.type\", 0);", "");
// Add the proxy enable line and write it back to the file
myPrefContents = myPrefContents.Replace("user_pref(\"network.proxy.type\", 0);", "");
File.Delete(myFfPrefFile); File.Delete(myFfPrefFile);
File.WriteAllText(myFfPrefFile, myPrefContents); File.WriteAllText(myFfPrefFile, myPrefContents);
}
}
} }
catch (Exception) catch (Exception)
{ {
...@@ -38,7 +37,10 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -38,7 +37,10 @@ namespace Titanium.Web.Proxy.Helpers
} }
} }
public void RemoveFirefox() /// <summary>
/// Remove firefox settings.
/// </summary>
internal void RemoveFirefox()
{ {
try try
{ {
...@@ -46,20 +48,18 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -46,20 +48,18 @@ namespace Titanium.Web.Proxy.Helpers
new DirectoryInfo(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + new DirectoryInfo(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) +
"\\Mozilla\\Firefox\\Profiles\\").GetDirectories("*.default"); "\\Mozilla\\Firefox\\Profiles\\").GetDirectories("*.default");
var myFfPrefFile = myProfileDirectory[0].FullName + "\\prefs.js"; var myFfPrefFile = myProfileDirectory[0].FullName + "\\prefs.js";
if (File.Exists(myFfPrefFile)) if (!File.Exists(myFfPrefFile)) return;
// We have a pref file so let''s make sure it has the proxy setting
var myReader = new StreamReader(myFfPrefFile);
var myPrefContents = myReader.ReadToEnd();
myReader.Close();
if (!myPrefContents.Contains("user_pref(\"network.proxy.type\", 0);"))
{ {
// We have a pref file so let''s make sure it has the proxy setting // Add the proxy enable line and write it back to the file
var myReader = new StreamReader(myFfPrefFile); myPrefContents = myPrefContents + "\n\r" + "user_pref(\"network.proxy.type\", 0);";
var myPrefContents = myReader.ReadToEnd();
myReader.Close();
if (!myPrefContents.Contains("user_pref(\"network.proxy.type\", 0);"))
{
// Add the proxy enable line and write it back to the file
myPrefContents = myPrefContents + "\n\r" + "user_pref(\"network.proxy.type\", 0);";
File.Delete(myFfPrefFile); File.Delete(myFfPrefFile);
File.WriteAllText(myFfPrefFile, myPrefContents); File.WriteAllText(myFfPrefFile, myPrefContents);
}
} }
} }
catch (Exception) catch (Exception)
...@@ -68,4 +68,4 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -68,4 +68,4 @@ namespace Titanium.Web.Proxy.Helpers
} }
} }
} }
} }
\ No newline at end of file
using System; using System.Linq;
using System.Collections.Generic;
using System.Linq;
using System.Net; using System.Net;
using System.Text; using System.Net.Sockets;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Helpers namespace Titanium.Web.Proxy.Helpers
{ {
...@@ -11,8 +8,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -11,8 +8,7 @@ namespace Titanium.Web.Proxy.Helpers
{ {
private static int FindProcessIdFromLocalPort(int port, IpVersion ipVersion) private static int FindProcessIdFromLocalPort(int port, IpVersion ipVersion)
{ {
var tcpRow = TcpHelper.GetExtendedTcpTable(ipVersion).FirstOrDefault( var tcpRow = TcpHelper.GetTcpRowByLocalPort(ipVersion, port);
row => row.LocalEndPoint.Port == port);
return tcpRow?.ProcessId ?? 0; return tcpRow?.ProcessId ?? 0;
} }
...@@ -33,28 +29,50 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -33,28 +29,50 @@ namespace Titanium.Web.Proxy.Helpers
/// 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)
{ {
try // 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)
{ {
// get local IP addresses localhost = Dns.GetHostEntry(Dns.GetHostName());
IPAddress[] localIPs = Dns.GetHostAddresses(Dns.GetHostName());
IPAddress ipAddress;
// test if any host IP equals to any local IP or to localhost if (IPAddress.TryParse(hostName, out ipAddress))
isLocalhost = localhost.AddressList.Any(x => x.Equals(ipAddress));
// is localhost if (!isLocalhost)
if (IPAddress.IsLoopback(address)) return true;
// is local address
foreach (IPAddress localIP in localIPs)
{ {
if (address.Equals(localIP)) return true; try
{
var hostEntry = Dns.GetHostEntry(hostName);
isLocalhost = localhost.AddressList.Any(x => hostEntry.AddressList.Any(x.Equals));
}
catch (SocketException)
{
}
} }
} }
catch { }
return false; return isLocalhost;
} }
} }
} }
using System;
namespace Titanium.Web.Proxy.Helpers
{
/// <summary>
/// Run time helpers
/// </summary>
internal class RunTime
{
/// <summary>
/// Checks if current run time is Mono
/// </summary>
/// <returns></returns>
internal static bool IsRunningOnMono()
{
return Type.GetType("Mono.Runtime") != null;
}
}
}
...@@ -4,11 +4,8 @@ using Microsoft.Win32; ...@@ -4,11 +4,8 @@ using Microsoft.Win32;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Net.Sockets;
/// <summary> // Helper classes for setting system proxy settings
/// Helper classes for setting system proxy settings
/// </summary>
namespace Titanium.Web.Proxy.Helpers namespace Titanium.Web.Proxy.Helpers
{ {
internal enum ProxyProtocolType internal enum ProxyProtocolType
...@@ -39,7 +36,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -39,7 +36,7 @@ namespace Titanium.Web.Proxy.Helpers
/// <summary> /// <summary>
/// Manage system proxy settings /// Manage system proxy settings
/// </summary> /// </summary>
internal class SystemProxyManager internal class SystemProxyManager
{ {
internal const int InternetOptionSettingsChanged = 39; internal const int InternetOptionSettingsChanged = 39;
internal const int InternetOptionRefresh = 37; internal const int InternetOptionRefresh = 37;
...@@ -128,7 +125,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -128,7 +125,7 @@ namespace Titanium.Web.Proxy.Helpers
} }
/// <summary> /// <summary>
/// Removes all types of proxy settings (both http & https) /// Removes all types of proxy settings (both http and https)
/// </summary> /// </summary>
internal void DisableAllProxy() internal void DisableAllProxy()
{ {
...@@ -184,17 +181,17 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -184,17 +181,17 @@ namespace Titanium.Web.Proxy.Helpers
if (tmp.StartsWith("http=") || tmp.StartsWith("https=")) if (tmp.StartsWith("http=") || tmp.StartsWith("https="))
{ {
var endPoint = tmp.Substring(5); var endPoint = tmp.Substring(5);
return new HttpSystemProxyValue() return new HttpSystemProxyValue
{ {
HostName = endPoint.Split(':')[0], HostName = endPoint.Split(':')[0],
Port = int.Parse(endPoint.Split(':')[1]), Port = int.Parse(endPoint.Split(':')[1]),
IsHttps = tmp.StartsWith("https=") IsHttps = tmp.StartsWith("https=")
}; };
} }
return null; return null;
} }
/// <summary> /// <summary>
/// Prepares the proxy server registry (create empty values if they don't exist) /// Prepares the proxy server registry (create empty values if they don't exist)
/// </summary> /// </summary>
...@@ -210,7 +207,6 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -210,7 +207,6 @@ namespace Titanium.Web.Proxy.Helpers
{ {
reg.SetValue("ProxyServer", string.Empty); reg.SetValue("ProxyServer", string.Empty);
} }
} }
/// <summary> /// <summary>
...@@ -222,4 +218,4 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -222,4 +218,4 @@ namespace Titanium.Web.Proxy.Helpers
NativeMethods.InternetSetOption(IntPtr.Zero, InternetOptionRefresh, IntPtr.Zero, 0); NativeMethods.InternetSetOption(IntPtr.Zero, InternetOptionRefresh, IntPtr.Zero, 0);
} }
} }
} }
\ No newline at end of file
...@@ -42,7 +42,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -42,7 +42,7 @@ namespace Titanium.Web.Proxy.Helpers
} }
/// <summary> /// <summary>
/// <see cref="http://msdn2.microsoft.com/en-us/library/aa366921.aspx"/> /// <see href="http://msdn2.microsoft.com/en-us/library/aa366921.aspx"/>
/// </summary> /// </summary>
[StructLayout(LayoutKind.Sequential)] [StructLayout(LayoutKind.Sequential)]
internal struct TcpTable internal struct TcpTable
...@@ -52,7 +52,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -52,7 +52,7 @@ namespace Titanium.Web.Proxy.Helpers
} }
/// <summary> /// <summary>
/// <see cref="http://msdn2.microsoft.com/en-us/library/aa366913.aspx"/> /// <see href="http://msdn2.microsoft.com/en-us/library/aa366913.aspx"/>
/// </summary> /// </summary>
[StructLayout(LayoutKind.Sequential)] [StructLayout(LayoutKind.Sequential)]
internal struct TcpRow internal struct TcpRow
...@@ -72,7 +72,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -72,7 +72,7 @@ namespace Titanium.Web.Proxy.Helpers
} }
/// <summary> /// <summary>
/// <see cref="http://msdn2.microsoft.com/en-us/library/aa365928.aspx"/> /// <see href="http://msdn2.microsoft.com/en-us/library/aa365928.aspx"/>
/// </summary> /// </summary>
[DllImport("iphlpapi.dll", SetLastError = true)] [DllImport("iphlpapi.dll", SetLastError = true)]
internal static extern uint GetExtendedTcpTable(IntPtr tcpTable, ref int size, bool sort, int ipVersion, int tableClass, int reserved); internal static extern uint GetExtendedTcpTable(IntPtr tcpTable, ref int size, bool sort, int ipVersion, int tableClass, int reserved);
...@@ -124,28 +124,70 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -124,28 +124,70 @@ namespace Titanium.Web.Proxy.Helpers
} }
/// <summary> /// <summary>
/// relays the input clientStream to the server at the specified host name & port with the given httpCmd & headers as prefix /// Gets the TCP row by local port number.
/// </summary>
/// <returns><see cref="TcpRow"/>.</returns>
internal static TcpRow GetTcpRowByLocalPort(IpVersion ipVersion, int localPort)
{
IntPtr tcpTable = IntPtr.Zero;
int tcpTableLength = 0;
var ipVersionValue = ipVersion == IpVersion.Ipv4 ? NativeMethods.AfInet : NativeMethods.AfInet6;
if (NativeMethods.GetExtendedTcpTable(tcpTable, ref tcpTableLength, false, ipVersionValue, (int)NativeMethods.TcpTableType.OwnerPidAll, 0) != 0)
{
try
{
tcpTable = Marshal.AllocHGlobal(tcpTableLength);
if (NativeMethods.GetExtendedTcpTable(tcpTable, ref tcpTableLength, true, ipVersionValue, (int)NativeMethods.TcpTableType.OwnerPidAll, 0) == 0)
{
NativeMethods.TcpTable table = (NativeMethods.TcpTable)Marshal.PtrToStructure(tcpTable, typeof(NativeMethods.TcpTable));
IntPtr rowPtr = (IntPtr)((long)tcpTable + Marshal.SizeOf(table.length));
for (int i = 0; i < table.length; ++i)
{
var tcpRow = (NativeMethods.TcpRow)Marshal.PtrToStructure(rowPtr, typeof(NativeMethods.TcpRow));
if (tcpRow.GetLocalPort() == localPort)
{
return new TcpRow(tcpRow);
}
rowPtr = (IntPtr)((long)rowPtr + Marshal.SizeOf(typeof(NativeMethods.TcpRow)));
}
}
}
finally
{
if (tcpTable != IntPtr.Zero)
{
Marshal.FreeHGlobal(tcpTable);
}
}
}
return null;
}
/// <summary>
/// relays the input clientStream to the server at the specified host name and port with the given httpCmd and headers as prefix
/// Usefull for websocket requests /// Usefull for websocket requests
/// </summary> /// </summary>
/// <param name="bufferSize"></param> /// <param name="server"></param>
/// <param name="connectionTimeOutSeconds"></param>
/// <param name="remoteHostName"></param> /// <param name="remoteHostName"></param>
/// <param name="remotePort"></param>
/// <param name="httpCmd"></param> /// <param name="httpCmd"></param>
/// <param name="httpVersion"></param> /// <param name="httpVersion"></param>
/// <param name="requestHeaders"></param> /// <param name="requestHeaders"></param>
/// <param name="isHttps"></param> /// <param name="isHttps"></param>
/// <param name="remotePort"></param>
/// <param name="supportedProtocols"></param>
/// <param name="remoteCertificateValidationCallback"></param>
/// <param name="localCertificateSelectionCallback"></param>
/// <param name="clientStream"></param> /// <param name="clientStream"></param>
/// <param name="tcpConnectionFactory"></param> /// <param name="tcpConnectionFactory"></param>
/// <returns></returns> /// <returns></returns>
internal static async Task SendRaw(int bufferSize, int connectionTimeOutSeconds, internal static async Task SendRaw(ProxyServer server,
string remoteHostName, int remotePort, string httpCmd, Version httpVersion, Dictionary<string, HttpHeader> requestHeaders, string remoteHostName, int remotePort,
bool isHttps, SslProtocols supportedProtocols, string httpCmd, Version httpVersion, Dictionary<string, HttpHeader> requestHeaders,
RemoteCertificateValidationCallback remoteCertificateValidationCallback, LocalCertificateSelectionCallback localCertificateSelectionCallback, bool isHttps,
Stream clientStream, TcpConnectionFactory tcpConnectionFactory, IPEndPoint upStreamEndPoint) Stream clientStream, TcpConnectionFactory tcpConnectionFactory)
{ {
//prepare the prefix content //prepare the prefix content
StringBuilder sb = null; StringBuilder sb = null;
...@@ -171,12 +213,11 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -171,12 +213,11 @@ namespace Titanium.Web.Proxy.Helpers
sb.Append(ProxyConstants.NewLine); sb.Append(ProxyConstants.NewLine);
} }
var tcpConnection = await tcpConnectionFactory.CreateClient(bufferSize, connectionTimeOutSeconds, var tcpConnection = await tcpConnectionFactory.CreateClient(server,
remoteHostName, remotePort, remoteHostName, remotePort,
httpVersion, isHttps, httpVersion, isHttps,
supportedProtocols, remoteCertificateValidationCallback, localCertificateSelectionCallback, null, null, clientStream);
null, null, clientStream, upStreamEndPoint);
try try
{ {
Stream tunnelStream = tcpConnection.Stream; Stream tunnelStream = tcpConnection.Stream;
...@@ -191,7 +232,8 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -191,7 +232,8 @@ namespace Titanium.Web.Proxy.Helpers
finally finally
{ {
tcpConnection.Dispose(); tcpConnection.Dispose();
server.ServerConnectionCount--;
} }
} }
} }
} }
\ No newline at end of file
using System.Collections.Generic;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Http
{
internal static class HeaderParser
{
internal static async Task ReadHeaders(CustomBinaryReader reader,
Dictionary<string, List<HttpHeader>> nonUniqueResponseHeaders,
Dictionary<string, HttpHeader> headers)
{
string tmpLine;
while (!string.IsNullOrEmpty(tmpLine = await reader.ReadLineAsync()))
{
var header = tmpLine.Split(ProxyConstants.ColonSplit, 2);
var newHeader = new HttpHeader(header[0], header[1]);
//if header exist in non-unique header collection add it there
if (nonUniqueResponseHeaders.ContainsKey(newHeader.Name))
{
nonUniqueResponseHeaders[newHeader.Name].Add(newHeader);
}
//if header is alread in unique header collection then move both to non-unique collection
else if (headers.ContainsKey(newHeader.Name))
{
var existing = headers[newHeader.Name];
var nonUniqueHeaders = new List<HttpHeader> { existing, newHeader };
nonUniqueResponseHeaders.Add(newHeader.Name, nonUniqueHeaders);
headers.Remove(newHeader.Name);
}
//add to unique header collection
else
{
headers.Add(newHeader.Name, newHeader);
}
}
}
}
}
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Titanium.Web.Proxy.Models; using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Network;
using Titanium.Web.Proxy.Network.Tcp; using Titanium.Web.Proxy.Network.Tcp;
using Titanium.Web.Proxy.Shared; using Titanium.Web.Proxy.Shared;
...@@ -15,17 +13,29 @@ namespace Titanium.Web.Proxy.Http ...@@ -15,17 +13,29 @@ namespace Titanium.Web.Proxy.Http
/// </summary> /// </summary>
public class HttpWebClient public class HttpWebClient
{ {
/// <summary> /// <summary>
/// Connection to server /// Connection to server
/// </summary> /// </summary>
internal TcpConnection ServerConnection { get; set; } internal TcpConnection ServerConnection { get; set; }
public Guid RequestId { get; private set; } /// <summary>
/// Request ID.
/// </summary>
public Guid RequestId { get; }
/// <summary>
/// Headers passed with Connect.
/// </summary>
public List<HttpHeader> ConnectHeaders { get; set; } public List<HttpHeader> ConnectHeaders { get; set; }
/// <summary>
/// Web Request.
/// </summary>
public Request Request { get; set; } public Request Request { get; set; }
/// <summary>
/// Web Response.
/// </summary>
public Response Response { get; set; } public Response Response { get; set; }
/// <summary> /// <summary>
...@@ -37,15 +47,14 @@ namespace Titanium.Web.Proxy.Http ...@@ -37,15 +47,14 @@ namespace Titanium.Web.Proxy.Http
/// <summary> /// <summary>
/// Is Https? /// Is Https?
/// </summary> /// </summary>
public bool IsHttps => this.Request.RequestUri.Scheme == Uri.UriSchemeHttps; public bool IsHttps => Request.RequestUri.Scheme == Uri.UriSchemeHttps;
internal HttpWebClient() internal HttpWebClient()
{ {
this.RequestId = Guid.NewGuid(); RequestId = Guid.NewGuid();
Request = new Request();
this.Request = new Request(); Response = new Response();
this.Response = new Response();
} }
/// <summary> /// <summary>
...@@ -57,68 +66,69 @@ namespace Titanium.Web.Proxy.Http ...@@ -57,68 +66,69 @@ namespace Titanium.Web.Proxy.Http
connection.LastAccess = DateTime.Now; connection.LastAccess = DateTime.Now;
ServerConnection = connection; ServerConnection = connection;
} }
/// <summary> /// <summary>
/// Prepare & send the http(s) request /// Prepare and send the http(s) request
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
internal async Task SendRequest(bool enable100ContinueBehaviour) internal async Task SendRequest(bool enable100ContinueBehaviour)
{ {
Stream stream = ServerConnection.Stream; var stream = ServerConnection.Stream;
StringBuilder 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 == true)) if ((ServerConnection.UpStreamHttpProxy != null && ServerConnection.IsHttps == false) || (ServerConnection.UpStreamHttpsProxy != null && ServerConnection.IsHttps))
{ {
requestLines.AppendLine(string.Join(" ", this.Request.Method, this.Request.RequestUri.AbsoluteUri, $"HTTP/{this.Request.HttpVersion.Major}.{this.Request.HttpVersion.Minor}")); requestLines.AppendLine($"{Request.Method} {Request.RequestUri.AbsoluteUri} HTTP/{Request.HttpVersion.Major}.{Request.HttpVersion.Minor}");
} }
else else
{ {
requestLines.AppendLine(string.Join(" ", this.Request.Method, this.Request.RequestUri.PathAndQuery, $"HTTP/{this.Request.HttpVersion.Major}.{this.Request.HttpVersion.Minor}")); requestLines.AppendLine($"{Request.Method} {Request.RequestUri.PathAndQuery} HTTP/{Request.HttpVersion.Major}.{Request.HttpVersion.Minor}");
} }
//Send Authentication to Upstream proxy if needed //Send Authentication to Upstream proxy if needed
if (ServerConnection.UpStreamHttpProxy != null && ServerConnection.IsHttps == false && !string.IsNullOrEmpty(ServerConnection.UpStreamHttpProxy.UserName) && ServerConnection.UpStreamHttpProxy.Password != null) if (ServerConnection.UpStreamHttpProxy != null && ServerConnection.IsHttps == false && !string.IsNullOrEmpty(ServerConnection.UpStreamHttpProxy.UserName) && ServerConnection.UpStreamHttpProxy.Password != null)
{ {
requestLines.AppendLine("Proxy-Connection: keep-alive"); requestLines.AppendLine("Proxy-Connection: keep-alive");
requestLines.AppendLine("Proxy-Authorization" + ": Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes(ServerConnection.UpStreamHttpProxy.UserName + ":" + ServerConnection.UpStreamHttpProxy.Password))); requestLines.AppendLine("Proxy-Authorization" + ": Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes(
$"{ServerConnection.UpStreamHttpProxy.UserName}:{ServerConnection.UpStreamHttpProxy.Password}")));
} }
//write request headers //write request headers
foreach (var headerItem in this.Request.RequestHeaders) foreach (var headerItem in Request.RequestHeaders)
{ {
var header = headerItem.Value; var header = headerItem.Value;
if (headerItem.Key != "Proxy-Authorization") if (headerItem.Key != "Proxy-Authorization")
{ {
requestLines.AppendLine(header.Name + ": " + header.Value); requestLines.AppendLine($"{header.Name}: {header.Value}");
} }
} }
//write non unique request headers //write non unique request headers
foreach (var headerItem in this.Request.NonUniqueRequestHeaders) foreach (var headerItem in Request.NonUniqueRequestHeaders)
{ {
var headers = headerItem.Value; var headers = headerItem.Value;
foreach (var header in headers) foreach (var header in headers)
{ {
if (headerItem.Key != "Proxy-Authorization") if (headerItem.Key != "Proxy-Authorization")
{ {
requestLines.AppendLine(header.Name + ": " + header.Value); requestLines.AppendLine($"{header.Name}: {header.Value}");
} }
} }
} }
requestLines.AppendLine(); requestLines.AppendLine();
string request = requestLines.ToString(); var request = requestLines.ToString();
byte[] requestBytes = Encoding.ASCII.GetBytes(request); var requestBytes = Encoding.ASCII.GetBytes(request);
await stream.WriteAsync(requestBytes, 0, requestBytes.Length); await stream.WriteAsync(requestBytes, 0, requestBytes.Length);
await stream.FlushAsync(); await stream.FlushAsync();
if (enable100ContinueBehaviour) if (enable100ContinueBehaviour)
{ {
if (this.Request.ExpectContinue) if (Request.ExpectContinue)
{ {
var httpResult = (await ServerConnection.StreamReader.ReadLineAsync()).Split(ProxyConstants.SpaceSplit, 3); var httpResult = (await ServerConnection.StreamReader.ReadLineAsync()).Split(ProxyConstants.SpaceSplit, 3);
var responseStatusCode = httpResult[1].Trim(); var responseStatusCode = httpResult[1].Trim();
...@@ -126,15 +136,15 @@ namespace Titanium.Web.Proxy.Http ...@@ -126,15 +136,15 @@ namespace Titanium.Web.Proxy.Http
//find if server is willing for expect continue //find if server is willing for expect continue
if (responseStatusCode.Equals("100") if (responseStatusCode.Equals("100")
&& responseStatusDescription.ToLower().Equals("continue")) && responseStatusDescription.Equals("continue", StringComparison.CurrentCultureIgnoreCase))
{ {
this.Request.Is100Continue = true; Request.Is100Continue = true;
await ServerConnection.StreamReader.ReadLineAsync(); await ServerConnection.StreamReader.ReadLineAsync();
} }
else if (responseStatusCode.Equals("417") else if (responseStatusCode.Equals("417")
&& responseStatusDescription.ToLower().Equals("expectation failed")) && responseStatusDescription.Equals("expectation failed", StringComparison.CurrentCultureIgnoreCase))
{ {
this.Request.ExpectationFailed = true; Request.ExpectationFailed = true;
await ServerConnection.StreamReader.ReadLineAsync(); await ServerConnection.StreamReader.ReadLineAsync();
} }
} }
...@@ -142,13 +152,13 @@ namespace Titanium.Web.Proxy.Http ...@@ -142,13 +152,13 @@ namespace Titanium.Web.Proxy.Http
} }
/// <summary> /// <summary>
/// Receive & parse the http response from server /// Receive and parse the http response from server
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
internal async Task ReceiveResponse() internal async Task ReceiveResponse()
{ {
//return if this is already read //return if this is already read
if (this.Response.ResponseStatusCode != null) return; if (Response.ResponseStatusCode != null) return;
var httpResult = (await ServerConnection.StreamReader.ReadLineAsync()).Split(ProxyConstants.SpaceSplit, 3); var httpResult = (await ServerConnection.StreamReader.ReadLineAsync()).Split(ProxyConstants.SpaceSplit, 3);
...@@ -160,70 +170,43 @@ namespace Titanium.Web.Proxy.Http ...@@ -160,70 +170,43 @@ namespace Titanium.Web.Proxy.Http
var httpVersion = httpResult[0].Trim().ToLower(); var httpVersion = httpResult[0].Trim().ToLower();
var version = new Version(1, 1); var version = HttpHeader.Version11;
if (httpVersion == "http/1.0") if (string.Equals(httpVersion, "HTTP/1.0", StringComparison.OrdinalIgnoreCase))
{ {
version = new Version(1, 0); version = HttpHeader.Version10;
} }
this.Response.HttpVersion = version; Response.HttpVersion = version;
this.Response.ResponseStatusCode = httpResult[1].Trim(); Response.ResponseStatusCode = httpResult[1].Trim();
this.Response.ResponseStatusDescription = httpResult[2].Trim(); Response.ResponseStatusDescription = httpResult[2].Trim();
//For HTTP 1.1 comptibility server may send expect-continue even if not asked for it in request //For HTTP 1.1 comptibility server may send expect-continue even if not asked for it in request
if (this.Response.ResponseStatusCode.Equals("100") if (Response.ResponseStatusCode.Equals("100")
&& this.Response.ResponseStatusDescription.ToLower().Equals("continue")) && Response.ResponseStatusDescription.Equals("continue", StringComparison.CurrentCultureIgnoreCase))
{ {
//Read the next line after 100-continue //Read the next line after 100-continue
this.Response.Is100Continue = true; Response.Is100Continue = true;
this.Response.ResponseStatusCode = null; Response.ResponseStatusCode = null;
await ServerConnection.StreamReader.ReadLineAsync(); await ServerConnection.StreamReader.ReadLineAsync();
//now receive response //now receive response
await ReceiveResponse(); await ReceiveResponse();
return; return;
} }
else if (this.Response.ResponseStatusCode.Equals("417")
&& this.Response.ResponseStatusDescription.ToLower().Equals("expectation failed")) if (Response.ResponseStatusCode.Equals("417")
&& Response.ResponseStatusDescription.Equals("expectation failed", StringComparison.CurrentCultureIgnoreCase))
{ {
//read next line after expectation failed response //read next line after expectation failed response
this.Response.ExpectationFailed = true; Response.ExpectationFailed = true;
this.Response.ResponseStatusCode = null; Response.ResponseStatusCode = null;
await ServerConnection.StreamReader.ReadLineAsync(); await ServerConnection.StreamReader.ReadLineAsync();
//now receive response //now receive response
await ReceiveResponse(); await ReceiveResponse();
return; return;
} }
//Read the Response headers
//Read the response headers in to unique and non-unique header collections //Read the response headers in to unique and non-unique header collections
string tmpLine; await HeaderParser.ReadHeaders(ServerConnection.StreamReader, Response.NonUniqueResponseHeaders, Response.ResponseHeaders);
while (!string.IsNullOrEmpty(tmpLine = await ServerConnection.StreamReader.ReadLineAsync()))
{
var header = tmpLine.Split(ProxyConstants.ColonSplit, 2);
var newHeader = new HttpHeader(header[0], header[1]);
//if header exist in non-unique header collection add it there
if (Response.NonUniqueResponseHeaders.ContainsKey(newHeader.Name))
{
Response.NonUniqueResponseHeaders[newHeader.Name].Add(newHeader);
}
//if header is alread in unique header collection then move both to non-unique collection
else if (Response.ResponseHeaders.ContainsKey(newHeader.Name))
{
var existing = Response.ResponseHeaders[newHeader.Name];
var nonUniqueHeaders = new List<HttpHeader> {existing, newHeader};
Response.NonUniqueResponseHeaders.Add(newHeader.Name, nonUniqueHeaders);
Response.ResponseHeaders.Remove(newHeader.Name);
}
//add to unique header collection
else
{
Response.ResponseHeaders.Add(newHeader.Name, newHeader);
}
}
} }
} }
} }
\ No newline at end of file
...@@ -34,12 +34,7 @@ namespace Titanium.Web.Proxy.Http ...@@ -34,12 +34,7 @@ namespace Titanium.Web.Proxy.Http
get get
{ {
var hasHeader = RequestHeaders.ContainsKey("host"); var hasHeader = RequestHeaders.ContainsKey("host");
if (hasHeader) return hasHeader ? RequestHeaders["host"].Value : null;
{
return RequestHeaders["host"].Value;
}
return null;
} }
set set
{ {
...@@ -52,7 +47,6 @@ namespace Titanium.Web.Proxy.Http ...@@ -52,7 +47,6 @@ namespace Titanium.Web.Proxy.Http
{ {
RequestHeaders.Add("Host", new HttpHeader("Host", value)); RequestHeaders.Add("Host", new HttpHeader("Host", value));
} }
} }
} }
...@@ -124,9 +118,7 @@ namespace Titanium.Web.Proxy.Http ...@@ -124,9 +118,7 @@ namespace Titanium.Web.Proxy.Http
{ {
RequestHeaders.Remove("content-length"); RequestHeaders.Remove("content-length");
} }
} }
} }
} }
...@@ -154,14 +146,13 @@ namespace Titanium.Web.Proxy.Http ...@@ -154,14 +146,13 @@ namespace Titanium.Web.Proxy.Http
if (hasHeader) if (hasHeader)
{ {
var header = RequestHeaders["content-type"]; var header = RequestHeaders["content-type"];
header.Value = value.ToString(); header.Value = value;
} }
else else
{ {
RequestHeaders.Add("content-type", new HttpHeader("content-type", value.ToString())); RequestHeaders.Add("content-type", new HttpHeader("content-type", value));
} }
} }
} }
/// <summary> /// <summary>
...@@ -177,7 +168,7 @@ namespace Titanium.Web.Proxy.Http ...@@ -177,7 +168,7 @@ namespace Titanium.Web.Proxy.Http
{ {
var header = RequestHeaders["transfer-encoding"]; var header = RequestHeaders["transfer-encoding"];
return header.Value.ToLower().Contains("chunked"); return header.Value.ContainsIgnoreCase("chunked");
} }
return false; return false;
...@@ -219,14 +210,10 @@ namespace Titanium.Web.Proxy.Http ...@@ -219,14 +210,10 @@ namespace Titanium.Web.Proxy.Http
{ {
var hasHeader = RequestHeaders.ContainsKey("expect"); var hasHeader = RequestHeaders.ContainsKey("expect");
if (hasHeader) if (!hasHeader) return false;
{ var header = RequestHeaders["expect"];
var header = RequestHeaders["expect"];
return header.Value.Equals("100-continue"); return header.Value.Equals("100-continue");
}
return false;
} }
} }
...@@ -235,12 +222,12 @@ namespace Titanium.Web.Proxy.Http ...@@ -235,12 +222,12 @@ namespace Titanium.Web.Proxy.Http
/// </summary> /// </summary>
public string Url => RequestUri.OriginalString; public string Url => RequestUri.OriginalString;
/// <summary> /// <summary>
/// Encoding for this request /// Encoding for this request
/// </summary> /// </summary>
internal Encoding Encoding => this.GetEncoding(); internal Encoding Encoding => this.GetEncoding();
/// <summary> /// <summary>
/// Terminates the underlying Tcp Connection to client after current request /// Terminates the underlying Tcp Connection to client after current request
/// </summary> /// </summary>
internal bool CancelRequest { get; set; } internal bool CancelRequest { get; set; }
...@@ -254,9 +241,9 @@ namespace Titanium.Web.Proxy.Http ...@@ -254,9 +241,9 @@ 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; }
/// <summary> /// <summary>
...@@ -275,13 +262,7 @@ namespace Titanium.Web.Proxy.Http ...@@ -275,13 +262,7 @@ namespace Titanium.Web.Proxy.Http
var header = RequestHeaders["upgrade"]; var header = RequestHeaders["upgrade"];
if (header.Value.ToLower() == "websocket") return header.Value.Equals("websocket", StringComparison.CurrentCultureIgnoreCase);
{
return true;
}
return false;
} }
} }
...@@ -305,11 +286,13 @@ namespace Titanium.Web.Proxy.Http ...@@ -305,11 +286,13 @@ namespace Titanium.Web.Proxy.Http
/// </summary> /// </summary>
public bool ExpectationFailed { get; internal set; } public bool ExpectationFailed { get; internal set; }
/// <summary>
/// Constructor.
/// </summary>
public Request() public Request()
{ {
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);
} }
} }
} }
...@@ -12,12 +12,19 @@ namespace Titanium.Web.Proxy.Http ...@@ -12,12 +12,19 @@ namespace Titanium.Web.Proxy.Http
/// </summary> /// </summary>
public class Response public class Response
{ {
/// <summary>
/// Response Status Code.
/// </summary>
public string ResponseStatusCode { get; set; } public string ResponseStatusCode { get; set; }
/// <summary>
/// Response Status description.
/// </summary>
public string ResponseStatusDescription { get; set; } public string ResponseStatusDescription { get; set; }
internal Encoding Encoding => this.GetResponseCharacterEncoding(); internal Encoding Encoding => this.GetResponseCharacterEncoding();
/// <summary> /// <summary>
/// Content encoding for this response /// Content encoding for this response
/// </summary> /// </summary>
internal string ContentEncoding internal string ContentEncoding
...@@ -26,14 +33,10 @@ namespace Titanium.Web.Proxy.Http ...@@ -26,14 +33,10 @@ namespace Titanium.Web.Proxy.Http
{ {
var hasHeader = ResponseHeaders.ContainsKey("content-encoding"); var hasHeader = ResponseHeaders.ContainsKey("content-encoding");
if (hasHeader) if (!hasHeader) return null;
{ var header = ResponseHeaders["content-encoding"];
var header = ResponseHeaders["content-encoding"];
return header.Value.Trim();
}
return null; return header.Value.Trim();
} }
} }
...@@ -52,14 +55,13 @@ namespace Titanium.Web.Proxy.Http ...@@ -52,14 +55,13 @@ namespace Titanium.Web.Proxy.Http
{ {
var header = ResponseHeaders["connection"]; var header = ResponseHeaders["connection"];
if (header.Value.ToLower().Contains("close")) if (header.Value.ContainsIgnoreCase("close"))
{ {
return false; return false;
} }
} }
return true; return true;
} }
} }
...@@ -80,7 +82,6 @@ namespace Titanium.Web.Proxy.Http ...@@ -80,7 +82,6 @@ namespace Titanium.Web.Proxy.Http
} }
return null; return null;
} }
} }
...@@ -108,7 +109,6 @@ namespace Titanium.Web.Proxy.Http ...@@ -108,7 +109,6 @@ namespace Titanium.Web.Proxy.Http
} }
return -1; return -1;
} }
set set
{ {
...@@ -151,14 +151,13 @@ namespace Titanium.Web.Proxy.Http ...@@ -151,14 +151,13 @@ namespace Titanium.Web.Proxy.Http
{ {
var header = ResponseHeaders["transfer-encoding"]; var header = ResponseHeaders["transfer-encoding"];
if (header.Value.ToLower().Contains("chunked")) if (header.Value.ContainsIgnoreCase("chunked"))
{ {
return true; return true;
} }
} }
return false; return false;
} }
set set
{ {
...@@ -184,9 +183,7 @@ namespace Titanium.Web.Proxy.Http ...@@ -184,9 +183,7 @@ namespace Titanium.Web.Proxy.Http
{ {
ResponseHeaders.Remove("transfer-encoding"); ResponseHeaders.Remove("transfer-encoding");
} }
} }
} }
} }
...@@ -229,11 +226,13 @@ namespace Titanium.Web.Proxy.Http ...@@ -229,11 +226,13 @@ namespace Titanium.Web.Proxy.Http
/// </summary> /// </summary>
public bool ExpectationFailed { get; internal set; } public bool ExpectationFailed { get; internal set; }
/// <summary>
/// Constructor.
/// </summary>
public Response() public Response()
{ {
this.ResponseHeaders = new Dictionary<string, HttpHeader>(StringComparer.OrdinalIgnoreCase); ResponseHeaders = new Dictionary<string, HttpHeader>(StringComparer.OrdinalIgnoreCase);
this.NonUniqueResponseHeaders = new Dictionary<string, List<HttpHeader>>(StringComparer.OrdinalIgnoreCase); NonUniqueResponseHeaders = new Dictionary<string, List<HttpHeader>>(StringComparer.OrdinalIgnoreCase);
} }
} }
} }
using System.Net;
namespace Titanium.Web.Proxy.Http.Responses
{
/// <summary>
/// Anything other than a 200 or 302 response
/// </summary>
public class GenericResponse : Response
{
/// <summary>
/// Constructor.
/// </summary>
/// <param name="status"></param>
public GenericResponse(HttpStatusCode status)
{
ResponseStatusCode = ((int) status).ToString();
ResponseStatusDescription = status.ToString();
}
/// <summary>
/// Constructor.
/// </summary>
/// <param name="statusCode"></param>
/// <param name="statusDescription"></param>
public GenericResponse(string statusCode, string statusDescription)
{
ResponseStatusCode = statusCode;
ResponseStatusDescription = statusDescription;
}
}
}
...@@ -5,6 +5,9 @@ ...@@ -5,6 +5,9 @@
/// </summary> /// </summary>
public sealed class OkResponse : Response public sealed class OkResponse : Response
{ {
/// <summary>
/// Constructor.
/// </summary>
public OkResponse() public OkResponse()
{ {
ResponseStatusCode = "200"; ResponseStatusCode = "200";
......
...@@ -5,6 +5,9 @@ ...@@ -5,6 +5,9 @@
/// </summary> /// </summary>
public sealed class RedirectResponse : Response public sealed class RedirectResponse : Response
{ {
/// <summary>
/// Constructor.
/// </summary>
public RedirectResponse() public RedirectResponse()
{ {
ResponseStatusCode = "302"; ResponseStatusCode = "302";
......
using System.Collections.Generic; using System;
using System.Collections.Generic;
using System.Linq;
using System.Net; using System.Net;
using System.Net.Sockets; using System.Net.Sockets;
using System.Security.Cryptography.X509Certificates;
using System.Text.RegularExpressions;
namespace Titanium.Web.Proxy.Models namespace Titanium.Web.Proxy.Models
{ {
...@@ -9,22 +13,42 @@ namespace Titanium.Web.Proxy.Models ...@@ -9,22 +13,42 @@ namespace Titanium.Web.Proxy.Models
/// </summary> /// </summary>
public abstract class ProxyEndPoint public abstract class ProxyEndPoint
{ {
public ProxyEndPoint(IPAddress IpAddress, int Port, bool EnableSsl) /// <summary>
/// Constructor.
/// </summary>
/// <param name="ipAddress"></param>
/// <param name="port"></param>
/// <param name="enableSsl"></param>
protected ProxyEndPoint(IPAddress ipAddress, int port, bool enableSsl)
{ {
this.IpAddress = IpAddress; IpAddress = ipAddress;
this.Port = Port; Port = port;
this.EnableSsl = EnableSsl; EnableSsl = enableSsl;
} }
/// <summary>
/// Ip Address.
/// </summary>
public IPAddress IpAddress { get; internal set; } public IPAddress IpAddress { get; internal set; }
/// <summary>
/// Port.
/// </summary>
public int Port { get; internal set; } public int Port { get; internal set; }
/// <summary>
/// Enable SSL?
/// </summary>
public bool EnableSsl { get; internal set; } public bool EnableSsl { get; internal set; }
public bool IpV6Enabled => IpAddress == IPAddress.IPv6Any /// <summary>
|| IpAddress == IPAddress.IPv6Loopback /// Is IPv6 enabled?
|| IpAddress == IPAddress.IPv6None; /// </summary>
public bool IpV6Enabled => Equals(IpAddress, IPAddress.IPv6Any)
|| Equals(IpAddress, IPAddress.IPv6Loopback)
|| Equals(IpAddress, IPAddress.IPv6None);
internal TcpListener listener { get; set; } internal TcpListener Listener { get; set; }
} }
/// <summary> /// <summary>
...@@ -33,15 +57,61 @@ namespace Titanium.Web.Proxy.Models ...@@ -33,15 +57,61 @@ namespace Titanium.Web.Proxy.Models
/// </summary> /// </summary>
public class ExplicitProxyEndPoint : ProxyEndPoint public class ExplicitProxyEndPoint : ProxyEndPoint
{ {
internal List<Regex> ExcludedHttpsHostNameRegexList;
internal List<Regex> IncludedHttpsHostNameRegexList;
internal bool IsSystemHttpProxy { get; set; } internal bool IsSystemHttpProxy { get; set; }
internal bool IsSystemHttpsProxy { get; set; } internal bool IsSystemHttpsProxy { get; set; }
public List<string> ExcludedHttpsHostNameRegex { get; set; } /// <summary>
/// List of host names to exclude using Regular Expressions.
/// </summary>
public IEnumerable<string> ExcludedHttpsHostNameRegex
{
get { return ExcludedHttpsHostNameRegexList?.Select(x => x.ToString()).ToList(); }
set
{
if (IncludedHttpsHostNameRegex != null)
{
throw new ArgumentException("Cannot set excluded when included is set");
}
ExcludedHttpsHostNameRegexList = value?.Select(x => new Regex(x, RegexOptions.Compiled)).ToList();
}
}
/// <summary>
/// List of host names to exclude using Regular Expressions.
/// </summary>
public IEnumerable<string> IncludedHttpsHostNameRegex
{
get { return IncludedHttpsHostNameRegexList?.Select(x => x.ToString()).ToList(); }
set
{
if (ExcludedHttpsHostNameRegex != null)
{
throw new ArgumentException("Cannot set included when excluded is set");
}
IncludedHttpsHostNameRegexList = value?.Select(x => new Regex(x, RegexOptions.Compiled)).ToList();
}
}
public ExplicitProxyEndPoint(IPAddress IpAddress, int Port, bool EnableSsl) /// <summary>
: base(IpAddress, Port, EnableSsl) /// Generic certificate to use for SSL decryption.
/// </summary>
public X509Certificate2 GenericCertificate { get; set; }
/// <summary>
/// Constructor.
/// </summary>
/// <param name="ipAddress"></param>
/// <param name="port"></param>
/// <param name="enableSsl"></param>
public ExplicitProxyEndPoint(IPAddress ipAddress, int port, bool enableSsl)
: base(ipAddress, port, enableSsl)
{ {
} }
} }
...@@ -51,18 +121,22 @@ namespace Titanium.Web.Proxy.Models ...@@ -51,18 +121,22 @@ namespace Titanium.Web.Proxy.Models
/// </summary> /// </summary>
public class TransparentProxyEndPoint : ProxyEndPoint public class TransparentProxyEndPoint : ProxyEndPoint
{ {
//Name of the Certificate need to be sent (same as the hostname we want to proxy) /// <summary>
//This is valid only when UseServerNameIndication is set to false /// Name of the Certificate need to be sent (same as the hostname we want to proxy)
/// This is valid only when UseServerNameIndication is set to false
/// </summary>
public string GenericCertificateName { get; set; } public string GenericCertificateName { get; set; }
/// <summary>
// public bool UseServerNameIndication { get; set; } /// Constructor.
/// </summary>
public TransparentProxyEndPoint(IPAddress IpAddress, int Port, bool EnableSsl) /// <param name="ipAddress"></param>
: base(IpAddress, Port, EnableSsl) /// <param name="port"></param>
/// <param name="enableSsl"></param>
public TransparentProxyEndPoint(IPAddress ipAddress, int port, bool enableSsl)
: base(ipAddress, port, enableSsl)
{ {
this.GenericCertificateName = "localhost"; GenericCertificateName = "localhost";
} }
} }
} }
...@@ -8,41 +8,63 @@ namespace Titanium.Web.Proxy.Models ...@@ -8,41 +8,63 @@ namespace Titanium.Web.Proxy.Models
/// </summary> /// </summary>
public class ExternalProxy public class ExternalProxy
{ {
private static readonly Lazy<NetworkCredential> DefaultCredentials = new Lazy<NetworkCredential>(() => CredentialCache.DefaultNetworkCredentials); private static readonly Lazy<NetworkCredential> defaultCredentials = new Lazy<NetworkCredential>(() => CredentialCache.DefaultNetworkCredentials);
private string userName; private string userName;
private string password; private string password;
/// <summary>
/// Use default windows credentials?
/// </summary>
public bool UseDefaultCredentials { get; set; } public bool UseDefaultCredentials { get; set; }
public string UserName { /// <summary>
get { return UseDefaultCredentials ? DefaultCredentials.Value.UserName : userName; } /// Bypass this proxy for connections to localhost?
/// </summary>
public bool BypassForLocalhost { get; set; }
/// <summary>
/// Username.
/// </summary>
public string UserName
{
get { return UseDefaultCredentials ? defaultCredentials.Value.UserName : userName; }
set set
{ {
userName = value; userName = value;
if (DefaultCredentials.Value.UserName != userName) if (defaultCredentials.Value.UserName != userName)
{ {
UseDefaultCredentials = false; UseDefaultCredentials = false;
} }
} }
} }
/// <summary>
/// Password.
/// </summary>
public string Password public string Password
{ {
get { return UseDefaultCredentials ? DefaultCredentials.Value.Password : password; } get { return UseDefaultCredentials ? defaultCredentials.Value.Password : password; }
set set
{ {
password = value; password = value;
if (DefaultCredentials.Value.Password != password) if (defaultCredentials.Value.Password != password)
{ {
UseDefaultCredentials = false; UseDefaultCredentials = false;
} }
} }
} }
/// <summary>
/// Host name.
/// </summary>
public string HostName { get; set; } public string HostName { get; set; }
/// <summary>
/// Port.
/// </summary>
public int Port { get; set; } public int Port { get; set; }
} }
} }
using System; using System;
using System.IO;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Models namespace Titanium.Web.Proxy.Models
{ {
...@@ -7,6 +9,16 @@ namespace Titanium.Web.Proxy.Models ...@@ -7,6 +9,16 @@ namespace Titanium.Web.Proxy.Models
/// </summary> /// </summary>
public class HttpHeader public class HttpHeader
{ {
internal static Version Version10 = new Version(1, 0);
internal static Version Version11 = new Version(1, 1);
/// <summary>
/// Constructor.
/// </summary>
/// <param name="name"></param>
/// <param name="value"></param>
/// <exception cref="Exception"></exception>
public HttpHeader(string name, string value) public HttpHeader(string name, string value)
{ {
if (string.IsNullOrEmpty(name)) if (string.IsNullOrEmpty(name))
...@@ -18,7 +30,14 @@ namespace Titanium.Web.Proxy.Models ...@@ -18,7 +30,14 @@ namespace Titanium.Web.Proxy.Models
Value = value.Trim(); Value = value.Trim();
} }
/// <summary>
/// Header Name.
/// </summary>
public string Name { get; set; } public string Name { get; set; }
/// <summary>
/// Header Value.
/// </summary>
public string Value { get; set; } public string Value { get; set; }
/// <summary> /// <summary>
...@@ -27,7 +46,14 @@ namespace Titanium.Web.Proxy.Models ...@@ -27,7 +46,14 @@ namespace Titanium.Web.Proxy.Models
/// <returns></returns> /// <returns></returns>
public override string ToString() public override string ToString()
{ {
return string.Format("{0}: {1}", Name, Value); return $"{Name}: {Value}";
}
internal async Task WriteToStream(StreamWriter writer)
{
await writer.WriteAsync(Name);
await writer.WriteAsync(": ");
await writer.WriteLineAsync(Value);
} }
} }
} }
\ No newline at end of file
...@@ -20,6 +20,5 @@ namespace Titanium.Web.Proxy.Network ...@@ -20,6 +20,5 @@ namespace Titanium.Web.Proxy.Network
{ {
LastAccess = DateTime.Now; LastAccess = DateTime.Now;
} }
} }
} }
using System;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using Org.BouncyCastle.Asn1;
using Org.BouncyCastle.Asn1.Pkcs;
using Org.BouncyCastle.Asn1.X509;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Generators;
using Org.BouncyCastle.Crypto.Operators;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Crypto.Prng;
using Org.BouncyCastle.Math;
using Org.BouncyCastle.OpenSsl;
using Org.BouncyCastle.Pkcs;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.Utilities;
using Org.BouncyCastle.X509;
namespace Titanium.Web.Proxy.Network.Certificate
{
/// <summary>
/// Implements certificate generation operations.
/// </summary>
internal class BCCertificateMaker : ICertificateMaker
{
private const int certificateValidDays = 1825;
private const int certificateGraceDays = 366;
/// <summary>
/// Makes the certificate.
/// </summary>
/// <param name="sSubjectCn">The s subject cn.</param>
/// <param name="isRoot">if set to <c>true</c> [is root].</param>
/// <param name="signingCert">The signing cert.</param>
/// <returns>X509Certificate2 instance.</returns>
public X509Certificate2 MakeCertificate(string sSubjectCn, bool isRoot, X509Certificate2 signingCert = null)
{
return MakeCertificateInternal(sSubjectCn, isRoot, true, signingCert);
}
/// <summary>
/// Generates the certificate.
/// </summary>
/// <param name="subjectName">Name of the subject.</param>
/// <param name="issuerName">Name of the issuer.</param>
/// <param name="validFrom">The valid from.</param>
/// <param name="validTo">The valid to.</param>
/// <param name="keyStrength">The key strength.</param>
/// <param name="signatureAlgorithm">The signature algorithm.</param>
/// <param name="issuerPrivateKey">The issuer private key.</param>
/// <param name="hostName">The host name</param>
/// <returns>X509Certificate2 instance.</returns>
/// <exception cref="PemException">Malformed sequence in RSA private key</exception>
private static X509Certificate2 GenerateCertificate(string hostName,
string subjectName,
string issuerName, DateTime validFrom,
DateTime validTo, int keyStrength = 2048,
string signatureAlgorithm = "SHA256WithRSA",
AsymmetricKeyParameter issuerPrivateKey = null)
{
// Generating Random Numbers
var randomGenerator = new CryptoApiRandomGenerator();
var secureRandom = new SecureRandom(randomGenerator);
// The Certificate Generator
var certificateGenerator = new X509V3CertificateGenerator();
// Serial Number
var serialNumber = BigIntegers.CreateRandomInRange(BigInteger.One, BigInteger.ValueOf(long.MaxValue), secureRandom);
certificateGenerator.SetSerialNumber(serialNumber);
// Issuer and Subject Name
var subjectDn = new X509Name(subjectName);
var issuerDn = new X509Name(issuerName);
certificateGenerator.SetIssuerDN(issuerDn);
certificateGenerator.SetSubjectDN(subjectDn);
certificateGenerator.SetNotBefore(validFrom);
certificateGenerator.SetNotAfter(validTo);
if (hostName != null)
{
//add subject alternative names
var subjectAlternativeNames = new Asn1Encodable[]
{
new GeneralName(GeneralName.DnsName, hostName),
};
var subjectAlternativeNamesExtension = new DerSequence(subjectAlternativeNames);
certificateGenerator.AddExtension(
X509Extensions.SubjectAlternativeName.Id, false, subjectAlternativeNamesExtension);
}
// Subject Public Key
var keyGenerationParameters = new KeyGenerationParameters(secureRandom, keyStrength);
var keyPairGenerator = new RsaKeyPairGenerator();
keyPairGenerator.Init(keyGenerationParameters);
var subjectKeyPair = keyPairGenerator.GenerateKeyPair();
certificateGenerator.SetPublicKey(subjectKeyPair.Public);
// Set certificate intended purposes to only Server Authentication
certificateGenerator.AddExtension(X509Extensions.ExtendedKeyUsage.Id, false, new ExtendedKeyUsage(KeyPurposeID.IdKPServerAuth));
var signatureFactory = new Asn1SignatureFactory(signatureAlgorithm, issuerPrivateKey ?? subjectKeyPair.Private, secureRandom);
// Self-sign the certificate
var certificate = certificateGenerator.Generate(signatureFactory);
var x509Certificate = new X509Certificate2(certificate.GetEncoded());
// Corresponding private key
var privateKeyInfo = PrivateKeyInfoFactory.CreatePrivateKeyInfo(subjectKeyPair.Private);
var seq = (Asn1Sequence) Asn1Object.FromByteArray(privateKeyInfo.ParsePrivateKey().GetDerEncoded());
if (seq.Count != 9)
{
throw new PemException("Malformed sequence in RSA private key");
}
var rsa = RsaPrivateKeyStructure.GetInstance(seq);
var rsaparams = new RsaPrivateCrtKeyParameters(rsa.Modulus, rsa.PublicExponent, rsa.PrivateExponent, rsa.Prime1, rsa.Prime2, rsa.Exponent1, rsa.Exponent2, rsa.Coefficient);
// Set private key onto certificate instance
x509Certificate.PrivateKey = DotNetUtilities.ToRSA(rsaparams);
x509Certificate.FriendlyName = subjectName;
return x509Certificate;
}
/// <summary>
/// Makes the certificate internal.
/// </summary>
/// <param name="isRoot">if set to <c>true</c> [is root].</param>
/// <param name="hostName">hostname for certificate</param>
/// <param name="subjectName">The full subject.</param>
/// <param name="validFrom">The valid from.</param>
/// <param name="validTo">The valid to.</param>
/// <param name="signingCertificate">The signing certificate.</param>
/// <returns>X509Certificate2 instance.</returns>
/// <exception cref="System.ArgumentException">You must specify a Signing Certificate if and only if you are not creating a root.</exception>
private X509Certificate2 MakeCertificateInternal(bool isRoot,
string hostName, string subjectName,
DateTime validFrom, DateTime validTo, X509Certificate2 signingCertificate)
{
if (isRoot != (null == signingCertificate))
{
throw new ArgumentException("You must specify a Signing Certificate if and only if you are not creating a root.", nameof(signingCertificate));
}
return isRoot
? GenerateCertificate(null, subjectName, subjectName, validFrom, validTo)
: GenerateCertificate(hostName, subjectName, signingCertificate.Subject, validFrom, validTo, issuerPrivateKey: DotNetUtilities.GetKeyPair(signingCertificate.PrivateKey).Private);
}
/// <summary>
/// Makes the certificate internal.
/// </summary>
/// <param name="subject">The s subject cn.</param>
/// <param name="isRoot">if set to <c>true</c> [is root].</param>
/// <param name="switchToMtaIfNeeded">if set to <c>true</c> [switch to MTA if needed].</param>
/// <param name="signingCert">The signing cert.</param>
/// <param name="cancellationToken">Task cancellation token</param>
/// <returns>X509Certificate2.</returns>
private X509Certificate2 MakeCertificateInternal(string subject, bool isRoot,
bool switchToMtaIfNeeded, X509Certificate2 signingCert = null,
CancellationToken cancellationToken = default(CancellationToken))
{
X509Certificate2 certificate = null;
if (switchToMtaIfNeeded && Thread.CurrentThread.GetApartmentState() != ApartmentState.MTA)
{
using (var manualResetEvent = new ManualResetEventSlim(false))
{
ThreadPool.QueueUserWorkItem(o =>
{
certificate = MakeCertificateInternal(subject, isRoot, false, signingCert);
if (!cancellationToken.IsCancellationRequested)
{
manualResetEvent?.Set();
}
});
manualResetEvent.Wait(TimeSpan.FromMinutes(1), cancellationToken);
}
return certificate;
}
return MakeCertificateInternal(isRoot, subject, $"CN={subject}", DateTime.UtcNow.AddDays(-certificateGraceDays), DateTime.UtcNow.AddDays(certificateValidDays), isRoot ? null : signingCert);
}
}
}
using System.Security.Cryptography.X509Certificates;
namespace Titanium.Web.Proxy.Network.Certificate
{
/// <summary>
/// Abstract interface for different Certificate Maker Engines
/// </summary>
internal interface ICertificateMaker
{
X509Certificate2 MakeCertificate(string sSubjectCn, bool isRoot, X509Certificate2 signingCert);
}
}
using System;
using System.Reflection;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
namespace Titanium.Web.Proxy.Network.Certificate
{
/// <summary>
/// Certificate Maker - uses MakeCert
/// Calls COM objects using reflection
/// </summary>
internal class WinCertificateMaker : ICertificateMaker
{
private readonly Type typeX500DN;
private readonly Type typeX509PrivateKey;
private readonly Type typeOID;
private readonly Type typeOIDS;
private readonly Type typeKUExt;
private readonly Type typeEKUExt;
private readonly Type typeRequestCert;
private readonly Type typeX509Extensions;
private readonly Type typeBasicConstraints;
private readonly Type typeSignerCertificate;
private readonly Type typeX509Enrollment;
private readonly Type typeAltNamesCollection;
private readonly Type typeExtNames;
private readonly Type typeCAlternativeName;
private readonly string sProviderName = "Microsoft Enhanced Cryptographic Provider v1.0";
private object sharedPrivateKey;
/// <summary>
/// Constructor.
/// </summary>
internal WinCertificateMaker()
{
typeX500DN = Type.GetTypeFromProgID("X509Enrollment.CX500DistinguishedName", true);
typeX509PrivateKey = Type.GetTypeFromProgID("X509Enrollment.CX509PrivateKey", true);
typeOID = Type.GetTypeFromProgID("X509Enrollment.CObjectId", true);
typeOIDS = Type.GetTypeFromProgID("X509Enrollment.CObjectIds.1", true);
typeEKUExt = Type.GetTypeFromProgID("X509Enrollment.CX509ExtensionEnhancedKeyUsage");
typeKUExt = Type.GetTypeFromProgID("X509Enrollment.CX509ExtensionKeyUsage");
typeRequestCert = Type.GetTypeFromProgID("X509Enrollment.CX509CertificateRequestCertificate");
typeX509Extensions = Type.GetTypeFromProgID("X509Enrollment.CX509Extensions");
typeBasicConstraints = Type.GetTypeFromProgID("X509Enrollment.CX509ExtensionBasicConstraints");
typeSignerCertificate = Type.GetTypeFromProgID("X509Enrollment.CSignerCertificate");
typeX509Enrollment = Type.GetTypeFromProgID("X509Enrollment.CX509Enrollment");
//for alternative names
typeAltNamesCollection = Type.GetTypeFromProgID("X509Enrollment.CAlternativeNames");
typeExtNames = Type.GetTypeFromProgID("X509Enrollment.CX509ExtensionAlternativeNames");
typeCAlternativeName = Type.GetTypeFromProgID("X509Enrollment.CAlternativeName");
}
/// <summary>
/// Make certificate.
/// </summary>
/// <param name="sSubjectCN"></param>
/// <param name="isRoot"></param>
/// <param name="signingCert"></param>
/// <returns></returns>
public X509Certificate2 MakeCertificate(string sSubjectCN, bool isRoot, X509Certificate2 signingCert = null)
{
return MakeCertificateInternal(sSubjectCN, isRoot, true, signingCert);
}
private X509Certificate2 MakeCertificate(bool isRoot, string subject, string fullSubject,
int privateKeyLength, string hashAlg, DateTime validFrom, DateTime validTo,
X509Certificate2 signingCertificate)
{
if (isRoot != (null == signingCertificate))
{
throw new ArgumentException("You must specify a Signing Certificate if and only if you are not creating a root.", nameof(isRoot));
}
var x500CertDN = Activator.CreateInstance(typeX500DN);
var typeValue = new object[] {fullSubject, 0};
typeX500DN.InvokeMember("Encode", BindingFlags.InvokeMethod, null, x500CertDN, typeValue);
var x500RootCertDN = Activator.CreateInstance(typeX500DN);
if (!isRoot)
{
typeValue[0] = signingCertificate.Subject;
}
typeX500DN.InvokeMember("Encode", BindingFlags.InvokeMethod, null, x500RootCertDN, typeValue);
object sharedPrivateKey = null;
if (!isRoot)
{
sharedPrivateKey = this.sharedPrivateKey;
}
if (sharedPrivateKey == null)
{
sharedPrivateKey = Activator.CreateInstance(typeX509PrivateKey);
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)};
typeX509PrivateKey.InvokeMember("KeySpec", BindingFlags.PutDispProperty, null, sharedPrivateKey, typeValue);
if (!isRoot)
{
typeValue = new object[] {176};
typeX509PrivateKey.InvokeMember("KeyUsage", BindingFlags.PutDispProperty, null, sharedPrivateKey, typeValue);
}
typeValue[0] = privateKeyLength;
typeX509PrivateKey.InvokeMember("Length", BindingFlags.PutDispProperty, null, sharedPrivateKey, typeValue);
typeX509PrivateKey.InvokeMember("Create", BindingFlags.InvokeMethod, null, sharedPrivateKey, null);
if (!isRoot)
{
this.sharedPrivateKey = sharedPrivateKey;
}
}
typeValue = new object[1];
var oid = Activator.CreateInstance(typeOID);
typeValue[0] = "1.3.6.1.5.5.7.3.1";
typeOID.InvokeMember("InitializeFromValue", BindingFlags.InvokeMethod, null, oid, typeValue);
var oids = Activator.CreateInstance(typeOIDS);
typeValue[0] = oid;
typeOIDS.InvokeMember("Add", BindingFlags.InvokeMethod, null, oids, typeValue);
var ekuExt = Activator.CreateInstance(typeEKUExt);
typeValue[0] = oids;
typeEKUExt.InvokeMember("InitializeEncode", BindingFlags.InvokeMethod, null, ekuExt, typeValue);
var requestCert = Activator.CreateInstance(typeRequestCert);
typeValue = new[] {1, sharedPrivateKey, string.Empty};
typeRequestCert.InvokeMember("InitializeFromPrivateKey", BindingFlags.InvokeMethod, null, requestCert, typeValue);
typeValue = new[] {x500CertDN};
typeRequestCert.InvokeMember("Subject", BindingFlags.PutDispProperty, null, requestCert, typeValue);
typeValue[0] = x500RootCertDN;
typeRequestCert.InvokeMember("Issuer", BindingFlags.PutDispProperty, null, requestCert, typeValue);
typeValue[0] = validFrom;
typeRequestCert.InvokeMember("NotBefore", BindingFlags.PutDispProperty, null, requestCert, typeValue);
typeValue[0] = validTo;
typeRequestCert.InvokeMember("NotAfter", BindingFlags.PutDispProperty, null, requestCert, typeValue);
var kuExt = Activator.CreateInstance(typeKUExt);
typeValue[0] = 176;
typeKUExt.InvokeMember("InitializeEncode", BindingFlags.InvokeMethod, null, kuExt, typeValue);
var certificate = typeRequestCert.InvokeMember("X509Extensions", BindingFlags.GetProperty, null, requestCert, null);
typeValue = new object[1];
if (!isRoot)
{
typeValue[0] = kuExt;
typeX509Extensions.InvokeMember("Add", BindingFlags.InvokeMethod, null, certificate, typeValue);
}
typeValue[0] = ekuExt;
typeX509Extensions.InvokeMember("Add", BindingFlags.InvokeMethod, null, certificate, typeValue);
if (!isRoot)
{
//add alternative names
// https://forums.iis.net/t/1180823.aspx
var altNameCollection = Activator.CreateInstance(typeAltNamesCollection);
var extNames = Activator.CreateInstance(typeExtNames);
var altDnsNames = Activator.CreateInstance(typeCAlternativeName);
typeValue = new object[] {3, subject};
typeCAlternativeName.InvokeMember("InitializeFromString", BindingFlags.InvokeMethod, null, altDnsNames, typeValue);
typeValue = new[] {altDnsNames};
typeAltNamesCollection.InvokeMember("Add", BindingFlags.InvokeMethod, null, altNameCollection, typeValue);
typeValue = new[] {altNameCollection};
typeExtNames.InvokeMember("InitializeEncode", BindingFlags.InvokeMethod, null, extNames, typeValue);
typeValue[0] = extNames;
typeX509Extensions.InvokeMember("Add", BindingFlags.InvokeMethod, null, certificate, typeValue);
}
if (!isRoot)
{
var signerCertificate = Activator.CreateInstance(typeSignerCertificate);
typeValue = new object[] {0, 0, 12, signingCertificate.Thumbprint};
typeSignerCertificate.InvokeMember("Initialize", BindingFlags.InvokeMethod, null, signerCertificate, typeValue);
typeValue = new[] {signerCertificate};
typeRequestCert.InvokeMember("SignerCertificate", BindingFlags.PutDispProperty, null, requestCert, typeValue);
}
else
{
var basicConstraints = Activator.CreateInstance(typeBasicConstraints);
typeValue = new object[] {"true", "0"};
typeBasicConstraints.InvokeMember("InitializeEncode", BindingFlags.InvokeMethod, null, basicConstraints, typeValue);
typeValue = new[] {basicConstraints};
typeX509Extensions.InvokeMember("Add", BindingFlags.InvokeMethod, null, certificate, typeValue);
}
oid = Activator.CreateInstance(typeOID);
typeValue = new object[] {1, 0, 0, hashAlg};
typeOID.InvokeMember("InitializeFromAlgorithmName", BindingFlags.InvokeMethod, null, oid, typeValue);
typeValue = new[] {oid};
typeRequestCert.InvokeMember("HashAlgorithm", BindingFlags.PutDispProperty, null, requestCert, typeValue);
typeRequestCert.InvokeMember("Encode", BindingFlags.InvokeMethod, null, requestCert, null);
var x509Enrollment = Activator.CreateInstance(typeX509Enrollment);
typeValue[0] = requestCert;
typeX509Enrollment.InvokeMember("InitializeFromRequest", BindingFlags.InvokeMethod, null, x509Enrollment, typeValue);
if (isRoot)
{
typeValue[0] = fullSubject;
typeX509Enrollment.InvokeMember("CertificateFriendlyName", BindingFlags.PutDispProperty, null, x509Enrollment, typeValue);
}
var members = typeX509Enrollment.GetMembers();
typeValue[0] = 0;
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};
try
{
var empty = (string) typeX509Enrollment.InvokeMember("CreatePFX", BindingFlags.InvokeMethod, null, x509Enrollment, typeValue);
return new X509Certificate2(Convert.FromBase64String(empty), string.Empty, X509KeyStorageFlags.Exportable);
}
catch (Exception)
{
// ignored
}
return null;
}
private X509Certificate2 MakeCertificateInternal(string sSubjectCN, bool isRoot,
bool switchToMTAIfNeeded,
X509Certificate2 signingCert = null)
{
X509Certificate2 rCert = null;
if (switchToMTAIfNeeded && Thread.CurrentThread.GetApartmentState() != ApartmentState.MTA)
{
var manualResetEvent = new ManualResetEvent(false);
ThreadPool.QueueUserWorkItem(o =>
{
rCert = MakeCertificateInternal(sSubjectCN, isRoot, false, signingCert);
manualResetEvent.Set();
});
manualResetEvent.WaitOne();
manualResetEvent.Close();
return rCert;
}
//Subject
var fullSubject = $"CN={sSubjectCN}";
//Sig Algo
var HashAlgo = "SHA256";
//Grace Days
var GraceDays = -366;
//ValiDays
var ValidDays = 1825;
//KeyLength
var keyLength = 2048;
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);
return rCert;
}
}
}
using System;
using System.Reflection;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
namespace Titanium.Web.Proxy.Network
{
public class CertificateMaker
{
private Type typeX500DN;
private Type typeX509PrivateKey;
private Type typeOID;
private Type typeOIDS;
private Type typeKUExt;
private Type typeEKUExt;
private Type typeRequestCert;
private Type typeX509Extensions;
private Type typeBasicConstraints;
private Type typeSignerCertificate;
private Type typeX509Enrollment;
private Type typeAlternativeName;
private Type typeAlternativeNames;
private Type typeAlternativeNamesExt;
private string sProviderName = "Microsoft Enhanced Cryptographic Provider v1.0";
private object _SharedPrivateKey;
public CertificateMaker()
{
this.typeX500DN = Type.GetTypeFromProgID("X509Enrollment.CX500DistinguishedName", true);
this.typeX509PrivateKey = Type.GetTypeFromProgID("X509Enrollment.CX509PrivateKey", true);
this.typeOID = Type.GetTypeFromProgID("X509Enrollment.CObjectId", true);
this.typeOIDS = Type.GetTypeFromProgID("X509Enrollment.CObjectIds.1", true);
this.typeEKUExt = Type.GetTypeFromProgID("X509Enrollment.CX509ExtensionEnhancedKeyUsage");
this.typeKUExt = Type.GetTypeFromProgID("X509Enrollment.CX509ExtensionKeyUsage");
this.typeRequestCert = Type.GetTypeFromProgID("X509Enrollment.CX509CertificateRequestCertificate");
this.typeX509Extensions = Type.GetTypeFromProgID("X509Enrollment.CX509Extensions");
this.typeBasicConstraints = Type.GetTypeFromProgID("X509Enrollment.CX509ExtensionBasicConstraints");
this.typeSignerCertificate = Type.GetTypeFromProgID("X509Enrollment.CSignerCertificate");
this.typeX509Enrollment = Type.GetTypeFromProgID("X509Enrollment.CX509Enrollment");
this.typeAlternativeName = Type.GetTypeFromProgID("X509Enrollment.CAlternativeName");
this.typeAlternativeNames = Type.GetTypeFromProgID("X509Enrollment.CAlternativeNames");
this.typeAlternativeNamesExt = Type.GetTypeFromProgID("X509Enrollment.CX509ExtensionAlternativeNames");
}
public X509Certificate2 MakeCertificate(string sSubjectCN, bool isRoot,X509Certificate2 signingCert=null)
{
return this.MakeCertificateInternal(sSubjectCN, isRoot, true, signingCert);
}
private X509Certificate2 MakeCertificate(bool IsRoot, string SubjectCN, string FullSubject, int PrivateKeyLength, string HashAlg, DateTime ValidFrom, DateTime ValidTo, X509Certificate2 SigningCertificate)
{
X509Certificate2 cert;
if (IsRoot != (null == SigningCertificate))
{
throw new ArgumentException("You must specify a Signing Certificate if and only if you are not creating a root.", "oSigningCertificate");
}
object x500DN = Activator.CreateInstance(this.typeX500DN);
object[] subject = new object[] { FullSubject, 0 };
this.typeX500DN.InvokeMember("Encode", BindingFlags.InvokeMethod, null, x500DN, subject);
object x500DN2 = Activator.CreateInstance(this.typeX500DN);
if (!IsRoot)
{
subject[0] = SigningCertificate.Subject;
}
this.typeX500DN.InvokeMember("Encode", BindingFlags.InvokeMethod, null, x500DN2, subject);
object sharedPrivateKey = null;
if (!IsRoot)
{
sharedPrivateKey = this._SharedPrivateKey;
}
if (sharedPrivateKey == null)
{
sharedPrivateKey = Activator.CreateInstance(this.typeX509PrivateKey);
subject = new object[] { this.sProviderName };
this.typeX509PrivateKey.InvokeMember("ProviderName", BindingFlags.PutDispProperty, null, sharedPrivateKey, subject);
subject[0] = 2;
this.typeX509PrivateKey.InvokeMember("ExportPolicy", BindingFlags.PutDispProperty, null, sharedPrivateKey, subject);
subject = new object[] { (IsRoot ? 2 : 1) };
this.typeX509PrivateKey.InvokeMember("KeySpec", BindingFlags.PutDispProperty, null, sharedPrivateKey, subject);
if (!IsRoot)
{
subject = new object[] { 176 };
this.typeX509PrivateKey.InvokeMember("KeyUsage", BindingFlags.PutDispProperty, null, sharedPrivateKey, subject);
}
subject[0] = PrivateKeyLength;
this.typeX509PrivateKey.InvokeMember("Length", BindingFlags.PutDispProperty, null, sharedPrivateKey, subject);
this.typeX509PrivateKey.InvokeMember("Create", BindingFlags.InvokeMethod, null, sharedPrivateKey, null);
if (!IsRoot)
{
this._SharedPrivateKey = sharedPrivateKey;
}
}
subject = new object[1];
object obj3 = Activator.CreateInstance(this.typeOID);
subject[0] = "1.3.6.1.5.5.7.3.1";
this.typeOID.InvokeMember("InitializeFromValue", BindingFlags.InvokeMethod, null, obj3, subject);
object obj4 = Activator.CreateInstance(this.typeOIDS);
subject[0] = obj3;
this.typeOIDS.InvokeMember("Add", BindingFlags.InvokeMethod, null, obj4, subject);
object obj5 = Activator.CreateInstance(this.typeEKUExt);
subject[0] = obj4;
this.typeEKUExt.InvokeMember("InitializeEncode", BindingFlags.InvokeMethod, null, obj5, subject);
object obj6 = Activator.CreateInstance(this.typeRequestCert);
subject = new object[] { 1, sharedPrivateKey, string.Empty };
this.typeRequestCert.InvokeMember("InitializeFromPrivateKey", BindingFlags.InvokeMethod, null, obj6, subject);
subject = new object[] { x500DN };
this.typeRequestCert.InvokeMember("Subject", BindingFlags.PutDispProperty, null, obj6, subject);
subject[0] = x500DN;
this.typeRequestCert.InvokeMember("Issuer", BindingFlags.PutDispProperty, null, obj6, subject);
subject[0] = ValidFrom;
this.typeRequestCert.InvokeMember("NotBefore", BindingFlags.PutDispProperty, null, obj6, subject);
subject[0] = ValidTo;
this.typeRequestCert.InvokeMember("NotAfter", BindingFlags.PutDispProperty, null, obj6, subject);
object obj7 = Activator.CreateInstance(this.typeKUExt);
subject[0] = 176;
this.typeKUExt.InvokeMember("InitializeEncode", BindingFlags.InvokeMethod, null, obj7, subject);
object obj8 = this.typeRequestCert.InvokeMember("X509Extensions", BindingFlags.GetProperty, null, obj6, null);
subject = new object[1];
if (!IsRoot)
{
subject[0] = obj7;
this.typeX509Extensions.InvokeMember("Add", BindingFlags.InvokeMethod, null, obj8, subject);
}
subject[0] = obj5;
this.typeX509Extensions.InvokeMember("Add", BindingFlags.InvokeMethod, null, obj8, subject);
if (!IsRoot)
{
object obj12 = Activator.CreateInstance(this.typeSignerCertificate);
subject = new object[] { 0, 0, 12, SigningCertificate.Thumbprint };
this.typeSignerCertificate.InvokeMember("Initialize", BindingFlags.InvokeMethod, null, obj12, subject);
subject = new object[] { obj12 };
this.typeRequestCert.InvokeMember("SignerCertificate", BindingFlags.PutDispProperty, null, obj6, subject);
}
else
{
object obj13 = Activator.CreateInstance(this.typeBasicConstraints);
subject = new object[] { "true", "0" };
this.typeBasicConstraints.InvokeMember("InitializeEncode", BindingFlags.InvokeMethod, null, obj13, subject);
subject = new object[] { obj13 };
this.typeX509Extensions.InvokeMember("Add", BindingFlags.InvokeMethod, null, obj8, subject);
}
object obj14 = Activator.CreateInstance(this.typeOID);
subject = new object[] { 1, 0, 0, HashAlg };
this.typeOID.InvokeMember("InitializeFromAlgorithmName", BindingFlags.InvokeMethod, null, obj14, subject);
subject = new object[] { obj14 };
this.typeRequestCert.InvokeMember("HashAlgorithm", BindingFlags.PutDispProperty, null, obj6, subject);
this.typeRequestCert.InvokeMember("Encode", BindingFlags.InvokeMethod, null, obj6, null);
object obj15 = Activator.CreateInstance(this.typeX509Enrollment);
subject[0] = obj6;
this.typeX509Enrollment.InvokeMember("InitializeFromRequest", BindingFlags.InvokeMethod, null, obj15, subject);
if (IsRoot)
{
subject[0] = "DO_NOT_TRUST_TitaniumProxy-CE";
this.typeX509Enrollment.InvokeMember("CertificateFriendlyName", BindingFlags.PutDispProperty, null, obj15, subject);
}
subject[0] = 0;
object obj16 = this.typeX509Enrollment.InvokeMember("CreateRequest", BindingFlags.InvokeMethod, null, obj15, subject);
subject = new object[] { 2, obj16, 0, string.Empty };
this.typeX509Enrollment.InvokeMember("InstallResponse", BindingFlags.InvokeMethod, null, obj15, subject);
subject = new object[] { null, 0, 1 };
string empty = string.Empty;
try
{
empty = (string)this.typeX509Enrollment.InvokeMember("CreatePFX", BindingFlags.InvokeMethod, null, obj15, subject);
return new X509Certificate2(Convert.FromBase64String(empty), string.Empty, X509KeyStorageFlags.Exportable);
}
catch (Exception exception1)
{
Exception exception = exception1;
cert = null;
}
return cert;
}
private X509Certificate2 MakeCertificateInternal(string sSubjectCN, bool isRoot, bool switchToMTAIfNeeded,X509Certificate2 signingCert=null)
{
X509Certificate2 rCert=null;
if (switchToMTAIfNeeded && Thread.CurrentThread.GetApartmentState() != ApartmentState.MTA)
{
ManualResetEvent manualResetEvent = new ManualResetEvent(false);
ThreadPool.QueueUserWorkItem((object o) =>
{
rCert = this.MakeCertificateInternal(sSubjectCN, isRoot, false,signingCert);
manualResetEvent.Set();
});
manualResetEvent.WaitOne();
manualResetEvent.Close();
return rCert;
}
string fullSubject = string.Format("CN={0}{1}", sSubjectCN, "");//Subject
string HashAlgo = "SHA256"; //Sig Algo
int GraceDays = -366; //Grace Days
int ValidDays = 1825; //ValiDays
int keyLength = 2048; //KeyLength
DateTime graceTime = DateTime.Now.AddDays((double)GraceDays);
DateTime now = DateTime.Now;
try
{
if (!isRoot)
{
rCert = this.MakeCertificate(false, sSubjectCN, fullSubject, keyLength, HashAlgo, graceTime, now.AddDays((double)ValidDays), signingCert);
}
else
{
rCert = this.MakeCertificate(true, sSubjectCN, fullSubject, keyLength, HashAlgo, graceTime, now.AddDays((double)ValidDays), null);
}
}
catch (Exception e)
{
throw e;
}
return rCert;
}
}
}
...@@ -5,116 +5,245 @@ using System.Threading.Tasks; ...@@ -5,116 +5,245 @@ using System.Threading.Tasks;
using System.Linq; using System.Linq;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.IO; using System.IO;
using Titanium.Web.Proxy.Network.Certificate;
using Titanium.Web.Proxy.Helpers;
namespace Titanium.Web.Proxy.Network namespace Titanium.Web.Proxy.Network
{ {
/// <summary>
/// Certificate Engine option
/// </summary>
public enum CertificateEngine
{
/// <summary>
/// Uses Windows Certification Generation API
/// </summary>
DefaultWindows = 0,
/// <summary>
/// Uses BouncyCastle 3rd party library
/// </summary>
BouncyCastle = 1
}
/// <summary> /// <summary>
/// A class to manage SSL certificates used by this proxy server /// A class to manage SSL certificates used by this proxy server
/// </summary> /// </summary>
internal class CertificateManager : IDisposable public class CertificateManager : IDisposable
{ {
private CertificateMaker certEngine = null; internal CertificateEngine Engine
{
get { return engine; }
set
{
//For Mono only Bouncy Castle is supported
if (RunTime.IsRunningOnMono())
{
value = CertificateEngine.BouncyCastle;
}
if (value != engine)
{
certEngine = null;
engine = value;
}
if (certEngine == null)
{
certEngine = engine == CertificateEngine.BouncyCastle
? (ICertificateMaker) new BCCertificateMaker()
: new WinCertificateMaker();
}
}
}
private const string defaultRootCertificateIssuer = "Titanium";
private const string defaultRootRootCertificateName = "Titanium Root Certificate Authority";
private CertificateEngine engine;
private ICertificateMaker certEngine;
private string issuer;
private string rootCertificateName;
private bool clearCertificates { get; set; } private bool clearCertificates { get; set; }
private X509Certificate2 rootCertificate;
/// <summary> /// <summary>
/// Cache dictionary /// Cache dictionary
/// </summary> /// </summary>
private readonly IDictionary<string, CachedCertificate> certificateCache; private readonly IDictionary<string, CachedCertificate> certificateCache;
private Action<Exception> exceptionFunc; private readonly Action<Exception> exceptionFunc;
internal string Issuer { get; private set; } internal string Issuer
internal string RootCertificateName { get; private set; } {
get { return issuer ?? defaultRootCertificateIssuer; }
set
{
issuer = value;
ClearRootCertificate();
}
}
internal X509Certificate2 rootCertificate { get; set; } internal string RootCertificateName
{
get { return rootCertificateName ?? defaultRootRootCertificateName; }
set
{
rootCertificateName = value;
ClearRootCertificate();
}
}
internal CertificateManager(string issuer, string rootCertificateName, Action<Exception> exceptionFunc) internal X509Certificate2 RootCertificate
{ {
this.exceptionFunc = exceptionFunc; get { return rootCertificate; }
set
{
ClearRootCertificate();
rootCertificate = value;
}
}
certEngine = new CertificateMaker(); /// <summary>
/// Is the root certificate used by this proxy is valid?
/// </summary>
internal bool CertValidated => RootCertificate != null;
Issuer = issuer;
RootCertificateName = rootCertificateName; internal CertificateManager(Action<Exception> exceptionFunc)
{
this.exceptionFunc = exceptionFunc;
Engine = CertificateEngine.DefaultWindows;
certificateCache = new ConcurrentDictionary<string, CachedCertificate>(); certificateCache = new ConcurrentDictionary<string, CachedCertificate>();
} }
internal X509Certificate2 GetRootCertificate() private void ClearRootCertificate()
{
certificateCache.Clear();
rootCertificate = null;
}
private string GetRootCertificatePath()
{ {
var fileName = Path.Combine(System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), "rootCert.pfx"); var assemblyLocation = System.Reflection.Assembly.GetExecutingAssembly().Location;
if (File.Exists(fileName)) // dynamically loaded assemblies returns string.Empty location
if (assemblyLocation == string.Empty)
{ {
try assemblyLocation = System.Reflection.Assembly.GetEntryAssembly().Location;
{
return new X509Certificate2(fileName, string.Empty, X509KeyStorageFlags.Exportable);
}
catch (Exception e)
{
exceptionFunc(e);
return null;
}
} }
return null;
var path = Path.GetDirectoryName(assemblyLocation);
if (null == path) throw new NullReferenceException();
var fileName = Path.Combine(path, "rootCert.pfx");
return fileName;
} }
internal X509Certificate2 LoadRootCertificate()
{
var fileName = GetRootCertificatePath();
if (!File.Exists(fileName)) return null;
try
{
return new X509Certificate2(fileName, string.Empty, X509KeyStorageFlags.Exportable);
}
catch (Exception e)
{
exceptionFunc(e);
return null;
}
}
/// <summary> /// <summary>
/// Attempts to create a RootCertificate /// Attempts to create a RootCertificate
/// </summary> /// </summary>
/// <returns>true if succeeded, else false</returns> /// <param name="persistToFile">if set to <c>true</c> try to load/save the certificate from rootCert.pfx.</param>
internal bool CreateTrustedRootCertificate() /// <returns>
/// true if succeeded, else false
/// </returns>
public bool CreateTrustedRootCertificate(bool persistToFile = true)
{ {
if (persistToFile && RootCertificate == null)
{
RootCertificate = LoadRootCertificate();
}
rootCertificate = GetRootCertificate(); if (RootCertificate != null)
if (rootCertificate != null)
{ {
return true; return true;
} }
try try
{ {
rootCertificate = CreateCertificate(RootCertificateName, true); RootCertificate = CreateCertificate(RootCertificateName, true);
} }
catch(Exception e) catch (Exception e)
{ {
exceptionFunc(e); exceptionFunc(e);
} }
if (rootCertificate != null)
if (persistToFile && RootCertificate != null)
{ {
try try
{ {
var fileName = Path.Combine(System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), "rootCert.pfx"); var fileName = GetRootCertificatePath();
File.WriteAllBytes(fileName, rootCertificate.Export(X509ContentType.Pkcs12)); File.WriteAllBytes(fileName, RootCertificate.Export(X509ContentType.Pkcs12));
} }
catch(Exception e) catch (Exception e)
{ {
exceptionFunc(e); exceptionFunc(e);
} }
} }
return rootCertificate != null;
return RootCertificate != null;
}
/// <summary>
/// Trusts the root certificate.
/// </summary>
public void TrustRootCertificate()
{
//current user
TrustRootCertificate(StoreLocation.CurrentUser);
//current system
TrustRootCertificate(StoreLocation.LocalMachine);
}
/// <summary>
/// Removes the trusted certificates.
/// </summary>
public void RemoveTrustedRootCertificates()
{
//current user
RemoveTrustedRootCertificates(StoreLocation.CurrentUser);
//current system
RemoveTrustedRootCertificates(StoreLocation.LocalMachine);
} }
/// <summary> /// <summary>
/// Create an SSL certificate /// Create an SSL certificate
/// </summary> /// </summary>
/// <param name="store"></param>
/// <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 virtual X509Certificate2 CreateCertificate(string certificateName, bool isRootCertificate)
{ {
try if (certificateCache.ContainsKey(certificateName))
{ {
if (certificateCache.ContainsKey(certificateName)) var cached = certificateCache[certificateName];
{ cached.LastAccess = DateTime.Now;
var cached = certificateCache[certificateName]; return cached.Certificate;
cached.LastAccess = DateTime.Now;
return cached.Certificate;
}
} }
catch
{
}
X509Certificate2 certificate = null; X509Certificate2 certificate = null;
lock (string.Intern(certificateName)) lock (string.Intern(certificateName))
{ {
...@@ -122,15 +251,20 @@ namespace Titanium.Web.Proxy.Network ...@@ -122,15 +251,20 @@ namespace Titanium.Web.Proxy.Network
{ {
try try
{ {
certificate = certEngine.MakeCertificate(certificateName, isRootCertificate, rootCertificate); if (!isRootCertificate && RootCertificate == null)
{
CreateTrustedRootCertificate();
}
certificate = certEngine.MakeCertificate(certificateName, isRootCertificate, RootCertificate);
} }
catch(Exception e) catch (Exception e)
{ {
exceptionFunc(e); exceptionFunc(e);
} }
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
...@@ -144,10 +278,7 @@ namespace Titanium.Web.Proxy.Network ...@@ -144,10 +278,7 @@ namespace Titanium.Web.Proxy.Network
} }
} }
return certificate; return certificate;
} }
/// <summary> /// <summary>
...@@ -166,56 +297,97 @@ namespace Titanium.Web.Proxy.Network ...@@ -166,56 +297,97 @@ namespace Titanium.Web.Proxy.Network
clearCertificates = true; clearCertificates = true;
while (clearCertificates) while (clearCertificates)
{ {
var cutOff = DateTime.Now.AddMinutes(-1 * certificateCacheTimeOutMinutes);
try var outdated = certificateCache
{ .Where(x => x.Value.LastAccess < cutOff)
var cutOff = DateTime.Now.AddMinutes(-1 * certificateCacheTimeOutMinutes); .ToList();
var outdated = certificateCache
.Where(x => x.Value.LastAccess < cutOff)
.ToList();
foreach (var cache in outdated) foreach (var cache in outdated)
certificateCache.Remove(cache.Key); certificateCache.Remove(cache.Key);
}
finally
{
}
//after a minute come back to check for outdated certificates in cache //after a minute come back to check for outdated certificates in cache
await Task.Delay(1000 * 60); await Task.Delay(1000 * 60);
} }
} }
internal bool TrustRootCertificate() /// <summary>
/// Make current machine trust the Root Certificate used by this proxy
/// </summary>
/// <param name="storeLocation"></param>
/// <returns></returns>
internal void TrustRootCertificate(StoreLocation storeLocation)
{ {
if (rootCertificate == null) if (RootCertificate == null)
{ {
return false; exceptionFunc(
new Exception("Could not set root certificate"
+ " as system proxy since it is null or empty."));
return;
} }
X509Store x509RootStore = new X509Store(StoreName.Root, storeLocation);
var x509PersonalStore = new X509Store(StoreName.My, storeLocation);
try try
{ {
X509Store x509RootStore = new X509Store(StoreName.Root, StoreLocation.CurrentUser); x509RootStore.Open(OpenFlags.ReadWrite);
var x509PersonalStore = new X509Store(StoreName.My, StoreLocation.CurrentUser); x509PersonalStore.Open(OpenFlags.ReadWrite);
x509RootStore.Add(RootCertificate);
x509PersonalStore.Add(RootCertificate);
}
catch (Exception e)
{
exceptionFunc(
new Exception("Failed to make system trust root certificate "
+ $" for {storeLocation} store location. You may need admin rights.", e));
}
finally
{
x509RootStore.Close();
x509PersonalStore.Close();
}
}
/// <summary>
/// Remove the Root Certificate trust
/// </summary>
/// <param name="storeLocation"></param>
/// <returns></returns>
internal void RemoveTrustedRootCertificates(StoreLocation storeLocation)
{
if (RootCertificate == null)
{
exceptionFunc(
new Exception("Could not set root certificate"
+ " as system proxy since it is null or empty."));
return;
}
X509Store x509RootStore = new X509Store(StoreName.Root, storeLocation);
var x509PersonalStore = new X509Store(StoreName.My, storeLocation);
try
{
x509RootStore.Open(OpenFlags.ReadWrite); x509RootStore.Open(OpenFlags.ReadWrite);
x509PersonalStore.Open(OpenFlags.ReadWrite); x509PersonalStore.Open(OpenFlags.ReadWrite);
try x509RootStore.Remove(RootCertificate);
{ x509PersonalStore.Remove(RootCertificate);
x509RootStore.Add(rootCertificate); }
x509PersonalStore.Add(rootCertificate); catch (Exception e)
} {
finally exceptionFunc(
{ new Exception("Failed to make system trust root certificate "
x509RootStore.Close(); + $" for {storeLocation} store location. You may need admin rights.", e));
x509PersonalStore.Close();
}
return true;
} }
catch finally
{ {
return false; x509RootStore.Close();
x509PersonalStore.Close();
} }
} }
...@@ -223,4 +395,4 @@ namespace Titanium.Web.Proxy.Network ...@@ -223,4 +395,4 @@ namespace Titanium.Web.Proxy.Network
{ {
} }
} }
} }
\ No newline at end of file
...@@ -7,7 +7,7 @@ namespace Titanium.Web.Proxy.Network ...@@ -7,7 +7,7 @@ namespace Titanium.Web.Proxy.Network
/// <summary> /// <summary>
/// This class wraps Tcp connection to client /// This class wraps Tcp connection to client
/// </summary> /// </summary>
public class ProxyClient internal class ProxyClient
{ {
/// <summary> /// <summary>
/// TcpClient used to communicate with client /// TcpClient used to communicate with client
...@@ -28,6 +28,5 @@ namespace Titanium.Web.Proxy.Network ...@@ -28,6 +28,5 @@ namespace Titanium.Web.Proxy.Network
/// used to write line by line to client /// used to write line by line to client
/// </summary> /// </summary>
internal StreamWriter ClientStreamWriter { get; set; } internal StreamWriter ClientStreamWriter { get; set; }
} }
} }
...@@ -7,15 +7,16 @@ using Titanium.Web.Proxy.Models; ...@@ -7,15 +7,16 @@ using Titanium.Web.Proxy.Models;
namespace Titanium.Web.Proxy.Network.Tcp namespace Titanium.Web.Proxy.Network.Tcp
{ {
/// <summary> /// <summary>
/// An object that holds TcpConnection to a particular server & port /// An object that holds TcpConnection to a particular server and port
/// </summary> /// </summary>
public class TcpConnection : IDisposable internal class TcpConnection : IDisposable
{ {
internal ExternalProxy UpStreamHttpProxy { get; set; } internal ExternalProxy UpStreamHttpProxy { get; set; }
internal ExternalProxy UpStreamHttpsProxy { get; set; } internal ExternalProxy UpStreamHttpsProxy { get; set; }
internal string HostName { get; set; } internal string HostName { get; set; }
internal int Port { get; set; } internal int Port { get; set; }
internal bool IsHttps { get; set; } internal bool IsHttps { get; set; }
...@@ -47,18 +48,17 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -47,18 +48,17 @@ namespace Titanium.Web.Proxy.Network.Tcp
LastAccess = DateTime.Now; LastAccess = DateTime.Now;
} }
/// <summary>
/// Dispose.
/// </summary>
public void Dispose() public void Dispose()
{ {
Stream.Close(); Stream?.Close();
Stream.Dispose(); Stream?.Dispose();
TcpClient.LingerState = new LingerOption(true, 0); StreamReader?.Dispose();
TcpClient.Client.Shutdown(SocketShutdown.Both);
TcpClient.Client.Close();
TcpClient.Client.Dispose();
TcpClient.Close(); TcpClient?.Close();
} }
} }
} }
...@@ -6,14 +6,12 @@ using System.IO; ...@@ -6,14 +6,12 @@ using System.IO;
using System.Net.Security; using System.Net.Security;
using Titanium.Web.Proxy.Helpers; using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Models; using Titanium.Web.Proxy.Models;
using System.Security.Authentication;
using System.Linq; using System.Linq;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Shared; using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Network.Tcp namespace Titanium.Web.Proxy.Network.Tcp
{ {
using System.Net;
/// <summary> /// <summary>
/// A class that manages Tcp Connection to server used by this proxy server /// A class that manages Tcp Connection to server used by this proxy server
/// </summary> /// </summary>
...@@ -23,44 +21,41 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -23,44 +21,41 @@ namespace Titanium.Web.Proxy.Network.Tcp
/// <summary> /// <summary>
/// Creates a TCP connection to server /// Creates a TCP connection to server
/// </summary> /// </summary>
/// <param name="bufferSize"></param> /// <param name="server"></param>
/// <param name="connectionTimeOutSeconds"></param>
/// <param name="remoteHostName"></param> /// <param name="remoteHostName"></param>
/// <param name="httpCmd"></param> /// <param name="remotePort"></param>
/// <param name="httpVersion"></param> /// <param name="httpVersion"></param>
/// <param name="isHttps"></param> /// <param name="isHttps"></param>
/// <param name="remotePort"></param>
/// <param name="supportedSslProtocols"></param>
/// <param name="remoteCertificateValidationCallback"></param>
/// <param name="localCertificateSelectionCallback"></param>
/// <param name="externalHttpProxy"></param> /// <param name="externalHttpProxy"></param>
/// <param name="externalHttpsProxy"></param> /// <param name="externalHttpsProxy"></param>
/// <param name="clientStream"></param> /// <param name="clientStream"></param>
/// <param name="upStreamEndPoint"></param>
/// <returns></returns> /// <returns></returns>
internal async Task<TcpConnection> CreateClient(int bufferSize, int connectionTimeOutSeconds, internal async Task<TcpConnection> CreateClient(ProxyServer server,
string remoteHostName, int remotePort, Version httpVersion, string remoteHostName, int remotePort, Version httpVersion,
bool isHttps, SslProtocols supportedSslProtocols, bool isHttps,
RemoteCertificateValidationCallback remoteCertificateValidationCallback, LocalCertificateSelectionCallback localCertificateSelectionCallback,
ExternalProxy externalHttpProxy, ExternalProxy externalHttpsProxy, ExternalProxy externalHttpProxy, ExternalProxy externalHttpsProxy,
Stream clientStream, EndPoint upStreamEndPoint) Stream clientStream)
{ {
TcpClient client; TcpClient client;
Stream stream; 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) if (isHttps)
{ {
SslStream sslStream = null; SslStream sslStream = null;
//If this proxy uses another external proxy then create a tunnel request for HTTPS connections //If this proxy uses another external proxy then create a tunnel request for HTTPS connections
if (externalHttpsProxy != null && externalHttpsProxy.HostName != remoteHostName) if (useHttpsProxy)
{ {
client = new TcpClient(); client = new TcpClient(server.UpStreamEndPoint);
client.Client.Bind(upStreamEndPoint); await client.ConnectAsync(externalHttpsProxy.HostName, externalHttpsProxy.Port);
client.Client.Connect(externalHttpsProxy.HostName, externalHttpsProxy.Port); stream = new CustomBufferedStream(client.GetStream(), server.BufferSize);
stream = client.GetStream();
using (var writer = new StreamWriter(stream, Encoding.ASCII, bufferSize, true) { NewLine = ProxyConstants.NewLine }) using (var writer = new StreamWriter(stream, Encoding.ASCII, server.BufferSize, true) {NewLine = ProxyConstants.NewLine})
{ {
await writer.WriteLineAsync($"CONNECT {remoteHostName}:{remotePort} HTTP/{httpVersion}"); await writer.WriteLineAsync($"CONNECT {remoteHostName}:{remotePort} HTTP/{httpVersion}");
await writer.WriteLineAsync($"Host: {remoteHostName}:{remotePort}"); await writer.WriteLineAsync($"Host: {remoteHostName}:{remotePort}");
...@@ -69,42 +64,40 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -69,42 +64,40 @@ namespace Titanium.Web.Proxy.Network.Tcp
if (!string.IsNullOrEmpty(externalHttpsProxy.UserName) && externalHttpsProxy.Password != null) if (!string.IsNullOrEmpty(externalHttpsProxy.UserName) && externalHttpsProxy.Password != null)
{ {
await writer.WriteLineAsync("Proxy-Connection: keep-alive"); await writer.WriteLineAsync("Proxy-Connection: keep-alive");
await writer.WriteLineAsync("Proxy-Authorization" + ": Basic " + Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(externalHttpsProxy.UserName + ":" + externalHttpsProxy.Password))); await writer.WriteLineAsync("Proxy-Authorization" + ": Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes(externalHttpsProxy.UserName + ":" + externalHttpsProxy.Password)));
} }
await writer.WriteLineAsync(); await writer.WriteLineAsync();
await writer.FlushAsync(); await writer.FlushAsync();
writer.Close(); writer.Close();
} }
using (var reader = new CustomBinaryReader(stream)) using (var reader = new CustomBinaryReader(stream, server.BufferSize))
{ {
var result = await reader.ReadLineAsync(); var result = await reader.ReadLineAsync();
if (!new[] {"200 OK", "connection established"}.Any(s => result.ContainsIgnoreCase(s)))
if (!new string[] { "200 OK", "connection established" }.Any(s => result.ToLower().Contains(s.ToLower())))
{ {
throw new Exception("Upstream proxy failed to create a secure tunnel"); throw new Exception("Upstream proxy failed to create a secure tunnel");
} }
await reader.ReadAllLinesAsync(); await reader.ReadAndIgnoreAllLinesAsync();
} }
} }
else else
{ {
client = new TcpClient(); client = new TcpClient(server.UpStreamEndPoint);
client.Client.Bind(upStreamEndPoint); await client.ConnectAsync(remoteHostName, remotePort);
client.Client.Connect(remoteHostName, remotePort); stream = new CustomBufferedStream(client.GetStream(), server.BufferSize);
stream = client.GetStream();
} }
try try
{ {
sslStream = new SslStream(stream, true, remoteCertificateValidationCallback, sslStream = new SslStream(stream, true, server.ValidateServerCertificate,
localCertificateSelectionCallback); server.SelectClientCertificate);
await sslStream.AuthenticateAsClientAsync(remoteHostName, null, supportedSslProtocols, false); await sslStream.AuthenticateAsClientAsync(remoteHostName, null, server.SupportedSslProtocols, false);
stream = sslStream; stream = new CustomBufferedStream(sslStream, server.BufferSize);
} }
catch catch
{ {
...@@ -115,30 +108,28 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -115,30 +108,28 @@ namespace Titanium.Web.Proxy.Network.Tcp
} }
else else
{ {
if (externalHttpProxy != null && externalHttpProxy.HostName != remoteHostName) if (useHttpProxy)
{ {
client = new TcpClient(); client = new TcpClient(server.UpStreamEndPoint);
client.Client.Bind(upStreamEndPoint); await client.ConnectAsync(externalHttpProxy.HostName, externalHttpProxy.Port);
client.Client.Connect(externalHttpProxy.HostName, externalHttpProxy.Port); stream = new CustomBufferedStream(client.GetStream(), server.BufferSize);
stream = client.GetStream();
} }
else else
{ {
client = new TcpClient(); client = new TcpClient(server.UpStreamEndPoint);
client.Client.Bind(upStreamEndPoint); await client.ConnectAsync(remoteHostName, remotePort);
client.Client.Connect(remoteHostName, remotePort); stream = new CustomBufferedStream(client.GetStream(), server.BufferSize);
stream = client.GetStream();
} }
} }
client.ReceiveTimeout = connectionTimeOutSeconds * 1000; client.ReceiveTimeout = server.ConnectionTimeOutSeconds * 1000;
client.SendTimeout = connectionTimeOutSeconds * 1000; client.SendTimeout = server.ConnectionTimeOutSeconds * 1000;
stream.ReadTimeout = connectionTimeOutSeconds * 1000; client.LingerState = new LingerOption(true, 0);
stream.WriteTimeout = connectionTimeOutSeconds * 1000;
server.ServerConnectionCount++;
return new TcpConnection() return new TcpConnection
{ {
UpStreamHttpProxy = externalHttpProxy, UpStreamHttpProxy = externalHttpProxy,
UpStreamHttpsProxy = externalHttpsProxy, UpStreamHttpsProxy = externalHttpsProxy,
...@@ -146,10 +137,10 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -146,10 +137,10 @@ namespace Titanium.Web.Proxy.Network.Tcp
Port = remotePort, Port = remotePort,
IsHttps = isHttps, IsHttps = isHttps,
TcpClient = client, TcpClient = client,
StreamReader = new CustomBinaryReader(stream), StreamReader = new CustomBinaryReader(stream, server.BufferSize),
Stream = stream, Stream = stream,
Version = httpVersion Version = httpVersion
}; };
} }
} }
} }
\ No newline at end of file
using System.Net; using System.Net;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Helpers; using Titanium.Web.Proxy.Helpers;
namespace Titanium.Web.Proxy.Network.Tcp namespace Titanium.Web.Proxy.Network.Tcp
{ {
/// <summary> /// <summary>
/// Represents a managed interface of IP Helper API TcpRow struct /// Represents a managed interface of IP Helper API TcpRow struct
/// <see cref="http://msdn2.microsoft.com/en-us/library/aa366913.aspx"/> /// <see href="http://msdn2.microsoft.com/en-us/library/aa366913.aspx"/>
/// </summary> /// </summary>
internal class TcpRow internal class TcpRow
{ {
...@@ -13,32 +14,50 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -13,32 +14,50 @@ namespace Titanium.Web.Proxy.Network.Tcp
/// Initializes a new instance of the <see cref="TcpRow"/> class. /// Initializes a new instance of the <see cref="TcpRow"/> class.
/// </summary> /// </summary>
/// <param name="tcpRow">TcpRow struct.</param> /// <param name="tcpRow">TcpRow struct.</param>
public TcpRow(NativeMethods.TcpRow tcpRow) internal TcpRow(NativeMethods.TcpRow tcpRow)
{ {
ProcessId = tcpRow.owningPid; ProcessId = tcpRow.owningPid;
int localPort = (tcpRow.localPort1 << 8) + (tcpRow.localPort2) + (tcpRow.localPort3 << 24) + (tcpRow.localPort4 << 16); LocalPort = tcpRow.GetLocalPort();
long localAddress = tcpRow.localAddr; LocalAddress = tcpRow.localAddr;
LocalEndPoint = new IPEndPoint(localAddress, localPort);
int remotePort = (tcpRow.remotePort1 << 8) + (tcpRow.remotePort2) + (tcpRow.remotePort3 << 24) + (tcpRow.remotePort4 << 16); RemotePort = tcpRow.GetRemotePort();
long remoteAddress = tcpRow.remoteAddr; RemoteAddress = tcpRow.remoteAddr;
RemoteEndPoint = new IPEndPoint(remoteAddress, remotePort); }
}
/// <summary>
/// Gets the local end point address.
/// </summary>
internal long LocalAddress { get; }
/// <summary>
/// Gets the local end point port.
/// </summary>
internal int LocalPort { get; }
/// <summary> /// <summary>
/// Gets the local end point. /// Gets the local end point.
/// </summary> /// </summary>
public IPEndPoint LocalEndPoint { get; private set; } internal IPEndPoint LocalEndPoint => new IPEndPoint(LocalAddress, LocalPort);
/// <summary>
/// Gets the remote end point address.
/// </summary>
internal long RemoteAddress { get; }
/// <summary> /// <summary>
/// Gets the remote end point. /// Gets the remote end point port.
/// </summary> /// </summary>
public IPEndPoint RemoteEndPoint { get; private set; } internal int RemotePort { get; }
/// <summary>
/// Gets the remote end point.
/// </summary>
internal IPEndPoint RemoteEndPoint => new IPEndPoint(RemoteAddress, RemotePort);
/// <summary> /// <summary>
/// Gets the process identifier. /// Gets the process identifier.
/// </summary> /// </summary>
public int ProcessId { get; private set; } internal int ProcessId { get; }
} }
} }
\ No newline at end of file
...@@ -6,7 +6,9 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -6,7 +6,9 @@ namespace Titanium.Web.Proxy.Network.Tcp
/// <summary> /// <summary>
/// Represents collection of TcpRows /// Represents collection of TcpRows
/// </summary> /// </summary>
/// <seealso cref="System.Collections.Generic.IEnumerable{Proxy.Tcp.TcpRow}" /> /// <seealso>
/// <cref>System.Collections.Generic.IEnumerable{Proxy.Tcp.TcpRow}</cref>
/// </seealso>
internal class TcpTable : IEnumerable<TcpRow> internal class TcpTable : IEnumerable<TcpRow>
{ {
private readonly IEnumerable<TcpRow> tcpRows; private readonly IEnumerable<TcpRow> tcpRows;
...@@ -15,7 +17,7 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -15,7 +17,7 @@ namespace Titanium.Web.Proxy.Network.Tcp
/// Initializes a new instance of the <see cref="TcpTable"/> class. /// Initializes a new instance of the <see cref="TcpTable"/> class.
/// </summary> /// </summary>
/// <param name="tcpRows">TcpRow collection to initialize with.</param> /// <param name="tcpRows">TcpRow collection to initialize with.</param>
public TcpTable(IEnumerable<TcpRow> tcpRows) internal TcpTable(IEnumerable<TcpRow> tcpRows)
{ {
this.tcpRows = tcpRows; this.tcpRows = tcpRows;
} }
...@@ -23,7 +25,7 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -23,7 +25,7 @@ namespace Titanium.Web.Proxy.Network.Tcp
/// <summary> /// <summary>
/// Gets the TCP rows. /// Gets the TCP rows.
/// </summary> /// </summary>
public IEnumerable<TcpRow> TcpRows => tcpRows; internal IEnumerable<TcpRow> TcpRows => tcpRows;
/// <summary> /// <summary>
/// Returns an enumerator that iterates through the collection. /// Returns an enumerator that iterates through the collection.
...@@ -43,4 +45,4 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -43,4 +45,4 @@ namespace Titanium.Web.Proxy.Network.Tcp
return GetEnumerator(); return GetEnumerator();
} }
} }
} }
\ No newline at end of file
...@@ -14,7 +14,12 @@ using System.Runtime.InteropServices; ...@@ -14,7 +14,12 @@ using System.Runtime.InteropServices;
[assembly: AssemblyCopyright("Copyright © 2015")] [assembly: AssemblyCopyright("Copyright © 2015")]
[assembly: AssemblyTrademark("")] [assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")] [assembly: AssemblyCulture("")]
[assembly: InternalsVisibleTo("Titanium.Web.Proxy.UnitTests")] [assembly: InternalsVisibleTo("Titanium.Web.Proxy.UnitTests, PublicKey=" +
"0024000004800000940000000602000000240000525341310004000001000100e7368e0ccc717e" +
"eb4d57d35ad6a8305cbbed14faa222e13869405e92c83856266d400887d857005f1393ffca2b92" +
"de7f3ba0bdad35ec2d6057ee1846091b34be2abc3f97dc7e72c16fd4958c15126b12923df76964" +
"7d84922c3f4f3b80ee0ae8e4cb40bc1973b782afb90bb00519fd16adf960f217e23696e7c31654" +
"01d0acd6")]
// Setting ComVisible to false makes the types in this assembly not visible // Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from // to COM components. If you need to access a type in this assembly from
......
...@@ -12,87 +12,71 @@ namespace Titanium.Web.Proxy ...@@ -12,87 +12,71 @@ namespace Titanium.Web.Proxy
{ {
public partial class ProxyServer public partial class ProxyServer
{ {
private async Task<bool> CheckAuthorization(StreamWriter clientStreamWriter, IEnumerable<HttpHeader> headers)
private async Task<bool> CheckAuthorization(StreamWriter clientStreamWriter, IEnumerable<HttpHeader> Headers)
{ {
if (AuthenticateUserFunc == null) if (AuthenticateUserFunc == null)
{ {
return true; return true;
} }
var httpHeaders = Headers as HttpHeader[] ?? Headers.ToArray(); var httpHeaders = headers as ICollection<HttpHeader> ?? headers.ToArray();
try try
{ {
if (!httpHeaders.Where(t => t.Name == "Proxy-Authorization").Any()) if (httpHeaders.All(t => t.Name != "Proxy-Authorization"))
{ {
await SendAuthentication407Response(clientStreamWriter, "Proxy Authentication Required");
await WriteResponseStatus(new Version(1, 1), "407",
"Proxy Authentication Required", clientStreamWriter);
var response = new Response();
response.ResponseHeaders = new Dictionary<string, HttpHeader>();
response.ResponseHeaders.Add("Proxy-Authenticate", new HttpHeader("Proxy-Authenticate", "Basic realm=\"TitaniumProxy\""));
response.ResponseHeaders.Add("Proxy-Connection", new HttpHeader("Proxy-Connection", "close"));
await WriteResponseHeaders(clientStreamWriter, response);
await clientStreamWriter.WriteLineAsync();
return false; return false;
} }
else
{
var headerValue = httpHeaders.Where(t => t.Name == "Proxy-Authorization").FirstOrDefault().Value.Trim();
if (!headerValue.ToLower().StartsWith("basic"))
{
//Return not authorized
await WriteResponseStatus(new Version(1, 1), "407",
"Proxy Authentication Invalid", clientStreamWriter);
var response = new Response();
response.ResponseHeaders = new Dictionary<string, HttpHeader>();
response.ResponseHeaders.Add("Proxy-Authenticate", new HttpHeader("Proxy-Authenticate", "Basic realm=\"TitaniumProxy\""));
response.ResponseHeaders.Add("Proxy-Connection", new HttpHeader("Proxy-Connection", "close"));
await WriteResponseHeaders(clientStreamWriter, response);
await clientStreamWriter.WriteLineAsync(); var header = httpHeaders.FirstOrDefault(t => t.Name == "Proxy-Authorization");
return false; if (header == null) throw new NullReferenceException();
} var headerValue = header.Value.Trim();
headerValue = headerValue.Substring(5).Trim(); if (!headerValue.StartsWith("basic", StringComparison.CurrentCultureIgnoreCase))
{
//Return not authorized
await SendAuthentication407Response(clientStreamWriter, "Proxy Authentication Invalid");
return false;
}
var decoded = Encoding.UTF8.GetString(Convert.FromBase64String(headerValue)); headerValue = headerValue.Substring(5).Trim();
if (decoded.Contains(":") == false)
{
//Return not authorized
await WriteResponseStatus(new Version(1, 1), "407",
"Proxy Authentication Invalid", clientStreamWriter);
var response = new Response();
response.ResponseHeaders = new Dictionary<string, HttpHeader>();
response.ResponseHeaders.Add("Proxy-Authenticate", new HttpHeader("Proxy-Authenticate", "Basic realm=\"TitaniumProxy\""));
response.ResponseHeaders.Add("Proxy-Connection", new HttpHeader("Proxy-Connection", "close"));
await WriteResponseHeaders(clientStreamWriter, response);
await clientStreamWriter.WriteLineAsync(); var decoded = Encoding.UTF8.GetString(Convert.FromBase64String(headerValue));
return false; if (decoded.Contains(":") == false)
} {
var username = decoded.Substring(0, decoded.IndexOf(':')); //Return not authorized
var password = decoded.Substring(decoded.IndexOf(':') + 1); await SendAuthentication407Response(clientStreamWriter, "Proxy Authentication Invalid");
return await AuthenticateUserFunc(username, password).ConfigureAwait(false); return false;
} }
var username = decoded.Substring(0, decoded.IndexOf(':'));
var password = decoded.Substring(decoded.IndexOf(':') + 1);
return await AuthenticateUserFunc(username, password);
} }
catch (Exception e) catch (Exception e)
{ {
ExceptionFunc(new ProxyAuthorizationException("Error whilst authorizing request", e, httpHeaders)); ExceptionFunc(new ProxyAuthorizationException("Error whilst authorizing request", e, httpHeaders));
//Return not authorized
await WriteResponseStatus(new Version(1, 1), "407",
"Proxy Authentication Invalid", clientStreamWriter);
var response = new Response();
response.ResponseHeaders = new Dictionary<string, HttpHeader>();
response.ResponseHeaders.Add("Proxy-Authenticate", new HttpHeader("Proxy-Authenticate", "Basic realm=\"TitaniumProxy\""));
response.ResponseHeaders.Add("Proxy-Connection", new HttpHeader("Proxy-Connection", "close"));
await WriteResponseHeaders(clientStreamWriter, response);
await clientStreamWriter.WriteLineAsync(); //Return not authorized
await SendAuthentication407Response(clientStreamWriter, "Proxy Authentication Invalid");
return false; return false;
} }
}
private async Task SendAuthentication407Response(StreamWriter clientStreamWriter, string description)
{
await WriteResponseStatus(HttpHeader.Version11, "407", description, clientStreamWriter);
var response = new Response
{
ResponseHeaders = new Dictionary<string, HttpHeader>
{
{"Proxy-Authenticate", new HttpHeader("Proxy-Authenticate", "Basic realm=\"TitaniumProxy\"")},
{"Proxy-Connection", new HttpHeader("Proxy-Connection", "close")}
}
};
await WriteResponseHeaders(clientStreamWriter, response);
await clientStreamWriter.WriteLineAsync();
} }
} }
} }
...@@ -10,6 +10,7 @@ using Titanium.Web.Proxy.Network; ...@@ -10,6 +10,7 @@ using Titanium.Web.Proxy.Network;
using System.Linq; using System.Linq;
using System.Security.Authentication; 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
{ {
...@@ -18,22 +19,11 @@ namespace Titanium.Web.Proxy ...@@ -18,22 +19,11 @@ namespace Titanium.Web.Proxy
/// </summary> /// </summary>
public partial class ProxyServer : IDisposable public partial class ProxyServer : IDisposable
{ {
/// <summary>
/// Is the root certificate used by this proxy is valid?
/// </summary>
private bool certValidated { get; set; }
/// <summary> /// <summary>
/// Is the proxy currently running /// Is the proxy currently running
/// </summary> /// </summary>
private bool proxyRunning { get; set; } private bool proxyRunning { get; set; }
/// <summary>
/// Manages certificates used by this proxy
/// </summary>
private CertificateManager certificateCacheManager { get; set; }
/// <summary> /// <summary>
/// An default exception log func /// An default exception log func
/// </summary> /// </summary>
...@@ -44,42 +34,96 @@ namespace Titanium.Web.Proxy ...@@ -44,42 +34,96 @@ namespace Titanium.Web.Proxy
/// </summary> /// </summary>
private Action<Exception> exceptionFunc; private Action<Exception> exceptionFunc;
private bool trustRootCertificate;
/// <summary> /// <summary>
/// A object that creates tcp connection to server /// A object that creates tcp connection to server
/// </summary> /// </summary>
private TcpConnectionFactory tcpConnectionFactory { get; set; } private TcpConnectionFactory tcpConnectionFactory { get; }
/// <summary> /// <summary>
/// Manage system proxy settings /// Manage system proxy settings
/// </summary> /// </summary>
private SystemProxyManager systemProxySettingsManager { get; set; } private SystemProxyManager systemProxySettingsManager { get; }
private FireFoxProxySettingsManager firefoxProxySettingsManager { get; set; } #if !DEBUG
/// <summary>
/// Set firefox to use default system proxy
/// </summary>
private FireFoxProxySettingsManager firefoxProxySettingsManager
= new FireFoxProxySettingsManager();
#endif
/// <summary> /// <summary>
/// Buffer size used throughout this proxy /// Buffer size used throughout this proxy
/// </summary> /// </summary>
public int BUFFER_SIZE { get; set; } = 8192; public int BufferSize { get; set; } = 8192;
/// <summary> /// <summary>
/// Name of the root certificate issuer /// Manages certificates used by this proxy
/// </summary> /// </summary>
public string RootCertificateIssuerName { get; set; } public CertificateManager CertificateManager { get; }
/// <summary>
/// The root certificate
/// </summary>
public X509Certificate2 RootCertificate
{
get { return CertificateManager.RootCertificate; }
set { CertificateManager.RootCertificate = value; }
}
/// <summary>
/// Name of the root certificate issuer
/// (This is valid only when RootCertificate property is not set)
/// </summary>
public string RootCertificateIssuerName
{
get { return CertificateManager.Issuer; }
set { CertificateManager.Issuer = value; }
}
/// <summary> /// <summary>
/// Name of the root certificate /// Name of the root certificate
/// (This is valid only when RootCertificate property is not set)
/// If no certificate is provided then a default Root Certificate will be created and used /// If no certificate is provided then a default Root Certificate will be created and used
/// The provided root certificate has to be in the proxy exe directory with the private key /// The provided root certificate will be stored in proxy exe directory with the private key
/// The root certificate file should be named as "rootCert.pfx" /// Root certificate file will be named as "rootCert.pfx"
/// </summary> /// </summary>
public string RootCertificateName { get; set; } public string RootCertificateName
{
get { return CertificateManager.RootCertificateName; }
set { CertificateManager.RootCertificateName = value; }
}
/// <summary> /// <summary>
/// Trust the RootCertificate used by this proxy server /// Trust the RootCertificate used by this proxy server
/// Note that this do not make the client trust the certificate! /// Note that this do not make the client trust the certificate!
/// This would import the root certificate to the certificate store of machine that runs this proxy server /// This would import the root certificate to the certificate store of machine that runs this proxy server
/// </summary> /// </summary>
public bool TrustRootCertificate { get; set; } public bool TrustRootCertificate
{
get { return trustRootCertificate; }
set
{
trustRootCertificate = value;
if (value)
{
EnsureRootCertificate();
}
}
}
/// <summary>
/// Select Certificate Engine
/// Optionally set to BouncyCastle
/// Mono only support BouncyCastle and it is the default
/// </summary>
public CertificateEngine CertificateEngine
{
get { return CertificateManager.Engine; }
set { CertificateManager.Engine = value; }
}
/// <summary> /// <summary>
/// Does this proxy uses the HTTP protocol 100 continue behaviour strictly? /// Does this proxy uses the HTTP protocol 100 continue behaviour strictly?
...@@ -123,6 +167,16 @@ namespace Titanium.Web.Proxy ...@@ -123,6 +167,16 @@ namespace Titanium.Web.Proxy
/// </summary> /// </summary>
public IPEndPoint UpStreamEndPoint { get; set; } = new IPEndPoint(IPAddress.Any, 0); public IPEndPoint UpStreamEndPoint { get; set; } = new IPEndPoint(IPAddress.Any, 0);
/// <summary>
/// Is the proxy currently running
/// </summary>
public bool ProxyRunning => proxyRunning;
/// <summary>
/// Gets or sets a value indicating whether requests will be chained to upstream gateway.
/// </summary>
public bool ForwardToUpstreamGateway { get; set; }
/// <summary> /// <summary>
/// Verifies the remote Secure Sockets Layer (SSL) certificate used for authentication /// Verifies the remote Secure Sockets Layer (SSL) certificate used for authentication
/// </summary> /// </summary>
...@@ -138,14 +192,8 @@ namespace Titanium.Web.Proxy ...@@ -138,14 +192,8 @@ namespace Titanium.Web.Proxy
/// </summary> /// </summary>
public Action<Exception> ExceptionFunc public Action<Exception> ExceptionFunc
{ {
get get { return exceptionFunc ?? defaultExceptionFunc.Value; }
{ set { exceptionFunc = value; }
return exceptionFunc ?? defaultExceptionFunc.Value;
}
set
{
exceptionFunc = value;
}
} }
/// <summary> /// <summary>
...@@ -153,30 +201,19 @@ namespace Titanium.Web.Proxy ...@@ -153,30 +201,19 @@ namespace Titanium.Web.Proxy
/// Parameters are username, password provided by client /// Parameters are username, password provided by client
/// return true for successful authentication /// return true for successful authentication
/// </summary> /// </summary>
public Func<string, string, Task<bool>> AuthenticateUserFunc public Func<string, string, Task<bool>> AuthenticateUserFunc { get; set; }
{
get;
set;
}
/// <summary> /// <summary>
/// A callback to provide authentication credentials for up stream proxy this proxy is using for HTTP requests /// A callback to provide authentication credentials for up stream proxy this proxy is using for HTTP requests
/// return the ExternalProxy object with valid credentials /// return the ExternalProxy object with valid credentials
/// </summary> /// </summary>
public Func<SessionEventArgs, Task<ExternalProxy>> GetCustomUpStreamHttpProxyFunc public Func<SessionEventArgs, Task<ExternalProxy>> GetCustomUpStreamHttpProxyFunc { get; set; }
{
get;
set;
}
/// <summary> /// <summary>
/// A callback to provide authentication credentials for up stream proxy this proxy is using for HTTPS requests /// A callback to provide authentication credentials for up stream proxy this proxy is using for HTTPS requests
/// return the ExternalProxy object with valid credentials /// return the ExternalProxy object with valid credentials
public Func<SessionEventArgs, Task<ExternalProxy>> GetCustomUpStreamHttpsProxyFunc /// </summary>
{ public Func<SessionEventArgs, Task<ExternalProxy>> GetCustomUpStreamHttpsProxyFunc { get; set; }
get;
set;
}
/// <summary> /// <summary>
/// A list of IpAddress & port this proxy is listening to /// A list of IpAddress & port this proxy is listening to
...@@ -186,38 +223,55 @@ namespace Titanium.Web.Proxy ...@@ -186,38 +223,55 @@ namespace Titanium.Web.Proxy
/// <summary> /// <summary>
/// List of supported Ssl versions /// List of supported Ssl versions
/// </summary> /// </summary>
public SslProtocols SupportedSslProtocols { get; set; } = SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12 | SslProtocols.Ssl3; public SslProtocols SupportedSslProtocols { get; set; } = SslProtocols.Tls
| SslProtocols.Tls11 | SslProtocols.Tls12 | SslProtocols.Ssl3;
/// <summary> /// <summary>
/// Is the proxy currently running /// Total number of active client connections
/// </summary> /// </summary>
public bool ProxyRunning => proxyRunning; public int ClientConnectionCount { get; private set; }
/// <summary> /// <summary>
/// Gets or sets a value indicating whether requests will be chained to upstream gateway. /// Total number of active server connections
/// </summary> /// </summary>
public bool ForwardToUpstreamGateway { get; set; } public int ServerConnectionCount { get; internal set; }
/// <summary> /// <summary>
/// Constructor /// Constructor
/// </summary> /// </summary>
public ProxyServer() : this(null, null) { } public ProxyServer() : this(null, null)
{
}
/// <summary>
/// Constructor.
/// </summary>
/// <param name="rootCertificateName">Name of root certificate.</param>
/// <param name="rootCertificateIssuerName">Name of root certificate issuer.</param>
public ProxyServer(string rootCertificateName, string rootCertificateIssuerName) public ProxyServer(string rootCertificateName, string rootCertificateIssuerName)
{ {
RootCertificateName = rootCertificateName;
RootCertificateIssuerName = rootCertificateIssuerName;
//default values //default values
ConnectionTimeOutSeconds = 120; ConnectionTimeOutSeconds = 30;
CertificateCacheTimeOutMinutes = 60; CertificateCacheTimeOutMinutes = 60;
ProxyEndPoints = new List<ProxyEndPoint>(); ProxyEndPoints = new List<ProxyEndPoint>();
tcpConnectionFactory = new TcpConnectionFactory(); tcpConnectionFactory = new TcpConnectionFactory();
systemProxySettingsManager = new SystemProxyManager(); systemProxySettingsManager = new SystemProxyManager();
firefoxProxySettingsManager = new FireFoxProxySettingsManager(); #if !DEBUG
new FireFoxProxySettingsManager();
#endif
RootCertificateName = RootCertificateName ?? "Titanium Root Certificate Authority"; CertificateManager = new CertificateManager(ExceptionFunc);
RootCertificateIssuerName = RootCertificateIssuerName ?? "Titanium"; if (rootCertificateName != null)
{
RootCertificateName = rootCertificateName;
}
if (rootCertificateIssuerName != null)
{
RootCertificateIssuerName = rootCertificateIssuerName;
}
} }
/// <summary> /// <summary>
...@@ -226,7 +280,8 @@ namespace Titanium.Web.Proxy ...@@ -226,7 +280,8 @@ namespace Titanium.Web.Proxy
/// <param name="endPoint"></param> /// <param name="endPoint"></param>
public void AddEndPoint(ProxyEndPoint endPoint) public void AddEndPoint(ProxyEndPoint endPoint)
{ {
if (ProxyEndPoints.Any(x => x.IpAddress.Equals(endPoint.IpAddress) && endPoint.Port != 0 && x.Port == endPoint.Port)) if (ProxyEndPoints.Any(x => x.IpAddress.Equals(endPoint.IpAddress)
&& endPoint.Port != 0 && x.Port == endPoint.Port))
{ {
throw new Exception("Cannot add another endpoint to same port & ip address"); throw new Exception("Cannot add another endpoint to same port & ip address");
} }
...@@ -265,6 +320,11 @@ namespace Titanium.Web.Proxy ...@@ -265,6 +320,11 @@ namespace Titanium.Web.Proxy
/// <param name="endPoint"></param> /// <param name="endPoint"></param>
public void SetAsSystemHttpProxy(ExplicitProxyEndPoint endPoint) public void SetAsSystemHttpProxy(ExplicitProxyEndPoint endPoint)
{ {
if (RunTime.IsRunningOnMono())
{
throw new Exception("Mono Runtime do not support system proxy settings.");
}
ValidateEndPointAsSystemProxy(endPoint); ValidateEndPointAsSystemProxy(endPoint);
//clear any settings previously added //clear any settings previously added
...@@ -277,8 +337,7 @@ namespace Titanium.Web.Proxy ...@@ -277,8 +337,7 @@ namespace Titanium.Web.Proxy
#if !DEBUG #if !DEBUG
firefoxProxySettingsManager.AddFirefox(); firefoxProxySettingsManager.AddFirefox();
#endif #endif
Console.WriteLine("Set endpoint at Ip {1} and port: {2} as System HTTP Proxy", endPoint.GetType().Name, endPoint.IpAddress, endPoint.Port); Console.WriteLine("Set endpoint at Ip {0} and port: {1} as System HTTP Proxy", endPoint.IpAddress, endPoint.Port);
} }
...@@ -288,6 +347,11 @@ namespace Titanium.Web.Proxy ...@@ -288,6 +347,11 @@ namespace Titanium.Web.Proxy
/// <param name="endPoint"></param> /// <param name="endPoint"></param>
public void SetAsSystemHttpsProxy(ExplicitProxyEndPoint endPoint) public void SetAsSystemHttpsProxy(ExplicitProxyEndPoint endPoint)
{ {
if (RunTime.IsRunningOnMono())
{
throw new Exception("Mono Runtime do not support system proxy settings.");
}
ValidateEndPointAsSystemProxy(endPoint); ValidateEndPointAsSystemProxy(endPoint);
if (!endPoint.EnableSsl) if (!endPoint.EnableSsl)
...@@ -296,14 +360,18 @@ namespace Titanium.Web.Proxy ...@@ -296,14 +360,18 @@ namespace Titanium.Web.Proxy
} }
//clear any settings previously added //clear any settings previously added
ProxyEndPoints.OfType<ExplicitProxyEndPoint>().ToList().ForEach(x => x.IsSystemHttpsProxy = false); ProxyEndPoints.OfType<ExplicitProxyEndPoint>()
.ToList()
.ForEach(x => x.IsSystemHttpsProxy = false);
EnsureRootCertificate();
//If certificate was trusted by the machine //If certificate was trusted by the machine
if (certValidated) if (CertificateManager.CertValidated)
{ {
systemProxySettingsManager.SetHttpsProxy( systemProxySettingsManager.SetHttpsProxy(
Equals(endPoint.IpAddress, IPAddress.Any) | Equals(endPoint.IpAddress, IPAddress.Loopback) ? "127.0.0.1" : endPoint.IpAddress.ToString(), Equals(endPoint.IpAddress, IPAddress.Any) |
Equals(endPoint.IpAddress, IPAddress.Loopback) ? "127.0.0.1" : endPoint.IpAddress.ToString(),
endPoint.Port); endPoint.Port);
} }
...@@ -313,7 +381,8 @@ namespace Titanium.Web.Proxy ...@@ -313,7 +381,8 @@ namespace Titanium.Web.Proxy
#if !DEBUG #if !DEBUG
firefoxProxySettingsManager.AddFirefox(); firefoxProxySettingsManager.AddFirefox();
#endif #endif
Console.WriteLine("Set endpoint at Ip {1} and port: {2} as System HTTPS Proxy", endPoint.GetType().Name, endPoint.IpAddress, endPoint.Port); Console.WriteLine("Set endpoint at Ip {0} and port: {1} as System HTTPS Proxy",
endPoint.IpAddress, endPoint.Port);
} }
/// <summary> /// <summary>
...@@ -321,6 +390,11 @@ namespace Titanium.Web.Proxy ...@@ -321,6 +390,11 @@ namespace Titanium.Web.Proxy
/// </summary> /// </summary>
public void DisableSystemHttpProxy() public void DisableSystemHttpProxy()
{ {
if (RunTime.IsRunningOnMono())
{
throw new Exception("Mono Runtime do not support system proxy settings.");
}
systemProxySettingsManager.RemoveHttpProxy(); systemProxySettingsManager.RemoveHttpProxy();
} }
...@@ -329,6 +403,11 @@ namespace Titanium.Web.Proxy ...@@ -329,6 +403,11 @@ namespace Titanium.Web.Proxy
/// </summary> /// </summary>
public void DisableSystemHttpsProxy() public void DisableSystemHttpsProxy()
{ {
if (RunTime.IsRunningOnMono())
{
throw new Exception("Mono Runtime do not support system proxy settings.");
}
systemProxySettingsManager.RemoveHttpsProxy(); systemProxySettingsManager.RemoveHttpsProxy();
} }
...@@ -337,6 +416,11 @@ namespace Titanium.Web.Proxy ...@@ -337,6 +416,11 @@ namespace Titanium.Web.Proxy
/// </summary> /// </summary>
public void DisableAllSystemProxies() public void DisableAllSystemProxies()
{ {
if (RunTime.IsRunningOnMono())
{
throw new Exception("Mono Runtime do not support system proxy settings.");
}
systemProxySettingsManager.DisableAllProxy(); systemProxySettingsManager.DisableAllProxy();
} }
...@@ -350,17 +434,8 @@ namespace Titanium.Web.Proxy ...@@ -350,17 +434,8 @@ namespace Titanium.Web.Proxy
throw new Exception("Proxy is already running."); throw new Exception("Proxy is already running.");
} }
certificateCacheManager = new CertificateManager(RootCertificateIssuerName, if (ForwardToUpstreamGateway && GetCustomUpStreamHttpProxyFunc == null
RootCertificateName, ExceptionFunc); && GetCustomUpStreamHttpsProxyFunc == null)
certValidated = certificateCacheManager.CreateTrustedRootCertificate();
if (TrustRootCertificate)
{
certificateCacheManager.TrustRootCertificate();
}
if (ForwardToUpstreamGateway && GetCustomUpStreamHttpProxyFunc == null && GetCustomUpStreamHttpsProxyFunc == null)
{ {
GetCustomUpStreamHttpProxyFunc = GetSystemUpStreamProxy; GetCustomUpStreamHttpProxyFunc = GetSystemUpStreamProxy;
GetCustomUpStreamHttpsProxyFunc = GetSystemUpStreamProxy; GetCustomUpStreamHttpsProxyFunc = GetSystemUpStreamProxy;
...@@ -371,32 +446,11 @@ namespace Titanium.Web.Proxy ...@@ -371,32 +446,11 @@ namespace Titanium.Web.Proxy
Listen(endPoint); Listen(endPoint);
} }
certificateCacheManager.ClearIdleCertificates(CertificateCacheTimeOutMinutes); CertificateManager.ClearIdleCertificates(CertificateCacheTimeOutMinutes);
proxyRunning = true; proxyRunning = true;
} }
/// <summary>
/// Gets the system up stream proxy.
/// </summary>
/// <param name="sessionEventArgs">The <see cref="SessionEventArgs"/> instance containing the event data.</param>
/// <returns><see cref="ExternalProxy"/> instance containing valid proxy configuration from PAC/WAPD scripts if any exists.</returns>
private Task<ExternalProxy> GetSystemUpStreamProxy(SessionEventArgs sessionEventArgs)
{
// Use built-in WebProxy class to handle PAC/WAPD scripts.
var systemProxyResolver = new WebProxy();
var systemProxyUri = systemProxyResolver.GetProxy(sessionEventArgs.WebSession.Request.RequestUri);
// TODO: Apply authorization
var systemProxy = new ExternalProxy
{
HostName = systemProxyUri.Host,
Port = systemProxyUri.Port
};
return Task.FromResult(systemProxy);
}
/// <summary> /// <summary>
/// Stop this proxy server /// Stop this proxy server
...@@ -425,34 +479,36 @@ namespace Titanium.Web.Proxy ...@@ -425,34 +479,36 @@ namespace Titanium.Web.Proxy
ProxyEndPoints.Clear(); ProxyEndPoints.Clear();
certificateCacheManager?.StopClearIdleCertificates(); CertificateManager?.StopClearIdleCertificates();
proxyRunning = false; proxyRunning = false;
} }
/// <summary> /// <summary>
/// Listen on the given end point on local machine /// Dispose Proxy.
/// </summary> /// </summary>
/// <param name="endPoint"></param> public void Dispose()
private void Listen(ProxyEndPoint endPoint)
{ {
endPoint.listener = new TcpListener(endPoint.IpAddress, endPoint.Port); if (proxyRunning)
endPoint.listener.Start(); {
Stop();
}
endPoint.Port = ((IPEndPoint)endPoint.listener.LocalEndpoint).Port; CertificateManager?.Dispose();
// accept clients asynchronously
endPoint.listener.BeginAcceptTcpClient(OnAcceptConnection, endPoint);
} }
/// <summary> /// <summary>
/// Quit listening on the given end point /// Listen on the given end point on local machine
/// </summary> /// </summary>
/// <param name="endPoint"></param> /// <param name="endPoint"></param>
private void QuitListen(ProxyEndPoint endPoint) private void Listen(ProxyEndPoint endPoint)
{ {
endPoint.listener.Stop(); endPoint.Listener = new TcpListener(endPoint.IpAddress, endPoint.Port);
endPoint.listener.Server.Close(); endPoint.Listener.Start();
endPoint.listener.Server.Dispose();
endPoint.Port = ((IPEndPoint)endPoint.Listener.LocalEndpoint).Port;
// accept clients asynchronously
endPoint.Listener.BeginAcceptTcpClient(OnAcceptConnection, endPoint);
} }
/// <summary> /// <summary>
...@@ -461,6 +517,7 @@ namespace Titanium.Web.Proxy ...@@ -461,6 +517,7 @@ namespace Titanium.Web.Proxy
/// <param name="endPoint"></param> /// <param name="endPoint"></param>
private void ValidateEndPointAsSystemProxy(ExplicitProxyEndPoint endPoint) private void ValidateEndPointAsSystemProxy(ExplicitProxyEndPoint endPoint)
{ {
if (endPoint == null) throw new ArgumentNullException(nameof(endPoint));
if (ProxyEndPoints.Contains(endPoint) == false) if (ProxyEndPoints.Contains(endPoint) == false)
{ {
throw new Exception("Cannot set endPoints not added to proxy as system proxy"); throw new Exception("Cannot set endPoints not added to proxy as system proxy");
...@@ -472,6 +529,42 @@ namespace Titanium.Web.Proxy ...@@ -472,6 +529,42 @@ namespace Titanium.Web.Proxy
} }
} }
/// <summary>
/// Gets the system up stream proxy.
/// </summary>
/// <param name="sessionEventArgs">The <see cref="SessionEventArgs"/> instance containing the event data.</param>
/// <returns><see cref="ExternalProxy"/> instance containing valid proxy configuration from PAC/WAPD scripts if any exists.</returns>
private Task<ExternalProxy> GetSystemUpStreamProxy(SessionEventArgs sessionEventArgs)
{
// Use built-in WebProxy class to handle PAC/WAPD scripts.
var systemProxyResolver = new WebProxy();
var systemProxyUri = systemProxyResolver.GetProxy(sessionEventArgs.WebSession.Request.RequestUri);
// TODO: Apply authorization
var systemProxy = new ExternalProxy
{
HostName = systemProxyUri.Host,
Port = systemProxyUri.Port
};
return Task.FromResult(systemProxy);
}
private void EnsureRootCertificate()
{
if (!CertificateManager.CertValidated)
{
CertificateManager.CreateTrustedRootCertificate();
if (TrustRootCertificate)
{
CertificateManager.TrustRootCertificate();
}
}
}
/// <summary> /// <summary>
/// When a connection is received from client act /// When a connection is received from client act
/// </summary> /// </summary>
...@@ -485,7 +578,7 @@ namespace Titanium.Web.Proxy ...@@ -485,7 +578,7 @@ namespace Titanium.Web.Proxy
try try
{ {
//based on end point type call appropriate request handlers //based on end point type call appropriate request handlers
tcpClient = endPoint.listener.EndAcceptTcpClient(asyn); tcpClient = endPoint.Listener.EndAcceptTcpClient(asyn);
} }
catch (ObjectDisposedException) catch (ObjectDisposedException)
{ {
...@@ -499,11 +592,18 @@ namespace Titanium.Web.Proxy ...@@ -499,11 +592,18 @@ namespace Titanium.Web.Proxy
//Other errors are discarded to keep proxy running //Other errors are discarded to keep proxy running
} }
if (tcpClient != null) if (tcpClient != null)
{ {
Task.Run(async () => Task.Run(async () =>
{ {
ClientConnectionCount++;
//This line is important!
//contributors please don't remove it without discussion
//It helps to avoid eventual deterioration of performance due to TCP port exhaustion
//due to default TCP CLOSE_WAIT timeout for 4 minutes
tcpClient.LingerState = new LingerOption(true, 0);
try try
{ {
if (endPoint.GetType() == typeof(TransparentProxyEndPoint)) if (endPoint.GetType() == typeof(TransparentProxyEndPoint))
...@@ -512,44 +612,71 @@ namespace Titanium.Web.Proxy ...@@ -512,44 +612,71 @@ namespace Titanium.Web.Proxy
} }
else else
{ {
await HandleClient(endPoint as ExplicitProxyEndPoint, tcpClient); await HandleClient(endPoint as ExplicitProxyEndPoint, tcpClient);
} }
} }
finally finally
{ {
if (tcpClient != null) ClientConnectionCount--;
{ tcpClient?.Close();
//This line is important!
//contributors please don't remove it without discussion
//It helps to avoid eventual deterioration of performance due to TCP port exhaustion
//due to default TCP CLOSE_WAIT timeout for 4 minutes
tcpClient.LingerState = new LingerOption(true, 0);
tcpClient.Client.Shutdown(SocketShutdown.Both);
tcpClient.Client.Close();
tcpClient.Client.Dispose();
tcpClient.Close();
}
} }
}); });
} }
// Get the listener that handles the client request. // Get the listener that handles the client request.
endPoint.listener.BeginAcceptTcpClient(OnAcceptConnection, endPoint); endPoint.Listener.BeginAcceptTcpClient(OnAcceptConnection, endPoint);
} }
public void Dispose() /// <summary>
/// Quit listening on the given end point
/// </summary>
/// <param name="endPoint"></param>
private void QuitListen(ProxyEndPoint endPoint)
{ {
if (proxyRunning) endPoint.Listener.Stop();
{ endPoint.Listener.Server.Close();
Stop(); 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);
}
certificateCacheManager?.Dispose(); /// <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);
} }
} }
} }
\ No newline at end of file
...@@ -6,12 +6,10 @@ using System.Net; ...@@ -6,12 +6,10 @@ 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 System.Text.RegularExpressions;
using Titanium.Web.Proxy.Exceptions; using Titanium.Web.Proxy.Exceptions;
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 System.Security.Cryptography.X509Certificates;
using Titanium.Web.Proxy.Shared; using Titanium.Web.Proxy.Shared;
using Titanium.Web.Proxy.Http; using Titanium.Web.Proxy.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
...@@ -25,22 +23,20 @@ namespace Titanium.Web.Proxy ...@@ -25,22 +23,20 @@ namespace Titanium.Web.Proxy
/// </summary> /// </summary>
partial class ProxyServer partial class ProxyServer
{ {
//This is called when client is aware of 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 //So for HTTPS requests client would send CONNECT header to negotiate a secure tcp tunnel via proxy
private async Task HandleClient(ExplicitProxyEndPoint endPoint, TcpClient tcpClient) private async Task HandleClient(ExplicitProxyEndPoint endPoint, TcpClient tcpClient)
{ {
CustomBufferedStream clientStream = new CustomBufferedStream(tcpClient.GetStream(), BufferSize);
Stream clientStream = tcpClient.GetStream();
clientStream.ReadTimeout = ConnectionTimeOutSeconds * 1000; clientStream.ReadTimeout = ConnectionTimeOutSeconds * 1000;
clientStream.WriteTimeout = ConnectionTimeOutSeconds * 1000; clientStream.WriteTimeout = ConnectionTimeOutSeconds * 1000;
var clientStreamReader = new CustomBinaryReader(clientStream); var clientStreamReader = new CustomBinaryReader(clientStream, BufferSize);
var clientStreamWriter = new StreamWriter(clientStream) { NewLine = ProxyConstants.NewLine }; var clientStreamWriter = new StreamWriter(clientStream) { NewLine = ProxyConstants.NewLine };
Uri httpRemoteUri; Uri httpRemoteUri;
try try
{ {
//read the first line HTTP command //read the first line HTTP command
...@@ -56,41 +52,43 @@ namespace Titanium.Web.Proxy ...@@ -56,41 +52,43 @@ namespace Titanium.Web.Proxy
var httpCmdSplit = httpCmd.Split(ProxyConstants.SpaceSplit, 3); var httpCmdSplit = httpCmd.Split(ProxyConstants.SpaceSplit, 3);
//Find the request Verb //Find the request Verb
var httpVerb = httpCmdSplit[0]; var httpVerb = httpCmdSplit[0].ToUpper();
if (httpVerb.ToUpper() == "CONNECT") httpRemoteUri = httpVerb == "CONNECT" ? new Uri("http://" + httpCmdSplit[1]) : new Uri(httpCmdSplit[1]);
{
httpRemoteUri = new Uri("http://" + httpCmdSplit[1]);
}
else
{
httpRemoteUri = new Uri(httpCmdSplit[1]);
}
//parse the HTTP version //parse the HTTP version
Version version = new Version(1, 1); var version = HttpHeader.Version11;
if (httpCmdSplit.Length == 3) if (httpCmdSplit.Length == 3)
{ {
string httpVersion = httpCmdSplit[2].Trim(); var httpVersion = httpCmdSplit[2].Trim();
if (httpVersion == "http/1.0") if (string.Equals(httpVersion, "HTTP/1.0", StringComparison.OrdinalIgnoreCase))
{ {
version = new Version(1, 0); version = HttpHeader.Version10;
} }
} }
//filter out excluded host names //filter out excluded host names
var excluded = endPoint.ExcludedHttpsHostNameRegex != null ? bool excluded = false;
endPoint.ExcludedHttpsHostNameRegex.Any(x => Regex.IsMatch(httpRemoteUri.Host, x)) : false;
if (endPoint.ExcludedHttpsHostNameRegex != null)
{
excluded = endPoint.ExcludedHttpsHostNameRegexList.Any(x => x.IsMatch(httpRemoteUri.Host));
}
if (endPoint.IncludedHttpsHostNameRegex != null)
{
excluded = !endPoint.IncludedHttpsHostNameRegexList.Any(x => x.IsMatch(httpRemoteUri.Host));
}
List<HttpHeader> connectRequestHeaders = null; List<HttpHeader> connectRequestHeaders = null;
//Client wants to create a secure tcp tunnel (its a HTTPS request) //Client wants to create a secure tcp tunnel (its a HTTPS request)
if (httpVerb.ToUpper() == "CONNECT" && !excluded && httpRemoteUri.Port != 80) if (httpVerb == "CONNECT" && !excluded && httpRemoteUri.Port != 80)
{ {
httpRemoteUri = new Uri("https://" + httpCmdSplit[1]); httpRemoteUri = new Uri("https://" + httpCmdSplit[1]);
string tmpLine = null;
connectRequestHeaders = new List<HttpHeader>(); connectRequestHeaders = new List<HttpHeader>();
string tmpLine;
while (!string.IsNullOrEmpty(tmpLine = await clientStreamReader.ReadLineAsync())) while (!string.IsNullOrEmpty(tmpLine = await clientStreamReader.ReadLineAsync()))
{ {
var header = tmpLine.Split(ProxyConstants.ColonSplit, 2); var header = tmpLine.Split(ProxyConstants.ColonSplit, 2);
...@@ -111,25 +109,22 @@ namespace Titanium.Web.Proxy ...@@ -111,25 +109,22 @@ namespace Titanium.Web.Proxy
try try
{ {
sslStream = new SslStream(clientStream, true); sslStream = new SslStream(clientStream, true);
var certificate = certificateCacheManager.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 = sslStream; clientStream = new CustomBufferedStream(sslStream, BufferSize);
clientStreamReader = new CustomBinaryReader(sslStream);
clientStreamWriter = new StreamWriter(sslStream) {NewLine = ProxyConstants.NewLine };
clientStreamReader = new CustomBinaryReader(clientStream, BufferSize);
clientStreamWriter = new StreamWriter(clientStream) { NewLine = ProxyConstants.NewLine };
} }
catch catch
{ {
if (sslStream != null) sslStream?.Dispose();
{
sslStream.Dispose();
}
Dispose(clientStream, clientStreamReader, clientStreamWriter, null); Dispose(clientStream, clientStreamReader, clientStreamWriter, null);
return; return;
...@@ -137,33 +132,33 @@ namespace Titanium.Web.Proxy ...@@ -137,33 +132,33 @@ namespace Titanium.Web.Proxy
//Now read the actual HTTPS request line //Now read the actual HTTPS request line
httpCmd = await clientStreamReader.ReadLineAsync(); httpCmd = await clientStreamReader.ReadLineAsync();
} }
//Sorry cannot do a HTTPS request decrypt to port 80 at this time //Sorry cannot do a HTTPS request decrypt to port 80 at this time
else if (httpVerb.ToUpper() == "CONNECT") else if (httpVerb == "CONNECT")
{ {
//Cyphen out CONNECT request headers //Siphon out CONNECT request headers
await clientStreamReader.ReadAllLinesAsync(); await clientStreamReader.ReadAndIgnoreAllLinesAsync();
//write back successfull CONNECT response //write back successfull CONNECT response
await WriteConnectResponse(clientStreamWriter, version); await WriteConnectResponse(clientStreamWriter, version);
await TcpHelper.SendRaw(BUFFER_SIZE, ConnectionTimeOutSeconds, httpRemoteUri.Host, httpRemoteUri.Port, await TcpHelper.SendRaw(this,
httpCmd, version, null, httpRemoteUri.Host, httpRemoteUri.Port,
false, SupportedSslProtocols, null, version, null,
new RemoteCertificateValidationCallback(ValidateServerCertificate), false,
new LocalCertificateSelectionCallback(SelectClientCertificate), clientStream, tcpConnectionFactory);
clientStream, tcpConnectionFactory, UpStreamEndPoint);
Dispose(clientStream, clientStreamReader, clientStreamWriter, null); Dispose(clientStream, clientStreamReader, clientStreamWriter, null);
return; return;
} }
//Now create the request //Now create the request
await HandleHttpSessionRequest(tcpClient, httpCmd, clientStream, clientStreamReader, clientStreamWriter, await HandleHttpSessionRequest(tcpClient, httpCmd, clientStream, clientStreamReader, clientStreamWriter,
httpRemoteUri.Scheme == Uri.UriSchemeHttps ? httpRemoteUri.Host : null, endPoint, connectRequestHeaders, null, null); httpRemoteUri.Scheme == Uri.UriSchemeHttps ? httpRemoteUri.Host : null, endPoint, connectRequestHeaders);
} }
catch (Exception) catch (Exception)
{ {
Dispose(clientStream, clientStreamReader, clientStreamWriter, null); Dispose(clientStream,
clientStreamReader,
clientStreamWriter, null);
} }
} }
...@@ -171,22 +166,20 @@ namespace Titanium.Web.Proxy ...@@ -171,22 +166,20 @@ namespace Titanium.Web.Proxy
//So for HTTPS requests we would start SSL negotiation right away without expecting a CONNECT request from client //So for HTTPS requests we would start SSL negotiation right away without expecting a CONNECT request from client
private async Task HandleClient(TransparentProxyEndPoint endPoint, TcpClient tcpClient) private async Task HandleClient(TransparentProxyEndPoint endPoint, TcpClient tcpClient)
{ {
CustomBufferedStream clientStream = new CustomBufferedStream(tcpClient.GetStream(), BufferSize);
Stream clientStream = tcpClient.GetStream();
clientStream.ReadTimeout = ConnectionTimeOutSeconds * 1000; clientStream.ReadTimeout = ConnectionTimeOutSeconds * 1000;
clientStream.WriteTimeout = ConnectionTimeOutSeconds * 1000; clientStream.WriteTimeout = ConnectionTimeOutSeconds * 1000;
CustomBinaryReader clientStreamReader = null; CustomBinaryReader clientStreamReader = null;
StreamWriter clientStreamWriter = null; StreamWriter clientStreamWriter = null;
X509Certificate2 certificate = null;
if (endPoint.EnableSsl) if (endPoint.EnableSsl)
{ {
var sslStream = new SslStream(clientStream, true); var sslStream = new SslStream(clientStream, true);
//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
certificate = certificateCacheManager.CreateCertificate(endPoint.GenericCertificateName, false); var certificate = CertificateManager.CreateCertificate(endPoint.GenericCertificateName, false);
try try
{ {
...@@ -194,23 +187,25 @@ namespace Titanium.Web.Proxy ...@@ -194,23 +187,25 @@ namespace Titanium.Web.Proxy
await sslStream.AuthenticateAsServerAsync(certificate, false, await sslStream.AuthenticateAsServerAsync(certificate, false,
SslProtocols.Tls, false); SslProtocols.Tls, false);
clientStreamReader = new CustomBinaryReader(sslStream); clientStream = new CustomBufferedStream(sslStream, BufferSize);
clientStreamWriter = new StreamWriter(sslStream) { NewLine = ProxyConstants.NewLine }; 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) catch (Exception)
{ {
sslStream.Dispose(); sslStream.Dispose();
Dispose(sslStream, clientStreamReader, clientStreamWriter, null); Dispose(sslStream,
clientStreamReader,
clientStreamWriter, null);
return; return;
} }
clientStream = sslStream;
} }
else else
{ {
clientStreamReader = new CustomBinaryReader(clientStream); clientStreamReader = new CustomBinaryReader(clientStream, BufferSize);
clientStreamWriter = new StreamWriter(clientStream) { NewLine = ProxyConstants.NewLine }; clientStreamWriter = new StreamWriter(clientStream) { NewLine = ProxyConstants.NewLine };
} }
...@@ -219,48 +214,65 @@ namespace Titanium.Web.Proxy ...@@ -219,48 +214,65 @@ namespace Titanium.Web.Proxy
//Now create the request //Now create the request
await HandleHttpSessionRequest(tcpClient, httpCmd, clientStream, clientStreamReader, clientStreamWriter, await HandleHttpSessionRequest(tcpClient, httpCmd, clientStream, clientStreamReader, clientStreamWriter,
endPoint.EnableSsl ? endPoint.GenericCertificateName : null, endPoint, null); endPoint.EnableSsl ? endPoint.GenericCertificateName : null, endPoint, null);
} }
private async Task HandleHttpSessionRequestInternal(TcpConnection connection, SessionEventArgs args, ExternalProxy customUpStreamHttpProxy, ExternalProxy customUpStreamHttpsProxy, bool CloseConnection) /// <summary>
/// Create a Server Connection
/// </summary>
/// <param name="args"></param>
/// <returns></returns>
private async Task<TcpConnection> GetServerConnection(
SessionEventArgs args)
{ {
try ExternalProxy customUpStreamHttpProxy = null;
ExternalProxy customUpStreamHttpsProxy = null;
if (args.WebSession.Request.RequestUri.Scheme == "http")
{ {
if (connection == null) if (GetCustomUpStreamHttpProxyFunc != null)
{ {
if (args.WebSession.Request.RequestUri.Scheme == "http") customUpStreamHttpProxy = await GetCustomUpStreamHttpProxyFunc(args);
{ }
if (GetCustomUpStreamHttpProxyFunc != null) }
{ else
customUpStreamHttpProxy = await GetCustomUpStreamHttpProxyFunc(args).ConfigureAwait(false); {
} if (GetCustomUpStreamHttpsProxyFunc != null)
} {
else customUpStreamHttpsProxy = await GetCustomUpStreamHttpsProxyFunc(args);
{ }
if (GetCustomUpStreamHttpsProxyFunc != null) }
{
customUpStreamHttpsProxy = await GetCustomUpStreamHttpsProxyFunc(args).ConfigureAwait(false);
}
}
args.CustomUpStreamHttpProxyUsed = customUpStreamHttpProxy; args.CustomUpStreamHttpProxyUsed = customUpStreamHttpProxy;
args.CustomUpStreamHttpsProxyUsed = customUpStreamHttpsProxy; 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);
}
connection = await tcpConnectionFactory.CreateClient(BUFFER_SIZE, ConnectionTimeOutSeconds,
args.WebSession.Request.RequestUri.Host, args.WebSession.Request.RequestUri.Port, args.WebSession.Request.HttpVersion,
args.IsHttps, SupportedSslProtocols,
new RemoteCertificateValidationCallback(ValidateServerCertificate),
new LocalCertificateSelectionCallback(SelectClientCertificate),
customUpStreamHttpProxy ?? UpStreamHttpProxy, customUpStreamHttpsProxy ?? UpStreamHttpsProxy, args.ProxyClient.ClientStream, UpStreamEndPoint);
}
private async Task<bool> HandleHttpSessionRequestInternal(TcpConnection connection,
SessionEventArgs args, bool closeConnection)
{
try
{
args.WebSession.Request.RequestLocked = true; args.WebSession.Request.RequestLocked = true;
//If request was cancelled by user then dispose the client //If request was cancelled by user then dispose the client
if (args.WebSession.Request.CancelRequest) if (args.WebSession.Request.CancelRequest)
{ {
Dispose(args.ProxyClient.ClientStream, args.ProxyClient.ClientStreamReader, args.ProxyClient.ClientStreamWriter, args); Dispose(args.ProxyClient.ClientStream,
return; args.ProxyClient.ClientStreamReader,
args.ProxyClient.ClientStreamWriter,
args.WebSession.ServerConnection);
return false;
} }
//if expect continue is enabled then send the headers first //if expect continue is enabled then send the headers first
...@@ -277,13 +289,13 @@ namespace Titanium.Web.Proxy ...@@ -277,13 +289,13 @@ namespace Titanium.Web.Proxy
if (args.WebSession.Request.Is100Continue) if (args.WebSession.Request.Is100Continue)
{ {
await WriteResponseStatus(args.WebSession.Response.HttpVersion, "100", await WriteResponseStatus(args.WebSession.Response.HttpVersion, "100",
"Continue", args.ProxyClient.ClientStreamWriter); "Continue", args.ProxyClient.ClientStreamWriter);
await args.ProxyClient.ClientStreamWriter.WriteLineAsync(); await args.ProxyClient.ClientStreamWriter.WriteLineAsync();
} }
else if (args.WebSession.Request.ExpectationFailed) else if (args.WebSession.Request.ExpectationFailed)
{ {
await WriteResponseStatus(args.WebSession.Response.HttpVersion, "417", await WriteResponseStatus(args.WebSession.Response.HttpVersion, "417",
"Expectation Failed", args.ProxyClient.ClientStreamWriter); "Expectation Failed", args.ProxyClient.ClientStreamWriter);
await args.ProxyClient.ClientStreamWriter.WriteLineAsync(); await args.ProxyClient.ClientStreamWriter.WriteLineAsync();
} }
} }
...@@ -324,28 +336,50 @@ namespace Titanium.Web.Proxy ...@@ -324,28 +336,50 @@ namespace Titanium.Web.Proxy
//If not expectation failed response was returned by server then parse response //If not expectation failed response was returned by server then parse response
if (!args.WebSession.Request.ExpectationFailed) if (!args.WebSession.Request.ExpectationFailed)
{ {
await HandleHttpSessionResponse(args); var result = await HandleHttpSessionResponse(args);
//already disposed inside above method
if (result == false)
{
return false;
}
} }
//if connection is closing exit //if connection is closing exit
if (args.WebSession.Response.ResponseKeepAlive == false) if (args.WebSession.Response.ResponseKeepAlive == false)
{ {
Dispose(args.ProxyClient.ClientStream, args.ProxyClient.ClientStreamReader, args.ProxyClient.ClientStreamWriter, args); Dispose(args.ProxyClient.ClientStream,
return; args.ProxyClient.ClientStreamReader,
args.ProxyClient.ClientStreamWriter,
args.WebSession.ServerConnection);
return false;
} }
} }
catch (Exception e) catch (Exception e)
{ {
ExceptionFunc(new ProxyHttpException("Error occured whilst handling session request (internal)", e, args)); ExceptionFunc(new ProxyHttpException("Error occured whilst handling session request (internal)", e, args));
Dispose(args.ProxyClient.ClientStream, args.ProxyClient.ClientStreamReader, args.ProxyClient.ClientStreamWriter, args);
return; Dispose(args.ProxyClient.ClientStream,
args.ProxyClient.ClientStreamReader,
args.ProxyClient.ClientStreamWriter,
args.WebSession.ServerConnection);
return false;
} }
if (CloseConnection) if (closeConnection)
{ {
//dispose //dispose
connection?.Dispose(); Dispose(args.ProxyClient.ClientStream,
args.ProxyClient.ClientStreamReader,
args.ProxyClient.ClientStreamWriter,
args.WebSession.ServerConnection);
return false;
} }
return true;
} }
/// <summary> /// <summary>
...@@ -357,9 +391,14 @@ namespace Titanium.Web.Proxy ...@@ -357,9 +391,14 @@ namespace Titanium.Web.Proxy
/// <param name="clientStreamReader"></param> /// <param name="clientStreamReader"></param>
/// <param name="clientStreamWriter"></param> /// <param name="clientStreamWriter"></param>
/// <param name="httpsHostName"></param> /// <param name="httpsHostName"></param>
/// <param name="endPoint"></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 HandleHttpSessionRequest(TcpClient client, string httpCmd, Stream clientStream,
CustomBinaryReader clientStreamReader, StreamWriter clientStreamWriter, string httpsHostName, ProxyEndPoint endPoint, List<HttpHeader> connectHeaders, ExternalProxy customUpStreamHttpProxy = null, ExternalProxy customUpStreamHttpsProxy = null) CustomBinaryReader clientStreamReader, StreamWriter clientStreamWriter, string httpsHostName,
ProxyEndPoint endPoint, List<HttpHeader> connectHeaders)
{ {
TcpConnection connection = null; TcpConnection connection = null;
...@@ -369,13 +408,19 @@ namespace Titanium.Web.Proxy ...@@ -369,13 +408,19 @@ namespace Titanium.Web.Proxy
{ {
if (string.IsNullOrEmpty(httpCmd)) if (string.IsNullOrEmpty(httpCmd))
{ {
Dispose(clientStream, clientStreamReader, clientStreamWriter, null); Dispose(clientStream,
clientStreamReader,
clientStreamWriter,
connection);
break; break;
} }
var args = new SessionEventArgs(BUFFER_SIZE, HandleHttpSessionResponse); var args = new SessionEventArgs(BufferSize, HandleHttpSessionResponse)
args.ProxyClient.TcpClient = client; {
args.WebSession.ConnectHeaders = connectHeaders; ProxyClient = { TcpClient = client },
WebSession = { ConnectHeaders = connectHeaders }
};
args.WebSession.ProcessId = new Lazy<int>(() => args.WebSession.ProcessId = new Lazy<int>(() =>
{ {
...@@ -389,8 +434,8 @@ namespace Titanium.Web.Proxy ...@@ -389,8 +434,8 @@ namespace Titanium.Web.Proxy
//can't access process Id of remote request from remote machine //can't access process Id of remote request from remote machine
return -1; return -1;
}); });
try try
{ {
//break up the line into three components (method, remote URL & Http Version) //break up the line into three components (method, remote URL & Http Version)
...@@ -399,54 +444,22 @@ namespace Titanium.Web.Proxy ...@@ -399,54 +444,22 @@ namespace Titanium.Web.Proxy
var httpMethod = httpCmdSplit[0]; var httpMethod = httpCmdSplit[0];
//find the request HTTP version //find the request HTTP version
Version httpVersion = new Version(1, 1); var httpVersion = HttpHeader.Version11;
if (httpCmdSplit.Length == 3) if (httpCmdSplit.Length == 3)
{ {
var httpVersionString = httpCmdSplit[2].ToLower().Trim(); var httpVersionString = httpCmdSplit[2].Trim();
if (httpVersionString == "http/1.0") if (string.Equals(httpVersionString, "HTTP/1.0", StringComparison.OrdinalIgnoreCase))
{ {
httpVersion = new Version(1, 0); httpVersion = HttpHeader.Version10;
} }
} }
//Read the request headers in to unique and non-unique header collections //Read the request headers in to unique and non-unique header collections
string tmpLine; await HeaderParser.ReadHeaders(clientStreamReader, args.WebSession.Request.NonUniqueRequestHeaders, args.WebSession.Request.RequestHeaders);
while (!string.IsNullOrEmpty(tmpLine = await clientStreamReader.ReadLineAsync()))
{
var header = tmpLine.Split(ProxyConstants.ColonSplit, 2);
var newHeader = new HttpHeader(header[0], header[1]);
//if header exist in non-unique header collection add it there
if (args.WebSession.Request.NonUniqueRequestHeaders.ContainsKey(newHeader.Name))
{
args.WebSession.Request.NonUniqueRequestHeaders[newHeader.Name].Add(newHeader);
}
//if header is alread in unique header collection then move both to non-unique collection
else if (args.WebSession.Request.RequestHeaders.ContainsKey(newHeader.Name))
{
var existing = args.WebSession.Request.RequestHeaders[newHeader.Name];
var nonUniqueHeaders = new List<HttpHeader>();
nonUniqueHeaders.Add(existing);
nonUniqueHeaders.Add(newHeader);
args.WebSession.Request.NonUniqueRequestHeaders.Add(newHeader.Name, nonUniqueHeaders);
args.WebSession.Request.RequestHeaders.Remove(newHeader.Name);
}
//add to unique header collection
else
{
args.WebSession.Request.RequestHeaders.Add(newHeader.Name, newHeader);
}
}
var httpRemoteUri = new Uri(httpsHostName == null ? httpCmdSplit[1] var httpRemoteUri = new Uri(httpsHostName == null ? httpCmdSplit[1]
: (string.Concat("https://", args.WebSession.Request.Host == null ? : string.Concat("https://", args.WebSession.Request.Host ?? httpsHostName, httpCmdSplit[1]));
httpsHostName : args.WebSession.Request.Host, httpCmdSplit[1])));
args.WebSession.Request.RequestUri = httpRemoteUri; args.WebSession.Request.RequestUri = httpRemoteUri;
...@@ -456,10 +469,15 @@ namespace Titanium.Web.Proxy ...@@ -456,10 +469,15 @@ namespace Titanium.Web.Proxy
args.ProxyClient.ClientStreamReader = clientStreamReader; args.ProxyClient.ClientStreamReader = clientStreamReader;
args.ProxyClient.ClientStreamWriter = clientStreamWriter; args.ProxyClient.ClientStreamWriter = clientStreamWriter;
if (httpsHostName == null && (await CheckAuthorization(clientStreamWriter, args.WebSession.Request.RequestHeaders.Values) == false)) if (httpsHostName == null &&
await CheckAuthorization(clientStreamWriter,
args.WebSession.Request.RequestHeaders.Values) == false)
{ {
Dispose(clientStream,
clientStreamReader,
clientStreamWriter,
connection);
Dispose(clientStream, clientStreamReader, clientStreamWriter, args);
break; break;
} }
...@@ -469,12 +487,12 @@ namespace Titanium.Web.Proxy ...@@ -469,12 +487,12 @@ namespace Titanium.Web.Proxy
//If user requested interception do it //If user requested interception do it
if (BeforeRequest != null) if (BeforeRequest != null)
{ {
Delegate[] invocationList = BeforeRequest.GetInvocationList(); var invocationList = BeforeRequest.GetInvocationList();
Task[] handlerTasks = new Task[invocationList.Length]; var handlerTasks = new Task[invocationList.Length];
for (int i = 0; i < invocationList.Length; i++) for (var i = 0; i < invocationList.Length; i++)
{ {
handlerTasks[i] = ((Func<object, SessionEventArgs, Task>)invocationList[i])(null, args); handlerTasks[i] = ((Func<object, SessionEventArgs, Task>)invocationList[i])(this, args);
} }
await Task.WhenAll(handlerTasks); await Task.WhenAll(handlerTasks);
...@@ -483,48 +501,67 @@ namespace Titanium.Web.Proxy ...@@ -483,48 +501,67 @@ namespace Titanium.Web.Proxy
//if upgrading to websocket then relay the requet without reading the contents //if upgrading to websocket then relay the requet without reading the contents
if (args.WebSession.Request.UpgradeToWebSocket) if (args.WebSession.Request.UpgradeToWebSocket)
{ {
await TcpHelper.SendRaw(BUFFER_SIZE, ConnectionTimeOutSeconds, httpRemoteUri.Host, httpRemoteUri.Port, await TcpHelper.SendRaw(this,
httpCmd, httpVersion, args.WebSession.Request.RequestHeaders, args.IsHttps, httpRemoteUri.Host, httpRemoteUri.Port,
SupportedSslProtocols, new RemoteCertificateValidationCallback(ValidateServerCertificate), httpCmd, httpVersion, args.WebSession.Request.RequestHeaders, args.IsHttps,
new LocalCertificateSelectionCallback(SelectClientCertificate), clientStream, tcpConnectionFactory);
clientStream, tcpConnectionFactory, UpStreamEndPoint);
Dispose(clientStream,
clientStreamReader,
clientStreamWriter,
connection);
Dispose(clientStream, clientStreamReader, clientStreamWriter, args);
break; break;
} }
if (connection == null)
{
connection = await GetServerConnection(args);
}
//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.
await HandleHttpSessionRequestInternal(connection, args, customUpStreamHttpProxy, customUpStreamHttpsProxy, false).ConfigureAwait(false); var result = await HandleHttpSessionRequestInternal(connection, args, false);
if (result == false)
{
//already disposed inside above method
break;
}
if (args.WebSession.Request.CancelRequest) if (args.WebSession.Request.CancelRequest)
{ {
Dispose(clientStream,
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,
clientStreamReader,
clientStreamWriter,
connection);
break; break;
} }
// 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, args);
Dispose(clientStream,
clientStreamReader,
clientStreamWriter,
connection);
break; break;
} }
}
if (connection != null)
{
//dispose
connection.Dispose();
} }
} }
...@@ -537,8 +574,9 @@ namespace Titanium.Web.Proxy ...@@ -537,8 +574,9 @@ namespace Titanium.Web.Proxy
/// <returns></returns> /// <returns></returns>
private async Task WriteConnectResponse(StreamWriter clientStreamWriter, Version httpVersion) private async Task WriteConnectResponse(StreamWriter clientStreamWriter, Version httpVersion)
{ {
await clientStreamWriter.WriteLineAsync(string.Format("HTTP/{0}.{1} {2}", httpVersion.Major, httpVersion.Minor, "200 Connection established")); await clientStreamWriter.WriteLineAsync(
await clientStreamWriter.WriteLineAsync(string.Format("Timestamp: {0}", DateTime.Now)); $"HTTP/{httpVersion.Major}.{httpVersion.Minor} 200 Connection established");
await clientStreamWriter.WriteLineAsync($"Timestamp: {DateTime.Now}");
await clientStreamWriter.WriteLineAsync(); await clientStreamWriter.WriteLineAsync();
await clientStreamWriter.FlushAsync(); await clientStreamWriter.FlushAsync();
} }
...@@ -558,10 +596,7 @@ namespace Titanium.Web.Proxy ...@@ -558,10 +596,7 @@ namespace Titanium.Web.Proxy
{ {
//these are the only encoding this proxy can read //these are the only encoding this proxy can read
case "accept-encoding": case "accept-encoding":
header.Value = "gzip,deflate,zlib"; header.Value = "gzip,deflate";
break;
default:
break; break;
} }
} }
...@@ -583,15 +618,14 @@ namespace Titanium.Web.Proxy ...@@ -583,15 +618,14 @@ namespace Titanium.Web.Proxy
//send the request body bytes to server //send the request body bytes to server
if (args.WebSession.Request.ContentLength > 0) if (args.WebSession.Request.ContentLength > 0)
{ {
await args.ProxyClient.ClientStreamReader.CopyBytesToStream(BUFFER_SIZE, postStream, args.WebSession.Request.ContentLength); await args.ProxyClient.ClientStreamReader.CopyBytesToStream(BufferSize, postStream, args.WebSession.Request.ContentLength);
} }
//Need to revist, find any potential bugs //Need to revist, find any potential bugs
//send the request body bytes to server in chunks //send the request body bytes to server in chunks
else if (args.WebSession.Request.IsChunked) else if (args.WebSession.Request.IsChunked)
{ {
await args.ProxyClient.ClientStreamReader.CopyBytesToStreamChunked(BUFFER_SIZE, postStream); await args.ProxyClient.ClientStreamReader.CopyBytesToStreamChunked(postStream);
} }
} }
} }
} }
\ No newline at end of file
...@@ -9,6 +9,7 @@ using Titanium.Web.Proxy.Exceptions; ...@@ -9,6 +9,7 @@ using Titanium.Web.Proxy.Exceptions;
using Titanium.Web.Proxy.Extensions; using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Http; using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Helpers; using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Network.Tcp;
namespace Titanium.Web.Proxy namespace Titanium.Web.Proxy
{ {
...@@ -17,14 +18,18 @@ namespace Titanium.Web.Proxy ...@@ -17,14 +18,18 @@ namespace Titanium.Web.Proxy
/// </summary> /// </summary>
partial class ProxyServer partial class ProxyServer
{ {
//Called asynchronously when a request was successfully and we received the response /// <summary>
public async Task HandleHttpSessionResponse(SessionEventArgs args) /// Called asynchronously when a request was successfully and we received the response
/// </summary>
/// <param name="args"></param>
/// <returns>true if no errors</returns>
private async Task<bool> HandleHttpSessionResponse(SessionEventArgs args)
{ {
//read response & headers from server
await args.WebSession.ReceiveResponse();
try try
{ {
//read response & headers from server
await args.WebSession.ReceiveResponse();
if (!args.WebSession.Response.ResponseBodyRead) if (!args.WebSession.Response.ResponseBodyRead)
{ {
args.WebSession.Response.ResponseStream = args.WebSession.ServerConnection.Stream; args.WebSession.Response.ResponseStream = args.WebSession.ServerConnection.Stream;
...@@ -46,10 +51,17 @@ namespace Titanium.Web.Proxy ...@@ -46,10 +51,17 @@ namespace Titanium.Web.Proxy
await Task.WhenAll(handlerTasks); await Task.WhenAll(handlerTasks);
} }
if(args.ReRequest) if (args.ReRequest)
{ {
await HandleHttpSessionRequestInternal(null, args, null, null, true).ConfigureAwait(false); if(args.WebSession.ServerConnection != null)
return; {
args.WebSession.ServerConnection.Dispose();
ServerConnectionCount--;
}
var connection = await GetServerConnection(args);
var result = await HandleHttpSessionRequestInternal(null, args, true);
return result;
} }
args.WebSession.Response.ResponseLocked = true; args.WebSession.Response.ResponseLocked = true;
...@@ -58,19 +70,19 @@ namespace Titanium.Web.Proxy ...@@ -58,19 +70,19 @@ namespace Titanium.Web.Proxy
if (args.WebSession.Response.Is100Continue) if (args.WebSession.Response.Is100Continue)
{ {
await WriteResponseStatus(args.WebSession.Response.HttpVersion, "100", await WriteResponseStatus(args.WebSession.Response.HttpVersion, "100",
"Continue", args.ProxyClient.ClientStreamWriter); "Continue", args.ProxyClient.ClientStreamWriter);
await args.ProxyClient.ClientStreamWriter.WriteLineAsync(); await args.ProxyClient.ClientStreamWriter.WriteLineAsync();
} }
else if (args.WebSession.Response.ExpectationFailed) else if (args.WebSession.Response.ExpectationFailed)
{ {
await WriteResponseStatus(args.WebSession.Response.HttpVersion, "417", await WriteResponseStatus(args.WebSession.Response.HttpVersion, "417",
"Expectation Failed", args.ProxyClient.ClientStreamWriter); "Expectation Failed", args.ProxyClient.ClientStreamWriter);
await args.ProxyClient.ClientStreamWriter.WriteLineAsync(); await args.ProxyClient.ClientStreamWriter.WriteLineAsync();
} }
//Write back response status to client //Write back response status to client
await WriteResponseStatus(args.WebSession.Response.HttpVersion, args.WebSession.Response.ResponseStatusCode, await WriteResponseStatus(args.WebSession.Response.HttpVersion, args.WebSession.Response.ResponseStatusCode,
args.WebSession.Response.ResponseStatusDescription, args.ProxyClient.ClientStreamWriter); args.WebSession.Response.ResponseStatusDescription, args.ProxyClient.ClientStreamWriter);
if (args.WebSession.Response.ResponseBodyRead) if (args.WebSession.Response.ResponseBodyRead)
{ {
...@@ -104,32 +116,33 @@ namespace Titanium.Web.Proxy ...@@ -104,32 +116,33 @@ namespace Titanium.Web.Proxy
|| !args.WebSession.Response.ResponseKeepAlive) || !args.WebSession.Response.ResponseKeepAlive)
{ {
await args.WebSession.ServerConnection.StreamReader await args.WebSession.ServerConnection.StreamReader
.WriteResponseBody(BUFFER_SIZE, args.ProxyClient.ClientStream, args.WebSession.Response.IsChunked, .WriteResponseBody(BufferSize, args.ProxyClient.ClientStream, args.WebSession.Response.IsChunked,
args.WebSession.Response.ContentLength); args.WebSession.Response.ContentLength);
} }
//write response if connection:keep-alive header exist and when version is http/1.0 //write response if connection:keep-alive header exist and when version is http/1.0
//Because in Http 1.0 server can return a response without content-length (expectation being client would read until end of stream) //Because in Http 1.0 server can return a response without content-length (expectation being client would read until end of stream)
else if (args.WebSession.Response.ResponseKeepAlive && args.WebSession.Response.HttpVersion.Minor == 0) else if (args.WebSession.Response.ResponseKeepAlive && args.WebSession.Response.HttpVersion.Minor == 0)
{ {
await args.WebSession.ServerConnection.StreamReader await args.WebSession.ServerConnection.StreamReader
.WriteResponseBody(BUFFER_SIZE, args.ProxyClient.ClientStream, args.WebSession.Response.IsChunked, .WriteResponseBody(BufferSize, args.ProxyClient.ClientStream, args.WebSession.Response.IsChunked,
args.WebSession.Response.ContentLength); args.WebSession.Response.ContentLength);
} }
} }
await args.ProxyClient.ClientStream.FlushAsync(); await args.ProxyClient.ClientStream.FlushAsync();
} }
catch(Exception e) catch (Exception e)
{ {
ExceptionFunc(new ProxyHttpException("Error occured wilst handling session response", e, args)); ExceptionFunc(new ProxyHttpException("Error occured whilst handling session response", e, args));
Dispose(args.ProxyClient.ClientStream, args.ProxyClient.ClientStreamReader, Dispose(args.ProxyClient.ClientStream, args.ProxyClient.ClientStreamReader,
args.ProxyClient.ClientStreamWriter, args); args.ProxyClient.ClientStreamWriter, args.WebSession.ServerConnection);
}
finally return false;
{
args.Dispose();
} }
args.Dispose();
return true;
} }
/// <summary> /// <summary>
...@@ -156,14 +169,14 @@ namespace Titanium.Web.Proxy ...@@ -156,14 +169,14 @@ namespace Titanium.Web.Proxy
private async Task WriteResponseStatus(Version version, string code, string description, private async Task WriteResponseStatus(Version version, string code, string description,
StreamWriter responseWriter) StreamWriter responseWriter)
{ {
await responseWriter.WriteLineAsync(string.Format("HTTP/{0}.{1} {2} {3}", version.Major, version.Minor, code, description)); await responseWriter.WriteLineAsync($"HTTP/{version.Major}.{version.Minor} {code} {description}");
} }
/// <summary> /// <summary>
/// Write response headers to client /// Write response headers to client
/// </summary> /// </summary>
/// <param name="responseWriter"></param> /// <param name="responseWriter"></param>
/// <param name="headers"></param> /// <param name="response"></param>
/// <returns></returns> /// <returns></returns>
private async Task WriteResponseHeaders(StreamWriter responseWriter, Response response) private async Task WriteResponseHeaders(StreamWriter responseWriter, Response response)
{ {
...@@ -171,7 +184,7 @@ namespace Titanium.Web.Proxy ...@@ -171,7 +184,7 @@ namespace Titanium.Web.Proxy
foreach (var header in response.ResponseHeaders) foreach (var header in response.ResponseHeaders)
{ {
await responseWriter.WriteLineAsync(header.Value.ToString()); await header.Value.WriteToStream(responseWriter);
} }
//write non unique request headers //write non unique request headers
...@@ -180,11 +193,10 @@ namespace Titanium.Web.Proxy ...@@ -180,11 +193,10 @@ namespace Titanium.Web.Proxy
var headers = headerItem.Value; var headers = headerItem.Value;
foreach (var header in headers) foreach (var header in headers)
{ {
await responseWriter.WriteLineAsync(header.ToString()); await header.WriteToStream(responseWriter);
} }
} }
await responseWriter.WriteLineAsync(); await responseWriter.WriteLineAsync();
await responseWriter.FlushAsync(); await responseWriter.FlushAsync();
} }
...@@ -214,42 +226,29 @@ namespace Titanium.Web.Proxy ...@@ -214,42 +226,29 @@ namespace Titanium.Web.Proxy
headers.Remove("proxy-connection"); headers.Remove("proxy-connection");
} }
} }
/// <summary> /// <summary>
/// Handle dispose of a client/server session /// Handle dispose of a client/server session
/// </summary> /// </summary>
/// <param name="tcpClient"></param>
/// <param name="clientStream"></param> /// <param name="clientStream"></param>
/// <param name="clientStreamReader"></param> /// <param name="clientStreamReader"></param>
/// <param name="clientStreamWriter"></param> /// <param name="clientStreamWriter"></param>
/// <param name="args"></param> /// <param name="serverConnection"></param>
private void Dispose(Stream clientStream, CustomBinaryReader clientStreamReader, private void Dispose(Stream clientStream,
StreamWriter clientStreamWriter, IDisposable args) CustomBinaryReader clientStreamReader,
StreamWriter clientStreamWriter,
TcpConnection serverConnection)
{ {
ServerConnectionCount--;
if (clientStream != null) clientStream?.Close();
{ clientStream?.Dispose();
clientStream.Close();
clientStream.Dispose();
}
if (args != null) clientStreamReader?.Dispose();
{ clientStreamWriter?.Dispose();
args.Dispose();
}
if (clientStreamReader != null)
{
clientStreamReader.Dispose();
}
if (clientStreamWriter != null) serverConnection?.Dispose();
{
clientStreamWriter.Close();
clientStreamWriter.Dispose();
}
} }
} }
} }
\ No newline at end of file
using System; using System.Text;
using System.Text;
namespace Titanium.Web.Proxy.Shared namespace Titanium.Web.Proxy.Shared
{ {
...@@ -7,10 +6,10 @@ namespace Titanium.Web.Proxy.Shared ...@@ -7,10 +6,10 @@ namespace Titanium.Web.Proxy.Shared
/// Literals shared by Proxy Server /// Literals shared by Proxy Server
/// </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);
......
...@@ -26,6 +26,7 @@ ...@@ -26,6 +26,7 @@
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion> <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<Prefer32Bit>false</Prefer32Bit> <Prefer32Bit>false</Prefer32Bit>
<DocumentationFile>bin\Debug\Titanium.Web.Proxy.XML</DocumentationFile> <DocumentationFile>bin\Debug\Titanium.Web.Proxy.XML</DocumentationFile>
<LangVersion>6</LangVersion>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|AnyCPU'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|AnyCPU'">
<PlatformTarget>AnyCPU</PlatformTarget> <PlatformTarget>AnyCPU</PlatformTarget>
...@@ -34,10 +35,18 @@ ...@@ -34,10 +35,18 @@
<DefineConstants>NET45</DefineConstants> <DefineConstants>NET45</DefineConstants>
<Prefer32Bit>false</Prefer32Bit> <Prefer32Bit>false</Prefer32Bit>
<DocumentationFile>bin\Release\Titanium.Web.Proxy.XML</DocumentationFile> <DocumentationFile>bin\Release\Titanium.Web.Proxy.XML</DocumentationFile>
<DebugType>none</DebugType>
<DebugSymbols>false</DebugSymbols>
</PropertyGroup>
<PropertyGroup>
<SignAssembly>true</SignAssembly>
</PropertyGroup>
<PropertyGroup>
<AssemblyOriginatorKeyFile>StrongNameKey.snk</AssemblyOriginatorKeyFile>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="Ionic.Zip, Version=1.9.8.0, Culture=neutral, PublicKeyToken=6583c7c814667745, processorArchitecture=MSIL"> <Reference Include="BouncyCastle.Crypto, Version=1.8.1.0, Culture=neutral, PublicKeyToken=0e99375e54769942">
<HintPath>..\packages\DotNetZip.1.9.8\lib\net20\Ionic.Zip.dll</HintPath> <HintPath>..\packages\BouncyCastle.1.8.1\lib\BouncyCastle.Crypto.dll</HintPath>
<Private>True</Private> <Private>True</Private>
</Reference> </Reference>
<Reference Include="System" /> <Reference Include="System" />
...@@ -56,19 +65,24 @@ ...@@ -56,19 +65,24 @@
<Compile Include="Compression\DeflateCompression.cs" /> <Compile Include="Compression\DeflateCompression.cs" />
<Compile Include="Compression\GZipCompression.cs" /> <Compile Include="Compression\GZipCompression.cs" />
<Compile Include="Compression\ICompression.cs" /> <Compile Include="Compression\ICompression.cs" />
<Compile Include="Compression\ZlibCompression.cs" />
<Compile Include="Decompression\DecompressionFactory.cs" /> <Compile Include="Decompression\DecompressionFactory.cs" />
<Compile Include="Decompression\DefaultDecompression.cs" /> <Compile Include="Decompression\DefaultDecompression.cs" />
<Compile Include="Decompression\DeflateDecompression.cs" /> <Compile Include="Decompression\DeflateDecompression.cs" />
<Compile Include="Decompression\GZipDecompression.cs" /> <Compile Include="Decompression\GZipDecompression.cs" />
<Compile Include="Decompression\IDecompression.cs" /> <Compile Include="Decompression\IDecompression.cs" />
<Compile Include="Decompression\ZlibDecompression.cs" />
<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\StringExtensions.cs" />
<Compile Include="Helpers\CustomBufferedStream.cs" />
<Compile Include="Helpers\Network.cs" /> <Compile Include="Helpers\Network.cs" />
<Compile Include="Helpers\RunTime.cs" />
<Compile Include="Http\HeaderParser.cs" />
<Compile Include="Http\Responses\GenericResponse.cs" />
<Compile Include="Network\CachedCertificate.cs" /> <Compile Include="Network\CachedCertificate.cs" />
<Compile Include="Network\CertificateMaker.cs" /> <Compile Include="Network\Certificate\WinCertificateMaker.cs" />
<Compile Include="Network\Certificate\BCCertificateMaker.cs" />
<Compile Include="Network\Certificate\ICertificateMaker.cs" />
<Compile Include="Network\ProxyClient.cs" /> <Compile Include="Network\ProxyClient.cs" />
<Compile Include="Exceptions\BodyNotFoundException.cs" /> <Compile Include="Exceptions\BodyNotFoundException.cs" />
<Compile Include="Exceptions\ProxyAuthorizationException.cs" /> <Compile Include="Exceptions\ProxyAuthorizationException.cs" />
...@@ -117,6 +131,7 @@ ...@@ -117,6 +131,7 @@
<ItemGroup> <ItemGroup>
<None Include="app.config" /> <None Include="app.config" />
<None Include="packages.config" /> <None Include="packages.config" />
<None Include="StrongNameKey.snk" />
</ItemGroup> </ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" /> <Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
......
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
<copyright>Copyright &#x00A9; Titanium. All rights reserved.</copyright> <copyright>Copyright &#x00A9; Titanium. All rights reserved.</copyright>
<tags></tags> <tags></tags>
<dependencies> <dependencies>
<dependency id="DotNetZip" version="1.9.8" /> <dependency id="BouncyCastle" version="1.8.1" />
</dependencies> </dependencies>
</metadata> </metadata>
<files> <files>
......
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<configuration> <configuration>
<runtime> <runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
...@@ -12,4 +12,7 @@ ...@@ -12,4 +12,7 @@
</dependentAssembly> </dependentAssembly>
</assemblyBinding> </assemblyBinding>
</runtime> </runtime>
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5"/></startup></configuration> <startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5"/>
</startup>
</configuration>
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<packages> <packages>
<package id="DotNetZip" version="1.9.8" targetFramework="net45" /> <package id="BouncyCastle" version="1.8.1" targetFramework="net45" />
</packages> </packages>
\ No newline at end of file
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