Commit af254c32 authored by justcoding121's avatar justcoding121

Merge pull request #73 from justcoding121/release

External Proxy, Async & Server Certificate Validation
parents aeb9f31e e1cd5fcd
using System; using System;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace Titanium.Web.Proxy.Test namespace Titanium.Web.Proxy.Examples.Basic
{ {
public class Program public class Program
{ {
......
...@@ -2,10 +2,11 @@ ...@@ -2,10 +2,11 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Net; using System.Net;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Titanium.Web.Proxy.EventArguments; using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Models; using Titanium.Web.Proxy.Models;
namespace Titanium.Web.Proxy.Test namespace Titanium.Web.Proxy.Examples.Basic
{ {
public class ProxyTestController public class ProxyTestController
{ {
...@@ -13,6 +14,7 @@ namespace Titanium.Web.Proxy.Test ...@@ -13,6 +14,7 @@ namespace Titanium.Web.Proxy.Test
{ {
ProxyServer.BeforeRequest += OnRequest; ProxyServer.BeforeRequest += OnRequest;
ProxyServer.BeforeResponse += OnResponse; ProxyServer.BeforeResponse += OnResponse;
ProxyServer.ServerCertificateValidationCallback += OnCertificateValidation;
//Exclude Https addresses you don't want to proxy //Exclude Https addresses you don't want to proxy
//Usefull for clients that use certificate pinning //Usefull for clients that use certificate pinning
...@@ -34,13 +36,14 @@ namespace Titanium.Web.Proxy.Test ...@@ -34,13 +36,14 @@ namespace Titanium.Web.Proxy.Test
//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
//In this example only google.com will work for HTTPS requests //In this example only google.com will work for HTTPS requests
//Other sites will receive a certificate mismatch warning on browser //Other sites will receive a certificate mismatch warning on browser
//Please read about it before asking questions!
var transparentEndPoint = new TransparentProxyEndPoint(IPAddress.Any, 8001, true) var transparentEndPoint = new TransparentProxyEndPoint(IPAddress.Any, 8001, true)
{ {
GenericCertificateName = "google.com" GenericCertificateName = "google.com"
}; };
ProxyServer.AddEndPoint(transparentEndPoint); ProxyServer.AddEndPoint(transparentEndPoint);
//ProxyServer.UpStreamHttpProxy = new ExternalProxy() { HostName = "localhost", Port = 8888 };
//ProxyServer.UpStreamHttpsProxy = new ExternalProxy() { HostName = "localhost", Port = 8888 };
foreach (var endPoint in ProxyServer.ProxyEndPoints) foreach (var endPoint in ProxyServer.ProxyEndPoints)
Console.WriteLine("Listening on '{0}' endpoint at Ip {1} and port: {2} ", Console.WriteLine("Listening on '{0}' endpoint at Ip {1} and port: {2} ",
...@@ -59,32 +62,31 @@ namespace Titanium.Web.Proxy.Test ...@@ -59,32 +62,31 @@ namespace Titanium.Web.Proxy.Test
ProxyServer.Stop(); ProxyServer.Stop();
} }
//Test On Request, intecept requests //intecept & cancel, redirect or update requests
//Read browser URL send back to proxy by the injection script in OnResponse event public async Task OnRequest(object sender, SessionEventArgs e)
public void OnRequest(object sender, SessionEventArgs e)
{ {
Console.WriteLine(e.ProxySession.Request.Url); Console.WriteLine(e.WebSession.Request.Url);
//read request headers ////read request headers
var requestHeaders = e.ProxySession.Request.RequestHeaders; var requestHeaders = e.WebSession.Request.RequestHeaders;
if ((e.RequestMethod.ToUpper() == "POST" || e.RequestMethod.ToUpper() == "PUT")) if ((e.WebSession.Request.Method.ToUpper() == "POST" || e.WebSession.Request.Method.ToUpper() == "PUT"))
{ {
//Get/Set request body bytes //Get/Set request body bytes
byte[] bodyBytes = e.GetRequestBody(); byte[] bodyBytes = await e.GetRequestBody();
e.SetRequestBody(bodyBytes); await e.SetRequestBody(bodyBytes);
//Get/Set request body as string //Get/Set request body as string
string bodyString = e.GetRequestBodyAsString(); string bodyString = await e.GetRequestBodyAsString();
e.SetRequestBodyString(bodyString); await e.SetRequestBodyString(bodyString);
} }
//To cancel a request with a custom HTML content //To cancel a request with a custom HTML content
//Filter URL //Filter URL
if (e.ProxySession.Request.RequestUri.AbsoluteUri.Contains("google.com")) if (e.WebSession.Request.RequestUri.AbsoluteUri.Contains("google.com"))
{ {
e.Ok("<!DOCTYPE html>" + await e.Ok("<!DOCTYPE html>" +
"<html><body><h1>" + "<html><body><h1>" +
"Website Blocked" + "Website Blocked" +
"</h1>" + "</h1>" +
...@@ -93,35 +95,47 @@ namespace Titanium.Web.Proxy.Test ...@@ -93,35 +95,47 @@ namespace Titanium.Web.Proxy.Test
"</html>"); "</html>");
} }
//Redirect example //Redirect example
if (e.ProxySession.Request.RequestUri.AbsoluteUri.Contains("wikipedia.org")) if (e.WebSession.Request.RequestUri.AbsoluteUri.Contains("wikipedia.org"))
{ {
e.Redirect("https://www.paypal.com"); await e.Redirect("https://www.paypal.com");
} }
} }
//Test script injection //Modify response
//Insert script to read the Browser URL and send it back to proxy public async Task OnResponse(object sender, SessionEventArgs e)
public void OnResponse(object sender, SessionEventArgs e)
{ {
//read response headers //read response headers
var responseHeaders = e.ProxySession.Response.ResponseHeaders; var responseHeaders = e.WebSession.Response.ResponseHeaders;
//if (!e.ProxySession.Request.Host.Equals("medeczane.sgk.gov.tr")) return; //if (!e.ProxySession.Request.Host.Equals("medeczane.sgk.gov.tr")) return;
if (e.RequestMethod == "GET" || e.RequestMethod == "POST") if (e.WebSession.Request.Method == "GET" || e.WebSession.Request.Method == "POST")
{ {
if (e.ProxySession.Response.ResponseStatusCode == "200") if (e.WebSession.Response.ResponseStatusCode == "200")
{ {
if (e.ProxySession.Response.ContentType.Trim().ToLower().Contains("text/html")) if (e.WebSession.Response.ContentType!=null && e.WebSession.Response.ContentType.Trim().ToLower().Contains("text/html"))
{ {
byte[] bodyBytes = e.GetResponseBody(); byte[] bodyBytes = await e.GetResponseBody();
e.SetResponseBody(bodyBytes); await e.SetResponseBody(bodyBytes);
string body = e.GetResponseBodyAsString(); string body = await e.GetResponseBodyAsString();
e.SetResponseBodyString(body); await e.SetResponseBodyString(body);
}
} }
} }
} }
/// <summary>
/// Allows overriding default certificate validation logic
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
public async 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;
else
await e.Session.Ok("Cannot validate server certificate! Not safe to proceed.");
} }
} }
} }
\ No newline at end of file
...@@ -8,8 +8,8 @@ ...@@ -8,8 +8,8 @@
<ProjectGuid>{F3B7E553-1904-4E80-BDC7-212342B5C952}</ProjectGuid> <ProjectGuid>{F3B7E553-1904-4E80-BDC7-212342B5C952}</ProjectGuid>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder> <AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Titanium.Web.Proxy.Test</RootNamespace> <RootNamespace>Titanium.Web.Proxy.Examples.Basic</RootNamespace>
<AssemblyName>Titanium.Web.Proxy.Test</AssemblyName> <AssemblyName>Titanium.Web.Proxy.Examples.Basic</AssemblyName>
<FileAlignment>512</FileAlignment> <FileAlignment>512</FileAlignment>
<TargetFrameworkProfile /> <TargetFrameworkProfile />
</PropertyGroup> </PropertyGroup>
...@@ -65,7 +65,7 @@ ...@@ -65,7 +65,7 @@
<None Include="App.config" /> <None Include="App.config" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Titanium.Web.Proxy\Titanium.Web.Proxy.csproj"> <ProjectReference Include="..\..\Titanium.Web.Proxy\Titanium.Web.Proxy.csproj">
<Project>{8d73a1be-868c-42d2-9ece-f32cc1a02906}</Project> <Project>{8d73a1be-868c-42d2-9ece-f32cc1a02906}</Project>
<Name>Titanium.Web.Proxy</Name> <Name>Titanium.Web.Proxy</Name>
</ProjectReference> </ProjectReference>
......
...@@ -6,7 +6,7 @@ A light weight http(s) proxy server written in C# ...@@ -6,7 +6,7 @@ A light weight http(s) proxy server written in C#
Kindly report only issues/bugs here . For programming help or questions use [StackOverflow](http://stackoverflow.com/questions/tagged/titanium-web-proxy) with the tag Titanium-Web-Proxy. Kindly report only issues/bugs here . For programming help or questions use [StackOverflow](http://stackoverflow.com/questions/tagged/titanium-web-proxy) with the tag Titanium-Web-Proxy.
![alt tag](https://raw.githubusercontent.com/titanium007/Titanium/master/Titanium.Web.Proxy.Test/Capture.PNG) ![alt tag](https://raw.githubusercontent.com/justcoding121/Titanium-Web-Proxy/release/Examples/Titanium.Web.Proxy.Examples.Basic/Capture.PNG)
Features Features
======== ========
...@@ -32,29 +32,47 @@ After installing nuget package mark following files to be copied to app director ...@@ -32,29 +32,47 @@ After installing nuget package mark following files to be copied to app director
Setup HTTP proxy: Setup HTTP proxy:
```csharp ```csharp
// listen to client request & server response events
ProxyServer.BeforeRequest += OnRequest; ProxyServer.BeforeRequest += OnRequest;
ProxyServer.BeforeResponse += OnResponse; ProxyServer.BeforeResponse += OnResponse;
ProxyServer.ServerCertificateValidationCallback += OnCertificateValidation;
var explicitEndPoint = new ExplicitProxyEndPoint(IPAddress.Any, 8000, true){ //Exclude Https addresses you don't want to proxy
//Exclude Https addresses you don't want to proxy/cannot be proxied //Usefull for clients that use certificate pinning
//for example exclude dropbox client which use certificate pinning //for example dropbox.com
ExcludedHttpsHostNameRegex = new List<string>() { "dropbox.com" } var explicitEndPoint = new ExplicitProxyEndPoint(IPAddress.Any, 8000, true)
{
// ExcludedHttpsHostNameRegex = new List<string>() { "google.com", "dropbox.com" }
}; };
//Add an explicit endpoint where the client is aware of the proxy //An explicit endpoint is where the client knows about the existance of a proxy
//So client would send 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();
//Only explicit proxies can be set as a system proxy!
ProxyServer.SetAsSystemHttpProxy(explicitEndPoint); //Transparent endpoint is usefull for reverse proxying (client is not aware of the existance of proxy)
ProxyServer.SetAsSystemHttpsProxy(explicitEndPoint); //A transparent endpoint usually requires a network router port forwarding HTTP(S) packets to this endpoint
//Currently do not support Server Name Indication (It is not currently supported by SslStream class)
//That means that the transparent endpoint will always provide the same Generic Certificate to all HTTPS requests
//In this example only google.com will work for HTTPS requests
//Other sites will receive a certificate mismatch warning on browser
var transparentEndPoint = new TransparentProxyEndPoint(IPAddress.Any, 8001, true)
{
GenericCertificateName = "google.com"
};
ProxyServer.AddEndPoint(transparentEndPoint);
//ProxyServer.UpStreamHttpProxy = new ExternalProxy() { HostName = "localhost", Port = 8888 };
//ProxyServer.UpStreamHttpsProxy = new ExternalProxy() { HostName = "localhost", Port = 8888 };
foreach (var endPoint in ProxyServer.ProxyEndPoints) foreach (var endPoint in ProxyServer.ProxyEndPoints)
Console.WriteLine("Listening on '{0}' endpoint at Ip {1} and port: {2} ", Console.WriteLine("Listening on '{0}' endpoint at Ip {1} and port: {2} ",
endPoint.GetType().Name, endPoint.IpAddress, endPoint.Port); endPoint.GetType().Name, endPoint.IpAddress, endPoint.Port);
//Only explicit proxies can be set as system proxy!
ProxyServer.SetAsSystemHttpProxy(explicitEndPoint);
ProxyServer.SetAsSystemHttpsProxy(explicitEndPoint);
//wait here (You can use something else as a wait function, I am using this as a demo) //wait here (You can use something else as a wait function, I am using this as a demo)
Console.Read(); Console.Read();
...@@ -68,66 +86,81 @@ Sample request and response event handlers ...@@ -68,66 +86,81 @@ Sample request and response event handlers
```csharp ```csharp
//Test On Request, intecept requests //intecept & cancel, redirect or update requests
//Read browser URL send back to proxy by the injection script in OnResponse event public async Task OnRequest(object sender, SessionEventArgs e)
public void OnRequest(object sender, SessionEventArgs e)
{ {
Console.WriteLine(e.ProxySession.Request.Url); Console.WriteLine(e.WebSession.Request.Url);
//read request headers ////read request headers
var requestHeaders = e.ProxySession.Request.RequestHeaders; var requestHeaders = e.WebSession.Request.RequestHeaders;
if ((e.RequestMethod.ToUpper() == "POST" || e.RequestMethod.ToUpper() == "PUT")) if ((e.WebSession.Request.Method.ToUpper() == "POST" || e.WebSession.Request.Method.ToUpper() == "PUT"))
{ {
//Get/Set request body bytes //Get/Set request body bytes
byte[] bodyBytes = e.GetRequestBody(); byte[] bodyBytes = await e.GetRequestBody();
e.SetRequestBody(bodyBytes); await e.SetRequestBody(bodyBytes);
//Get/Set request body as string //Get/Set request body as string
string bodyString = e.GetRequestBodyAsString(); string bodyString = await e.GetRequestBodyAsString();
e.SetRequestBodyString(bodyString); await e.SetRequestBodyString(bodyString);
} }
//To cancel a request with a custom HTML content //To cancel a request with a custom HTML content
//Filter URL //Filter URL
if (e.WebSession.Request.RequestUri.AbsoluteUri.Contains("google.com"))
if (e.ProxySession.Request.RequestUri.AbsoluteUri.Contains("google.com"))
{ {
e.Ok("<!DOCTYPE html>"+ await e.Ok("<!DOCTYPE html>" +
"<html><body><h1>"+ "<html><body><h1>" +
"Website Blocked"+ "Website Blocked" +
"</h1>"+ "</h1>" +
"<p>Blocked by titanium web proxy.</p>"+ "<p>Blocked by titanium web proxy.</p>" +
"</body>"+ "</body>" +
"</html>"); "</html>");
} }
//Redirect example
if (e.WebSession.Request.RequestUri.AbsoluteUri.Contains("wikipedia.org"))
{
await e.Redirect("https://www.paypal.com");
}
} }
//Test script injection //Modify response
//Insert script to read the Browser URL and send it back to proxy public async Task OnResponse(object sender, SessionEventArgs e)
public void OnResponse(object sender, SessionEventArgs e)
{ {
//read response headers //read response headers
var responseHeaders = e.ProxySession.Response.ResponseHeaders; var responseHeaders = e.WebSession.Response.ResponseHeaders;
if (e.RequestMethod == "GET" || e.RequestMethod == "POST") //if (!e.ProxySession.Request.Host.Equals("medeczane.sgk.gov.tr")) return;
if (e.WebSession.Request.Method == "GET" || e.WebSession.Request.Method == "POST")
{ {
if (e.ProxySession.Response.ResponseStatusCode == "200") if (e.WebSession.Response.ResponseStatusCode == "200")
{ {
if (e.ProxySession.Response.ContentType.Trim().ToLower().Contains("text/html")) if (e.WebSession.Response.ContentType!=null && e.WebSession.Response.ContentType.Trim().ToLower().Contains("text/html"))
{ {
string body = e.GetResponseBodyAsString(); byte[] bodyBytes = await e.GetResponseBody();
await e.SetResponseBody(bodyBytes);
string body = await e.GetResponseBodyAsString();
await e.SetResponseBodyString(body);
}
} }
} }
} }
/// Allows overriding default certificate validation logic
public async 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;
else
await e.Session.Ok("Cannot validate server certificate! Not safe to proceed.");
} }
``` ```
Future roadmap Future roadmap
============ ============
* Add callbacks for client/server certificate validation/selection
* Support mutual authentication * Support mutual authentication
* Support Server Name Indication (SNI) for transparent endpoints * Support Server Name Indication (SNI) for transparent endpoints
* Support HTTP 2.0 * Support HTTP 2.0
......
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<ProjectView>ProjectFiles</ProjectView>
</PropertyGroup>
</Project>
\ No newline at end of file
...@@ -3,9 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00 ...@@ -3,9 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14 # Visual Studio 14
VisualStudioVersion = 14.0.25123.0 VisualStudioVersion = 14.0.25123.0
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{B6DBABDC-C985-4872-9C38-B4E5079CBC4B}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Examples", "Examples", "{B6DBABDC-C985-4872-9C38-B4E5079CBC4B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Titanium.Web.Proxy.Test", "Titanium.Web.Proxy.Test\Titanium.Web.Proxy.Test.csproj", "{F3B7E553-1904-4E80-BDC7-212342B5C952}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Titanium.Web.Proxy", "Titanium.Web.Proxy\Titanium.Web.Proxy.csproj", "{8D73A1BE-868C-42D2-9ECE-F32CC1A02906}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Titanium.Web.Proxy", "Titanium.Web.Proxy\Titanium.Web.Proxy.csproj", "{8D73A1BE-868C-42D2-9ECE-F32CC1A02906}"
EndProject EndProject
...@@ -16,20 +14,35 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{6FD3B8 ...@@ -16,20 +14,35 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{6FD3B8
.nuget\NuGet.targets = .nuget\NuGet.targets .nuget\NuGet.targets = .nuget\NuGet.targets
EndProjectSection EndProjectSection
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Titanium.Web.Proxy.Examples.Basic", "Examples\Titanium.Web.Proxy.Examples.Basic\Titanium.Web.Proxy.Examples.Basic.csproj", "{F3B7E553-1904-4E80-BDC7-212342B5C952}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Documentation", "Documentation", "{38EA62D0-D2CB-465D-AF4F-407C5B4D4A1E}"
ProjectSection(SolutionItems) = preProject
LICENSE = LICENSE
README.md = README.md
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{AC9AE37A-3059-4FDB-9A5C-363AD86F2EEF}"
ProjectSection(SolutionItems) = preProject
.build\Bootstrap.ps1 = .build\Bootstrap.ps1
.build\Common.psm1 = .build\Common.psm1
.build\default.ps1 = .build\default.ps1
EndProjectSection
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU Release|Any CPU = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution GlobalSection(ProjectConfigurationPlatforms) = postSolution
{F3B7E553-1904-4E80-BDC7-212342B5C952}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F3B7E553-1904-4E80-BDC7-212342B5C952}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F3B7E553-1904-4E80-BDC7-212342B5C952}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F3B7E553-1904-4E80-BDC7-212342B5C952}.Release|Any CPU.Build.0 = Release|Any CPU
{8D73A1BE-868C-42D2-9ECE-F32CC1A02906}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {8D73A1BE-868C-42D2-9ECE-F32CC1A02906}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8D73A1BE-868C-42D2-9ECE-F32CC1A02906}.Debug|Any CPU.Build.0 = Debug|Any CPU {8D73A1BE-868C-42D2-9ECE-F32CC1A02906}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8D73A1BE-868C-42D2-9ECE-F32CC1A02906}.Release|Any CPU.ActiveCfg = Release|Any CPU {8D73A1BE-868C-42D2-9ECE-F32CC1A02906}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8D73A1BE-868C-42D2-9ECE-F32CC1A02906}.Release|Any CPU.Build.0 = Release|Any CPU {8D73A1BE-868C-42D2-9ECE-F32CC1A02906}.Release|Any CPU.Build.0 = Release|Any CPU
{F3B7E553-1904-4E80-BDC7-212342B5C952}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F3B7E553-1904-4E80-BDC7-212342B5C952}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F3B7E553-1904-4E80-BDC7-212342B5C952}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F3B7E553-1904-4E80-BDC7-212342B5C952}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
......
using System.IO; using System.IO;
using System.IO.Compression; using System.IO.Compression;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Compression namespace Titanium.Web.Proxy.Compression
{ {
class DeflateCompression : ICompression class DeflateCompression : ICompression
{ {
public byte[] Compress(byte[] responseBody) public async Task<byte[]> Compress(byte[] responseBody)
{ {
using (var ms = new MemoryStream()) using (var ms = new MemoryStream())
{ {
using (var zip = new DeflateStream(ms, CompressionMode.Compress, true)) using (var zip = new DeflateStream(ms, CompressionMode.Compress, true))
{ {
zip.Write(responseBody, 0, responseBody.Length); await zip.WriteAsync(responseBody, 0, responseBody.Length).ConfigureAwait(false);
} }
return ms.ToArray(); return ms.ToArray();
......
using Ionic.Zlib; using Ionic.Zlib;
using System.IO; using System.IO;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Compression namespace Titanium.Web.Proxy.Compression
{ {
class GZipCompression : ICompression class GZipCompression : ICompression
{ {
public byte[] Compress(byte[] responseBody) public async Task<byte[]> Compress(byte[] responseBody)
{ {
using (var ms = new MemoryStream()) using (var ms = new MemoryStream())
{ {
using (var zip = new GZipStream(ms, CompressionMode.Compress, true)) using (var zip = new GZipStream(ms, CompressionMode.Compress, true))
{ {
zip.Write(responseBody, 0, responseBody.Length); await zip.WriteAsync(responseBody, 0, responseBody.Length).ConfigureAwait(false);
} }
return ms.ToArray(); return ms.ToArray();
......
namespace Titanium.Web.Proxy.Compression using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Compression
{ {
interface ICompression interface ICompression
{ {
byte[] Compress(byte[] responseBody); Task<byte[]> Compress(byte[] responseBody);
} }
} }
using Ionic.Zlib; using Ionic.Zlib;
using System.IO; using System.IO;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Compression namespace Titanium.Web.Proxy.Compression
{ {
class ZlibCompression : ICompression class ZlibCompression : ICompression
{ {
public byte[] Compress(byte[] responseBody) public async Task<byte[]> Compress(byte[] responseBody)
{ {
using (var ms = new MemoryStream()) using (var ms = new MemoryStream())
{ {
using (var zip = new ZlibStream(ms, CompressionMode.Compress, true)) using (var zip = new ZlibStream(ms, CompressionMode.Compress, true))
{ {
zip.Write(responseBody, 0, responseBody.Length); await zip.WriteAsync(responseBody, 0, responseBody.Length).ConfigureAwait(false);
} }
return ms.ToArray(); return ms.ToArray();
......
namespace Titanium.Web.Proxy.Decompression using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Decompression
{ {
class DefaultDecompression : IDecompression class DefaultDecompression : IDecompression
{ {
public byte[] Decompress(byte[] compressedArray) public Task<byte[]> Decompress(byte[] compressedArray)
{ {
return compressedArray; return Task.FromResult(compressedArray);
} }
} }
} }
using Ionic.Zlib; using Ionic.Zlib;
using System.IO; using System.IO;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Decompression namespace Titanium.Web.Proxy.Decompression
{ {
class DeflateDecompression : IDecompression class DeflateDecompression : IDecompression
{ {
public byte[] Decompress(byte[] compressedArray) public async Task<byte[]> Decompress(byte[] compressedArray)
{ {
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[ProxyServer.BUFFER_SIZE]; var buffer = new byte[Constants.BUFFER_SIZE];
using (var output = new MemoryStream()) using (var output = new MemoryStream())
{ {
int read; int read;
while ((read = decompressor.Read(buffer, 0, buffer.Length)) > 0) while ((read = await decompressor.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false)) > 0)
{ {
output.Write(buffer, 0, read); await output.WriteAsync(buffer, 0, read).ConfigureAwait(false);
} }
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 Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Decompression namespace Titanium.Web.Proxy.Decompression
{ {
class GZipDecompression : IDecompression class GZipDecompression : IDecompression
{ {
public byte[] Decompress(byte[] compressedArray) public async Task<byte[]> Decompress(byte[] compressedArray)
{ {
using (var decompressor = new GZipStream(new MemoryStream(compressedArray), CompressionMode.Decompress)) using (var decompressor = new GZipStream(new MemoryStream(compressedArray), CompressionMode.Decompress))
{ {
var buffer = new byte[ProxyServer.BUFFER_SIZE]; var buffer = new byte[Constants.BUFFER_SIZE];
using (var output = new MemoryStream()) using (var output = new MemoryStream())
{ {
int read; int read;
while ((read = decompressor.Read(buffer, 0, buffer.Length)) > 0) while ((read = await decompressor.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false)) > 0)
{ {
output.Write(buffer, 0, read); await output.WriteAsync(buffer, 0, read).ConfigureAwait(false);
} }
return output.ToArray(); return output.ToArray();
} }
......
using System.IO; using System.IO;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Decompression namespace Titanium.Web.Proxy.Decompression
{ {
interface IDecompression interface IDecompression
{ {
byte[] Decompress(byte[] compressedArray); Task<byte[]> Decompress(byte[] compressedArray);
} }
} }
using Ionic.Zlib; using Ionic.Zlib;
using System.IO; using System.IO;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Decompression namespace Titanium.Web.Proxy.Decompression
{ {
class ZlibDecompression : IDecompression class ZlibDecompression : IDecompression
{ {
public byte[] Decompress(byte[] compressedArray) public async Task<byte[]> Decompress(byte[] compressedArray)
{ {
var memoryStream = new MemoryStream(compressedArray); var memoryStream = new MemoryStream(compressedArray);
using (var decompressor = new ZlibStream(memoryStream, CompressionMode.Decompress)) using (var decompressor = new ZlibStream(memoryStream, CompressionMode.Decompress))
{ {
var buffer = new byte[ProxyServer.BUFFER_SIZE]; var buffer = new byte[Constants.BUFFER_SIZE];
using (var output = new MemoryStream()) using (var output = new MemoryStream())
{ {
int read; int read;
while ((read = decompressor.Read(buffer, 0, buffer.Length)) > 0) while ((read = await decompressor.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false)) > 0)
{ {
output.Write(buffer, 0, read); await output.WriteAsync(buffer, 0, read).ConfigureAwait(false);
} }
return output.ToArray(); return output.ToArray();
} }
......
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Http;
namespace Titanium.Web.Proxy.EventArguments
{
public class CertificateValidationEventArgs : EventArgs, IDisposable
{
public string HostName => Session.WebSession.Request.Host;
public SessionEventArgs Session { get; internal set; }
public X509Certificate Certificate { get; internal set; }
public X509Chain Chain { get; internal set; }
public SslPolicyErrors SslPolicyErrors { get; internal set; }
public bool IsValid { get; set; }
public void Dispose()
{
}
}
}
using System.Net; using System.Text;
using System.Text; using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Network; using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Extensions namespace Titanium.Web.Proxy.Extensions
{ {
...@@ -15,10 +15,11 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -15,10 +15,11 @@ namespace Titanium.Web.Proxy.Extensions
try try
{ {
//return default if not specified //return default if not specified
if (request.ContentType == null) return Encoding.GetEncoding("ISO-8859-1"); if (request.ContentType == null)
return Encoding.GetEncoding("ISO-8859-1");
//extract the encoding by finding the charset //extract the encoding by finding the charset
var contentTypes = request.ContentType.Split(';'); var contentTypes = request.ContentType.Split(Constants.SemiColonSplit);
foreach (var contentType in contentTypes) foreach (var contentType in contentTypes)
{ {
var encodingSplit = contentType.Split('='); var encodingSplit = contentType.Split('=');
......
using System.Net; using System.Text;
using System.Text; using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Network; using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Extensions namespace Titanium.Web.Proxy.Extensions
{ {
public static class HttpWebResponseExtensions public static class HttpWebResponseExtensions
{ {
public static Encoding GetResponseEncoding(this Response response) public static Encoding GetResponseCharacterEncoding(this Response response)
{ {
if (string.IsNullOrEmpty(response.CharacterSet)) try
{
//return default if not specified
if (response.ContentType == null)
return Encoding.GetEncoding("ISO-8859-1"); return Encoding.GetEncoding("ISO-8859-1");
try //extract the encoding by finding the charset
var contentTypes = response.ContentType.Split(Constants.SemiColonSplit);
foreach (var contentType in contentTypes)
{
var encodingSplit = contentType.Split('=');
if (encodingSplit.Length == 2 && encodingSplit[0].ToLower().Trim() == "charset")
{
return Encoding.GetEncoding(encodingSplit[1]);
}
}
}
catch
{ {
return Encoding.GetEncoding(response.CharacterSet.Replace(@"""", string.Empty)); //parsing errors
// ignored
} }
catch { return Encoding.GetEncoding("ISO-8859-1"); }
//return default if not specified
return Encoding.GetEncoding("ISO-8859-1");
} }
} }
} }
\ No newline at end of file
using System; using System;
using System.Globalization;
using System.IO; using System.IO;
using System.Text; using System.Text;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Extensions namespace Titanium.Web.Proxy.Extensions
{ {
public static class StreamHelper public static class StreamHelper
{ {
public static void CopyToAsync(this Stream input, string initialData, Stream output, int bufferSize) public static async Task CopyToAsync(this Stream input, string initialData, Stream output)
{ {
if(!string.IsNullOrEmpty(initialData)) if (!string.IsNullOrEmpty(initialData))
{ {
var bytes = Encoding.ASCII.GetBytes(initialData); var bytes = Encoding.ASCII.GetBytes(initialData);
output.Write(bytes, 0, bytes.Length); await output.WriteAsync(bytes, 0, bytes.Length);
} }
await input.CopyToAsync(output);
CopyToAsync(input, output, bufferSize);
} }
//http://stackoverflow.com/questions/1540658/net-asynchronous-stream-read-write internal static async Task CopyBytesToStream(this CustomBinaryReader streamReader, Stream stream, long totalBytesToRead)
private static void CopyToAsync(this Stream input, Stream output, int bufferSize)
{ {
try var totalbytesRead = 0;
long bytesToRead;
if (totalBytesToRead < Constants.BUFFER_SIZE)
{ {
if (!input.CanRead) throw new InvalidOperationException("input must be open for reading"); bytesToRead = totalBytesToRead;
if (!output.CanWrite) throw new InvalidOperationException("output must be open for writing"); }
else
bytesToRead = Constants.BUFFER_SIZE;
byte[][] buf = {new byte[bufferSize], new byte[bufferSize]};
int[] bufl = {0, 0};
var bufno = 0;
var read = input.BeginRead(buf[bufno], 0, buf[bufno].Length, null, null);
IAsyncResult write = null;
while (true) while (totalbytesRead < totalBytesToRead)
{ {
// wait for the read operation to complete var buffer = await streamReader.ReadBytesAsync(bytesToRead);
read.AsyncWaitHandle.WaitOne();
bufl[bufno] = input.EndRead(read);
// if zero bytes read, the copy is complete if (buffer.Length == 0)
if (bufl[bufno] == 0)
{
break; break;
}
// wait for the in-flight write operation, if one exists, to complete totalbytesRead += buffer.Length;
// the only time one won't exist is after the very first read operation completes
if (write != null) var remainingBytes = totalBytesToRead - totalbytesRead;
if (remainingBytes < bytesToRead)
{ {
write.AsyncWaitHandle.WaitOne(); bytesToRead = remainingBytes;
output.EndWrite(write);
} }
await stream.WriteAsync(buffer, 0, buffer.Length);
// start the new write operation
write = output.BeginWrite(buf[bufno], 0, bufl[bufno], null, null);
// toggle the current, in-use buffer
// and start the read operation on the new buffer.
//
// Changed to use XOR to toggle between 0 and 1.
// A little speedier than using a ternary expression.
bufno ^= 1; // bufno = ( bufno == 0 ? 1 : 0 ) ;
read = input.BeginRead(buf[bufno], 0, buf[bufno].Length, null, null);
} }
// wait for the final in-flight write operation, if one exists, to complete
// the only time one won't exist is if the input stream is empty.
if (write != null)
{
write.AsyncWaitHandle.WaitOne();
output.EndWrite(write);
} }
internal static async Task CopyBytesToStreamChunked(this CustomBinaryReader clientStreamReader, Stream stream)
{
while (true)
{
var chuchkHead = await clientStreamReader.ReadLineAsync().ConfigureAwait(false);
var chunkSize = int.Parse(chuchkHead, NumberStyles.HexNumber);
output.Flush(); if (chunkSize != 0)
{
var buffer = await clientStreamReader.ReadBytesAsync(chunkSize);
await stream.WriteAsync(buffer, 0, buffer.Length);
//chunk trail
await clientStreamReader.ReadLineAsync().ConfigureAwait(false);
} }
catch else
{ {
// ignored await clientStreamReader.ReadLineAsync().ConfigureAwait(false);
break;
}
} }
// return to the caller ;
} }
} }
} }
\ No newline at end of file
using System; using System.Net.Sockets;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Extensions namespace Titanium.Web.Proxy.Extensions
......
...@@ -4,6 +4,8 @@ using System.Diagnostics; ...@@ -4,6 +4,8 @@ using System.Diagnostics;
using System.Collections.Generic; using System.Collections.Generic;
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
using System.Reflection; using System.Reflection;
using System.Threading.Tasks;
using System.Threading;
namespace Titanium.Web.Proxy.Helpers namespace Titanium.Web.Proxy.Helpers
{ {
...@@ -13,6 +15,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -13,6 +15,7 @@ namespace Titanium.Web.Proxy.Helpers
"-ss {0} -n \"CN={1}, O={2}\" -sky {3} -cy {4} -m 120 -a sha256 -eku 1.3.6.1.5.5.7.3.1 {5}"; "-ss {0} -n \"CN={1}, O={2}\" -sky {3} -cy {4} -m 120 -a sha256 -eku 1.3.6.1.5.5.7.3.1 {5}";
private readonly IDictionary<string, X509Certificate2> _certificateCache; private readonly IDictionary<string, X509Certificate2> _certificateCache;
private static SemaphoreSlim semaphoreLock = new SemaphoreSlim(1);
public string Issuer { get; private set; } public string Issuer { get; private set; }
public string RootCertificateName { get; private set; } public string RootCertificateName { get; private set; }
...@@ -35,10 +38,10 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -35,10 +38,10 @@ namespace Titanium.Web.Proxy.Helpers
/// Attempts to move a self-signed certificate to the root store. /// Attempts to move a self-signed certificate to the root store.
/// </summary> /// </summary>
/// <returns>true if succeeded, else false</returns> /// <returns>true if succeeded, else false</returns>
public bool CreateTrustedRootCertificate() public async Task<bool> CreateTrustedRootCertificate()
{ {
X509Certificate2 rootCertificate = X509Certificate2 rootCertificate =
CreateCertificate(RootStore, RootCertificateName); await CreateCertificate(RootStore, RootCertificateName);
return rootCertificate != null; return rootCertificate != null;
} }
...@@ -46,9 +49,9 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -46,9 +49,9 @@ namespace Titanium.Web.Proxy.Helpers
/// Attempts to remove the self-signed certificate from the root store. /// Attempts to remove the self-signed certificate from the root store.
/// </summary> /// </summary>
/// <returns>true if succeeded, else false</returns> /// <returns>true if succeeded, else false</returns>
public bool DestroyTrustedRootCertificate() public async Task<bool> DestroyTrustedRootCertificate()
{ {
return DestroyCertificate(RootStore, RootCertificateName); return await DestroyCertificate(RootStore, RootCertificateName);
} }
public X509Certificate2Collection FindCertificates(string certificateSubject) public X509Certificate2Collection FindCertificates(string certificateSubject)
...@@ -64,18 +67,18 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -64,18 +67,18 @@ namespace Titanium.Web.Proxy.Helpers
discoveredCertificates : null; discoveredCertificates : null;
} }
public X509Certificate2 CreateCertificate(string certificateName) public async Task<X509Certificate2> CreateCertificate(string certificateName)
{ {
return CreateCertificate(MyStore, certificateName); return await CreateCertificate(MyStore, certificateName);
} }
protected virtual X509Certificate2 CreateCertificate(X509Store store, string certificateName) protected async virtual Task<X509Certificate2> CreateCertificate(X509Store store, string certificateName)
{ {
if (_certificateCache.ContainsKey(certificateName)) if (_certificateCache.ContainsKey(certificateName))
return _certificateCache[certificateName]; return _certificateCache[certificateName];
lock (store) await semaphoreLock.WaitAsync();
{
X509Certificate2 certificate = null; X509Certificate2 certificate = null;
try try
{ {
...@@ -93,26 +96,33 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -93,26 +96,33 @@ namespace Titanium.Web.Proxy.Helpers
string[] args = new[] { string[] args = new[] {
GetCertificateCreateArgs(store, certificateName) }; GetCertificateCreateArgs(store, certificateName) };
CreateCertificate(args); await CreateCertificate(args);
certificates = FindCertificates(store, certificateSubject); certificates = FindCertificates(store, certificateSubject);
return certificates != null ? return certificates != null ?
certificates[0] : null; certificates[0] : null;
} }
return certificate;
}
finally
{
store.Close(); store.Close();
if (certificate != null && !_certificateCache.ContainsKey(certificateName)) if (certificate != null && !_certificateCache.ContainsKey(certificateName))
_certificateCache.Add(certificateName, certificate); _certificateCache.Add(certificateName, certificate);
return certificate;
} }
finally
{
semaphoreLock.Release();
} }
} }
protected virtual void CreateCertificate(string[] args) protected virtual Task<int> CreateCertificate(string[] args)
{
using (var process = new Process())
{ {
// there is no non-generic TaskCompletionSource
var tcs = new TaskCompletionSource<int>();
var process = new Process();
string file = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "makecert.exe"); string file = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "makecert.exe");
if (!File.Exists(file)) if (!File.Exists(file))
...@@ -123,20 +133,34 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -123,20 +133,34 @@ namespace Titanium.Web.Proxy.Helpers
process.StartInfo.CreateNoWindow = true; process.StartInfo.CreateNoWindow = true;
process.StartInfo.UseShellExecute = false; process.StartInfo.UseShellExecute = false;
process.StartInfo.FileName = file; process.StartInfo.FileName = file;
process.EnableRaisingEvents = true;
process.Start(); process.Exited += (sender, processArgs) =>
process.WaitForExit(); {
tcs.SetResult(process.ExitCode);
process.Dispose();
};
bool started = process.Start();
if (!started)
{
//you may allow for the process to be re-used (started = false)
//but I'm not sure about the guarantees of the Exited event in such a case
throw new InvalidOperationException("Could not start process: " + process);
} }
return tcs.Task;
} }
public bool DestroyCertificate(string certificateName) public async Task<bool> DestroyCertificate(string certificateName)
{ {
return DestroyCertificate(MyStore, certificateName); return await DestroyCertificate(MyStore, certificateName);
} }
protected virtual bool DestroyCertificate(X509Store store, string certificateName) protected virtual async Task<bool> DestroyCertificate(X509Store store, string certificateName)
{
lock (store)
{ {
await semaphoreLock.WaitAsync();
X509Certificate2Collection certificates = null; X509Certificate2Collection certificates = null;
try try
{ {
...@@ -149,18 +173,20 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -149,18 +173,20 @@ namespace Titanium.Web.Proxy.Helpers
store.RemoveRange(certificates); store.RemoveRange(certificates);
certificates = FindCertificates(store, certificateSubject); certificates = FindCertificates(store, certificateSubject);
} }
return certificates == null;
}
finally
{
store.Close(); store.Close();
if (certificates == null && if (certificates == null &&
_certificateCache.ContainsKey(certificateName)) _certificateCache.ContainsKey(certificateName))
{ {
_certificateCache.Remove(certificateName); _certificateCache.Remove(certificateName);
} }
return certificates == null;
} }
finally
{
semaphoreLock.Release();
} }
} }
protected virtual string GetCertificateCreateArgs(X509Store store, string certificateName) protected virtual string GetCertificateCreateArgs(X509Store store, string certificateName)
......
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Net.Security;
using System.Net.Sockets;
using System.Text; using System.Text;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Network;
using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Helpers namespace Titanium.Web.Proxy.Helpers
{ {
...@@ -10,27 +15,33 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -10,27 +15,33 @@ namespace Titanium.Web.Proxy.Helpers
/// using the specified encoding /// using the specified encoding
/// as well as to read bytes as required /// as well as to read bytes as required
/// </summary> /// </summary>
public class CustomBinaryReader : BinaryReader public class CustomBinaryReader : IDisposable
{ {
internal CustomBinaryReader(Stream stream, Encoding encoding) private Stream stream;
: base(stream, encoding)
internal CustomBinaryReader(Stream stream)
{ {
this.stream = stream;
} }
public Stream BaseStream => stream;
/// <summary> /// <summary>
/// Read a line from the byte stream /// Read a line from the byte stream
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
internal string ReadLine() internal async Task<string> ReadLineAsync()
{ {
var readBuffer = new StringBuilder(); var readBuffer = new StringBuilder();
try try
{ {
var lastChar = default(char); var lastChar = default(char);
var buffer = new char[1]; var buffer = new byte[1];
while (Read(buffer, 0, 1) > 0) while (await this.stream.ReadAsync(buffer, 0, 1).ConfigureAwait(false) > 0)
{ {
if (lastChar == '\r' && buffer[0] == '\n') if (lastChar == '\r' && buffer[0] == '\n')
{ {
...@@ -40,8 +51,8 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -40,8 +51,8 @@ namespace Titanium.Web.Proxy.Helpers
{ {
return readBuffer.ToString(); return readBuffer.ToString();
} }
readBuffer.Append(buffer); readBuffer.Append((char)buffer[0]);
lastChar = buffer[0]; lastChar = (char)buffer[0];
} }
return readBuffer.ToString(); return readBuffer.ToString();
...@@ -56,15 +67,52 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -56,15 +67,52 @@ namespace Titanium.Web.Proxy.Helpers
/// Read until the last new line /// Read until the last new line
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
internal List<string> ReadAllLines() internal async Task<List<string>> ReadAllLinesAsync()
{ {
string tmpLine; string tmpLine;
var requestLines = new List<string>(); var requestLines = new List<string>();
while (!string.IsNullOrEmpty(tmpLine = ReadLine())) while (!string.IsNullOrEmpty(tmpLine = await ReadLineAsync().ConfigureAwait(false)))
{ {
requestLines.Add(tmpLine); requestLines.Add(tmpLine);
} }
return requestLines; return requestLines;
} }
internal async Task<byte[]> ReadBytesAsync(long totalBytesToRead)
{
int bytesToRead = Constants.BUFFER_SIZE;
if (totalBytesToRead < Constants.BUFFER_SIZE)
bytesToRead = (int)totalBytesToRead;
var buffer = new byte[Constants.BUFFER_SIZE];
var bytesRead = 0;
var totalBytesRead = 0;
using (var outStream = new MemoryStream())
{
while ((bytesRead += await this.stream.ReadAsync(buffer, 0, bytesToRead).ConfigureAwait(false)) > 0)
{
await outStream.WriteAsync(buffer, 0, bytesRead).ConfigureAwait(false);
totalBytesRead += bytesRead;
if (totalBytesRead == totalBytesToRead)
break;
bytesRead = 0;
var remainingBytes = (totalBytesToRead - totalBytesRead);
bytesToRead = remainingBytes > (long)Constants.BUFFER_SIZE ? Constants.BUFFER_SIZE : (int)remainingBytes;
}
return outStream.ToArray();
}
}
public void Dispose()
{
}
} }
} }
\ No newline at end of file
...@@ -8,14 +8,13 @@ using System.Text; ...@@ -8,14 +8,13 @@ using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Titanium.Web.Proxy.Extensions; using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Models; using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Helpers namespace Titanium.Web.Proxy.Helpers
{ {
public class TcpHelper public class TcpHelper
{ {
private static readonly int BUFFER_SIZE = 8192; public async static Task SendRaw(Stream clientStream, string httpCmd, List<HttpHeader> requestHeaders, string hostName,
public static void SendRaw(Stream clientStream, string httpCmd, List<HttpHeader> requestHeaders, string hostName,
int tunnelPort, bool isHttps) int tunnelPort, bool isHttps)
{ {
StringBuilder sb = null; StringBuilder sb = null;
...@@ -51,7 +50,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -51,7 +50,7 @@ namespace Titanium.Web.Proxy.Helpers
try try
{ {
sslStream = new SslStream(tunnelStream); sslStream = new SslStream(tunnelStream);
sslStream.AuthenticateAsClient(hostName, null, ProxyServer.SupportedProtocols, false); await sslStream.AuthenticateAsClientAsync(hostName, null, Constants.SupportedProtocols, false);
tunnelStream = sslStream; tunnelStream = sslStream;
} }
catch catch
...@@ -63,18 +62,17 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -63,18 +62,17 @@ namespace Titanium.Web.Proxy.Helpers
} }
} }
Task sendRelay;
var sendRelay = Task.Factory.StartNew(() =>
{
if (sb != null) if (sb != null)
clientStream.CopyToAsync(sb.ToString(), tunnelStream, BUFFER_SIZE); sendRelay = clientStream.CopyToAsync(sb.ToString(), tunnelStream);
else else
clientStream.CopyToAsync(string.Empty, tunnelStream, BUFFER_SIZE); sendRelay = clientStream.CopyToAsync(string.Empty, tunnelStream);
});
var receiveRelay = Task.Factory.StartNew(() => tunnelStream.CopyToAsync(string.Empty, clientStream, BUFFER_SIZE)); var receiveRelay = tunnelStream.CopyToAsync(string.Empty, clientStream);
Task.WaitAll(sendRelay, receiveRelay); await Task.WhenAll(sendRelay, receiveRelay).ConfigureAwait(false);
} }
catch catch
{ {
......
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Network;
using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Http
{
public class HttpWebSession
{
internal TcpConnection ServerConnection { get; set; }
public Request Request { get; set; }
public Response Response { get; set; }
public bool IsSecure
{
get
{
return this.Request.RequestUri.Scheme == Uri.UriSchemeHttps;
}
}
internal void SetConnection(TcpConnection Connection)
{
Connection.LastAccess = DateTime.Now;
ServerConnection = Connection;
}
internal HttpWebSession()
{
this.Request = new Request();
this.Response = new Response();
}
internal async Task SendRequest()
{
Stream stream = ServerConnection.Stream;
StringBuilder requestLines = new StringBuilder();
requestLines.AppendLine(string.Join(" ", new string[3]
{
this.Request.Method,
this.Request.RequestUri.PathAndQuery,
this.Request.HttpVersion
}));
foreach (HttpHeader httpHeader in this.Request.RequestHeaders)
{
requestLines.AppendLine(httpHeader.Name + ':' + httpHeader.Value);
}
requestLines.AppendLine();
string request = requestLines.ToString();
byte[] requestBytes = Encoding.ASCII.GetBytes(request);
await stream.WriteAsync(requestBytes, 0, requestBytes.Length);
await stream.FlushAsync();
if (ProxyServer.Enable100ContinueBehaviour)
if (this.Request.ExpectContinue)
{
var httpResult = (await ServerConnection.StreamReader.ReadLineAsync()).Split(Constants.SpaceSplit, 3);
var responseStatusCode = httpResult[1].Trim();
var responseStatusDescription = httpResult[2].Trim();
//find if server is willing for expect continue
if (responseStatusCode.Equals("100")
&& responseStatusDescription.ToLower().Equals("continue"))
{
this.Request.Is100Continue = true;
await ServerConnection.StreamReader.ReadLineAsync().ConfigureAwait(false);
}
else if (responseStatusCode.Equals("417")
&& responseStatusDescription.ToLower().Equals("expectation failed"))
{
this.Request.ExpectationFailed = true;
await ServerConnection.StreamReader.ReadLineAsync().ConfigureAwait(false);
}
}
}
internal async Task ReceiveResponse()
{
//return if this is already read
if (this.Response.ResponseStatusCode != null) return;
var httpResult = (await ServerConnection.StreamReader.ReadLineAsync()).Split(Constants.SpaceSplit, 3);
if (string.IsNullOrEmpty(httpResult[0]))
{
await ServerConnection.StreamReader.ReadLineAsync().ConfigureAwait(false);
}
this.Response.HttpVersion = httpResult[0].Trim();
this.Response.ResponseStatusCode = httpResult[1].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
if (this.Response.ResponseStatusCode.Equals("100")
&& this.Response.ResponseStatusDescription.ToLower().Equals("continue"))
{
this.Response.Is100Continue = true;
this.Response.ResponseStatusCode = null;
await ServerConnection.StreamReader.ReadLineAsync().ConfigureAwait(false);
await ReceiveResponse();
return;
}
else if (this.Response.ResponseStatusCode.Equals("417")
&& this.Response.ResponseStatusDescription.ToLower().Equals("expectation failed"))
{
this.Response.ExpectationFailed = true;
this.Response.ResponseStatusCode = null;
await ServerConnection.StreamReader.ReadLineAsync().ConfigureAwait(false);
await ReceiveResponse();
return;
}
List<string> responseLines = await ServerConnection.StreamReader.ReadAllLinesAsync().ConfigureAwait(false);
for (int index = 0; index < responseLines.Count; ++index)
{
string[] strArray = responseLines[index].Split(Constants.ColonSplit, 2);
this.Response.ResponseHeaders.Add(new HttpHeader(strArray[0], strArray[1]));
}
}
}
}
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Extensions;
namespace Titanium.Web.Proxy.Http
{
public class Response
{
public string ResponseStatusCode { get; set; }
public string ResponseStatusDescription { get; set; }
internal Encoding Encoding { get { return this.GetResponseCharacterEncoding(); } }
internal string ContentEncoding
{
get
{
var header = this.ResponseHeaders.FirstOrDefault(x => x.Name.ToLower().Equals("content-encoding"));
if (header != null)
{
return header.Value.Trim();
}
return null;
}
}
internal string HttpVersion { get; set; }
internal bool ResponseKeepAlive
{
get
{
var header = this.ResponseHeaders.FirstOrDefault(x => x.Name.ToLower().Equals("connection"));
if (header != null && header.Value.ToLower().Contains("close"))
{
return false;
}
return true;
}
}
public string ContentType
{
get
{
var header = this.ResponseHeaders.FirstOrDefault(x => x.Name.ToLower().Equals("content-type"));
if (header != null)
{
return header.Value;
}
return null;
}
}
internal long ContentLength
{
get
{
var header = this.ResponseHeaders.FirstOrDefault(x => x.Name.ToLower().Equals("content-length"));
if (header == null)
return -1;
long contentLen;
long.TryParse(header.Value, out contentLen);
if (contentLen >= 0)
return contentLen;
return -1;
}
set
{
var header = this.ResponseHeaders.FirstOrDefault(x => x.Name.ToLower().Equals("content-length"));
if (value >= 0)
{
if (header != null)
header.Value = value.ToString();
else
ResponseHeaders.Add(new HttpHeader("content-length", value.ToString()));
IsChunked = false;
}
else
{
if (header != null)
this.ResponseHeaders.Remove(header);
}
}
}
internal bool IsChunked
{
get
{
var header = this.ResponseHeaders.FirstOrDefault(x => x.Name.ToLower().Equals("transfer-encoding"));
if (header != null && header.Value.ToLower().Contains("chunked"))
{
return true;
}
return false;
}
set
{
var header = this.ResponseHeaders.FirstOrDefault(x => x.Name.ToLower().Equals("transfer-encoding"));
if (value)
{
if (header != null)
{
header.Value = "chunked";
}
else
ResponseHeaders.Add(new HttpHeader("transfer-encoding", "chunked"));
this.ContentLength = -1;
}
else
{
if (header != null)
ResponseHeaders.Remove(header);
}
}
}
public List<HttpHeader> ResponseHeaders { get; set; }
internal Stream ResponseStream { get; set; }
internal byte[] ResponseBody { get; set; }
internal string ResponseBodyString { get; set; }
internal bool ResponseBodyRead { get; set; }
internal bool ResponseLocked { get; set; }
public bool Is100Continue { get; internal set; }
public bool ExpectationFailed { get; internal set; }
public Response()
{
this.ResponseHeaders = new List<HttpHeader>();
}
}
}
using System; using Titanium.Web.Proxy.Network;
using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Network;
namespace Titanium.Web.Proxy.Responses namespace Titanium.Web.Proxy.Http.Responses
{ {
public class OkResponse : Response public class OkResponse : Response
{ {
......
using System; using Titanium.Web.Proxy.Network;
using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Network;
namespace Titanium.Web.Proxy.Responses namespace Titanium.Web.Proxy.Http.Responses
{ {
public class RedirectResponse : Response public class RedirectResponse : Response
{ {
......
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.Text;
namespace Titanium.Web.Proxy.Models namespace Titanium.Web.Proxy.Models
{ {
......
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Models
{
public class ExternalProxy
{
public string HostName { get; set; }
public int Port { get; set; }
}
}
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Security;
using System.Text;
using System.Threading.Tasks;
using Titanium.Web.Proxy.EventArguments;
namespace Titanium.Web.Proxy.Network
{
/// <summary>
/// Used to pass in Session object for ServerCertificateValidation Callback
/// </summary>
internal class CustomSslStream : SslStream
{
/// <summary>
/// Holds the current session
/// </summary>
internal SessionEventArgs Session { get; set; }
public CustomSslStream(Stream innerStream, bool leaveInnerStreamOpen, RemoteCertificateValidationCallback userCertificateValidationCallback)
:base(innerStream, leaveInnerStreamOpen, userCertificateValidationCallback)
{
}
}
}
using System; using System.IO;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Sockets; using System.Net.Sockets;
using System.Text;
using Titanium.Web.Proxy.Helpers; using Titanium.Web.Proxy.Helpers;
namespace Titanium.Web.Proxy.EventArguments namespace Titanium.Web.Proxy.Network
{ {
/// <summary> /// <summary>
/// This class wraps Tcp connection to Server /// This class wraps Tcp connection to Server
......
...@@ -3,30 +3,33 @@ using System.Collections.Generic; ...@@ -3,30 +3,33 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Net.Sockets; using System.Net.Sockets;
using System.Text; using System.Text;
using System.Collections.Concurrent;
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 System.Threading; using System.Threading;
using System.Security.Authentication;
using Titanium.Web.Proxy.Extensions; using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Shared;
using System.Security.Cryptography.X509Certificates;
using Titanium.Web.Proxy.EventArguments;
namespace Titanium.Web.Proxy.Network namespace Titanium.Web.Proxy.Network
{ {
public class TcpConnection public class TcpConnection
{ {
public string HostName { get; set; } internal string HostName { get; set; }
public int port { get; set; } internal int port { get; set; }
public bool IsSecure { get; set; } internal bool IsSecure { get; set; }
internal Version Version { get; set; }
public TcpClient TcpClient { get; set; } internal TcpClient TcpClient { get; set; }
public CustomBinaryReader ServerStreamReader { get; set; } internal CustomBinaryReader StreamReader { get; set; }
public Stream Stream { get; set; } internal Stream Stream { get; set; }
public DateTime LastAccess { get; set; } internal DateTime LastAccess { get; set; }
public TcpConnection()
internal TcpConnection()
{ {
LastAccess = DateTime.Now; LastAccess = DateTime.Now;
} }
...@@ -36,14 +39,15 @@ namespace Titanium.Web.Proxy.Network ...@@ -36,14 +39,15 @@ namespace Titanium.Web.Proxy.Network
{ {
static List<TcpConnection> ConnectionCache = new List<TcpConnection>(); static List<TcpConnection> ConnectionCache = new List<TcpConnection>();
public static TcpConnection GetClient(string Hostname, int port, bool IsSecure) internal static async Task<TcpConnection> GetClient(SessionEventArgs sessionArgs, string hostname, int port, bool isSecure, Version version)
{ {
TcpConnection cached = null; TcpConnection cached = null;
while (true) while (true)
{ {
lock (ConnectionCache) lock (ConnectionCache)
{ {
cached = ConnectionCache.FirstOrDefault(x => x.HostName == Hostname && x.port == port && x.IsSecure == IsSecure && x.TcpClient.Connected); cached = ConnectionCache.FirstOrDefault(x => x.HostName == hostname && x.port == port &&
x.IsSecure == isSecure && x.TcpClient.Connected && x.Version.Equals(version));
if (cached != null) if (cached != null)
ConnectionCache.Remove(cached); ConnectionCache.Remove(cached);
...@@ -57,28 +61,64 @@ namespace Titanium.Web.Proxy.Network ...@@ -57,28 +61,64 @@ namespace Titanium.Web.Proxy.Network
} }
if (cached == null) if (cached == null)
cached = CreateClient(Hostname, port, IsSecure); cached = await CreateClient(sessionArgs, hostname, port, isSecure, version).ConfigureAwait(false);
if (ConnectionCache.Where(x => x.HostName == Hostname && x.port == port && x.IsSecure == IsSecure && x.TcpClient.Connected).Count() < 2) //just create one more preemptively
if (ConnectionCache.Where(x => x.HostName == hostname && x.port == port &&
x.IsSecure == isSecure && x.TcpClient.Connected && x.Version.Equals(version)).Count() < 2)
{ {
Task.Factory.StartNew(() => ReleaseClient(CreateClient(Hostname, port, IsSecure))); var task = CreateClient(sessionArgs, hostname, port, isSecure, version)
.ContinueWith(x => { if (x.Status == TaskStatus.RanToCompletion) ReleaseClient(x.Result); });
} }
return cached; return cached;
} }
private static TcpConnection CreateClient(string Hostname, int port, bool IsSecure) private static async Task<TcpConnection> CreateClient(SessionEventArgs sessionArgs, string hostname, int port, bool isSecure, Version version)
{
TcpClient client;
Stream stream;
if (isSecure)
{
CustomSslStream sslStream = null;
if (ProxyServer.UpStreamHttpsProxy != null)
{
client = new TcpClient(ProxyServer.UpStreamHttpsProxy.HostName, ProxyServer.UpStreamHttpsProxy.Port);
stream = (Stream)client.GetStream();
using (var writer = new StreamWriter(stream, Encoding.ASCII, Constants.BUFFER_SIZE, true))
{ {
var client = new TcpClient(Hostname, port); await writer.WriteLineAsync(string.Format("CONNECT {0}:{1} {2}", sessionArgs.WebSession.Request.RequestUri.Host, sessionArgs.WebSession.Request.RequestUri.Port, sessionArgs.WebSession.Request.HttpVersion));
var stream = (Stream)client.GetStream(); await writer.WriteLineAsync(string.Format("Host: {0}:{1}", sessionArgs.WebSession.Request.RequestUri.Host, sessionArgs.WebSession.Request.RequestUri.Port));
await writer.WriteLineAsync("Connection: Keep-Alive");
await writer.WriteLineAsync();
await writer.FlushAsync();
writer.Close();
}
if (IsSecure) using (var reader = new CustomBinaryReader(stream))
{ {
var sslStream = (SslStream)null; var result = await reader.ReadLineAsync().ConfigureAwait(false);
if (!result.ToLower().Contains("200 connection established"))
throw new Exception("Upstream proxy failed to create a secure tunnel");
await reader.ReadAllLinesAsync().ConfigureAwait(false);
}
}
else
{
client = new TcpClient(hostname, port);
stream = (Stream)client.GetStream();
}
try try
{ {
sslStream = new SslStream(stream); sslStream = new CustomSslStream(stream, true, new RemoteCertificateValidationCallback(ProxyServer.ValidateServerCertificate));
sslStream.AuthenticateAsClient(Hostname, null, ProxyServer.SupportedProtocols , false); sslStream.Session = sessionArgs;
await sslStream.AuthenticateAsClientAsync(hostname, null, Constants.SupportedProtocols, false).ConfigureAwait(false);
stream = (Stream)sslStream; stream = (Stream)sslStream;
} }
catch catch
...@@ -88,25 +128,40 @@ namespace Titanium.Web.Proxy.Network ...@@ -88,25 +128,40 @@ namespace Titanium.Web.Proxy.Network
throw; throw;
} }
} }
else
{
if (ProxyServer.UpStreamHttpProxy != null)
{
client = new TcpClient(ProxyServer.UpStreamHttpProxy.HostName, ProxyServer.UpStreamHttpProxy.Port);
stream = (Stream)client.GetStream();
}
else
{
client = new TcpClient(hostname, port);
stream = (Stream)client.GetStream();
}
}
return new TcpConnection() return new TcpConnection()
{ {
HostName = Hostname, HostName = hostname,
port = port, port = port,
IsSecure = IsSecure, IsSecure = isSecure,
TcpClient = client, TcpClient = client,
ServerStreamReader = new CustomBinaryReader(stream, Encoding.ASCII), StreamReader = new CustomBinaryReader(stream),
Stream = stream Stream = stream,
Version = version
}; };
} }
public static void ReleaseClient(TcpConnection Connection)
internal static void ReleaseClient(TcpConnection Connection)
{ {
Connection.LastAccess = DateTime.Now; Connection.LastAccess = DateTime.Now;
ConnectionCache.Add(Connection); ConnectionCache.Add(Connection);
} }
public static void ClearIdleConnections() internal async static void ClearIdleConnections()
{ {
while (true) while (true)
{ {
...@@ -122,11 +177,10 @@ namespace Titanium.Web.Proxy.Network ...@@ -122,11 +177,10 @@ namespace Titanium.Web.Proxy.Network
ConnectionCache.RemoveAll(x => x.LastAccess < cutOff); ConnectionCache.RemoveAll(x => x.LastAccess < cutOff);
} }
Thread.Sleep(1000 * 60 * 3); await Task.Delay(1000 * 60 * 3).ConfigureAwait(false);
} }
} }
} }
} }
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Net; using System.Net;
using System.Net.Security;
using System.Net.Sockets; using System.Net.Sockets;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks; using System.Threading.Tasks;
using Titanium.Web.Proxy.EventArguments; using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Helpers; using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Models; using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Network; using Titanium.Web.Proxy.Network;
using System.Linq; using System.Linq;
using System.Security.Authentication; using System.Security.Cryptography.X509Certificates;
using System.Net.Security;
namespace Titanium.Web.Proxy namespace Titanium.Web.Proxy
{ {
...@@ -22,35 +19,14 @@ namespace Titanium.Web.Proxy ...@@ -22,35 +19,14 @@ namespace Titanium.Web.Proxy
public partial class ProxyServer public partial class ProxyServer
{ {
private static readonly char[] SemiSplit = { ';' };
private static readonly string[] ColonSpaceSplit = { ": " };
private static readonly char[] SpaceSplit = { ' ' };
private static readonly Regex CookieSplitRegEx = new Regex(@",(?! )");
private static readonly byte[] NewLineBytes = Encoding.ASCII.GetBytes(Environment.NewLine);
private static readonly byte[] ChunkEnd =
Encoding.ASCII.GetBytes(0.ToString("x2") + Environment.NewLine + Environment.NewLine);
public static readonly int BUFFER_SIZE = 8192;
#if NET45
internal static SslProtocols SupportedProtocols = SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12 | SslProtocols.Ssl3;
#else
internal static SslProtocols SupportedProtocols = SslProtocols.Tls | SslProtocols.Ssl3;
#endif
static ProxyServer() static ProxyServer()
{ {
ProxyEndPoints = new List<ProxyEndPoint>(); ProxyEndPoints = new List<ProxyEndPoint>();
Initialize(); Initialize();
} }
private static CertificateManager CertManager { get; set; } private static CertificateManager CertManager { get; set; }
private static bool EnableSsl { get; set; }
private static bool certTrusted { get; set; } private static bool certTrusted { get; set; }
private static bool proxyRunning { get; set; } private static bool proxyRunning { get; set; }
...@@ -58,14 +34,30 @@ namespace Titanium.Web.Proxy ...@@ -58,14 +34,30 @@ namespace Titanium.Web.Proxy
public static string RootCertificateName { get; set; } public static string RootCertificateName { get; set; }
public static bool Enable100ContinueBehaviour { get; set; } public static bool Enable100ContinueBehaviour { get; set; }
public static event EventHandler<SessionEventArgs> BeforeRequest; public static event Func<object, SessionEventArgs, Task> BeforeRequest;
public static event EventHandler<SessionEventArgs> BeforeResponse; public static event Func<object, SessionEventArgs, Task> BeforeResponse;
/// <summary>
/// External proxy for Http
/// </summary>
public static ExternalProxy UpStreamHttpProxy { get; set; }
/// <summary>
/// External proxy for Http
/// </summary>
public static ExternalProxy UpStreamHttpsProxy { get; set; }
/// <summary>
/// Verifies the remote Secure Sockets Layer (SSL) certificate used for authentication
/// </summary>
public static event Func<object, CertificateValidationEventArgs, Task> ServerCertificateValidationCallback;
public static List<ProxyEndPoint> ProxyEndPoints { get; set; } public static List<ProxyEndPoint> ProxyEndPoints { get; set; }
public static void Initialize() public static void Initialize()
{ {
Task.Factory.StartNew(() => TcpConnectionManager.ClearIdleConnections()); TcpConnectionManager.ClearIdleConnections();
} }
public static void AddEndPoint(ProxyEndPoint endPoint) public static void AddEndPoint(ProxyEndPoint endPoint)
...@@ -162,10 +154,7 @@ namespace Titanium.Web.Proxy ...@@ -162,10 +154,7 @@ namespace Titanium.Web.Proxy
CertManager = new CertificateManager(RootCertificateIssuerName, CertManager = new CertificateManager(RootCertificateIssuerName,
RootCertificateName); RootCertificateName);
EnableSsl = ProxyEndPoints.Any(x => x.EnableSsl); certTrusted = CertManager.CreateTrustedRootCertificate().Result;
if (EnableSsl)
certTrusted = CertManager.CreateTrustedRootCertificate();
foreach (var endPoint in ProxyEndPoints) foreach (var endPoint in ProxyEndPoints)
{ {
...@@ -233,19 +222,21 @@ namespace Titanium.Web.Proxy ...@@ -233,19 +222,21 @@ namespace Titanium.Web.Proxy
{ {
var client = endPoint.listener.EndAcceptTcpClient(asyn); var client = endPoint.listener.EndAcceptTcpClient(asyn);
if (endPoint.GetType() == typeof(TransparentProxyEndPoint)) if (endPoint.GetType() == typeof(TransparentProxyEndPoint))
Task.Factory.StartNew(() => HandleClient(endPoint as TransparentProxyEndPoint, client)); HandleClient(endPoint as TransparentProxyEndPoint, client);
else else
Task.Factory.StartNew(() => HandleClient(endPoint as ExplicitProxyEndPoint, client)); HandleClient(endPoint as ExplicitProxyEndPoint, client);
// 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);
} }
catch catch (ObjectDisposedException)
{ {
// ignored // The listener was Stop()'d, disposing the underlying socket and
// triggering the completion of the callback. We're already exiting,
// so just return.
return;
} }
} }
} }
......
This diff is collapsed.
This diff is collapsed.
using System;
using System.Security.Authentication;
using System.Text;
using System.Text.RegularExpressions;
namespace Titanium.Web.Proxy.Shared
{
/// <summary>
/// Literals shared by Proxy Server
/// </summary>
internal class Constants
{
public static readonly int BUFFER_SIZE = 8192;
internal static readonly char[] SpaceSplit = { ' ' };
internal static readonly char[] ColonSplit = { ':' };
internal static readonly char[] SemiColonSplit = { ';' };
internal static readonly byte[] NewLineBytes = Encoding.ASCII.GetBytes(Environment.NewLine);
internal static readonly byte[] ChunkEnd =
Encoding.ASCII.GetBytes(0.ToString("x2") + Environment.NewLine + Environment.NewLine);
internal static SslProtocols SupportedProtocols = SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12 | SslProtocols.Ssl3;
}
}
...@@ -60,7 +60,8 @@ ...@@ -60,7 +60,8 @@
<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="Decompression\ZlibDecompression.cs" />
<Compile Include="EventArguments\ProxyClient.cs" /> <Compile Include="EventArguments\CertificateValidationEventArgs.cs" />
<Compile Include="Network\ProxyClient.cs" />
<Compile Include="Exceptions\BodyNotFoundException.cs" /> <Compile Include="Exceptions\BodyNotFoundException.cs" />
<Compile Include="Extensions\HttpWebResponseExtensions.cs" /> <Compile Include="Extensions\HttpWebResponseExtensions.cs" />
<Compile Include="Extensions\HttpWebRequestExtensions.cs" /> <Compile Include="Extensions\HttpWebRequestExtensions.cs" />
...@@ -69,9 +70,13 @@ ...@@ -69,9 +70,13 @@
<Compile Include="Helpers\SystemProxy.cs" /> <Compile Include="Helpers\SystemProxy.cs" />
<Compile Include="Models\EndPoint.cs" /> <Compile Include="Models\EndPoint.cs" />
<Compile Include="Extensions\TcpExtensions.cs" /> <Compile Include="Extensions\TcpExtensions.cs" />
<Compile Include="Http\Request.cs" />
<Compile Include="Http\Response.cs" />
<Compile Include="Models\ExternalProxy.cs" />
<Compile Include="Network\CustomSslStream.cs" />
<Compile Include="Network\TcpConnectionManager.cs" /> <Compile Include="Network\TcpConnectionManager.cs" />
<Compile Include="Models\HttpHeader.cs" /> <Compile Include="Models\HttpHeader.cs" />
<Compile Include="Network\HttpWebClient.cs" /> <Compile Include="Http\HttpWebClient.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="RequestHandler.cs" /> <Compile Include="RequestHandler.cs" />
<Compile Include="ResponseHandler.cs" /> <Compile Include="ResponseHandler.cs" />
...@@ -80,8 +85,9 @@ ...@@ -80,8 +85,9 @@
<Compile Include="EventArguments\SessionEventArgs.cs" /> <Compile Include="EventArguments\SessionEventArgs.cs" />
<Compile Include="Helpers\Tcp.cs" /> <Compile Include="Helpers\Tcp.cs" />
<Compile Include="Extensions\StreamExtensions.cs" /> <Compile Include="Extensions\StreamExtensions.cs" />
<Compile Include="Responses\OkResponse.cs" /> <Compile Include="Http\Responses\OkResponse.cs" />
<Compile Include="Responses\RedirectResponse.cs" /> <Compile Include="Http\Responses\RedirectResponse.cs" />
<Compile Include="Shared\Constants.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup /> <ItemGroup />
<ItemGroup> <ItemGroup>
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
# - Section names should be unique on each level. # - Section names should be unique on each level.
# version format # version format
version: 2.1.{build} version: 2.2{build}
shallow_clone: true shallow_clone: true
......
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