Commit 45d3a905 authored by Jehonathan Thomas's avatar Jehonathan Thomas Committed by GitHub

Merge pull request #226 from justcoding121/revert-225-beta

Revert "Stable"
parents bf310af0 7164aaaa
...@@ -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
{ {
......
...@@ -9,23 +9,16 @@ namespace Titanium.Web.Proxy.Examples.Basic ...@@ -9,23 +9,16 @@ namespace Titanium.Web.Proxy.Examples.Basic
{ {
public class ProxyTestController public class ProxyTestController
{ {
private readonly ProxyServer proxyServer; private ProxyServer proxyServer;
//share requestBody outside handlers //share requestBody outside handlers
private readonly Dictionary<Guid, string> requestBodyHistory = new Dictionary<Guid, string>(); private Dictionary<Guid, string> requestBodyHistory;
public ProxyTestController() public ProxyTestController()
{ {
proxyServer = new ProxyServer(); proxyServer = new ProxyServer();
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()
...@@ -35,32 +28,21 @@ namespace Titanium.Web.Proxy.Examples.Basic ...@@ -35,32 +28,21 @@ 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)
{ {
//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>() { "google.com", "dropbox.com" } // ExcludedHttpsHostNameRegex = new List<string>() { "google.com", "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 existence of a proxy //An explicit endpoint is where the client knows about the existance 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 useful for reverse proxying (client is not aware of the existence of proxy) //Transparent endpoint is usefull for reverse proxying (client is not aware of the existance 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
...@@ -94,12 +76,12 @@ namespace Titanium.Web.Proxy.Examples.Basic ...@@ -94,12 +76,12 @@ namespace Titanium.Web.Proxy.Examples.Basic
proxyServer.Stop(); proxyServer.Stop();
} }
//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(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();
...@@ -128,7 +110,6 @@ namespace Titanium.Web.Proxy.Examples.Basic ...@@ -128,7 +110,6 @@ namespace Titanium.Web.Proxy.Examples.Basic
"</body>" + "</body>" +
"</html>"); "</html>");
} }
//Redirect example //Redirect example
if (e.WebSession.Request.RequestUri.AbsoluteUri.Contains("wikipedia.org")) if (e.WebSession.Request.RequestUri.AbsoluteUri.Contains("wikipedia.org"))
{ {
...@@ -144,7 +125,6 @@ namespace Titanium.Web.Proxy.Examples.Basic ...@@ -144,7 +125,6 @@ namespace Titanium.Web.Proxy.Examples.Basic
//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;
...@@ -196,4 +176,4 @@ namespace Titanium.Web.Proxy.Examples.Basic ...@@ -196,4 +176,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>none</DebugType> <DebugType>pdbonly</DebugType>
<Optimize>true</Optimize> <Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath> <OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE;NET45</DefineConstants> <DefineConstants>TRACE;NET45</DefineConstants>
...@@ -33,7 +33,6 @@ ...@@ -33,7 +33,6 @@
<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 doesn't have any bugs that you are aware of. - [ ] No Bugs - I made sure that this change is working properly as expected. It does'nt 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 Web Socket requests over HTTP * Safely relays WebSocket 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. ([Wiki & Contribution guidelines](https://github.com/justcoding121/Titanium-Web-Proxy/wiki)) Refer the HTTP Proxy Server library in your project, look up Test project to learn usage.
Install by nuget: Install by nuget:
...@@ -38,171 +38,150 @@ For stable releases on [stable branch](https://github.com/justcoding121/Titanium ...@@ -38,171 +38,150 @@ 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;
//optionally set the Certificate Engine proxyServer.BeforeRequest += OnRequest;
//Under Mono only BouncyCastle will be supported proxyServer.BeforeResponse += OnResponse;
//proxyServer.CertificateEngine = Network.CertificateEngine.BouncyCastle; proxyServer.ServerCertificateValidationCallback += OnCertificateValidation;
proxyServer.ClientCertificateSelectionCallback += OnCertificateSelection;
proxyServer.BeforeRequest += OnRequest;
proxyServer.BeforeResponse += OnResponse;
proxyServer.ServerCertificateValidationCallback += OnCertificateValidation; //Exclude Https addresses you don't want to proxy
proxyServer.ClientCertificateSelectionCallback += OnCertificateSelection; //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 dropbox.com //An explicit endpoint is where the client knows about the existance of a proxy
// ExcludedHttpsHostNameRegex = new List<string>() { "google.com", "dropbox.com" } //So client sends request in a proxy friendly manner
proxyServer.AddEndPoint(explicitEndPoint);
//Use self-issued generic certificate on all HTTPS requests proxyServer.Start();
//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") //Transparent endpoint is usefull for reverse proxying (client is not aware of the existance of proxy)
}; //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)
//An explicit endpoint is where the client knows about the existence of a proxy //That means that the transparent endpoint will always provide the same Generic Certificate to all HTTPS requests
//So client sends request in a proxy friendly manner //In this example only google.com will work for HTTPS requests
proxyServer.AddEndPoint(explicitEndPoint); //Other sites will receive a certificate mismatch warning on browser
proxyServer.Start(); var transparentEndPoint = new TransparentProxyEndPoint(IPAddress.Any, 8001, true)
{
//Warning! Transparent endpoint is not tested end to end GenericCertificateName = "google.com"
//Transparent endpoint is useful for reverse proxy (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 proxyServer.AddEndPoint(transparentEndPoint);
//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 //proxyServer.UpStreamHttpProxy = new ExternalProxy() { HostName = "localhost", Port = 8888 };
//In this example only google.com will work for HTTPS requests //proxyServer.UpStreamHttpsProxy = new ExternalProxy() { HostName = "localhost", Port = 8888 };
//Other sites will receive a certificate mismatch warning on browser
var transparentEndPoint = new TransparentProxyEndPoint(IPAddress.Any, 8001, true) foreach (var endPoint in proxyServer.ProxyEndPoints)
{ Console.WriteLine("Listening on '{0}' endpoint at Ip {1} and port: {2} ",
GenericCertificateName = "google.com" endPoint.GetType().Name, endPoint.IpAddress, endPoint.Port);
};
proxyServer.AddEndPoint(transparentEndPoint); //Only explicit proxies can be set as system proxy!
proxyServer.SetAsSystemHttpProxy(explicitEndPoint);
//proxyServer.UpStreamHttpProxy = new ExternalProxy() { HostName = "localhost", Port = 8888 }; proxyServer.SetAsSystemHttpsProxy(explicitEndPoint);
//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)
foreach (var endPoint in proxyServer.ProxyEndPoints) Console.Read();
Console.WriteLine("Listening on '{0}' endpoint at Ip {1} and port: {2} ",
endPoint.GetType().Name, endPoint.IpAddress, endPoint.Port); //Unsubscribe & Quit
proxyServer.BeforeRequest -= OnRequest;
//Only explicit proxies can be set as system proxy! proxyServer.BeforeResponse -= OnResponse;
proxyServer.SetAsSystemHttpProxy(explicitEndPoint); proxyServer.ServerCertificateValidationCallback -= OnCertificateValidation;
proxyServer.SetAsSystemHttpsProxy(explicitEndPoint); proxyServer.ClientCertificateSelectionCallback -= OnCertificateSelection;
//wait here (You can use something else as a wait function, I am using this as a demo) proxyServer.Stop();
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 {
private Dictionary<Guid, string> requestBodyHistory = new Dictionary<Guid, string>(); Console.WriteLine(e.WebSession.Request.Url);
public async Task OnRequest(object sender, SessionEventArgs e) ////read request headers
{ var requestHeaders = e.WebSession.Request.RequestHeaders;
Console.WriteLine(e.WebSession.Request.Url);
var method = e.WebSession.Request.Method.ToUpper();
////read request headers if ((method == "POST" || method == "PUT" || method == "PATCH"))
var requestHeaders = e.WebSession.Request.RequestHeaders; {
//Get/Set request body bytes
var method = e.WebSession.Request.Method.ToUpper(); byte[] bodyBytes = await e.GetRequestBody();
if ((method == "POST" || method == "PUT" || method == "PATCH")) await e.SetRequestBody(bodyBytes);
{
//Get/Set request body bytes //Get/Set request body as string
byte[] bodyBytes = await e.GetRequestBody(); string bodyString = await e.GetRequestBodyAsString();
await e.SetRequestBody(bodyBytes); await e.SetRequestBodyString(bodyString);
//Get/Set request body as string }
string bodyString = await e.GetRequestBodyAsString();
await e.SetRequestBodyString(bodyString); //To cancel a request with a custom HTML content
//Filter URL
//store request Body/request headers etc with request Id as key if (e.WebSession.Request.RequestUri.AbsoluteUri.Contains("google.com"))
//so that you can find it from response handler using request Id {
requestBodyHistory[e.Id] = bodyString; await e.Ok("<!DOCTYPE html>" +
} "<html><body><h1>" +
"Website Blocked" +
//To cancel a request with a custom HTML content "</h1>" +
//Filter URL "<p>Blocked by titanium web proxy.</p>" +
if (e.WebSession.Request.RequestUri.AbsoluteUri.Contains("google.com")) "</body>" +
{ "</html>");
await e.Ok("<!DOCTYPE html>" + }
"<html><body><h1>" + //Redirect example
"Website Blocked" + if (e.WebSession.Request.RequestUri.AbsoluteUri.Contains("wikipedia.org"))
"</h1>" + {
"<p>Blocked by titanium web proxy.</p>" + await e.Redirect("https://www.paypal.com");
"</body>" + }
"</html>"); }
}
//Redirect example //Modify response
if (e.WebSession.Request.RequestUri.AbsoluteUri.Contains("wikipedia.org")) public async Task OnResponse(object sender, SessionEventArgs e)
{ {
await e.Redirect("https://www.paypal.com"); //read response headers
} var responseHeaders = e.WebSession.Response.ResponseHeaders;
}
//if (!e.ProxySession.Request.Host.Equals("medeczane.sgk.gov.tr")) return;
//Modify response if (e.WebSession.Request.Method == "GET" || e.WebSession.Request.Method == "POST")
public async Task OnResponse(object sender, SessionEventArgs e) {
{ if (e.WebSession.Response.ResponseStatusCode == "200")
//read response headers {
var responseHeaders = e.WebSession.Response.ResponseHeaders; if (e.WebSession.Response.ContentType!=null && e.WebSession.Response.ContentType.Trim().ToLower().Contains("text/html"))
{
//if (!e.ProxySession.Request.Host.Equals("medeczane.sgk.gov.tr")) return; byte[] bodyBytes = await e.GetResponseBody();
if (e.WebSession.Request.Method == "GET" || e.WebSession.Request.Method == "POST") await e.SetResponseBody(bodyBytes);
{
if (e.WebSession.Response.ResponseStatusCode == "200") string body = await e.GetResponseBodyAsString();
{ 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); }
string body = await e.GetResponseBodyAsString(); /// Allows overriding default certificate validation logic
await e.SetResponseBodyString(body); 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;
//access request body/request headers etc by looking up using requestId
if(requestBodyHistory.ContainsKey(e.Id)) return Task.FromResult(0);
{ }
var requestBody = requestBodyHistory[e.Id];
} /// Allows overriding default client certificate selection logic during mutual authentication
} public Task OnCertificateSelection(object sender, CertificateSelectionEventArgs e)
{
/// Allows overriding default certificate validation logic //set e.clientCertificate to override
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 road map (Pull requests are welcome!) Future roadmap
============ ============
* 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 static readonly string[] hostNames private readonly static 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,7 +20,8 @@ namespace Titanium.Web.Proxy.UnitTests ...@@ -20,7 +20,8 @@ namespace Titanium.Web.Proxy.UnitTests
{ {
var tasks = new List<Task>(); var tasks = new List<Task>();
var mgr = new CertificateManager(new Lazy<Action<Exception>>(() => (e => { })).Value); var mgr = new CertificateManager("Titanium", "Titanium Root Certificate Authority",
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,12 +34,6 @@ ...@@ -34,12 +34,6 @@
<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>
...@@ -66,9 +60,6 @@ ...@@ -66,9 +60,6 @@
<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.25420.1 VisualStudioVersion = 14.0.25123.0
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,8 +33,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{BC1E0789 ...@@ -33,8 +33,6 @@ 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
...@@ -53,10 +51,6 @@ Global ...@@ -53,10 +51,6 @@ 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
...@@ -64,7 +58,6 @@ Global ...@@ -64,7 +58,6 @@ 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;
...@@ -16,21 +17,20 @@ namespace Titanium.Web.Proxy ...@@ -16,21 +17,20 @@ namespace Titanium.Web.Proxy
/// <param name="chain"></param> /// <param name="chain"></param>
/// <param name="sslPolicyErrors"></param> /// <param name="sslPolicyErrors"></param>
/// <returns></returns> /// <returns></returns>
private bool ValidateServerCertificate( internal bool ValidateServerCertificate(
object sender, object sender,
X509Certificate certificate, X509Certificate certificate,
X509Chain chain, X509Chain chain,
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();
{
Certificate = certificate, args.Certificate = certificate;
Chain = chain, args.Chain = chain;
SslPolicyErrors = sslPolicyErrors args.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();
...@@ -65,7 +65,7 @@ namespace Titanium.Web.Proxy ...@@ -65,7 +65,7 @@ namespace Titanium.Web.Proxy
/// <param name="remoteCertificate"></param> /// <param name="remoteCertificate"></param>
/// <param name="acceptableIssuers"></param> /// <param name="acceptableIssuers"></param>
/// <returns></returns> /// <returns></returns>
private X509Certificate SelectClientCertificate( internal X509Certificate SelectClientCertificate(
object sender, object sender,
string targetHost, string targetHost,
X509CertificateCollection localCertificates, X509CertificateCollection localCertificates,
...@@ -73,6 +73,7 @@ namespace Titanium.Web.Proxy ...@@ -73,6 +73,7 @@ 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 &&
...@@ -99,22 +100,20 @@ namespace Titanium.Web.Proxy ...@@ -99,22 +100,20 @@ 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,6 +13,8 @@ ...@@ -13,6 +13,8 @@
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 System.IO; using Ionic.Zlib;
using System.IO.Compression; using System.IO;
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,12 +7,14 @@ ...@@ -7,12 +7,14 @@
{ {
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,6 +2,7 @@ ...@@ -2,6 +2,7 @@
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 System.IO; using Ionic.Zlib;
using System.IO.Compression; using System.IO;
using System.Threading.Tasks; using System.Threading.Tasks;
using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Decompression namespace Titanium.Web.Proxy.Decompression
{ {
...@@ -11,7 +12,8 @@ namespace Titanium.Web.Proxy.Decompression ...@@ -11,7 +12,8 @@ namespace Titanium.Web.Proxy.Decompression
{ {
public async Task<byte[]> Decompress(byte[] compressedArray, int bufferSize) public async Task<byte[]> Decompress(byte[] compressedArray, int bufferSize)
{ {
using (var stream = new MemoryStream(compressedArray)) 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];
...@@ -21,7 +23,7 @@ namespace Titanium.Web.Proxy.Decompression ...@@ -21,7 +23,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)
{ {
output.Write(buffer, 0, read); await output.WriteAsync(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
{ {
...@@ -19,9 +20,8 @@ namespace Titanium.Web.Proxy.Decompression ...@@ -19,9 +20,8 @@ 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)
{ {
output.Write(buffer, 0, read); await output.WriteAsync(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,34 +8,13 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -8,34 +8,13 @@ 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,26 +7,17 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -7,26 +7,17 @@ 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 public class CertificateValidationEventArgs : EventArgs, IDisposable
{ {
/// <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,6 +22,7 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -22,6 +22,7 @@ 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>
...@@ -43,20 +44,20 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -43,20 +44,20 @@ namespace Titanium.Web.Proxy.EventArguments
/// </summary> /// </summary>
public Guid Id => WebSession.RequestId; public Guid Id => WebSession.RequestId;
/// <summary> //Should we send a rerequest
/// Should we send a rerequest public bool ReRequest
/// </summary> {
public bool ReRequest { get; set; } get;
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>
/// Client End Point. public IPEndPoint ClientEndPoint => (IPEndPoint)ProxyClient.TcpClient.Client.RemoteEndPoint;
/// </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
...@@ -64,14 +65,8 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -64,14 +65,8 @@ 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>
...@@ -103,13 +98,14 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -103,13 +98,14 @@ 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 ProxyClient.ClientStreamReader.CopyBytesToStreamChunked(requestBodyStream); await this.ProxyClient.ClientStreamReader.CopyBytesToStreamChunked(bufferSize, requestBodyStream);
} }
else else
{ {
...@@ -117,8 +113,9 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -117,8 +113,9 @@ 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 ProxyClient.ClientStreamReader.CopyBytesToStream(bufferSize, requestBodyStream, await this.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)
{ {
...@@ -133,6 +130,7 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -133,6 +130,7 @@ 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>
...@@ -148,7 +146,7 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -148,7 +146,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(responseBodyStream); await WebSession.ServerConnection.StreamReader.CopyBytesToStreamChunked(bufferSize, responseBodyStream);
} }
else else
{ {
...@@ -157,6 +155,7 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -157,6 +155,7 @@ 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)
{ {
...@@ -166,6 +165,7 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -166,6 +165,7 @@ 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,7 +189,6 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -189,7 +189,6 @@ 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>
...@@ -256,6 +255,7 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -256,6 +255,7 @@ 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,7 +406,6 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -406,7 +406,6 @@ 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) if (headers != null && headers.Count > 0)
{ {
response.ResponseHeaders = headers; response.ResponseHeaders = headers;
...@@ -419,83 +418,12 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -419,83 +418,12 @@ namespace Titanium.Web.Proxy.EventArguments
WebSession.Request.CancelRequest = true; 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)
{
response.ResponseHeaders = headers;
}
response.HttpVersion = WebSession.Request.HttpVersion;
response.ResponseBody = result;
await Respond(response);
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 HttpHeader("Location", url)); response.ResponseHeaders.Add("Location", new Models.HttpHeader("Location", url));
response.ResponseBody = Encoding.ASCII.GetBytes(string.Empty); response.ResponseBody = Encoding.ASCII.GetBytes(string.Empty);
await Respond(response); await Respond(response);
...@@ -521,6 +449,7 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -521,6 +449,7 @@ namespace Titanium.Web.Proxy.EventArguments
/// </summary> /// </summary>
public void Dispose() public void Dispose()
{ {
} }
} }
} }
\ No newline at end of file
namespace Titanium.Web.Proxy.Exceptions using System;
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,10 +2,7 @@ ...@@ -2,10 +2,7 @@
namespace Titanium.Web.Proxy.Extensions namespace Titanium.Web.Proxy.Extensions
{ {
/// <summary> public static class ByteArrayExtensions
/// 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
...@@ -15,11 +12,12 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -15,11 +12,12 @@ 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>
internal static T[] SubArray<T>(this T[] data, int index, int length) public static T[] SubArray<T>(this T[] data, int index, int length)
{ {
var result = new T[length]; T[] result = new T[length];
Array.Copy(data, index, result, 0, length); Array.Copy(data, index, result, 0, length);
return result; return result;
} }
} }
} }
using System; using System.Text;
using System.Text;
using Titanium.Web.Proxy.Http; using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Shared; using Titanium.Web.Proxy.Shared;
...@@ -30,7 +29,7 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -30,7 +29,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].Trim().Equals("charset", StringComparison.CurrentCultureIgnoreCase)) if (encodingSplit.Length == 2 && encodingSplit[0].ToLower().Trim() == "charset")
{ {
return Encoding.GetEncoding(encodingSplit[1]); return Encoding.GetEncoding(encodingSplit[1]);
} }
...@@ -46,4 +45,4 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -46,4 +45,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; using System.Text;
using System.Text;
using Titanium.Web.Proxy.Http; using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Shared; using Titanium.Web.Proxy.Shared;
...@@ -27,7 +26,7 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -27,7 +26,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].Trim().Equals("charset", StringComparison.CurrentCultureIgnoreCase)) if (encodingSplit.Length == 2 && encodingSplit[0].ToLower().Trim() == "charset")
{ {
return Encoding.GetEncoding(encodingSplit[1]); return Encoding.GetEncoding(encodingSplit[1]);
} }
...@@ -43,4 +42,4 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -43,4 +42,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(bytesToRead); var buffer = await streamReader.ReadBytesAsync(bufferSize, bytesToRead);
if (buffer.Length == 0) if (buffer.Length == 0)
{ {
...@@ -65,13 +65,14 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -65,13 +65,14 @@ 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="stream"></param> /// <param name="bufferSize"></param>
/// <returns></returns> /// <param name="stream"></param>
internal static async Task CopyBytesToStreamChunked(this CustomBinaryReader clientStreamReader, Stream stream) /// <returns></returns>
internal static async Task CopyBytesToStreamChunked(this CustomBinaryReader clientStreamReader, int bufferSize, Stream stream)
{ {
while (true) while (true)
{ {
...@@ -80,7 +81,7 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -80,7 +81,7 @@ namespace Titanium.Web.Proxy.Extensions
if (chunkSize != 0) if (chunkSize != 0)
{ {
var buffer = await clientStreamReader.ReadBytesAsync(chunkSize); var buffer = await clientStreamReader.ReadBytesAsync(bufferSize, 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();
...@@ -92,7 +93,6 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -92,7 +93,6 @@ 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,22 +154,23 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -154,22 +154,23 @@ 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, outStream); await WriteResponseBodyChunked(inStreamReader, bufferSize, outStream);
} }
} }
/// <summary> /// <summary>
/// Copies the streams chunked /// Copies the streams chunked
/// </summary> /// </summary>
/// <param name="inStreamReader"></param> /// <param name="inStreamReader"></param>
/// <param name="outStream"></param> /// <param name="bufferSize"></param>
/// <returns></returns> /// <param name="outStream"></param>
internal static async Task WriteResponseBodyChunked(this CustomBinaryReader inStreamReader, Stream outStream) /// <returns></returns>
internal static async Task WriteResponseBodyChunked(this CustomBinaryReader inStreamReader, int bufferSize, Stream outStream)
{ {
while (true) while (true)
{ {
...@@ -178,7 +179,7 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -178,7 +179,7 @@ namespace Titanium.Web.Proxy.Extensions
if (chunkSize != 0) if (chunkSize != 0)
{ {
var buffer = await inStreamReader.ReadBytesAsync(chunkSize); var buffer = await inStreamReader.ReadBytesAsync(bufferSize, chunkSize);
var chunkHeadBytes = Encoding.ASCII.GetBytes(chunkSize.ToString("x2")); var chunkHeadBytes = Encoding.ASCII.GetBytes(chunkSize.ToString("x2"));
...@@ -198,7 +199,6 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -198,7 +199,6 @@ 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,5 +216,6 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -216,5 +216,6 @@ 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;
}
}
}
...@@ -12,11 +12,11 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -12,11 +12,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.
var blockingState = client.Blocking; bool blockingState = client.Blocking;
try try
{ {
var tmp = new byte[1]; byte[] tmp = new byte[1];
client.Blocking = false; client.Blocking = false;
client.Send(tmp, 0, 0); client.Send(tmp, 0, 0);
...@@ -25,7 +25,14 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -25,7 +25,14 @@ namespace Titanium.Web.Proxy.Extensions
catch (SocketException e) catch (SocketException e)
{ {
// 10035 == WSAEWOULDBLOCK // 10035 == WSAEWOULDBLOCK
return e.NativeErrorCode.Equals(10035); if (e.NativeErrorCode.Equals(10035))
{
return true;
}
else
{
return false;
}
} }
finally finally
{ {
...@@ -33,4 +40,5 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -33,4 +40,5 @@ namespace Titanium.Web.Proxy.Extensions
} }
} }
} }
} }
...@@ -3,9 +3,12 @@ using System.Collections.Generic; ...@@ -3,9 +3,12 @@ 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
...@@ -13,33 +16,15 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -13,33 +16,15 @@ namespace Titanium.Web.Proxy.Helpers
/// </summary> /// </summary>
internal class CustomBinaryReader : IDisposable internal class CustomBinaryReader : IDisposable
{ {
private readonly CustomBufferedStream stream; private Stream stream;
private readonly int bufferSize; private Encoding encoding;
private readonly Encoding encoding;
[ThreadStatic]
private static byte[] staticBufferField;
private byte[] staticBuffer
{
get
{
if (staticBufferField == null || staticBufferField.Length != bufferSize)
{
staticBufferField = new byte[bufferSize];
}
return staticBufferField;
}
}
internal CustomBinaryReader(CustomBufferedStream stream, int bufferSize) internal CustomBinaryReader(Stream stream)
{ {
this.stream = stream; this.stream = stream;
this.bufferSize = bufferSize;
//default to UTF-8 //default to UTF-8
encoding = Encoding.UTF8; this.encoding = Encoding.UTF8;
} }
internal Stream BaseStream => stream; internal Stream BaseStream => stream;
...@@ -50,41 +35,33 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -50,41 +35,33 @@ namespace Titanium.Web.Proxy.Helpers
/// <returns></returns> /// <returns></returns>
internal async Task<string> ReadLineAsync() internal async Task<string> ReadLineAsync()
{ {
var lastChar = default(byte); using (var readBuffer = new MemoryStream())
int bufferDataLength = 0;
// try to use the thread static buffer, usually it is enough
var buffer = staticBuffer;
while (stream.DataAvailable || await stream.FillBufferAsync())
{ {
var newChar = stream.ReadByteFromBuffer(); var lastChar = default(char);
buffer[bufferDataLength] = newChar; var buffer = new byte[1];
//if new line while ((await this.stream.ReadAsync(buffer, 0, 1)) > 0)
if (lastChar == '\r' && newChar == '\n') {
{ //if new line
return encoding.GetString(buffer, 0, bufferDataLength - 1); if (lastChar == '\r' && buffer[0] == '\n')
} {
//end of stream var result = readBuffer.ToArray();
if (newChar == '\0') return encoding.GetString(result.SubArray(0, result.Length - 1));
{ }
return encoding.GetString(buffer, 0, bufferDataLength); //end of stream
} if (buffer[0] == '\0')
{
bufferDataLength++; return encoding.GetString(readBuffer.ToArray());
}
//store last char for new line comparison
lastChar = newChar; await readBuffer.WriteAsync(buffer,0,1);
if (bufferDataLength == buffer.Length) //store last char for new line comparison
{ lastChar = (char)buffer[0];
ResizeBuffer(ref buffer, bufferDataLength * 2); }
}
return encoding.GetString(readBuffer.ToArray());
} }
return encoding.GetString(buffer, 0, bufferDataLength);
} }
/// <summary> /// <summary>
...@@ -102,69 +79,46 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -102,69 +79,46 @@ 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(long totalBytesToRead) internal async Task<byte[]> ReadBytesAsync(int bufferSize, long totalBytesToRead)
{ {
int bytesToRead = bufferSize; int bytesToRead = bufferSize;
if (totalBytesToRead < bufferSize) if (totalBytesToRead < bufferSize)
bytesToRead = (int) totalBytesToRead; bytesToRead = (int)totalBytesToRead;
var buffer = new byte[bufferSize];
var buffer = staticBuffer; var bytesRead = 0;
int bytesRead;
var totalBytesRead = 0; var totalBytesRead = 0;
while ((bytesRead = await stream.ReadAsync(buffer, totalBytesRead, bytesToRead)) > 0) using (var outStream = new MemoryStream())
{ {
totalBytesRead += bytesRead; while ((bytesRead += await this.stream.ReadAsync(buffer, 0, bytesToRead)) > 0)
{
if (totalBytesRead == totalBytesToRead) await outStream.WriteAsync(buffer, 0, bytesRead);
break; totalBytesRead += bytesRead;
var remainingBytes = totalBytesToRead - totalBytesRead; if (totalBytesRead == totalBytesToRead)
bytesToRead = Math.Min(bufferSize, (int) remainingBytes); break;
if (totalBytesRead + bytesToRead > buffer.Length) bytesRead = 0;
{ var remainingBytes = (totalBytesToRead - totalBytesRead);
ResizeBuffer(ref buffer, Math.Min(totalBytesToRead, buffer.Length * 2)); bytesToRead = remainingBytes > (long)bufferSize ? bufferSize : (int)remainingBytes;
} }
}
if (totalBytesRead != buffer.Length) return outStream.ToArray();
{
//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);
}
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>
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,12 +6,9 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -6,12 +6,9 @@ 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>
internal class FireFoxProxySettingsManager public class FireFoxProxySettingsManager
{ {
/// <summary> public void AddFirefox()
/// Add Firefox settings.
/// </summary>
internal void AddFirefox()
{ {
try try
{ {
...@@ -19,17 +16,21 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -19,17 +16,21 @@ 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)) return; if (File.Exists(myFfPrefFile))
// We have a pref file so let''s make sure it has the proxy setting {
var myReader = new StreamReader(myFfPrefFile); // We have a pref file so let''s make sure it has the proxy setting
var myPrefContents = myReader.ReadToEnd(); var myReader = new StreamReader(myFfPrefFile);
myReader.Close(); var myPrefContents = myReader.ReadToEnd();
if (!myPrefContents.Contains("user_pref(\"network.proxy.type\", 0);")) return; myReader.Close();
// Add the proxy enable line and write it back to the file if (myPrefContents.Contains("user_pref(\"network.proxy.type\", 0);"))
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)
{ {
...@@ -37,10 +38,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -37,10 +38,7 @@ namespace Titanium.Web.Proxy.Helpers
} }
} }
/// <summary> public void RemoveFirefox()
/// Remove firefox settings.
/// </summary>
internal void RemoveFirefox()
{ {
try try
{ {
...@@ -48,18 +46,20 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -48,18 +46,20 @@ 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)) return; if (File.Exists(myFfPrefFile))
// 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);"))
{ {
// Add the proxy enable line and write it back to the file // We have a pref file so let''s make sure it has the proxy setting
myPrefContents = myPrefContents + "\n\r" + "user_pref(\"network.proxy.type\", 0);"; var myReader = new StreamReader(myFfPrefFile);
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.Linq; using System;
using System.Net; using System.Collections.Generic;
using System.Net.Sockets; using System.Linq;
using System.Net;
namespace Titanium.Web.Proxy.Helpers using System.Text;
{ using System.Threading.Tasks;
internal class NetworkHelper
{ namespace Titanium.Web.Proxy.Helpers
private static int FindProcessIdFromLocalPort(int port, IpVersion ipVersion) {
{ internal class NetworkHelper
var tcpRow = TcpHelper.GetExtendedTcpTable(ipVersion).FirstOrDefault( {
row => row.LocalEndPoint.Port == port); private static int FindProcessIdFromLocalPort(int port, IpVersion ipVersion)
{
return tcpRow?.ProcessId ?? 0; var tcpRow = TcpHelper.GetExtendedTcpTable(ipVersion).FirstOrDefault(
} row => row.LocalEndPoint.Port == port);
internal static int GetProcessIdFromPort(int port, bool ipV6Enabled) return tcpRow?.ProcessId ?? 0;
{ }
var processId = FindProcessIdFromLocalPort(port, IpVersion.Ipv4);
internal static int GetProcessIdFromPort(int port, bool ipV6Enabled)
if (processId > 0 && !ipV6Enabled) {
{ var processId = FindProcessIdFromLocalPort(port, IpVersion.Ipv4);
return processId;
} if (processId > 0 && !ipV6Enabled)
{
return FindProcessIdFromLocalPort(port, IpVersion.Ipv6); return processId;
} }
/// <summary> return FindProcessIdFromLocalPort(port, IpVersion.Ipv6);
/// Adapated from below link }
/// http://stackoverflow.com/questions/11834091/how-to-check-if-localhost
/// </summary> /// <summary>
/// <param name="address"></param> /// Adapated from below link
/// <returns></returns> /// http://stackoverflow.com/questions/11834091/how-to-check-if-localhost
internal static bool IsLocalIpAddress(IPAddress address) /// </summary>
{ /// <param name="address></param>
// get local IP addresses /// <returns></returns>
var localIPs = Dns.GetHostAddresses(Dns.GetHostName()); internal static bool IsLocalIpAddress(IPAddress address)
// test if any host IP equals to any local IP or to localhost {
return IPAddress.IsLoopback(address) || localIPs.Contains(address); try
} {
// get local IP addresses
internal static bool IsLocalIpAddress(string hostName) IPAddress[] localIPs = Dns.GetHostAddresses(Dns.GetHostName());
{
bool isLocalhost = false; // test if any host IP equals to any local IP or to localhost
IPHostEntry localhost = Dns.GetHostEntry("127.0.0.1"); // is localhost
if (hostName == localhost.HostName) if (IPAddress.IsLoopback(address)) return true;
{ // is local address
IPHostEntry hostEntry = Dns.GetHostEntry(hostName); foreach (IPAddress localIP in localIPs)
isLocalhost = hostEntry.AddressList.Any(IPAddress.IsLoopback); {
} if (address.Equals(localIP)) return true;
}
if (!isLocalhost)
{ }
localhost = Dns.GetHostEntry(Dns.GetHostName()); catch { }
return false;
IPAddress ipAddress; }
}
if (IPAddress.TryParse(hostName, out ipAddress)) }
isLocalhost = localhost.AddressList.Any(x => x.Equals(ipAddress));
if (!isLocalhost)
{
try
{
var hostEntry = Dns.GetHostEntry(hostName);
isLocalhost = localhost.AddressList.Any(x => hostEntry.AddressList.Any(x.Equals));
}
catch (SocketException)
{
}
}
}
return isLocalhost;
}
}
}
using System;
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,8 +4,11 @@ using Microsoft.Win32; ...@@ -4,8 +4,11 @@ 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;
// Helper classes for setting system proxy settings /// <summary>
/// Helper classes for setting system proxy settings
/// </summary>
namespace Titanium.Web.Proxy.Helpers namespace Titanium.Web.Proxy.Helpers
{ {
internal enum ProxyProtocolType internal enum ProxyProtocolType
...@@ -36,7 +39,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -36,7 +39,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;
...@@ -125,7 +128,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -125,7 +128,7 @@ namespace Titanium.Web.Proxy.Helpers
} }
/// <summary> /// <summary>
/// Removes all types of proxy settings (both http and https) /// Removes all types of proxy settings (both http & https)
/// </summary> /// </summary>
internal void DisableAllProxy() internal void DisableAllProxy()
{ {
...@@ -181,17 +184,17 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -181,17 +184,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>
...@@ -207,6 +210,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -207,6 +210,7 @@ namespace Titanium.Web.Proxy.Helpers
{ {
reg.SetValue("ProxyServer", string.Empty); reg.SetValue("ProxyServer", string.Empty);
} }
} }
/// <summary> /// <summary>
...@@ -218,4 +222,4 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -218,4 +222,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 href="http://msdn2.microsoft.com/en-us/library/aa366921.aspx"/> /// <see cref="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 href="http://msdn2.microsoft.com/en-us/library/aa366913.aspx"/> /// <see cref="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 href="http://msdn2.microsoft.com/en-us/library/aa365928.aspx"/> /// <see cref="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);
...@@ -93,21 +93,21 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -93,21 +93,21 @@ namespace Titanium.Web.Proxy.Helpers
var ipVersionValue = ipVersion == IpVersion.Ipv4 ? NativeMethods.AfInet : NativeMethods.AfInet6; var ipVersionValue = ipVersion == IpVersion.Ipv4 ? NativeMethods.AfInet : NativeMethods.AfInet6;
if (NativeMethods.GetExtendedTcpTable(tcpTable, ref tcpTableLength, false, ipVersionValue, (int) NativeMethods.TcpTableType.OwnerPidAll, 0) != 0) if (NativeMethods.GetExtendedTcpTable(tcpTable, ref tcpTableLength, false, ipVersionValue, (int)NativeMethods.TcpTableType.OwnerPidAll, 0) != 0)
{ {
try try
{ {
tcpTable = Marshal.AllocHGlobal(tcpTableLength); tcpTable = Marshal.AllocHGlobal(tcpTableLength);
if (NativeMethods.GetExtendedTcpTable(tcpTable, ref tcpTableLength, true, ipVersionValue, (int) NativeMethods.TcpTableType.OwnerPidAll, 0) == 0) if (NativeMethods.GetExtendedTcpTable(tcpTable, ref tcpTableLength, true, ipVersionValue, (int)NativeMethods.TcpTableType.OwnerPidAll, 0) == 0)
{ {
NativeMethods.TcpTable table = (NativeMethods.TcpTable) Marshal.PtrToStructure(tcpTable, typeof(NativeMethods.TcpTable)); NativeMethods.TcpTable table = (NativeMethods.TcpTable)Marshal.PtrToStructure(tcpTable, typeof(NativeMethods.TcpTable));
IntPtr rowPtr = (IntPtr) ((long) tcpTable + Marshal.SizeOf(table.length)); IntPtr rowPtr = (IntPtr)((long)tcpTable + Marshal.SizeOf(table.length));
for (int i = 0; i < table.length; ++i) for (int i = 0; i < table.length; ++i)
{ {
tcpRows.Add(new TcpRow((NativeMethods.TcpRow) Marshal.PtrToStructure(rowPtr, typeof(NativeMethods.TcpRow)))); tcpRows.Add(new TcpRow((NativeMethods.TcpRow)Marshal.PtrToStructure(rowPtr, typeof(NativeMethods.TcpRow))));
rowPtr = (IntPtr) ((long) rowPtr + Marshal.SizeOf(typeof(NativeMethods.TcpRow))); rowPtr = (IntPtr)((long)rowPtr + Marshal.SizeOf(typeof(NativeMethods.TcpRow)));
} }
} }
} }
...@@ -124,7 +124,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -124,7 +124,7 @@ namespace Titanium.Web.Proxy.Helpers
} }
/// <summary> /// <summary>
/// relays the input clientStream to the server at the specified host name and port with the given httpCmd and headers as prefix /// relays the input clientStream to the server at the specified host name & port with the given httpCmd & headers as prefix
/// Usefull for websocket requests /// Usefull for websocket requests
/// </summary> /// </summary>
/// <param name="bufferSize"></param> /// <param name="bufferSize"></param>
...@@ -140,11 +140,10 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -140,11 +140,10 @@ namespace Titanium.Web.Proxy.Helpers
/// <param name="localCertificateSelectionCallback"></param> /// <param name="localCertificateSelectionCallback"></param>
/// <param name="clientStream"></param> /// <param name="clientStream"></param>
/// <param name="tcpConnectionFactory"></param> /// <param name="tcpConnectionFactory"></param>
/// <param name="upStreamEndPoint"></param>
/// <returns></returns> /// <returns></returns>
internal static async Task SendRaw(int bufferSize, int connectionTimeOutSeconds, internal static async Task SendRaw(int bufferSize, int connectionTimeOutSeconds,
string remoteHostName, int remotePort, string httpCmd, Version httpVersion, Dictionary<string, HttpHeader> requestHeaders, string remoteHostName, int remotePort, string httpCmd, Version httpVersion, Dictionary<string, HttpHeader> requestHeaders,
bool isHttps, SslProtocols supportedProtocols, bool isHttps, SslProtocols supportedProtocols,
RemoteCertificateValidationCallback remoteCertificateValidationCallback, LocalCertificateSelectionCallback localCertificateSelectionCallback, RemoteCertificateValidationCallback remoteCertificateValidationCallback, LocalCertificateSelectionCallback localCertificateSelectionCallback,
Stream clientStream, TcpConnectionFactory tcpConnectionFactory, IPEndPoint upStreamEndPoint) Stream clientStream, TcpConnectionFactory tcpConnectionFactory, IPEndPoint upStreamEndPoint)
{ {
...@@ -173,11 +172,11 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -173,11 +172,11 @@ namespace Titanium.Web.Proxy.Helpers
} }
var tcpConnection = await tcpConnectionFactory.CreateClient(bufferSize, connectionTimeOutSeconds, var tcpConnection = await tcpConnectionFactory.CreateClient(bufferSize, connectionTimeOutSeconds,
remoteHostName, remotePort, remoteHostName, remotePort,
httpVersion, isHttps, httpVersion, isHttps,
supportedProtocols, remoteCertificateValidationCallback, localCertificateSelectionCallback, supportedProtocols, remoteCertificateValidationCallback, localCertificateSelectionCallback,
null, null, clientStream, upStreamEndPoint); null, null, clientStream, upStreamEndPoint);
try try
{ {
Stream tunnelStream = tcpConnection.Stream; Stream tunnelStream = tcpConnection.Stream;
...@@ -195,4 +194,4 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -195,4 +194,4 @@ namespace Titanium.Web.Proxy.Helpers
} }
} }
} }
} }
\ No newline at end of file
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;
...@@ -13,29 +15,17 @@ namespace Titanium.Web.Proxy.Http ...@@ -13,29 +15,17 @@ 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; }
/// <summary> public Guid RequestId { get; private set; }
/// 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>
...@@ -47,14 +37,15 @@ namespace Titanium.Web.Proxy.Http ...@@ -47,14 +37,15 @@ namespace Titanium.Web.Proxy.Http
/// <summary> /// <summary>
/// Is Https? /// Is Https?
/// </summary> /// </summary>
public bool IsHttps => Request.RequestUri.Scheme == Uri.UriSchemeHttps; public bool IsHttps => this.Request.RequestUri.Scheme == Uri.UriSchemeHttps;
internal HttpWebClient() internal HttpWebClient()
{ {
RequestId = Guid.NewGuid(); this.RequestId = Guid.NewGuid();
Request = new Request();
Response = new Response(); this.Request = new Request();
this.Response = new Response();
} }
/// <summary> /// <summary>
...@@ -66,69 +57,68 @@ namespace Titanium.Web.Proxy.Http ...@@ -66,69 +57,68 @@ namespace Titanium.Web.Proxy.Http
connection.LastAccess = DateTime.Now; connection.LastAccess = DateTime.Now;
ServerConnection = connection; ServerConnection = connection;
} }
/// <summary> /// <summary>
/// Prepare and send the http(s) request /// Prepare & send the http(s) request
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
internal async Task SendRequest(bool enable100ContinueBehaviour) internal async Task SendRequest(bool enable100ContinueBehaviour)
{ {
var stream = ServerConnection.Stream; Stream stream = ServerConnection.Stream;
var requestLines = new StringBuilder(); StringBuilder requestLines = new StringBuilder();
//prepare the request & headers //prepare the request & headers
if ((ServerConnection.UpStreamHttpProxy != null && ServerConnection.IsHttps == false) || (ServerConnection.UpStreamHttpsProxy != null && ServerConnection.IsHttps)) if ((ServerConnection.UpStreamHttpProxy != null && ServerConnection.IsHttps == false) || (ServerConnection.UpStreamHttpsProxy != null && ServerConnection.IsHttps == true))
{ {
requestLines.AppendLine($"{Request.Method} {Request.RequestUri.AbsoluteUri} HTTP/{Request.HttpVersion.Major}.{Request.HttpVersion.Minor}"); requestLines.AppendLine(string.Join(" ", this.Request.Method, this.Request.RequestUri.AbsoluteUri, $"HTTP/{this.Request.HttpVersion.Major}.{this.Request.HttpVersion.Minor}"));
} }
else else
{ {
requestLines.AppendLine($"{Request.Method} {Request.RequestUri.PathAndQuery} HTTP/{Request.HttpVersion.Major}.{Request.HttpVersion.Minor}"); requestLines.AppendLine(string.Join(" ", this.Request.Method, this.Request.RequestUri.PathAndQuery, $"HTTP/{this.Request.HttpVersion.Major}.{this.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( requestLines.AppendLine("Proxy-Authorization" + ": Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes(ServerConnection.UpStreamHttpProxy.UserName + ":" + ServerConnection.UpStreamHttpProxy.Password)));
$"{ServerConnection.UpStreamHttpProxy.UserName}:{ServerConnection.UpStreamHttpProxy.Password}")));
} }
//write request headers //write request headers
foreach (var headerItem in Request.RequestHeaders) foreach (var headerItem in this.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 Request.NonUniqueRequestHeaders) foreach (var headerItem in this.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();
var request = requestLines.ToString(); string request = requestLines.ToString();
var requestBytes = Encoding.ASCII.GetBytes(request); byte[] 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 (Request.ExpectContinue) if (this.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();
...@@ -136,15 +126,15 @@ namespace Titanium.Web.Proxy.Http ...@@ -136,15 +126,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.Equals("continue", StringComparison.CurrentCultureIgnoreCase)) && responseStatusDescription.ToLower().Equals("continue"))
{ {
Request.Is100Continue = true; this.Request.Is100Continue = true;
await ServerConnection.StreamReader.ReadLineAsync(); await ServerConnection.StreamReader.ReadLineAsync();
} }
else if (responseStatusCode.Equals("417") else if (responseStatusCode.Equals("417")
&& responseStatusDescription.Equals("expectation failed", StringComparison.CurrentCultureIgnoreCase)) && responseStatusDescription.ToLower().Equals("expectation failed"))
{ {
Request.ExpectationFailed = true; this.Request.ExpectationFailed = true;
await ServerConnection.StreamReader.ReadLineAsync(); await ServerConnection.StreamReader.ReadLineAsync();
} }
} }
...@@ -152,13 +142,13 @@ namespace Titanium.Web.Proxy.Http ...@@ -152,13 +142,13 @@ namespace Titanium.Web.Proxy.Http
} }
/// <summary> /// <summary>
/// Receive and parse the http response from server /// Receive & 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 (Response.ResponseStatusCode != null) return; if (this.Response.ResponseStatusCode != null) return;
var httpResult = (await ServerConnection.StreamReader.ReadLineAsync()).Split(ProxyConstants.SpaceSplit, 3); var httpResult = (await ServerConnection.StreamReader.ReadLineAsync()).Split(ProxyConstants.SpaceSplit, 3);
...@@ -171,33 +161,33 @@ namespace Titanium.Web.Proxy.Http ...@@ -171,33 +161,33 @@ 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 = new Version(1, 1);
if (0 == string.CompareOrdinal(httpVersion, "http/1.0")) if (httpVersion == "http/1.0")
{ {
version = new Version(1, 0); version = new Version(1, 0);
} }
Response.HttpVersion = version; this.Response.HttpVersion = version;
Response.ResponseStatusCode = httpResult[1].Trim(); this.Response.ResponseStatusCode = httpResult[1].Trim();
Response.ResponseStatusDescription = httpResult[2].Trim(); this.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 (Response.ResponseStatusCode.Equals("100") if (this.Response.ResponseStatusCode.Equals("100")
&& Response.ResponseStatusDescription.Equals("continue", StringComparison.CurrentCultureIgnoreCase)) && this.Response.ResponseStatusDescription.ToLower().Equals("continue"))
{ {
//Read the next line after 100-continue //Read the next line after 100-continue
Response.Is100Continue = true; this.Response.Is100Continue = true;
Response.ResponseStatusCode = null; this.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 (Response.ResponseStatusCode.Equals("417") else if (this.Response.ResponseStatusCode.Equals("417")
&& Response.ResponseStatusDescription.Equals("expectation failed", StringComparison.CurrentCultureIgnoreCase)) && this.Response.ResponseStatusDescription.ToLower().Equals("expectation failed"))
{ {
//read next line after expectation failed response //read next line after expectation failed response
Response.ExpectationFailed = true; this.Response.ExpectationFailed = true;
Response.ResponseStatusCode = null; this.Response.ResponseStatusCode = null;
await ServerConnection.StreamReader.ReadLineAsync(); await ServerConnection.StreamReader.ReadLineAsync();
//now receive response //now receive response
await ReceiveResponse(); await ReceiveResponse();
...@@ -236,4 +226,4 @@ namespace Titanium.Web.Proxy.Http ...@@ -236,4 +226,4 @@ namespace Titanium.Web.Proxy.Http
} }
} }
} }
} }
\ No newline at end of file
...@@ -34,7 +34,12 @@ namespace Titanium.Web.Proxy.Http ...@@ -34,7 +34,12 @@ namespace Titanium.Web.Proxy.Http
get get
{ {
var hasHeader = RequestHeaders.ContainsKey("host"); var hasHeader = RequestHeaders.ContainsKey("host");
return hasHeader ? RequestHeaders["host"].Value : null; if (hasHeader)
{
return RequestHeaders["host"].Value;
}
return null;
} }
set set
{ {
...@@ -47,6 +52,7 @@ namespace Titanium.Web.Proxy.Http ...@@ -47,6 +52,7 @@ namespace Titanium.Web.Proxy.Http
{ {
RequestHeaders.Add("Host", new HttpHeader("Host", value)); RequestHeaders.Add("Host", new HttpHeader("Host", value));
} }
} }
} }
...@@ -118,7 +124,9 @@ namespace Titanium.Web.Proxy.Http ...@@ -118,7 +124,9 @@ namespace Titanium.Web.Proxy.Http
{ {
RequestHeaders.Remove("content-length"); RequestHeaders.Remove("content-length");
} }
} }
} }
} }
...@@ -146,13 +154,14 @@ namespace Titanium.Web.Proxy.Http ...@@ -146,13 +154,14 @@ namespace Titanium.Web.Proxy.Http
if (hasHeader) if (hasHeader)
{ {
var header = RequestHeaders["content-type"]; var header = RequestHeaders["content-type"];
header.Value = value; header.Value = value.ToString();
} }
else else
{ {
RequestHeaders.Add("content-type", new HttpHeader("content-type", value)); RequestHeaders.Add("content-type", new HttpHeader("content-type", value.ToString()));
} }
} }
} }
/// <summary> /// <summary>
...@@ -168,7 +177,7 @@ namespace Titanium.Web.Proxy.Http ...@@ -168,7 +177,7 @@ namespace Titanium.Web.Proxy.Http
{ {
var header = RequestHeaders["transfer-encoding"]; var header = RequestHeaders["transfer-encoding"];
return header.Value.ContainsIgnoreCase("chunked"); return header.Value.ToLower().Contains("chunked");
} }
return false; return false;
...@@ -210,10 +219,14 @@ namespace Titanium.Web.Proxy.Http ...@@ -210,10 +219,14 @@ namespace Titanium.Web.Proxy.Http
{ {
var hasHeader = RequestHeaders.ContainsKey("expect"); var hasHeader = RequestHeaders.ContainsKey("expect");
if (!hasHeader) return false; if (hasHeader)
var header = RequestHeaders["expect"]; {
var header = RequestHeaders["expect"];
return header.Value.Equals("100-continue");
}
return header.Value.Equals("100-continue"); return false;
} }
} }
...@@ -222,12 +235,12 @@ namespace Titanium.Web.Proxy.Http ...@@ -222,12 +235,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; }
...@@ -262,7 +275,13 @@ namespace Titanium.Web.Proxy.Http ...@@ -262,7 +275,13 @@ namespace Titanium.Web.Proxy.Http
var header = RequestHeaders["upgrade"]; var header = RequestHeaders["upgrade"];
return header.Value.Equals("websocket", StringComparison.CurrentCultureIgnoreCase); if (header.Value.ToLower() == "websocket")
{
return true;
}
return false;
} }
} }
...@@ -286,13 +305,11 @@ namespace Titanium.Web.Proxy.Http ...@@ -286,13 +305,11 @@ 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,19 +12,12 @@ namespace Titanium.Web.Proxy.Http ...@@ -12,19 +12,12 @@ 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
...@@ -33,10 +26,14 @@ namespace Titanium.Web.Proxy.Http ...@@ -33,10 +26,14 @@ namespace Titanium.Web.Proxy.Http
{ {
var hasHeader = ResponseHeaders.ContainsKey("content-encoding"); var hasHeader = ResponseHeaders.ContainsKey("content-encoding");
if (!hasHeader) return null; if (hasHeader)
var header = ResponseHeaders["content-encoding"]; {
var header = ResponseHeaders["content-encoding"];
return header.Value.Trim();
}
return header.Value.Trim(); return null;
} }
} }
...@@ -55,13 +52,14 @@ namespace Titanium.Web.Proxy.Http ...@@ -55,13 +52,14 @@ namespace Titanium.Web.Proxy.Http
{ {
var header = ResponseHeaders["connection"]; var header = ResponseHeaders["connection"];
if (header.Value.ContainsIgnoreCase("close")) if (header.Value.ToLower().Contains("close"))
{ {
return false; return false;
} }
} }
return true; return true;
} }
} }
...@@ -82,6 +80,7 @@ namespace Titanium.Web.Proxy.Http ...@@ -82,6 +80,7 @@ namespace Titanium.Web.Proxy.Http
} }
return null; return null;
} }
} }
...@@ -109,6 +108,7 @@ namespace Titanium.Web.Proxy.Http ...@@ -109,6 +108,7 @@ namespace Titanium.Web.Proxy.Http
} }
return -1; return -1;
} }
set set
{ {
...@@ -151,13 +151,14 @@ namespace Titanium.Web.Proxy.Http ...@@ -151,13 +151,14 @@ namespace Titanium.Web.Proxy.Http
{ {
var header = ResponseHeaders["transfer-encoding"]; var header = ResponseHeaders["transfer-encoding"];
if (header.Value.ContainsIgnoreCase("chunked")) if (header.Value.ToLower().Contains("chunked"))
{ {
return true; return true;
} }
} }
return false; return false;
} }
set set
{ {
...@@ -183,7 +184,9 @@ namespace Titanium.Web.Proxy.Http ...@@ -183,7 +184,9 @@ namespace Titanium.Web.Proxy.Http
{ {
ResponseHeaders.Remove("transfer-encoding"); ResponseHeaders.Remove("transfer-encoding");
} }
} }
} }
} }
...@@ -226,13 +229,11 @@ namespace Titanium.Web.Proxy.Http ...@@ -226,13 +229,11 @@ 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()
{ {
ResponseHeaders = new Dictionary<string, HttpHeader>(StringComparer.OrdinalIgnoreCase); this.ResponseHeaders = new Dictionary<string, HttpHeader>(StringComparer.OrdinalIgnoreCase);
NonUniqueResponseHeaders = new Dictionary<string, List<HttpHeader>>(StringComparer.OrdinalIgnoreCase); this.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,9 +5,6 @@ ...@@ -5,9 +5,6 @@
/// </summary> /// </summary>
public sealed class OkResponse : Response public sealed class OkResponse : Response
{ {
/// <summary>
/// Constructor.
/// </summary>
public OkResponse() public OkResponse()
{ {
ResponseStatusCode = "200"; ResponseStatusCode = "200";
......
...@@ -5,9 +5,6 @@ ...@@ -5,9 +5,6 @@
/// </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; using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
using System.Net; using System.Net;
using System.Net.Sockets; using System.Net.Sockets;
using System.Security.Cryptography.X509Certificates;
using System.Text.RegularExpressions;
namespace Titanium.Web.Proxy.Models namespace Titanium.Web.Proxy.Models
{ {
...@@ -13,42 +9,22 @@ namespace Titanium.Web.Proxy.Models ...@@ -13,42 +9,22 @@ namespace Titanium.Web.Proxy.Models
/// </summary> /// </summary>
public abstract class ProxyEndPoint public abstract class ProxyEndPoint
{ {
/// <summary> public ProxyEndPoint(IPAddress IpAddress, int Port, bool EnableSsl)
/// Constructor.
/// </summary>
/// <param name="ipAddress"></param>
/// <param name="port"></param>
/// <param name="enableSsl"></param>
protected ProxyEndPoint(IPAddress ipAddress, int port, bool enableSsl)
{ {
IpAddress = ipAddress; this.IpAddress = IpAddress;
Port = port; this.Port = Port;
EnableSsl = enableSsl; this.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; }
/// <summary> public bool IpV6Enabled => IpAddress == IPAddress.IPv6Any
/// Is IPv6 enabled? || IpAddress == IPAddress.IPv6Loopback
/// </summary> || IpAddress == IPAddress.IPv6None;
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>
...@@ -57,61 +33,15 @@ namespace Titanium.Web.Proxy.Models ...@@ -57,61 +33,15 @@ 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; }
/// <summary> public List<string> ExcludedHttpsHostNameRegex { get; set; }
/// 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();
}
}
/// <summary> public ExplicitProxyEndPoint(IPAddress IpAddress, int Port, bool EnableSsl)
/// Generic certificate to use for SSL decryption. : base(IpAddress, Port, EnableSsl)
/// </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)
{ {
} }
} }
...@@ -121,22 +51,18 @@ namespace Titanium.Web.Proxy.Models ...@@ -121,22 +51,18 @@ namespace Titanium.Web.Proxy.Models
/// </summary> /// </summary>
public class TransparentProxyEndPoint : ProxyEndPoint public class TransparentProxyEndPoint : ProxyEndPoint
{ {
/// <summary> //Name of the Certificate need to be sent (same as the hostname we want to proxy)
/// 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
/// This is valid only when UseServerNameIndication is set to false
/// </summary>
public string GenericCertificateName { get; set; } public string GenericCertificateName { get; set; }
/// <summary>
/// Constructor. // public bool UseServerNameIndication { get; set; }
/// </summary>
/// <param name="ipAddress"></param> public TransparentProxyEndPoint(IPAddress IpAddress, int Port, bool EnableSsl)
/// <param name="port"></param> : base(IpAddress, Port, EnableSsl)
/// <param name="enableSsl"></param>
public TransparentProxyEndPoint(IPAddress ipAddress, int port, bool enableSsl)
: base(ipAddress, port, enableSsl)
{ {
GenericCertificateName = "localhost"; this.GenericCertificateName = "localhost";
} }
} }
} }
...@@ -8,63 +8,41 @@ namespace Titanium.Web.Proxy.Models ...@@ -8,63 +8,41 @@ 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; }
/// <summary> public string UserName {
/// Bypass this proxy for connections to localhost? get { return UseDefaultCredentials ? DefaultCredentials.Value.UserName : userName; }
/// </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
{ {
...@@ -9,12 +7,6 @@ namespace Titanium.Web.Proxy.Models ...@@ -9,12 +7,6 @@ namespace Titanium.Web.Proxy.Models
/// </summary> /// </summary>
public class HttpHeader public class HttpHeader
{ {
/// <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))
...@@ -26,14 +18,7 @@ namespace Titanium.Web.Proxy.Models ...@@ -26,14 +18,7 @@ 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>
...@@ -42,14 +27,7 @@ namespace Titanium.Web.Proxy.Models ...@@ -42,14 +27,7 @@ namespace Titanium.Web.Proxy.Models
/// <returns></returns> /// <returns></returns>
public override string ToString() public override string ToString()
{ {
return $"{Name}: {Value}"; return string.Format("{0}: {1}", 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,5 +20,6 @@ namespace Titanium.Web.Proxy.Network ...@@ -20,5 +20,6 @@ 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,230 +5,116 @@ using System.Threading.Tasks; ...@@ -5,230 +5,116 @@ 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 internal class CertificateManager : IDisposable
{ {
public CertificateEngine Engine private CertificateMaker certEngine = null;
{
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 readonly Action<Exception> exceptionFunc; private Action<Exception> exceptionFunc;
internal string Issuer internal string Issuer { get; private set; }
{ internal string RootCertificateName { get; private set; }
get { return issuer ?? defaultRootCertificateIssuer; }
set
{
issuer = value;
ClearRootCertificate();
}
}
internal string RootCertificateName internal X509Certificate2 rootCertificate { get; set; }
{
get { return rootCertificateName ?? defaultRootRootCertificateName; }
set
{
rootCertificateName = value;
ClearRootCertificate();
}
}
internal X509Certificate2 RootCertificate internal CertificateManager(string issuer, string rootCertificateName, Action<Exception> exceptionFunc)
{ {
get { return rootCertificate; } this.exceptionFunc = exceptionFunc;
set
{
ClearRootCertificate();
rootCertificate = value;
}
}
/// <summary>
/// Is the root certificate used by this proxy is valid?
/// </summary>
internal bool CertValidated => RootCertificate != null;
certEngine = new CertificateMaker();
internal CertificateManager(Action<Exception> exceptionFunc) Issuer = issuer;
{ RootCertificateName = rootCertificateName;
this.exceptionFunc = exceptionFunc;
Engine = CertificateEngine.DefaultWindows;
certificateCache = new ConcurrentDictionary<string, CachedCertificate>(); certificateCache = new ConcurrentDictionary<string, CachedCertificate>();
} }
private void ClearRootCertificate() internal X509Certificate2 GetRootCertificate()
{
certificateCache.Clear();
rootCertificate = null;
}
private string GetRootCertificatePath()
{ {
var assemblyLocation = System.Reflection.Assembly.GetExecutingAssembly().Location; var fileName = Path.Combine(System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), "rootCert.pfx");
// dynamically loaded assemblies returns string.Empty location
if (assemblyLocation == string.Empty)
{
assemblyLocation = System.Reflection.Assembly.GetEntryAssembly().Location;
}
var path = Path.GetDirectoryName(assemblyLocation); if (File.Exists(fileName))
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); try
return null; {
return new X509Certificate2(fileName, string.Empty, X509KeyStorageFlags.Exportable);
}
catch (Exception e)
{
exceptionFunc(e);
return null;
}
} }
return null;
} }
/// <summary> /// <summary>
/// Attempts to create a RootCertificate /// Attempts to create a RootCertificate
/// </summary> /// </summary>
/// <returns>true if succeeded, else false</returns> /// <returns>true if succeeded, else false</returns>
internal bool CreateTrustedRootCertificate() internal bool CreateTrustedRootCertificate()
{ {
if (RootCertificate == null)
{
RootCertificate = LoadRootCertificate();
}
if (RootCertificate != null) rootCertificate = GetRootCertificate();
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 (RootCertificate != null)
{ {
try try
{ {
var fileName = GetRootCertificatePath(); var fileName = Path.Combine(System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), "rootCert.pfx");
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>
internal void TrustRootCertificate()
{
//current user
TrustRootCertificate(StoreLocation.CurrentUser);
//current system
TrustRootCertificate(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)
{ {
if (certificateCache.ContainsKey(certificateName)) try
{ {
var cached = certificateCache[certificateName]; if (certificateCache.ContainsKey(certificateName))
cached.LastAccess = DateTime.Now; {
return cached.Certificate; var cached = certificateCache[certificateName];
cached.LastAccess = DateTime.Now;
return cached.Certificate;
}
} }
catch
{
}
X509Certificate2 certificate = null; X509Certificate2 certificate = null;
lock (string.Intern(certificateName)) lock (string.Intern(certificateName))
{ {
...@@ -236,20 +122,15 @@ namespace Titanium.Web.Proxy.Network ...@@ -236,20 +122,15 @@ namespace Titanium.Web.Proxy.Network
{ {
try try
{ {
if (!isRootCertificate && RootCertificate == null) certificate = certEngine.MakeCertificate(certificateName, isRootCertificate, rootCertificate);
{
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
...@@ -263,7 +144,10 @@ namespace Titanium.Web.Proxy.Network ...@@ -263,7 +144,10 @@ namespace Titanium.Web.Proxy.Network
} }
} }
return certificate; return certificate;
} }
/// <summary> /// <summary>
...@@ -282,57 +166,56 @@ namespace Titanium.Web.Proxy.Network ...@@ -282,57 +166,56 @@ namespace Titanium.Web.Proxy.Network
clearCertificates = true; clearCertificates = true;
while (clearCertificates) while (clearCertificates)
{ {
var cutOff = DateTime.Now.AddMinutes(-1 * certificateCacheTimeOutMinutes);
var outdated = certificateCache try
.Where(x => x.Value.LastAccess < cutOff) {
.ToList(); var cutOff = DateTime.Now.AddMinutes(-1 * certificateCacheTimeOutMinutes);
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);
} }
} }
/// <summary> internal bool TrustRootCertificate()
/// 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)
{ {
exceptionFunc( return false;
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);
var x509PersonalStore = new X509Store(StoreName.My, StoreLocation.CurrentUser);
x509RootStore.Open(OpenFlags.ReadWrite); x509RootStore.Open(OpenFlags.ReadWrite);
x509PersonalStore.Open(OpenFlags.ReadWrite); x509PersonalStore.Open(OpenFlags.ReadWrite);
x509RootStore.Add(RootCertificate); try
x509PersonalStore.Add(RootCertificate); {
} x509RootStore.Add(rootCertificate);
catch (Exception e) x509PersonalStore.Add(rootCertificate);
{ }
exceptionFunc( finally
new Exception("Failed to make system trust root certificate " {
+ $" for {storeLocation} store location. You may need admin rights.", e)); x509RootStore.Close();
x509PersonalStore.Close();
}
return true;
} }
finally catch
{ {
x509RootStore.Close(); return false;
x509PersonalStore.Close();
} }
} }
...@@ -340,4 +223,4 @@ namespace Titanium.Web.Proxy.Network ...@@ -340,4 +223,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>
internal class ProxyClient public class ProxyClient
{ {
/// <summary> /// <summary>
/// TcpClient used to communicate with client /// TcpClient used to communicate with client
...@@ -28,5 +28,6 @@ namespace Titanium.Web.Proxy.Network ...@@ -28,5 +28,6 @@ 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,16 +7,15 @@ using Titanium.Web.Proxy.Models; ...@@ -7,16 +7,15 @@ 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 and port /// An object that holds TcpConnection to a particular server & port
/// </summary> /// </summary>
internal class TcpConnection : IDisposable public 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; }
...@@ -48,17 +47,18 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -48,17 +47,18 @@ 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();
StreamReader?.Dispose();
TcpClient.LingerState = new LingerOption(true, 0); TcpClient.LingerState = new LingerOption(true, 0);
TcpClient.Client.Shutdown(SocketShutdown.Both);
TcpClient.Client.Close();
TcpClient.Client.Dispose();
TcpClient.Close(); TcpClient.Close();
} }
} }
} }
using System; using System;
using System.Net.Sockets; using System.Net.Sockets;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.IO; 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.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;
using System.Net;
/// <summary>
/// <summary> /// A class that manages Tcp Connection to server used by this proxy server
/// A class that manages Tcp Connection to server used by this proxy server /// </summary>
/// </summary> internal class TcpConnectionFactory
internal class TcpConnectionFactory {
{
/// <summary> /// <summary>
/// Creates a TCP connection to server /// Creates a TCP connection to server
/// </summary> /// </summary>
/// <param name="bufferSize"></param> /// <param name="bufferSize"></param>
/// <param name="connectionTimeOutSeconds"></param> /// <param name="connectionTimeOutSeconds"></param>
/// <param name="remoteHostName"></param> /// <param name="remoteHostName"></param>
/// <param name="httpVersion"></param> /// <param name="httpCmd"></param>
/// <param name="isHttps"></param> /// <param name="httpVersion"></param>
/// <param name="remotePort"></param> /// <param name="isHttps"></param>
/// <param name="supportedSslProtocols"></param> /// <param name="remotePort"></param>
/// <param name="remoteCertificateValidationCallback"></param> /// <param name="supportedSslProtocols"></param>
/// <param name="localCertificateSelectionCallback"></param> /// <param name="remoteCertificateValidationCallback"></param>
/// <param name="externalHttpProxy"></param> /// <param name="localCertificateSelectionCallback"></param>
/// <param name="externalHttpsProxy"></param> /// <param name="externalHttpProxy"></param>
/// <param name="clientStream"></param> /// <param name="externalHttpsProxy"></param>
/// <param name="upStreamEndPoint"></param> /// <param name="clientStream"></param>
/// <returns></returns> /// <param name="upStreamEndPoint"></param>
internal async Task<TcpConnection> CreateClient(int bufferSize, int connectionTimeOutSeconds, /// <returns></returns>
string remoteHostName, int remotePort, Version httpVersion, internal async Task<TcpConnection> CreateClient(int bufferSize, int connectionTimeOutSeconds,
bool isHttps, SslProtocols supportedSslProtocols, string remoteHostName, int remotePort, Version httpVersion,
RemoteCertificateValidationCallback remoteCertificateValidationCallback, LocalCertificateSelectionCallback localCertificateSelectionCallback, bool isHttps, SslProtocols supportedSslProtocols,
ExternalProxy externalHttpProxy, ExternalProxy externalHttpsProxy, RemoteCertificateValidationCallback remoteCertificateValidationCallback, LocalCertificateSelectionCallback localCertificateSelectionCallback,
Stream clientStream, IPEndPoint upStreamEndPoint) ExternalProxy externalHttpProxy, ExternalProxy externalHttpsProxy,
{ Stream clientStream, EndPoint upStreamEndPoint)
TcpClient client; {
CustomBufferedStream stream; TcpClient client;
Stream stream;
bool isLocalhost = (externalHttpsProxy == null && externalHttpProxy == null) ? false : NetworkHelper.IsLocalIpAddress(remoteHostName);
if (isHttps)
bool useHttpsProxy = externalHttpsProxy != null && externalHttpsProxy.HostName != remoteHostName && (externalHttpsProxy.BypassForLocalhost && !isLocalhost); {
bool useHttpProxy = externalHttpProxy != null && externalHttpProxy.HostName != remoteHostName && (externalHttpProxy.BypassForLocalhost && !isLocalhost); SslStream sslStream = null;
if (isHttps) //If this proxy uses another external proxy then create a tunnel request for HTTPS connections
{ if (externalHttpsProxy != null && externalHttpsProxy.HostName != remoteHostName)
SslStream sslStream = null; {
client = new TcpClient();
//If this proxy uses another external proxy then create a tunnel request for HTTPS connections client.Client.Bind(upStreamEndPoint);
if (useHttpsProxy) client.Client.Connect(externalHttpsProxy.HostName, externalHttpsProxy.Port);
{ stream = client.GetStream();
client = new TcpClient(upStreamEndPoint);
await client.ConnectAsync(externalHttpsProxy.HostName, externalHttpsProxy.Port); using (var writer = new StreamWriter(stream, Encoding.ASCII, bufferSize, true) { NewLine = ProxyConstants.NewLine })
stream = new CustomBufferedStream(client.GetStream(), bufferSize); {
await writer.WriteLineAsync($"CONNECT {remoteHostName}:{remotePort} HTTP/{httpVersion}");
using (var writer = new StreamWriter(stream, Encoding.ASCII, bufferSize, true) {NewLine = ProxyConstants.NewLine}) await writer.WriteLineAsync($"Host: {remoteHostName}:{remotePort}");
{ await writer.WriteLineAsync("Connection: Keep-Alive");
await writer.WriteLineAsync($"CONNECT {remoteHostName}:{remotePort} HTTP/{httpVersion}");
await writer.WriteLineAsync($"Host: {remoteHostName}:{remotePort}"); if (!string.IsNullOrEmpty(externalHttpsProxy.UserName) && externalHttpsProxy.Password != null)
await writer.WriteLineAsync("Connection: Keep-Alive"); {
await writer.WriteLineAsync("Proxy-Connection: keep-alive");
if (!string.IsNullOrEmpty(externalHttpsProxy.UserName) && externalHttpsProxy.Password != null) await writer.WriteLineAsync("Proxy-Authorization" + ": Basic " + Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(externalHttpsProxy.UserName + ":" + externalHttpsProxy.Password)));
{ }
await writer.WriteLineAsync("Proxy-Connection: keep-alive"); await writer.WriteLineAsync();
await writer.WriteLineAsync("Proxy-Authorization" + ": Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes(externalHttpsProxy.UserName + ":" + externalHttpsProxy.Password))); await writer.FlushAsync();
} writer.Close();
await writer.WriteLineAsync(); }
await writer.FlushAsync();
writer.Close(); using (var reader = new CustomBinaryReader(stream))
} {
var result = await reader.ReadLineAsync();
using (var reader = new CustomBinaryReader(stream, bufferSize))
{
var result = await reader.ReadLineAsync(); if (!new string[] { "200 OK", "connection established" }.Any(s => result.ToLower().Contains(s.ToLower())))
{
if (!new[] {"200 OK", "connection established"}.Any(s => result.ContainsIgnoreCase(s))) 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.Client.Bind(upStreamEndPoint);
client = new TcpClient(upStreamEndPoint); client.Client.Connect(remoteHostName, remotePort);
await client.ConnectAsync(remoteHostName, remotePort); stream = client.GetStream();
stream = new CustomBufferedStream(client.GetStream(), bufferSize); }
}
try
try {
{ sslStream = new SslStream(stream, true, remoteCertificateValidationCallback,
sslStream = new SslStream(stream, true, remoteCertificateValidationCallback, localCertificateSelectionCallback);
localCertificateSelectionCallback);
await sslStream.AuthenticateAsClientAsync(remoteHostName, null, supportedSslProtocols, false);
await sslStream.AuthenticateAsClientAsync(remoteHostName, null, supportedSslProtocols, false);
stream = sslStream;
stream = new CustomBufferedStream(sslStream, bufferSize); }
} catch
catch {
{ sslStream?.Dispose();
sslStream?.Dispose();
throw;
throw; }
} }
} else
else {
{ if (externalHttpProxy != null && externalHttpProxy.HostName != remoteHostName)
if (useHttpProxy) {
{ client = new TcpClient();
client = new TcpClient(upStreamEndPoint); client.Client.Bind(upStreamEndPoint);
await client.ConnectAsync(externalHttpProxy.HostName, externalHttpProxy.Port); client.Client.Connect(externalHttpProxy.HostName, externalHttpProxy.Port);
stream = new CustomBufferedStream(client.GetStream(), bufferSize); stream = client.GetStream();
} }
else else
{ {
client = new TcpClient(upStreamEndPoint); client = new TcpClient();
await client.ConnectAsync(remoteHostName, remotePort); client.Client.Bind(upStreamEndPoint);
stream = new CustomBufferedStream(client.GetStream(), bufferSize); client.Client.Connect(remoteHostName, remotePort);
} stream = client.GetStream();
} }
}
client.ReceiveTimeout = connectionTimeOutSeconds * 1000;
client.SendTimeout = connectionTimeOutSeconds * 1000; client.ReceiveTimeout = connectionTimeOutSeconds * 1000;
client.SendTimeout = connectionTimeOutSeconds * 1000;
stream.ReadTimeout = connectionTimeOutSeconds * 1000;
stream.WriteTimeout = connectionTimeOutSeconds * 1000; stream.ReadTimeout = connectionTimeOutSeconds * 1000;
stream.WriteTimeout = connectionTimeOutSeconds * 1000;
return new TcpConnection
{ return new TcpConnection()
UpStreamHttpProxy = externalHttpProxy, {
UpStreamHttpsProxy = externalHttpsProxy, UpStreamHttpProxy = externalHttpProxy,
HostName = remoteHostName, UpStreamHttpsProxy = externalHttpsProxy,
Port = remotePort, HostName = remoteHostName,
IsHttps = isHttps, Port = remotePort,
TcpClient = client, IsHttps = isHttps,
StreamReader = new CustomBinaryReader(stream, bufferSize), TcpClient = client,
Stream = stream, StreamReader = new CustomBinaryReader(stream),
Version = httpVersion Stream = stream,
}; Version = httpVersion
} };
} }
} }
}
\ No newline at end of file
...@@ -5,7 +5,7 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -5,7 +5,7 @@ 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 href="http://msdn2.microsoft.com/en-us/library/aa366913.aspx"/> /// <see cref="http://msdn2.microsoft.com/en-us/library/aa366913.aspx"/>
/// </summary> /// </summary>
internal class TcpRow internal class TcpRow
{ {
...@@ -13,7 +13,7 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -13,7 +13,7 @@ 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>
internal TcpRow(NativeMethods.TcpRow tcpRow) public TcpRow(NativeMethods.TcpRow tcpRow)
{ {
ProcessId = tcpRow.owningPid; ProcessId = tcpRow.owningPid;
...@@ -21,24 +21,24 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -21,24 +21,24 @@ namespace Titanium.Web.Proxy.Network.Tcp
long localAddress = tcpRow.localAddr; long localAddress = tcpRow.localAddr;
LocalEndPoint = new IPEndPoint(localAddress, localPort); LocalEndPoint = new IPEndPoint(localAddress, localPort);
int remotePort = (tcpRow.remotePort1 << 8) + (tcpRow.remotePort2) + (tcpRow.remotePort3 << 24) + (tcpRow.remotePort4 << 16); int remotePort = (tcpRow.remotePort1 << 8) + (tcpRow.remotePort2) + (tcpRow.remotePort3 << 24) + (tcpRow.remotePort4 << 16);
long remoteAddress = tcpRow.remoteAddr; long remoteAddress = tcpRow.remoteAddr;
RemoteEndPoint = new IPEndPoint(remoteAddress, remotePort); RemoteEndPoint = new IPEndPoint(remoteAddress, remotePort);
} }
/// <summary> /// <summary>
/// Gets the local end point. /// Gets the local end point.
/// </summary> /// </summary>
internal IPEndPoint LocalEndPoint { get; } public IPEndPoint LocalEndPoint { get; private set; }
/// <summary> /// <summary>
/// Gets the remote end point. /// Gets the remote end point.
/// </summary> /// </summary>
internal IPEndPoint RemoteEndPoint { get; } public IPEndPoint RemoteEndPoint { get; private set; }
/// <summary> /// <summary>
/// Gets the process identifier. /// Gets the process identifier.
/// </summary> /// </summary>
internal int ProcessId { get; } public int ProcessId { get; private set; }
} }
} }
\ No newline at end of file
...@@ -6,9 +6,7 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -6,9 +6,7 @@ namespace Titanium.Web.Proxy.Network.Tcp
/// <summary> /// <summary>
/// Represents collection of TcpRows /// Represents collection of TcpRows
/// </summary> /// </summary>
/// <seealso> /// <seealso cref="System.Collections.Generic.IEnumerable{Proxy.Tcp.TcpRow}" />
/// <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;
...@@ -17,7 +15,7 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -17,7 +15,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>
internal TcpTable(IEnumerable<TcpRow> tcpRows) public TcpTable(IEnumerable<TcpRow> tcpRows)
{ {
this.tcpRows = tcpRows; this.tcpRows = tcpRows;
} }
...@@ -25,7 +23,7 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -25,7 +23,7 @@ namespace Titanium.Web.Proxy.Network.Tcp
/// <summary> /// <summary>
/// Gets the TCP rows. /// Gets the TCP rows.
/// </summary> /// </summary>
internal IEnumerable<TcpRow> TcpRows => tcpRows; public IEnumerable<TcpRow> TcpRows => tcpRows;
/// <summary> /// <summary>
/// Returns an enumerator that iterates through the collection. /// Returns an enumerator that iterates through the collection.
...@@ -45,4 +43,4 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -45,4 +43,4 @@ namespace Titanium.Web.Proxy.Network.Tcp
return GetEnumerator(); return GetEnumerator();
} }
} }
} }
\ No newline at end of file
...@@ -14,12 +14,7 @@ using System.Runtime.InteropServices; ...@@ -14,12 +14,7 @@ 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, PublicKey=" + [assembly: InternalsVisibleTo("Titanium.Web.Proxy.UnitTests")]
"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,108 +12,87 @@ namespace Titanium.Web.Proxy ...@@ -12,108 +12,87 @@ 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 HttpHeader[] ?? Headers.ToArray();
try try
{ {
if (httpHeaders.All(t => t.Name != "Proxy-Authorization")) if (!httpHeaders.Where(t => t.Name == "Proxy-Authorization").Any())
{ {
await WriteResponseStatus(new Version(1, 1), "407", await WriteResponseStatus(new Version(1, 1), "407",
"Proxy Authentication Required", clientStreamWriter); "Proxy Authentication Required", clientStreamWriter);
var response = new Response var response = new Response();
{ response.ResponseHeaders = new Dictionary<string, HttpHeader>();
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"));
{
"Proxy-Authenticate",
new HttpHeader("Proxy-Authenticate", "Basic realm=\"TitaniumProxy\"")
},
{"Proxy-Connection", new HttpHeader("Proxy-Connection", "close")}
}
};
await WriteResponseHeaders(clientStreamWriter, response); await WriteResponseHeaders(clientStreamWriter, response);
await clientStreamWriter.WriteLineAsync(); await clientStreamWriter.WriteLineAsync();
return false; return false;
} }
var header = httpHeaders.FirstOrDefault(t => t.Name == "Proxy-Authorization"); else
if (null == header) throw new NullReferenceException();
var headerValue = header.Value.Trim();
if (!headerValue.StartsWith("basic", StringComparison.CurrentCultureIgnoreCase))
{ {
//Return not authorized var headerValue = httpHeaders.Where(t => t.Name == "Proxy-Authorization").FirstOrDefault().Value.Trim();
await WriteResponseStatus(new Version(1, 1), "407", if (!headerValue.ToLower().StartsWith("basic"))
"Proxy Authentication Invalid", clientStreamWriter);
var response = new Response
{ {
ResponseHeaders = new Dictionary<string, HttpHeader> //Return not authorized
{ await WriteResponseStatus(new Version(1, 1), "407",
{ "Proxy Authentication Invalid", clientStreamWriter);
"Proxy-Authenticate", var response = new Response();
new HttpHeader("Proxy-Authenticate", "Basic realm=\"TitaniumProxy\"") response.ResponseHeaders = new Dictionary<string, HttpHeader>();
}, response.ResponseHeaders.Add("Proxy-Authenticate", new HttpHeader("Proxy-Authenticate", "Basic realm=\"TitaniumProxy\""));
{"Proxy-Connection", new HttpHeader("Proxy-Connection", "close")} response.ResponseHeaders.Add("Proxy-Connection", new HttpHeader("Proxy-Connection", "close"));
} await WriteResponseHeaders(clientStreamWriter, response);
};
await WriteResponseHeaders(clientStreamWriter, response);
await clientStreamWriter.WriteLineAsync(); await clientStreamWriter.WriteLineAsync();
return false; return false;
} }
headerValue = headerValue.Substring(5).Trim(); headerValue = headerValue.Substring(5).Trim();
var decoded = Encoding.UTF8.GetString(Convert.FromBase64String(headerValue)); var decoded = Encoding.UTF8.GetString(Convert.FromBase64String(headerValue));
if (decoded.Contains(":") == false) if (decoded.Contains(":") == false)
{
//Return not authorized
await WriteResponseStatus(new Version(1, 1), "407",
"Proxy Authentication Invalid", clientStreamWriter);
var response = new Response
{ {
ResponseHeaders = new Dictionary<string, HttpHeader> //Return not authorized
{ await WriteResponseStatus(new Version(1, 1), "407",
{ "Proxy Authentication Invalid", clientStreamWriter);
"Proxy-Authenticate", var response = new Response();
new HttpHeader("Proxy-Authenticate", "Basic realm=\"TitaniumProxy\"") response.ResponseHeaders = new Dictionary<string, HttpHeader>();
}, response.ResponseHeaders.Add("Proxy-Authenticate", new HttpHeader("Proxy-Authenticate", "Basic realm=\"TitaniumProxy\""));
{"Proxy-Connection", new HttpHeader("Proxy-Connection", "close")} response.ResponseHeaders.Add("Proxy-Connection", new HttpHeader("Proxy-Connection", "close"));
} await WriteResponseHeaders(clientStreamWriter, response);
};
await WriteResponseHeaders(clientStreamWriter, response);
await clientStreamWriter.WriteLineAsync(); await clientStreamWriter.WriteLineAsync();
return false; return false;
}
var username = decoded.Substring(0, decoded.IndexOf(':'));
var password = decoded.Substring(decoded.IndexOf(':') + 1);
return await AuthenticateUserFunc(username, password).ConfigureAwait(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 //Return not authorized
await WriteResponseStatus(new Version(1, 1), "407", await WriteResponseStatus(new Version(1, 1), "407",
"Proxy Authentication Invalid", clientStreamWriter); "Proxy Authentication Invalid", clientStreamWriter);
var response = new Response var response = new Response();
{ response.ResponseHeaders = new Dictionary<string, HttpHeader>();
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"));
{"Proxy-Authenticate", new HttpHeader("Proxy-Authenticate", "Basic realm=\"TitaniumProxy\"")},
{"Proxy-Connection", new HttpHeader("Proxy-Connection", "close")}
}
};
await WriteResponseHeaders(clientStreamWriter, response); await WriteResponseHeaders(clientStreamWriter, response);
await clientStreamWriter.WriteLineAsync(); await clientStreamWriter.WriteLineAsync();
return false; return false;
} }
} }
} }
} }
...@@ -10,7 +10,6 @@ using Titanium.Web.Proxy.Network; ...@@ -10,7 +10,6 @@ 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
{ {
...@@ -19,6 +18,12 @@ namespace Titanium.Web.Proxy ...@@ -19,6 +18,12 @@ 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>
...@@ -27,7 +32,7 @@ namespace Titanium.Web.Proxy ...@@ -27,7 +32,7 @@ namespace Titanium.Web.Proxy
/// <summary> /// <summary>
/// Manages certificates used by this proxy /// Manages certificates used by this proxy
/// </summary> /// </summary>
private CertificateManager certificateManager { get; set; } private CertificateManager certificateCacheManager { get; set; }
/// <summary> /// <summary>
/// An default exception log func /// An default exception log func
...@@ -39,91 +44,42 @@ namespace Titanium.Web.Proxy ...@@ -39,91 +44,42 @@ 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; } private TcpConnectionFactory tcpConnectionFactory { get; set; }
/// <summary> /// <summary>
/// Manage system proxy settings /// Manage system proxy settings
/// </summary> /// </summary>
private SystemProxyManager systemProxySettingsManager { get; } private SystemProxyManager systemProxySettingsManager { get; set; }
#if !DEBUG private FireFoxProxySettingsManager firefoxProxySettingsManager { get; set; }
/// <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 BufferSize { get; set; } = 8192; public int BUFFER_SIZE { get; set; } = 8192;
/// <summary>
/// The root certificate
/// </summary>
public X509Certificate2 RootCertificate
{
get { return certificateManager.RootCertificate; }
set { certificateManager.RootCertificate = value; }
}
/// <summary> /// <summary>
/// Name of the root certificate issuer /// Name of the root certificate issuer
/// (This is valid only when RootCertificate property is not set)
/// </summary> /// </summary>
public string RootCertificateIssuerName public string RootCertificateIssuerName { get; set; }
{
get { return certificateManager.Issuer; }
set { certificateManager.RootCertificateName = 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 will be stored in proxy exe directory with the private key /// The provided root certificate has to be in the proxy exe directory with the private key
/// Root certificate file will be named as "rootCert.pfx" /// The root certificate file should be named as "rootCert.pfx"
/// </summary> /// </summary>
public string RootCertificateName public string RootCertificateName { get; set; }
{
get { return certificateManager.RootCertificateName; }
set { certificateManager.Issuer = 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 public bool TrustRootCertificate { get; set; }
{
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?
...@@ -167,16 +123,6 @@ namespace Titanium.Web.Proxy ...@@ -167,16 +123,6 @@ 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>
...@@ -192,8 +138,14 @@ namespace Titanium.Web.Proxy ...@@ -192,8 +138,14 @@ namespace Titanium.Web.Proxy
/// </summary> /// </summary>
public Action<Exception> ExceptionFunc public Action<Exception> ExceptionFunc
{ {
get { return exceptionFunc ?? defaultExceptionFunc.Value; } get
set { exceptionFunc = value; } {
return exceptionFunc ?? defaultExceptionFunc.Value;
}
set
{
exceptionFunc = value;
}
} }
/// <summary> /// <summary>
...@@ -201,19 +153,30 @@ namespace Titanium.Web.Proxy ...@@ -201,19 +153,30 @@ 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 { get; set; } public Func<string, string, Task<bool>> AuthenticateUserFunc
{
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 { get; set; } public Func<SessionEventArgs, Task<ExternalProxy>> GetCustomUpStreamHttpProxyFunc
{
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
/// </summary> public Func<SessionEventArgs, Task<ExternalProxy>> GetCustomUpStreamHttpsProxyFunc
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
...@@ -223,23 +186,27 @@ namespace Titanium.Web.Proxy ...@@ -223,23 +186,27 @@ 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 public SslProtocols SupportedSslProtocols { get; set; } = SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12 | SslProtocols.Ssl3;
| SslProtocols.Tls11 | SslProtocols.Tls12 | SslProtocols.Ssl3;
/// <summary> /// <summary>
/// Constructor /// Is the proxy currently running
/// </summary> /// </summary>
public ProxyServer() : this(null, null) 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>
/// Constructor. /// Constructor
/// </summary> /// </summary>
/// <param name="rootCertificateName">Name of root certificate.</param> public ProxyServer() : this(null, null) { }
/// <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 = 120;
CertificateCacheTimeOutMinutes = 60; CertificateCacheTimeOutMinutes = 60;
...@@ -247,20 +214,10 @@ namespace Titanium.Web.Proxy ...@@ -247,20 +214,10 @@ namespace Titanium.Web.Proxy
ProxyEndPoints = new List<ProxyEndPoint>(); ProxyEndPoints = new List<ProxyEndPoint>();
tcpConnectionFactory = new TcpConnectionFactory(); tcpConnectionFactory = new TcpConnectionFactory();
systemProxySettingsManager = new SystemProxyManager(); systemProxySettingsManager = new SystemProxyManager();
#if !DEBUG firefoxProxySettingsManager = new FireFoxProxySettingsManager();
new FireFoxProxySettingsManager();
#endif
certificateManager = new CertificateManager(ExceptionFunc);
if (rootCertificateName != null)
{
RootCertificateName = rootCertificateName;
}
if (rootCertificateIssuerName != null) RootCertificateName = RootCertificateName ?? "Titanium Root Certificate Authority";
{ RootCertificateIssuerName = RootCertificateIssuerName ?? "Titanium";
RootCertificateIssuerName = rootCertificateIssuerName;
}
} }
/// <summary> /// <summary>
...@@ -269,8 +226,7 @@ namespace Titanium.Web.Proxy ...@@ -269,8 +226,7 @@ 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) if (ProxyEndPoints.Any(x => x.IpAddress.Equals(endPoint.IpAddress) && endPoint.Port != 0 && x.Port == endPoint.Port))
&& 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");
} }
...@@ -309,11 +265,6 @@ namespace Titanium.Web.Proxy ...@@ -309,11 +265,6 @@ 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
...@@ -326,7 +277,8 @@ namespace Titanium.Web.Proxy ...@@ -326,7 +277,8 @@ namespace Titanium.Web.Proxy
#if !DEBUG #if !DEBUG
firefoxProxySettingsManager.AddFirefox(); firefoxProxySettingsManager.AddFirefox();
#endif #endif
Console.WriteLine("Set endpoint at Ip {0} and port: {1} as System HTTP Proxy", endPoint.IpAddress, endPoint.Port); Console.WriteLine("Set endpoint at Ip {1} and port: {2} as System HTTP Proxy", endPoint.GetType().Name, endPoint.IpAddress, endPoint.Port);
} }
...@@ -336,11 +288,6 @@ namespace Titanium.Web.Proxy ...@@ -336,11 +288,6 @@ 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)
...@@ -349,18 +296,14 @@ namespace Titanium.Web.Proxy ...@@ -349,18 +296,14 @@ namespace Titanium.Web.Proxy
} }
//clear any settings previously added //clear any settings previously added
ProxyEndPoints.OfType<ExplicitProxyEndPoint>() ProxyEndPoints.OfType<ExplicitProxyEndPoint>().ToList().ForEach(x => x.IsSystemHttpsProxy = false);
.ToList()
.ForEach(x => x.IsSystemHttpsProxy = false);
EnsureRootCertificate();
//If certificate was trusted by the machine //If certificate was trusted by the machine
if (certificateManager.CertValidated) if (certValidated)
{ {
systemProxySettingsManager.SetHttpsProxy( systemProxySettingsManager.SetHttpsProxy(
Equals(endPoint.IpAddress, IPAddress.Any) | Equals(endPoint.IpAddress, IPAddress.Any) | Equals(endPoint.IpAddress, IPAddress.Loopback) ? "127.0.0.1" : endPoint.IpAddress.ToString(),
Equals(endPoint.IpAddress, IPAddress.Loopback) ? "127.0.0.1" : endPoint.IpAddress.ToString(),
endPoint.Port); endPoint.Port);
} }
...@@ -370,8 +313,7 @@ namespace Titanium.Web.Proxy ...@@ -370,8 +313,7 @@ namespace Titanium.Web.Proxy
#if !DEBUG #if !DEBUG
firefoxProxySettingsManager.AddFirefox(); firefoxProxySettingsManager.AddFirefox();
#endif #endif
Console.WriteLine("Set endpoint at Ip {0} and port: {1} as System HTTPS Proxy", Console.WriteLine("Set endpoint at Ip {1} and port: {2} as System HTTPS Proxy", endPoint.GetType().Name, endPoint.IpAddress, endPoint.Port);
endPoint.IpAddress, endPoint.Port);
} }
/// <summary> /// <summary>
...@@ -379,11 +321,6 @@ namespace Titanium.Web.Proxy ...@@ -379,11 +321,6 @@ 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();
} }
...@@ -392,11 +329,6 @@ namespace Titanium.Web.Proxy ...@@ -392,11 +329,6 @@ 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();
} }
...@@ -405,11 +337,6 @@ namespace Titanium.Web.Proxy ...@@ -405,11 +337,6 @@ 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();
} }
...@@ -423,8 +350,17 @@ namespace Titanium.Web.Proxy ...@@ -423,8 +350,17 @@ namespace Titanium.Web.Proxy
throw new Exception("Proxy is already running."); throw new Exception("Proxy is already running.");
} }
if (ForwardToUpstreamGateway && GetCustomUpStreamHttpProxyFunc == null certificateCacheManager = new CertificateManager(RootCertificateIssuerName,
&& GetCustomUpStreamHttpsProxyFunc == null) RootCertificateName, ExceptionFunc);
certValidated = certificateCacheManager.CreateTrustedRootCertificate();
if (TrustRootCertificate)
{
certificateCacheManager.TrustRootCertificate();
}
if (ForwardToUpstreamGateway && GetCustomUpStreamHttpProxyFunc == null && GetCustomUpStreamHttpsProxyFunc == null)
{ {
GetCustomUpStreamHttpProxyFunc = GetSystemUpStreamProxy; GetCustomUpStreamHttpProxyFunc = GetSystemUpStreamProxy;
GetCustomUpStreamHttpsProxyFunc = GetSystemUpStreamProxy; GetCustomUpStreamHttpsProxyFunc = GetSystemUpStreamProxy;
...@@ -435,11 +371,32 @@ namespace Titanium.Web.Proxy ...@@ -435,11 +371,32 @@ namespace Titanium.Web.Proxy
Listen(endPoint); Listen(endPoint);
} }
certificateManager.ClearIdleCertificates(CertificateCacheTimeOutMinutes); certificateCacheManager.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
...@@ -468,36 +425,34 @@ namespace Titanium.Web.Proxy ...@@ -468,36 +425,34 @@ namespace Titanium.Web.Proxy
ProxyEndPoints.Clear(); ProxyEndPoints.Clear();
certificateManager?.StopClearIdleCertificates(); certificateCacheManager?.StopClearIdleCertificates();
proxyRunning = false; proxyRunning = false;
} }
/// <summary> /// <summary>
/// Dispose Proxy. /// Listen on the given end point on local machine
/// </summary> /// </summary>
public void Dispose() /// <param name="endPoint"></param>
private void Listen(ProxyEndPoint endPoint)
{ {
if (proxyRunning) endPoint.listener = new TcpListener(endPoint.IpAddress, endPoint.Port);
{ endPoint.listener.Start();
Stop();
}
certificateManager?.Dispose(); endPoint.Port = ((IPEndPoint)endPoint.listener.LocalEndpoint).Port;
// accept clients asynchronously
endPoint.listener.BeginAcceptTcpClient(OnAcceptConnection, endPoint);
} }
/// <summary> /// <summary>
/// Listen on the given end point on local machine /// Quit listening on the given end point
/// </summary> /// </summary>
/// <param name="endPoint"></param> /// <param name="endPoint"></param>
private void Listen(ProxyEndPoint endPoint) private void QuitListen(ProxyEndPoint endPoint)
{ {
endPoint.Listener = new TcpListener(endPoint.IpAddress, endPoint.Port); endPoint.listener.Stop();
endPoint.Listener.Start(); endPoint.listener.Server.Close();
endPoint.listener.Server.Dispose();
endPoint.Port = ((IPEndPoint) endPoint.Listener.LocalEndpoint).Port;
// accept clients asynchronously
endPoint.Listener.BeginAcceptTcpClient(OnAcceptConnection, endPoint);
} }
/// <summary> /// <summary>
...@@ -506,7 +461,6 @@ namespace Titanium.Web.Proxy ...@@ -506,7 +461,6 @@ 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");
...@@ -518,56 +472,20 @@ namespace Titanium.Web.Proxy ...@@ -518,56 +472,20 @@ 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>
/// <param name="asyn"></param> /// <param name="asyn"></param>
private void OnAcceptConnection(IAsyncResult asyn) private void OnAcceptConnection(IAsyncResult asyn)
{ {
var endPoint = (ProxyEndPoint) asyn.AsyncState; var endPoint = (ProxyEndPoint)asyn.AsyncState;
TcpClient tcpClient = null; TcpClient tcpClient = null;
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)
{ {
...@@ -594,8 +512,11 @@ namespace Titanium.Web.Proxy ...@@ -594,8 +512,11 @@ namespace Titanium.Web.Proxy
} }
else else
{ {
await HandleClient(endPoint as ExplicitProxyEndPoint, tcpClient); await HandleClient(endPoint as ExplicitProxyEndPoint, tcpClient);
} }
} }
finally finally
{ {
...@@ -606,66 +527,29 @@ namespace Titanium.Web.Proxy ...@@ -606,66 +527,29 @@ namespace Titanium.Web.Proxy
//It helps to avoid eventual deterioration of performance due to TCP port exhaustion //It helps to avoid eventual deterioration of performance due to TCP port exhaustion
//due to default TCP CLOSE_WAIT timeout for 4 minutes //due to default TCP CLOSE_WAIT timeout for 4 minutes
tcpClient.LingerState = new LingerOption(true, 0); tcpClient.LingerState = new LingerOption(true, 0);
tcpClient.Client.Shutdown(SocketShutdown.Both);
tcpClient.Client.Close();
tcpClient.Client.Dispose();
tcpClient.Close(); 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);
} }
/// <summary> public void Dispose()
/// Quit listening on the given end point
/// </summary>
/// <param name="endPoint"></param>
private void QuitListen(ProxyEndPoint endPoint)
{
endPoint.Listener.Stop();
endPoint.Listener.Server.Close();
endPoint.Listener.Server.Dispose();
}
/// <summary>
/// Invocator for BeforeRequest event.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
protected virtual void OnBeforeRequest(object sender, SessionEventArgs e)
{
BeforeRequest?.Invoke(sender, e);
}
/// <summary>
/// Invocator for BeforeResponse event.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
/// <returns></returns>
protected virtual void OnBeforeResponse(object sender, SessionEventArgs e)
{
BeforeResponse?.Invoke(sender, e);
}
/// <summary>
/// Invocator for ServerCertificateValidationCallback event.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
protected virtual void OnServerCertificateValidationCallback(object sender, CertificateValidationEventArgs e)
{ {
ServerCertificateValidationCallback?.Invoke(sender, e); if (proxyRunning)
} {
Stop();
}
/// <summary> certificateCacheManager?.Dispose();
/// 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,10 +6,12 @@ using System.Net; ...@@ -6,10 +6,12 @@ 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;
...@@ -23,17 +25,20 @@ namespace Titanium.Web.Proxy ...@@ -23,17 +25,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, BufferSize); var clientStreamReader = new CustomBinaryReader(clientStream);
var clientStreamWriter = new StreamWriter(clientStream) {NewLine = ProxyConstants.NewLine}; var clientStreamWriter = new StreamWriter(clientStream) { NewLine = ProxyConstants.NewLine };
Uri httpRemoteUri; Uri httpRemoteUri;
try try
...@@ -51,42 +56,40 @@ namespace Titanium.Web.Proxy ...@@ -51,42 +56,40 @@ 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].ToUpper(); var httpVerb = httpCmdSplit[0];
httpRemoteUri = httpVerb == "CONNECT" ? new Uri("http://" + httpCmdSplit[1]) : new Uri(httpCmdSplit[1]); if (httpVerb.ToUpper() == "CONNECT")
{
httpRemoteUri = new Uri("http://" + httpCmdSplit[1]);
}
else
{
httpRemoteUri = new Uri(httpCmdSplit[1]);
}
//parse the HTTP version //parse the HTTP version
var version = new Version(1, 1); Version version = new Version(1, 1);
if (httpCmdSplit.Length == 3) if (httpCmdSplit.Length == 3)
{ {
var httpVersion = httpCmdSplit[2].Trim(); string httpVersion = httpCmdSplit[2].Trim();
if (0 == string.CompareOrdinal(httpVersion, "http/1.0")) if (httpVersion == "http/1.0")
{ {
version = new Version(1, 0); version = new Version(1, 0);
} }
} }
//filter out excluded host names //filter out excluded host names
bool excluded = false; var excluded = endPoint.ExcludedHttpsHostNameRegex != null ?
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 == "CONNECT" && !excluded && httpRemoteUri.Port != 80) if (httpVerb.ToUpper() == "CONNECT" && !excluded && httpRemoteUri.Port != 80)
{ {
httpRemoteUri = new Uri("https://" + httpCmdSplit[1]); httpRemoteUri = new Uri("https://" + httpCmdSplit[1]);
string tmpLine; string tmpLine = null;
connectRequestHeaders = new List<HttpHeader>(); connectRequestHeaders = new List<HttpHeader>();
while (!string.IsNullOrEmpty(tmpLine = await clientStreamReader.ReadLineAsync())) while (!string.IsNullOrEmpty(tmpLine = await clientStreamReader.ReadLineAsync()))
{ {
...@@ -108,22 +111,25 @@ namespace Titanium.Web.Proxy ...@@ -108,22 +111,25 @@ namespace Titanium.Web.Proxy
try try
{ {
sslStream = new SslStream(clientStream, true);
var certificate = endPoint.GenericCertificate ?? certificateManager.CreateCertificate(httpRemoteUri.Host, false);
sslStream = new SslStream(clientStream, true);
var certificate = certificateCacheManager.CreateCertificate(httpRemoteUri.Host, false);
//Successfully managed to authenticate the client using the fake certificate //Successfully managed to authenticate the client using the fake certificate
await sslStream.AuthenticateAsServerAsync(certificate, false, await sslStream.AuthenticateAsServerAsync(certificate, false,
SupportedSslProtocols, false); SupportedSslProtocols, false);
//HTTPS server created - we can now decrypt the client's traffic //HTTPS server created - we can now decrypt the client's traffic
clientStream = new CustomBufferedStream(sslStream, BufferSize); clientStream = sslStream;
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
{ {
sslStream?.Dispose(); if (sslStream != null)
{
sslStream.Dispose();
}
Dispose(clientStream, clientStreamReader, clientStreamWriter, null); Dispose(clientStream, clientStreamReader, clientStreamWriter, null);
return; return;
...@@ -131,28 +137,29 @@ namespace Titanium.Web.Proxy ...@@ -131,28 +137,29 @@ 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 == "CONNECT") else if (httpVerb.ToUpper() == "CONNECT")
{ {
//Siphon out CONNECT request headers //Cyphen out CONNECT request headers
await clientStreamReader.ReadAndIgnoreAllLinesAsync(); await clientStreamReader.ReadAllLinesAsync();
//write back successfull CONNECT response //write back successfull CONNECT response
await WriteConnectResponse(clientStreamWriter, version); await WriteConnectResponse(clientStreamWriter, version);
await TcpHelper.SendRaw(BufferSize, ConnectionTimeOutSeconds, httpRemoteUri.Host, httpRemoteUri.Port, await TcpHelper.SendRaw(BUFFER_SIZE, ConnectionTimeOutSeconds, httpRemoteUri.Host, httpRemoteUri.Port,
null, version, null, httpCmd, version, null,
false, SupportedSslProtocols, false, SupportedSslProtocols,
ValidateServerCertificate, new RemoteCertificateValidationCallback(ValidateServerCertificate),
SelectClientCertificate, new LocalCertificateSelectionCallback(SelectClientCertificate),
clientStream, tcpConnectionFactory, UpStreamEndPoint); 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); httpRemoteUri.Scheme == Uri.UriSchemeHttps ? httpRemoteUri.Host : null, endPoint, connectRequestHeaders, null, null);
} }
catch (Exception) catch (Exception)
{ {
...@@ -164,20 +171,22 @@ namespace Titanium.Web.Proxy ...@@ -164,20 +171,22 @@ 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
var certificate = certificateManager.CreateCertificate(endPoint.GenericCertificateName, false); certificate = certificateCacheManager.CreateCertificate(endPoint.GenericCertificateName, false);
try try
{ {
...@@ -185,10 +194,10 @@ namespace Titanium.Web.Proxy ...@@ -185,10 +194,10 @@ namespace Titanium.Web.Proxy
await sslStream.AuthenticateAsServerAsync(certificate, false, await sslStream.AuthenticateAsServerAsync(certificate, false,
SslProtocols.Tls, false); SslProtocols.Tls, false);
clientStream = new CustomBufferedStream(sslStream, BufferSize); clientStreamReader = new CustomBinaryReader(sslStream);
clientStreamReader = new CustomBinaryReader(clientStream, BufferSize); clientStreamWriter = new StreamWriter(sslStream) { NewLine = ProxyConstants.NewLine };
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)
{ {
...@@ -197,11 +206,12 @@ namespace Titanium.Web.Proxy ...@@ -197,11 +206,12 @@ namespace Titanium.Web.Proxy
Dispose(sslStream, clientStreamReader, clientStreamWriter, null); Dispose(sslStream, clientStreamReader, clientStreamWriter, null);
return; return;
} }
clientStream = sslStream;
} }
else else
{ {
clientStreamReader = new CustomBinaryReader(clientStream, BufferSize); clientStreamReader = new CustomBinaryReader(clientStream);
clientStreamWriter = new StreamWriter(clientStream) {NewLine = ProxyConstants.NewLine}; clientStreamWriter = new StreamWriter(clientStream) { NewLine = ProxyConstants.NewLine };
} }
//now read the request line //now read the request line
...@@ -209,10 +219,10 @@ namespace Titanium.Web.Proxy ...@@ -209,10 +219,10 @@ 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) private async Task HandleHttpSessionRequestInternal(TcpConnection connection, SessionEventArgs args, ExternalProxy customUpStreamHttpProxy, ExternalProxy customUpStreamHttpsProxy, bool CloseConnection)
{ {
try try
{ {
...@@ -222,25 +232,25 @@ namespace Titanium.Web.Proxy ...@@ -222,25 +232,25 @@ namespace Titanium.Web.Proxy
{ {
if (GetCustomUpStreamHttpProxyFunc != null) if (GetCustomUpStreamHttpProxyFunc != null)
{ {
customUpStreamHttpProxy = await GetCustomUpStreamHttpProxyFunc(args); customUpStreamHttpProxy = await GetCustomUpStreamHttpProxyFunc(args).ConfigureAwait(false);
} }
} }
else else
{ {
if (GetCustomUpStreamHttpsProxyFunc != null) if (GetCustomUpStreamHttpsProxyFunc != null)
{ {
customUpStreamHttpsProxy = await GetCustomUpStreamHttpsProxyFunc(args); customUpStreamHttpsProxy = await GetCustomUpStreamHttpsProxyFunc(args).ConfigureAwait(false);
} }
} }
args.CustomUpStreamHttpProxyUsed = customUpStreamHttpProxy; args.CustomUpStreamHttpProxyUsed = customUpStreamHttpProxy;
args.CustomUpStreamHttpsProxyUsed = customUpStreamHttpsProxy; args.CustomUpStreamHttpsProxyUsed = customUpStreamHttpsProxy;
connection = await tcpConnectionFactory.CreateClient(BufferSize, ConnectionTimeOutSeconds, connection = await tcpConnectionFactory.CreateClient(BUFFER_SIZE, ConnectionTimeOutSeconds,
args.WebSession.Request.RequestUri.Host, args.WebSession.Request.RequestUri.Port, args.WebSession.Request.HttpVersion, args.WebSession.Request.RequestUri.Host, args.WebSession.Request.RequestUri.Port, args.WebSession.Request.HttpVersion,
args.IsHttps, SupportedSslProtocols, args.IsHttps, SupportedSslProtocols,
ValidateServerCertificate, new RemoteCertificateValidationCallback(ValidateServerCertificate),
SelectClientCertificate, new LocalCertificateSelectionCallback(SelectClientCertificate),
customUpStreamHttpProxy ?? UpStreamHttpProxy, customUpStreamHttpsProxy ?? UpStreamHttpsProxy, args.ProxyClient.ClientStream, UpStreamEndPoint); customUpStreamHttpProxy ?? UpStreamHttpProxy, customUpStreamHttpsProxy ?? UpStreamHttpsProxy, args.ProxyClient.ClientStream, UpStreamEndPoint);
} }
...@@ -267,13 +277,13 @@ namespace Titanium.Web.Proxy ...@@ -267,13 +277,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();
} }
} }
...@@ -331,7 +341,7 @@ namespace Titanium.Web.Proxy ...@@ -331,7 +341,7 @@ namespace Titanium.Web.Proxy
return; return;
} }
if (closeConnection) if (CloseConnection)
{ {
//dispose //dispose
connection?.Dispose(); connection?.Dispose();
...@@ -347,14 +357,9 @@ namespace Titanium.Web.Proxy ...@@ -347,14 +357,9 @@ 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, CustomBinaryReader clientStreamReader, StreamWriter clientStreamWriter, string httpsHostName, ProxyEndPoint endPoint, List<HttpHeader> connectHeaders, ExternalProxy customUpStreamHttpProxy = null, ExternalProxy customUpStreamHttpsProxy = null)
ProxyEndPoint endPoint, List<HttpHeader> connectHeaders, ExternalProxy customUpStreamHttpProxy = null, ExternalProxy customUpStreamHttpsProxy = null)
{ {
TcpConnection connection = null; TcpConnection connection = null;
...@@ -368,16 +373,13 @@ namespace Titanium.Web.Proxy ...@@ -368,16 +373,13 @@ namespace Titanium.Web.Proxy
break; break;
} }
var args = var args = new SessionEventArgs(BUFFER_SIZE, HandleHttpSessionResponse);
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>(() =>
{ {
var remoteEndPoint = (IPEndPoint) args.ProxyClient.TcpClient.Client.RemoteEndPoint; var remoteEndPoint = (IPEndPoint)args.ProxyClient.TcpClient.Client.RemoteEndPoint;
//If client is localhost get the process id //If client is localhost get the process id
if (NetworkHelper.IsLocalIpAddress(remoteEndPoint.Address)) if (NetworkHelper.IsLocalIpAddress(remoteEndPoint.Address))
...@@ -387,6 +389,7 @@ namespace Titanium.Web.Proxy ...@@ -387,6 +389,7 @@ 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
{ {
...@@ -396,12 +399,12 @@ namespace Titanium.Web.Proxy ...@@ -396,12 +399,12 @@ namespace Titanium.Web.Proxy
var httpMethod = httpCmdSplit[0]; var httpMethod = httpCmdSplit[0];
//find the request HTTP version //find the request HTTP version
var httpVersion = new Version(1, 1); Version httpVersion = new Version(1, 1);
if (httpCmdSplit.Length == 3) if (httpCmdSplit.Length == 3)
{ {
var httpVersionString = httpCmdSplit[2].ToLower().Trim(); var httpVersionString = httpCmdSplit[2].ToLower().Trim();
if (0 == string.CompareOrdinal(httpVersionString, "http/1.0")) if (httpVersionString == "http/1.0")
{ {
httpVersion = new Version(1, 0); httpVersion = new Version(1, 0);
} }
...@@ -426,8 +429,10 @@ namespace Titanium.Web.Proxy ...@@ -426,8 +429,10 @@ namespace Titanium.Web.Proxy
{ {
var existing = args.WebSession.Request.RequestHeaders[newHeader.Name]; var existing = args.WebSession.Request.RequestHeaders[newHeader.Name];
var nonUniqueHeaders = new List<HttpHeader> {existing, newHeader}; var nonUniqueHeaders = new List<HttpHeader>();
nonUniqueHeaders.Add(existing);
nonUniqueHeaders.Add(newHeader);
args.WebSession.Request.NonUniqueRequestHeaders.Add(newHeader.Name, nonUniqueHeaders); args.WebSession.Request.NonUniqueRequestHeaders.Add(newHeader.Name, nonUniqueHeaders);
args.WebSession.Request.RequestHeaders.Remove(newHeader.Name); args.WebSession.Request.RequestHeaders.Remove(newHeader.Name);
...@@ -440,7 +445,8 @@ namespace Titanium.Web.Proxy ...@@ -440,7 +445,8 @@ namespace Titanium.Web.Proxy
} }
var httpRemoteUri = new Uri(httpsHostName == null ? httpCmdSplit[1] var httpRemoteUri = new Uri(httpsHostName == null ? httpCmdSplit[1]
: (string.Concat("https://", args.WebSession.Request.Host ?? httpsHostName, httpCmdSplit[1]))); : (string.Concat("https://", args.WebSession.Request.Host == null ?
httpsHostName : args.WebSession.Request.Host, httpCmdSplit[1])));
args.WebSession.Request.RequestUri = httpRemoteUri; args.WebSession.Request.RequestUri = httpRemoteUri;
...@@ -452,6 +458,7 @@ namespace Titanium.Web.Proxy ...@@ -452,6 +458,7 @@ namespace Titanium.Web.Proxy
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, args); Dispose(clientStream, clientStreamReader, clientStreamWriter, args);
break; break;
} }
...@@ -462,12 +469,12 @@ namespace Titanium.Web.Proxy ...@@ -462,12 +469,12 @@ namespace Titanium.Web.Proxy
//If user requested interception do it //If user requested interception do it
if (BeforeRequest != null) if (BeforeRequest != null)
{ {
var invocationList = BeforeRequest.GetInvocationList(); Delegate[] invocationList = BeforeRequest.GetInvocationList();
var handlerTasks = new Task[invocationList.Length]; Task[] handlerTasks = new Task[invocationList.Length];
for (var i = 0; i < invocationList.Length; i++) for (int i = 0; i < invocationList.Length; i++)
{ {
handlerTasks[i] = ((Func<object, SessionEventArgs, Task>) invocationList[i])(null, args); handlerTasks[i] = ((Func<object, SessionEventArgs, Task>)invocationList[i])(null, args);
} }
await Task.WhenAll(handlerTasks); await Task.WhenAll(handlerTasks);
...@@ -476,18 +483,18 @@ namespace Titanium.Web.Proxy ...@@ -476,18 +483,18 @@ 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(BufferSize, ConnectionTimeOutSeconds, httpRemoteUri.Host, httpRemoteUri.Port, await TcpHelper.SendRaw(BUFFER_SIZE, ConnectionTimeOutSeconds, httpRemoteUri.Host, httpRemoteUri.Port,
httpCmd, httpVersion, args.WebSession.Request.RequestHeaders, args.IsHttps, httpCmd, httpVersion, args.WebSession.Request.RequestHeaders, args.IsHttps,
SupportedSslProtocols, ValidateServerCertificate, SupportedSslProtocols, new RemoteCertificateValidationCallback(ValidateServerCertificate),
SelectClientCertificate, new LocalCertificateSelectionCallback(SelectClientCertificate),
clientStream, tcpConnectionFactory, UpStreamEndPoint); clientStream, tcpConnectionFactory, UpStreamEndPoint);
Dispose(clientStream, clientStreamReader, clientStreamWriter, args); Dispose(clientStream, clientStreamReader, clientStreamWriter, args);
break; break;
} }
//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(null, args, customUpStreamHttpProxy, customUpStreamHttpsProxy, false); await HandleHttpSessionRequestInternal(connection, args, customUpStreamHttpProxy, customUpStreamHttpsProxy, false).ConfigureAwait(false);
if (args.WebSession.Request.CancelRequest) if (args.WebSession.Request.CancelRequest)
...@@ -503,6 +510,7 @@ namespace Titanium.Web.Proxy ...@@ -503,6 +510,7 @@ namespace Titanium.Web.Proxy
// read the next request // read the next request
httpCmd = await clientStreamReader.ReadLineAsync(); httpCmd = await clientStreamReader.ReadLineAsync();
} }
catch (Exception e) catch (Exception e)
{ {
...@@ -510,10 +518,15 @@ namespace Titanium.Web.Proxy ...@@ -510,10 +518,15 @@ namespace Titanium.Web.Proxy
Dispose(clientStream, clientStreamReader, clientStreamWriter, args); Dispose(clientStream, clientStreamReader, clientStreamWriter, args);
break; break;
} }
}
if (connection != null)
{
//dispose
connection.Dispose();
} }
//dispose
connection?.Dispose();
} }
/// <summary> /// <summary>
...@@ -524,9 +537,8 @@ namespace Titanium.Web.Proxy ...@@ -524,9 +537,8 @@ 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( await clientStreamWriter.WriteLineAsync(string.Format("HTTP/{0}.{1} {2}", httpVersion.Major, httpVersion.Minor, "200 Connection established"));
$"HTTP/{httpVersion.Major}.{httpVersion.Minor} 200 Connection established"); await clientStreamWriter.WriteLineAsync(string.Format("Timestamp: {0}", DateTime.Now));
await clientStreamWriter.WriteLineAsync($"Timestamp: {DateTime.Now}");
await clientStreamWriter.WriteLineAsync(); await clientStreamWriter.WriteLineAsync();
await clientStreamWriter.FlushAsync(); await clientStreamWriter.FlushAsync();
} }
...@@ -546,7 +558,10 @@ namespace Titanium.Web.Proxy ...@@ -546,7 +558,10 @@ 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"; header.Value = "gzip,deflate,zlib";
break;
default:
break; break;
} }
} }
...@@ -568,14 +583,15 @@ namespace Titanium.Web.Proxy ...@@ -568,14 +583,15 @@ 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(BufferSize, postStream, args.WebSession.Request.ContentLength); await args.ProxyClient.ClientStreamReader.CopyBytesToStream(BUFFER_SIZE, 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(postStream); await args.ProxyClient.ClientStreamReader.CopyBytesToStreamChunked(BUFFER_SIZE, postStream);
} }
} }
} }
} }
\ No newline at end of file
...@@ -17,12 +17,8 @@ namespace Titanium.Web.Proxy ...@@ -17,12 +17,8 @@ namespace Titanium.Web.Proxy
/// </summary> /// </summary>
partial class ProxyServer partial class ProxyServer
{ {
/// <summary> //Called asynchronously when a request was successfully and we received the response
/// Called asynchronously when a request was successfully and we received the response public async Task HandleHttpSessionResponse(SessionEventArgs args)
/// </summary>
/// <param name="args"></param>
/// <returns></returns>
private async Task HandleHttpSessionResponse(SessionEventArgs args)
{ {
//read response & headers from server //read response & headers from server
await args.WebSession.ReceiveResponse(); await args.WebSession.ReceiveResponse();
...@@ -44,15 +40,15 @@ namespace Titanium.Web.Proxy ...@@ -44,15 +40,15 @@ namespace Titanium.Web.Proxy
for (int i = 0; i < invocationList.Length; i++) for (int i = 0; i < invocationList.Length; i++)
{ {
handlerTasks[i] = ((Func<object, SessionEventArgs, Task>) invocationList[i])(this, args); handlerTasks[i] = ((Func<object, SessionEventArgs, Task>)invocationList[i])(this, args);
} }
await Task.WhenAll(handlerTasks); await Task.WhenAll(handlerTasks);
} }
if (args.ReRequest) if(args.ReRequest)
{ {
await HandleHttpSessionRequestInternal(null, args, null, null, true); await HandleHttpSessionRequestInternal(null, args, null, null, true).ConfigureAwait(false);
return; return;
} }
...@@ -62,19 +58,19 @@ namespace Titanium.Web.Proxy ...@@ -62,19 +58,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)
{ {
...@@ -108,22 +104,23 @@ namespace Titanium.Web.Proxy ...@@ -108,22 +104,23 @@ namespace Titanium.Web.Proxy
|| !args.WebSession.Response.ResponseKeepAlive) || !args.WebSession.Response.ResponseKeepAlive)
{ {
await args.WebSession.ServerConnection.StreamReader await args.WebSession.ServerConnection.StreamReader
.WriteResponseBody(BufferSize, args.ProxyClient.ClientStream, args.WebSession.Response.IsChunked, .WriteResponseBody(BUFFER_SIZE, 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(BufferSize, args.ProxyClient.ClientStream, args.WebSession.Response.IsChunked, .WriteResponseBody(BUFFER_SIZE, 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 wilst handling session response", e, args));
Dispose(args.ProxyClient.ClientStream, args.ProxyClient.ClientStreamReader, Dispose(args.ProxyClient.ClientStream, args.ProxyClient.ClientStreamReader,
...@@ -159,14 +156,14 @@ namespace Titanium.Web.Proxy ...@@ -159,14 +156,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($"HTTP/{version.Major}.{version.Minor} {code} {description}"); await responseWriter.WriteLineAsync(string.Format("HTTP/{0}.{1} {2} {3}", 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="response"></param> /// <param name="headers"></param>
/// <returns></returns> /// <returns></returns>
private async Task WriteResponseHeaders(StreamWriter responseWriter, Response response) private async Task WriteResponseHeaders(StreamWriter responseWriter, Response response)
{ {
...@@ -174,7 +171,7 @@ namespace Titanium.Web.Proxy ...@@ -174,7 +171,7 @@ namespace Titanium.Web.Proxy
foreach (var header in response.ResponseHeaders) foreach (var header in response.ResponseHeaders)
{ {
await header.Value.WriteToStream(responseWriter); await responseWriter.WriteLineAsync(header.Value.ToString());
} }
//write non unique request headers //write non unique request headers
...@@ -183,10 +180,11 @@ namespace Titanium.Web.Proxy ...@@ -183,10 +180,11 @@ namespace Titanium.Web.Proxy
var headers = headerItem.Value; var headers = headerItem.Value;
foreach (var header in headers) foreach (var header in headers)
{ {
await header.WriteToStream(responseWriter); await responseWriter.WriteLineAsync(header.ToString());
} }
} }
await responseWriter.WriteLineAsync(); await responseWriter.WriteLineAsync();
await responseWriter.FlushAsync(); await responseWriter.FlushAsync();
} }
...@@ -216,11 +214,13 @@ namespace Titanium.Web.Proxy ...@@ -216,11 +214,13 @@ 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>
...@@ -228,15 +228,28 @@ namespace Titanium.Web.Proxy ...@@ -228,15 +228,28 @@ namespace Titanium.Web.Proxy
private void Dispose(Stream clientStream, CustomBinaryReader clientStreamReader, private void Dispose(Stream clientStream, CustomBinaryReader clientStreamReader,
StreamWriter clientStreamWriter, IDisposable args) StreamWriter clientStreamWriter, IDisposable args)
{ {
clientStream?.Close();
clientStream?.Dispose();
clientStreamReader?.Dispose(); if (clientStream != null)
{
clientStream.Close();
clientStream.Dispose();
}
if (args != null)
{
args.Dispose();
}
clientStreamWriter?.Close(); if (clientStreamReader != null)
clientStreamWriter?.Dispose(); {
clientStreamReader.Dispose();
}
args?.Dispose(); if (clientStreamWriter != null)
{
clientStreamWriter.Close();
clientStreamWriter.Dispose();
}
} }
} }
} }
\ No newline at end of file
using System.Text; using System;
using System.Text;
namespace Titanium.Web.Proxy.Shared namespace Titanium.Web.Proxy.Shared
{ {
...@@ -6,10 +7,10 @@ namespace Titanium.Web.Proxy.Shared ...@@ -6,10 +7,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,7 +26,6 @@ ...@@ -26,7 +26,6 @@
<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>
...@@ -35,18 +34,10 @@ ...@@ -35,18 +34,10 @@
<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="BouncyCastle.Crypto, Version=1.8.1.0, Culture=neutral, PublicKeyToken=0e99375e54769942"> <Reference Include="Ionic.Zip, Version=1.9.8.0, Culture=neutral, PublicKeyToken=6583c7c814667745, processorArchitecture=MSIL">
<HintPath>..\packages\BouncyCastle.1.8.1\lib\BouncyCastle.Crypto.dll</HintPath> <HintPath>..\packages\DotNetZip.1.9.8\lib\net20\Ionic.Zip.dll</HintPath>
<Private>True</Private> <Private>True</Private>
</Reference> </Reference>
<Reference Include="System" /> <Reference Include="System" />
...@@ -65,23 +56,19 @@ ...@@ -65,23 +56,19 @@
<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\Responses\GenericResponse.cs" />
<Compile Include="Network\CachedCertificate.cs" /> <Compile Include="Network\CachedCertificate.cs" />
<Compile Include="Network\Certificate\WinCertificateMaker.cs" /> <Compile Include="Network\CertificateMaker.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" />
...@@ -130,7 +117,6 @@ ...@@ -130,7 +117,6 @@
<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="BouncyCastle" version="1.8.1" /> <dependency id="DotNetZip" version="1.9.8" />
</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,7 +12,4 @@ ...@@ -12,7 +12,4 @@
</dependentAssembly> </dependentAssembly>
</assemblyBinding> </assemblyBinding>
</runtime> </runtime>
<startup> <startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5"/></startup></configuration>
<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="BouncyCastle" version="1.8.1" targetFramework="net45" /> <package id="DotNetZip" version="1.9.8" 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