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.Runtime.InteropServices;
namespace Titanium.Web.Proxy.Test
namespace Titanium.Web.Proxy.Examples.Basic
{
public class Program
{
......
......@@ -2,10 +2,11 @@
using System.Collections.Generic;
using System.Net;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Models;
namespace Titanium.Web.Proxy.Test
namespace Titanium.Web.Proxy.Examples.Basic
{
public class ProxyTestController
{
......@@ -13,6 +14,7 @@ namespace Titanium.Web.Proxy.Test
{
ProxyServer.BeforeRequest += OnRequest;
ProxyServer.BeforeResponse += OnResponse;
ProxyServer.ServerCertificateValidationCallback += OnCertificateValidation;
//Exclude Https addresses you don't want to proxy
//Usefull for clients that use certificate pinning
......@@ -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
//In this example only google.com will work for HTTPS requests
//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)
{
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)
Console.WriteLine("Listening on '{0}' endpoint at Ip {1} and port: {2} ",
......@@ -59,69 +62,80 @@ namespace Titanium.Web.Proxy.Test
ProxyServer.Stop();
}
//Test On Request, intecept requests
//Read browser URL send back to proxy by the injection script in OnResponse event
public void OnRequest(object sender, SessionEventArgs e)
//intecept & cancel, redirect or update requests
public async Task OnRequest(object sender, SessionEventArgs e)
{
Console.WriteLine(e.ProxySession.Request.Url);
Console.WriteLine(e.WebSession.Request.Url);
//read request headers
var requestHeaders = e.ProxySession.Request.RequestHeaders;
////read request headers
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
byte[] bodyBytes = e.GetRequestBody();
e.SetRequestBody(bodyBytes);
byte[] bodyBytes = await e.GetRequestBody();
await e.SetRequestBody(bodyBytes);
//Get/Set request body as string
string bodyString = e.GetRequestBodyAsString();
e.SetRequestBodyString(bodyString);
string bodyString = await e.GetRequestBodyAsString();
await e.SetRequestBodyString(bodyString);
}
//To cancel a request with a custom HTML content
//Filter URL
if (e.ProxySession.Request.RequestUri.AbsoluteUri.Contains("google.com"))
if (e.WebSession.Request.RequestUri.AbsoluteUri.Contains("google.com"))
{
e.Ok("<!DOCTYPE html>" +
"<html><body><h1>" +
"Website Blocked" +
"</h1>" +
"<p>Blocked by titanium web proxy.</p>" +
"</body>" +
"</html>");
await e.Ok("<!DOCTYPE html>" +
"<html><body><h1>" +
"Website Blocked" +
"</h1>" +
"<p>Blocked by titanium web proxy.</p>" +
"</body>" +
"</html>");
}
//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
//Insert script to read the Browser URL and send it back to proxy
public void OnResponse(object sender, SessionEventArgs e)
//Modify response
public async Task OnResponse(object sender, SessionEventArgs e)
{
//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.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();
e.SetResponseBody(bodyBytes);
byte[] bodyBytes = await e.GetResponseBody();
await e.SetResponseBody(bodyBytes);
string body = e.GetResponseBodyAsString();
e.SetResponseBodyString(body);
string body = await e.GetResponseBodyAsString();
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 @@
<ProjectGuid>{F3B7E553-1904-4E80-BDC7-212342B5C952}</ProjectGuid>
<OutputType>Exe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Titanium.Web.Proxy.Test</RootNamespace>
<AssemblyName>Titanium.Web.Proxy.Test</AssemblyName>
<RootNamespace>Titanium.Web.Proxy.Examples.Basic</RootNamespace>
<AssemblyName>Titanium.Web.Proxy.Examples.Basic</AssemblyName>
<FileAlignment>512</FileAlignment>
<TargetFrameworkProfile />
</PropertyGroup>
......@@ -65,7 +65,7 @@
<None Include="App.config" />
</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>
<Name>Titanium.Web.Proxy</Name>
</ProjectReference>
......
......@@ -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.
![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
========
......@@ -32,102 +32,135 @@ After installing nuget package mark following files to be copied to app director
Setup HTTP proxy:
```csharp
// listen to client request & server response events
ProxyServer.BeforeRequest += OnRequest;
ProxyServer.BeforeResponse += OnResponse;
var explicitEndPoint = new ExplicitProxyEndPoint(IPAddress.Any, 8000, true){
//Exclude Https addresses you don't want to proxy/cannot be proxied
//for example exclude dropbox client which use certificate pinning
ExcludedHttpsHostNameRegex = new List<string>() { "dropbox.com" }
};
//Add an explicit endpoint where the client is aware of the proxy
//So client would send request in a proxy friendly manner
ProxyServer.AddEndPoint(explicitEndPoint);
ProxyServer.Start();
//Only explicit proxies can be set as a system proxy!
ProxyServer.SetAsSystemHttpProxy(explicitEndPoint);
ProxyServer.SetAsSystemHttpsProxy(explicitEndPoint);
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);
ProxyServer.BeforeRequest += OnRequest;
ProxyServer.BeforeResponse += OnResponse;
ProxyServer.ServerCertificateValidationCallback += OnCertificateValidation;
//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)
{
// ExcludedHttpsHostNameRegex = new List<string>() { "google.com", "dropbox.com" }
};
//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();
//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)
//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);
//wait here (You can use something else as a wait function, I am using this as a demo)
Console.Read();
//ProxyServer.UpStreamHttpProxy = new ExternalProxy() { HostName = "localhost", Port = 8888 };
//ProxyServer.UpStreamHttpsProxy = new ExternalProxy() { HostName = "localhost", Port = 8888 };
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);
//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)
Console.Read();
//Unsubscribe & Quit
ProxyServer.BeforeRequest -= OnRequest;
ProxyServer.BeforeResponse -= OnResponse;
ProxyServer.Stop();
//Unsubscribe & Quit
ProxyServer.BeforeRequest -= OnRequest;
ProxyServer.BeforeResponse -= OnResponse;
ProxyServer.Stop();
```
Sample request and response event handlers
```csharp
//Test On Request, intecept requests
//Read browser URL send back to proxy by the injection script in OnResponse event
public void OnRequest(object sender, SessionEventArgs e)
//intecept & cancel, redirect or update requests
public async Task OnRequest(object sender, SessionEventArgs e)
{
Console.WriteLine(e.ProxySession.Request.Url);
Console.WriteLine(e.WebSession.Request.Url);
//read request headers
var requestHeaders = e.ProxySession.Request.RequestHeaders;
////read request headers
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
byte[] bodyBytes = e.GetRequestBody();
e.SetRequestBody(bodyBytes);
byte[] bodyBytes = await e.GetRequestBody();
await e.SetRequestBody(bodyBytes);
//Get/Set request body as string
string bodyString = e.GetRequestBodyAsString();
e.SetRequestBodyString(bodyString);
string bodyString = await e.GetRequestBodyAsString();
await e.SetRequestBodyString(bodyString);
}
//To cancel a request with a custom HTML content
//Filter URL
if (e.ProxySession.Request.RequestUri.AbsoluteUri.Contains("google.com"))
if (e.WebSession.Request.RequestUri.AbsoluteUri.Contains("google.com"))
{
e.Ok("<!DOCTYPE html>"+
"<html><body><h1>"+
"Website Blocked"+
"</h1>"+
"<p>Blocked by titanium web proxy.</p>"+
"</body>"+
"</html>");
await e.Ok("<!DOCTYPE html>" +
"<html><body><h1>" +
"Website Blocked" +
"</h1>" +
"<p>Blocked by titanium web proxy.</p>" +
"</body>" +
"</html>");
}
//Redirect example
if (e.WebSession.Request.RequestUri.AbsoluteUri.Contains("wikipedia.org"))
{
await e.Redirect("https://www.paypal.com");
}
}
//Test script injection
//Insert script to read the Browser URL and send it back to proxy
public void OnResponse(object sender, SessionEventArgs e)
//Modify response
public async Task OnResponse(object sender, SessionEventArgs e)
{
//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
============
* Add callbacks for client/server certificate validation/selection
* Support mutual authentication
* Support Server Name Indication (SNI) for transparent endpoints
* 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
# Visual Studio 14
VisualStudioVersion = 14.0.25123.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{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}"
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Examples", "Examples", "{B6DBABDC-C985-4872-9C38-B4E5079CBC4B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Titanium.Web.Proxy", "Titanium.Web.Proxy\Titanium.Web.Proxy.csproj", "{8D73A1BE-868C-42D2-9ECE-F32CC1A02906}"
EndProject
......@@ -16,20 +14,35 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{6FD3B8
.nuget\NuGet.targets = .nuget\NuGet.targets
EndProjectSection
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
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
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.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.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
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
......
using System.IO;
using System.IO.Compression;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Compression
{
class DeflateCompression : ICompression
{
public byte[] Compress(byte[] responseBody)
public async Task<byte[]> Compress(byte[] responseBody)
{
using (var ms = new MemoryStream())
{
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();
......
using Ionic.Zlib;
using System.IO;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Compression
{
class GZipCompression : ICompression
{
public byte[] Compress(byte[] responseBody)
public async Task<byte[]> Compress(byte[] responseBody)
{
using (var ms = new MemoryStream())
{
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();
......
namespace Titanium.Web.Proxy.Compression
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Compression
{
interface ICompression
{
byte[] Compress(byte[] responseBody);
Task<byte[]> Compress(byte[] responseBody);
}
}
using Ionic.Zlib;
using System.IO;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Compression
{
class ZlibCompression : ICompression
{
public byte[] Compress(byte[] responseBody)
public async Task<byte[]> Compress(byte[] responseBody)
{
using (var ms = new MemoryStream())
{
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();
......
namespace Titanium.Web.Proxy.Decompression
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Decompression
{
class DefaultDecompression : IDecompression
{
public byte[] Decompress(byte[] compressedArray)
public Task<byte[]> Decompress(byte[] compressedArray)
{
return compressedArray;
return Task.FromResult(compressedArray);
}
}
}
using Ionic.Zlib;
using System.IO;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Decompression
{
class DeflateDecompression : IDecompression
{
public byte[] Decompress(byte[] compressedArray)
public async Task<byte[]> Decompress(byte[] compressedArray)
{
var stream = new MemoryStream(compressedArray);
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())
{
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();
......
using System.IO;
using System.IO.Compression;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Decompression
{
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))
{
var buffer = new byte[ProxyServer.BUFFER_SIZE];
var buffer = new byte[Constants.BUFFER_SIZE];
using (var output = new MemoryStream())
{
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();
}
......
using System.IO;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Decompression
{
interface IDecompression
{
byte[] Decompress(byte[] compressedArray);
Task<byte[]> Decompress(byte[] compressedArray);
}
}
using Ionic.Zlib;
using System.IO;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Decompression
{
class ZlibDecompression : IDecompression
{
public byte[] Decompress(byte[] compressedArray)
public async Task<byte[]> Decompress(byte[] compressedArray)
{
var memoryStream = new MemoryStream(compressedArray);
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())
{
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();
}
......
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 Titanium.Web.Proxy.Network;
using System.Text;
using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Extensions
{
......@@ -15,10 +15,11 @@ namespace Titanium.Web.Proxy.Extensions
try
{
//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
var contentTypes = request.ContentType.Split(';');
var contentTypes = request.ContentType.Split(Constants.SemiColonSplit);
foreach (var contentType in contentTypes)
{
var encodingSplit = contentType.Split('=');
......
using System.Net;
using System.Text;
using Titanium.Web.Proxy.Network;
using System.Text;
using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Extensions
{
public static class HttpWebResponseExtensions
{
public static Encoding GetResponseEncoding(this Response response)
public static Encoding GetResponseCharacterEncoding(this Response response)
{
if (string.IsNullOrEmpty(response.CharacterSet))
return Encoding.GetEncoding("ISO-8859-1");
try
{
return Encoding.GetEncoding(response.CharacterSet.Replace(@"""", string.Empty));
//return default if not specified
if (response.ContentType == null)
return Encoding.GetEncoding("ISO-8859-1");
//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("ISO-8859-1"); }
catch
{
//parsing errors
// ignored
}
//return default if not specified
return Encoding.GetEncoding("ISO-8859-1");
}
}
}
\ No newline at end of file
using System;
using System.Globalization;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Extensions
{
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);
output.Write(bytes, 0, bytes.Length);
await output.WriteAsync(bytes, 0, bytes.Length);
}
CopyToAsync(input, output, bufferSize);
await input.CopyToAsync(output);
}
//http://stackoverflow.com/questions/1540658/net-asynchronous-stream-read-write
private static void CopyToAsync(this Stream input, Stream output, int bufferSize)
internal static async Task CopyBytesToStream(this CustomBinaryReader streamReader, Stream stream, long totalBytesToRead)
{
try
{
if (!input.CanRead) throw new InvalidOperationException("input must be open for reading");
if (!output.CanWrite) throw new InvalidOperationException("output must be open for writing");
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;
var totalbytesRead = 0;
while (true)
{
// wait for the read operation to complete
read.AsyncWaitHandle.WaitOne();
bufl[bufno] = input.EndRead(read);
long bytesToRead;
if (totalBytesToRead < Constants.BUFFER_SIZE)
{
bytesToRead = totalBytesToRead;
}
else
bytesToRead = Constants.BUFFER_SIZE;
// if zero bytes read, the copy is complete
if (bufl[bufno] == 0)
{
break;
}
// wait for the in-flight write operation, if one exists, to complete
// the only time one won't exist is after the very first read operation completes
if (write != null)
{
write.AsyncWaitHandle.WaitOne();
output.EndWrite(write);
}
while (totalbytesRead < totalBytesToRead)
{
var buffer = await streamReader.ReadBytesAsync(bytesToRead);
// start the new write operation
write = output.BeginWrite(buf[bufno], 0, bufl[bufno], null, null);
if (buffer.Length == 0)
break;
// 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);
}
totalbytesRead += buffer.Length;
// 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)
var remainingBytes = totalBytesToRead - totalbytesRead;
if (remainingBytes < bytesToRead)
{
write.AsyncWaitHandle.WaitOne();
output.EndWrite(write);
bytesToRead = remainingBytes;
}
output.Flush();
await stream.WriteAsync(buffer, 0, buffer.Length);
}
catch
}
internal static async Task CopyBytesToStreamChunked(this CustomBinaryReader clientStreamReader, Stream stream)
{
while (true)
{
// ignored
var chuchkHead = await clientStreamReader.ReadLineAsync().ConfigureAwait(false);
var chunkSize = int.Parse(chuchkHead, NumberStyles.HexNumber);
if (chunkSize != 0)
{
var buffer = await clientStreamReader.ReadBytesAsync(chunkSize);
await stream.WriteAsync(buffer, 0, buffer.Length);
//chunk trail
await clientStreamReader.ReadLineAsync().ConfigureAwait(false);
}
else
{
await clientStreamReader.ReadLineAsync().ConfigureAwait(false);
break;
}
}
// return to the caller ;
}
}
}
\ No newline at end of file
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using System.Net.Sockets;
namespace Titanium.Web.Proxy.Extensions
......
......@@ -4,15 +4,18 @@ using System.Diagnostics;
using System.Collections.Generic;
using System.Security.Cryptography.X509Certificates;
using System.Reflection;
using System.Threading.Tasks;
using System.Threading;
namespace Titanium.Web.Proxy.Helpers
{
public class CertificateManager : IDisposable
public class CertificateManager : IDisposable
{
private const string CertCreateFormat =
"-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 static SemaphoreSlim semaphoreLock = new SemaphoreSlim(1);
public string Issuer { get; private set; }
public string RootCertificateName { get; private set; }
......@@ -35,10 +38,10 @@ namespace Titanium.Web.Proxy.Helpers
/// Attempts to move a self-signed certificate to the root store.
/// </summary>
/// <returns>true if succeeded, else false</returns>
public bool CreateTrustedRootCertificate()
public async Task<bool> CreateTrustedRootCertificate()
{
X509Certificate2 rootCertificate =
CreateCertificate(RootStore, RootCertificateName);
await CreateCertificate(RootStore, RootCertificateName);
return rootCertificate != null;
}
......@@ -46,9 +49,9 @@ namespace Titanium.Web.Proxy.Helpers
/// Attempts to remove the self-signed certificate from the root store.
/// </summary>
/// <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)
......@@ -64,103 +67,126 @@ namespace Titanium.Web.Proxy.Helpers
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))
return _certificateCache[certificateName];
lock (store)
await semaphoreLock.WaitAsync();
X509Certificate2 certificate = null;
try
{
X509Certificate2 certificate = null;
try
{
store.Open(OpenFlags.ReadWrite);
string certificateSubject = string.Format("CN={0}, O={1}", certificateName, Issuer);
store.Open(OpenFlags.ReadWrite);
string certificateSubject = string.Format("CN={0}, O={1}", certificateName, Issuer);
var certificates =
FindCertificates(store, certificateSubject);
var certificates =
FindCertificates(store, certificateSubject);
if (certificates != null)
certificate = certificates[0];
if (certificates != null)
certificate = certificates[0];
if (certificate == null)
{
string[] args = new[] {
if (certificate == null)
{
string[] args = new[] {
GetCertificateCreateArgs(store, certificateName) };
CreateCertificate(args);
certificates = FindCertificates(store, certificateSubject);
await CreateCertificate(args);
certificates = FindCertificates(store, certificateSubject);
return certificates != null ?
certificates[0] : null;
}
return certificate;
}
finally
{
store.Close();
if (certificate != null && !_certificateCache.ContainsKey(certificateName))
_certificateCache.Add(certificateName, certificate);
return certificates != null ?
certificates[0] : null;
}
store.Close();
if (certificate != null && !_certificateCache.ContainsKey(certificateName))
_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())
{
string file = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "makecert.exe");
if (!File.Exists(file))
throw new Exception("Unable to locate 'makecert.exe'.");
// there is no non-generic TaskCompletionSource
var tcs = new TaskCompletionSource<int>();
var process = new Process();
process.StartInfo.Verb = "runas";
process.StartInfo.Arguments = args != null ? args[0] : string.Empty;
process.StartInfo.CreateNoWindow = true;
process.StartInfo.UseShellExecute = false;
process.StartInfo.FileName = file;
string file = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "makecert.exe");
process.Start();
process.WaitForExit();
if (!File.Exists(file))
throw new Exception("Unable to locate 'makecert.exe'.");
process.StartInfo.Verb = "runas";
process.StartInfo.Arguments = args != null ? args[0] : string.Empty;
process.StartInfo.CreateNoWindow = true;
process.StartInfo.UseShellExecute = false;
process.StartInfo.FileName = file;
process.EnableRaisingEvents = true;
process.Exited += (sender, processArgs) =>
{
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;
try
{
X509Certificate2Collection certificates = null;
try
{
store.Open(OpenFlags.ReadWrite);
string certificateSubject = string.Format("CN={0}, O={1}", certificateName, Issuer);
store.Open(OpenFlags.ReadWrite);
string certificateSubject = string.Format("CN={0}, O={1}", certificateName, Issuer);
certificates = FindCertificates(store, certificateSubject);
if (certificates != null)
{
store.RemoveRange(certificates);
certificates = FindCertificates(store, certificateSubject);
if (certificates != null)
{
store.RemoveRange(certificates);
certificates = FindCertificates(store, certificateSubject);
}
return certificates == null;
}
finally
store.Close();
if (certificates == null &&
_certificateCache.ContainsKey(certificateName))
{
store.Close();
if (certificates == null &&
_certificateCache.ContainsKey(certificateName))
{
_certificateCache.Remove(certificateName);
}
_certificateCache.Remove(certificateName);
}
return certificates == null;
}
finally
{
semaphoreLock.Release();
}
}
protected virtual string GetCertificateCreateArgs(X509Store store, string certificateName)
......
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Security;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Network;
using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Helpers
{
......@@ -10,27 +15,33 @@ namespace Titanium.Web.Proxy.Helpers
/// using the specified encoding
/// as well as to read bytes as required
/// </summary>
public class CustomBinaryReader : BinaryReader
public class CustomBinaryReader : IDisposable
{
internal CustomBinaryReader(Stream stream, Encoding encoding)
: base(stream, encoding)
private Stream stream;
internal CustomBinaryReader(Stream stream)
{
this.stream = stream;
}
public Stream BaseStream => stream;
/// <summary>
/// Read a line from the byte stream
/// </summary>
/// <returns></returns>
internal string ReadLine()
internal async Task<string> ReadLineAsync()
{
var readBuffer = new StringBuilder();
try
{
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')
{
......@@ -40,8 +51,8 @@ namespace Titanium.Web.Proxy.Helpers
{
return readBuffer.ToString();
}
readBuffer.Append(buffer);
lastChar = buffer[0];
readBuffer.Append((char)buffer[0]);
lastChar = (char)buffer[0];
}
return readBuffer.ToString();
......@@ -56,15 +67,52 @@ namespace Titanium.Web.Proxy.Helpers
/// Read until the last new line
/// </summary>
/// <returns></returns>
internal List<string> ReadAllLines()
internal async Task<List<string>> ReadAllLinesAsync()
{
string tmpLine;
var requestLines = new List<string>();
while (!string.IsNullOrEmpty(tmpLine = ReadLine()))
while (!string.IsNullOrEmpty(tmpLine = await ReadLineAsync().ConfigureAwait(false)))
{
requestLines.Add(tmpLine);
}
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;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Helpers
{
public class TcpHelper
{
private static readonly int BUFFER_SIZE = 8192;
public static void SendRaw(Stream clientStream, string httpCmd, List<HttpHeader> requestHeaders, string hostName,
public async static Task SendRaw(Stream clientStream, string httpCmd, List<HttpHeader> requestHeaders, string hostName,
int tunnelPort, bool isHttps)
{
StringBuilder sb = null;
......@@ -51,7 +50,7 @@ namespace Titanium.Web.Proxy.Helpers
try
{
sslStream = new SslStream(tunnelStream);
sslStream.AuthenticateAsClient(hostName, null, ProxyServer.SupportedProtocols, false);
await sslStream.AuthenticateAsClientAsync(hostName, null, Constants.SupportedProtocols, false);
tunnelStream = sslStream;
}
catch
......@@ -63,18 +62,17 @@ namespace Titanium.Web.Proxy.Helpers
}
}
Task sendRelay;
if (sb != null)
sendRelay = clientStream.CopyToAsync(sb.ToString(), tunnelStream);
else
sendRelay = clientStream.CopyToAsync(string.Empty, tunnelStream);
var sendRelay = Task.Factory.StartNew(() =>
{
if (sb != null)
clientStream.CopyToAsync(sb.ToString(), tunnelStream, BUFFER_SIZE);
else
clientStream.CopyToAsync(string.Empty, tunnelStream, BUFFER_SIZE);
});
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
{
......
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.Models;
using Titanium.Web.Proxy.Network;
using Titanium.Web.Proxy.Network;
namespace Titanium.Web.Proxy.Responses
namespace Titanium.Web.Proxy.Http.Responses
{
public class OkResponse : Response
{
......
using System;
using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Network;
using Titanium.Web.Proxy.Network;
namespace Titanium.Web.Proxy.Responses
namespace Titanium.Web.Proxy.Http.Responses
{
public class RedirectResponse : Response
{
......
using System;
using System.Collections.Generic;
using System.Linq;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
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.Collections.Generic;
using System.IO;
using System.Linq;
using System.IO;
using System.Net.Sockets;
using System.Text;
using Titanium.Web.Proxy.Helpers;
namespace Titanium.Web.Proxy.EventArguments
namespace Titanium.Web.Proxy.Network
{
/// <summary>
/// This class wraps Tcp connection to Server
......
......@@ -3,30 +3,33 @@ using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Collections.Concurrent;
using System.Threading.Tasks;
using System.IO;
using System.Net.Security;
using Titanium.Web.Proxy.Helpers;
using System.Threading;
using System.Security.Authentication;
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
{
public class TcpConnection
{
public string HostName { get; set; }
public int port { get; set; }
public bool IsSecure { get; set; }
internal string HostName { get; set; }
internal int port { get; set; }
internal bool IsSecure { get; set; }
internal Version Version { get; set; }
public TcpClient TcpClient { get; set; }
public CustomBinaryReader ServerStreamReader { get; set; }
public Stream Stream { get; set; }
internal TcpClient TcpClient { get; set; }
internal CustomBinaryReader StreamReader { get; set; }
internal Stream Stream { get; set; }
public DateTime LastAccess { get; set; }
internal DateTime LastAccess { get; set; }
public TcpConnection()
internal TcpConnection()
{
LastAccess = DateTime.Now;
}
......@@ -36,14 +39,15 @@ namespace Titanium.Web.Proxy.Network
{
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;
while (true)
{
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)
ConnectionCache.Remove(cached);
......@@ -57,28 +61,64 @@ namespace Titanium.Web.Proxy.Network
}
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;
}
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)
{
var client = new TcpClient(Hostname, port);
var stream = (Stream)client.GetStream();
TcpClient client;
Stream stream;
if (IsSecure)
if (isSecure)
{
var sslStream = (SslStream)null;
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))
{
await writer.WriteLineAsync(string.Format("CONNECT {0}:{1} {2}", sessionArgs.WebSession.Request.RequestUri.Host, sessionArgs.WebSession.Request.RequestUri.Port, sessionArgs.WebSession.Request.HttpVersion));
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();
}
using (var reader = new CustomBinaryReader(stream))
{
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
{
sslStream = new SslStream(stream);
sslStream.AuthenticateAsClient(Hostname, null, ProxyServer.SupportedProtocols , false);
sslStream = new CustomSslStream(stream, true, new RemoteCertificateValidationCallback(ProxyServer.ValidateServerCertificate));
sslStream.Session = sessionArgs;
await sslStream.AuthenticateAsClientAsync(hostname, null, Constants.SupportedProtocols, false).ConfigureAwait(false);
stream = (Stream)sslStream;
}
catch
......@@ -88,25 +128,40 @@ namespace Titanium.Web.Proxy.Network
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()
{
HostName = Hostname,
HostName = hostname,
port = port,
IsSecure = IsSecure,
IsSecure = isSecure,
TcpClient = client,
ServerStreamReader = new CustomBinaryReader(stream, Encoding.ASCII),
Stream = stream
StreamReader = new CustomBinaryReader(stream),
Stream = stream,
Version = version
};
}
public static void ReleaseClient(TcpConnection Connection)
internal static void ReleaseClient(TcpConnection Connection)
{
Connection.LastAccess = DateTime.Now;
ConnectionCache.Add(Connection);
}
public static void ClearIdleConnections()
internal async static void ClearIdleConnections()
{
while (true)
{
......@@ -122,11 +177,10 @@ namespace Titanium.Web.Proxy.Network
ConnectionCache.RemoveAll(x => x.LastAccess < cutOff);
}
Thread.Sleep(1000 * 60 * 3);
await Task.Delay(1000 * 60 * 3).ConfigureAwait(false);
}
}
}
}
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Security;
using System.Net.Sockets;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Network;
using System.Linq;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using System.Net.Security;
namespace Titanium.Web.Proxy
{
......@@ -21,36 +18,15 @@ namespace Titanium.Web.Proxy
/// </summary>
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()
{
ProxyEndPoints = new List<ProxyEndPoint>();
Initialize();
}
private static CertificateManager CertManager { get; set; }
private static bool EnableSsl { get; set; }
private static bool certTrusted { get; set; }
private static bool proxyRunning { get; set; }
......@@ -58,14 +34,30 @@ namespace Titanium.Web.Proxy
public static string RootCertificateName { get; set; }
public static bool Enable100ContinueBehaviour { get; set; }
public static event EventHandler<SessionEventArgs> BeforeRequest;
public static event EventHandler<SessionEventArgs> BeforeResponse;
public static event Func<object, SessionEventArgs, Task> BeforeRequest;
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 void Initialize()
{
Task.Factory.StartNew(() => TcpConnectionManager.ClearIdleConnections());
TcpConnectionManager.ClearIdleConnections();
}
public static void AddEndPoint(ProxyEndPoint endPoint)
......@@ -162,10 +154,7 @@ namespace Titanium.Web.Proxy
CertManager = new CertificateManager(RootCertificateIssuerName,
RootCertificateName);
EnableSsl = ProxyEndPoints.Any(x => x.EnableSsl);
if (EnableSsl)
certTrusted = CertManager.CreateTrustedRootCertificate();
certTrusted = CertManager.CreateTrustedRootCertificate().Result;
foreach (var endPoint in ProxyEndPoints)
{
......@@ -228,25 +217,27 @@ namespace Titanium.Web.Proxy
private static void OnAcceptConnection(IAsyncResult asyn)
{
var endPoint = (ProxyEndPoint)asyn.AsyncState;
try
{
var client = endPoint.listener.EndAcceptTcpClient(asyn);
if (endPoint.GetType() == typeof(TransparentProxyEndPoint))
Task.Factory.StartNew(() => HandleClient(endPoint as TransparentProxyEndPoint, client));
HandleClient(endPoint as TransparentProxyEndPoint, client);
else
Task.Factory.StartNew(() => HandleClient(endPoint as ExplicitProxyEndPoint, client));
HandleClient(endPoint as ExplicitProxyEndPoint, client);
// Get the listener that handles the client request.
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;
}
}
}
}
\ No newline at end of file
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 @@
<Compile Include="Decompression\GZipDecompression.cs" />
<Compile Include="Decompression\IDecompression.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="Extensions\HttpWebResponseExtensions.cs" />
<Compile Include="Extensions\HttpWebRequestExtensions.cs" />
......@@ -69,9 +70,13 @@
<Compile Include="Helpers\SystemProxy.cs" />
<Compile Include="Models\EndPoint.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="Models\HttpHeader.cs" />
<Compile Include="Network\HttpWebClient.cs" />
<Compile Include="Http\HttpWebClient.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="RequestHandler.cs" />
<Compile Include="ResponseHandler.cs" />
......@@ -80,8 +85,9 @@
<Compile Include="EventArguments\SessionEventArgs.cs" />
<Compile Include="Helpers\Tcp.cs" />
<Compile Include="Extensions\StreamExtensions.cs" />
<Compile Include="Responses\OkResponse.cs" />
<Compile Include="Responses\RedirectResponse.cs" />
<Compile Include="Http\Responses\OkResponse.cs" />
<Compile Include="Http\Responses\RedirectResponse.cs" />
<Compile Include="Shared\Constants.cs" />
</ItemGroup>
<ItemGroup />
<ItemGroup>
......
......@@ -7,7 +7,7 @@
# - Section names should be unique on each level.
# version format
version: 2.1.{build}
version: 2.2{build}
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