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,69 +62,80 @@ namespace Titanium.Web.Proxy.Test ...@@ -59,69 +62,80 @@ 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>" +
"<p>Blocked by titanium web proxy.</p>" + "<p>Blocked by titanium web proxy.</p>" +
"</body>" + "</body>" +
"</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,102 +32,135 @@ After installing nuget package mark following files to be copied to app director ...@@ -32,102 +32,135 @@ 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 };
//So client would send request in a proxy friendly manner
ProxyServer.AddEndPoint(explicitEndPoint); //An explicit endpoint is where the client knows about the existance of a proxy
ProxyServer.Start(); //So client sends request in a proxy friendly manner
ProxyServer.AddEndPoint(explicitEndPoint);
//Only explicit proxies can be set as a system proxy! ProxyServer.Start();
ProxyServer.SetAsSystemHttpProxy(explicitEndPoint);
ProxyServer.SetAsSystemHttpsProxy(explicitEndPoint);
//Transparent endpoint is usefull for reverse proxying (client is not aware of the existance of proxy)
foreach (var endPoint in ProxyServer.ProxyEndPoints) //A transparent endpoint usually requires a network router port forwarding HTTP(S) packets to this endpoint
Console.WriteLine("Listening on '{0}' endpoint at Ip {1} and port: {2} ", //Currently do not support Server Name Indication (It is not currently supported by SslStream class)
endPoint.GetType().Name, endPoint.IpAddress, endPoint.Port); //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) //ProxyServer.UpStreamHttpProxy = new ExternalProxy() { HostName = "localhost", Port = 8888 };
Console.Read(); //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 //Unsubscribe & Quit
ProxyServer.BeforeRequest -= OnRequest; ProxyServer.BeforeRequest -= OnRequest;
ProxyServer.BeforeResponse -= OnResponse; ProxyServer.BeforeResponse -= OnResponse;
ProxyServer.Stop(); ProxyServer.Stop();
``` ```
Sample request and response event handlers 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; using System;
using System.Globalization;
using System.IO; using System.IO;
using System.Linq;
using System.Text; using System.Text;
using Titanium.Web.Proxy.Exceptions; using Titanium.Web.Proxy.Exceptions;
using Titanium.Web.Proxy.Network;
using Titanium.Web.Proxy.Responses;
using Titanium.Web.Proxy.Decompression; using Titanium.Web.Proxy.Decompression;
using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Http.Responses;
using Titanium.Web.Proxy.Extensions;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Network;
namespace Titanium.Web.Proxy.EventArguments namespace Titanium.Web.Proxy.EventArguments
{ {
...@@ -24,7 +25,7 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -24,7 +25,7 @@ namespace Titanium.Web.Proxy.EventArguments
internal SessionEventArgs() internal SessionEventArgs()
{ {
Client = new ProxyClient(); Client = new ProxyClient();
ProxySession = new HttpWebSession(); WebSession = new HttpWebSession();
} }
/// <summary> /// <summary>
...@@ -41,45 +42,8 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -41,45 +42,8 @@ namespace Titanium.Web.Proxy.EventArguments
/// A web session corresponding to a single request/response sequence /// A web session corresponding to a single request/response sequence
/// within a proxy connection /// within a proxy connection
/// </summary> /// </summary>
public HttpWebSession ProxySession { get; set; } public HttpWebSession WebSession { get; set; }
/// <summary>
/// A shortcut to get the request content length
/// </summary>
public int RequestContentLength
{
get
{
return ProxySession.Request.ContentLength;
}
}
/// <summary>
/// A shortcut to get the request Method (GET/POST/PUT etc)
/// </summary>
public string RequestMethod
{
get { return ProxySession.Request.Method; }
}
/// <summary>
/// A shortcut to get the response status code (200 OK, 404 etc)
/// </summary>
public string ResponseStatusCode
{
get { return ProxySession.Response.ResponseStatusCode; }
}
/// <summary>
/// A shortcut to get the response content type
/// </summary>
public string ResponseContentType
{
get
{
return ProxySession.Response.ContentType;
}
}
/// <summary> /// <summary>
/// implement any cleanup here /// implement any cleanup here
...@@ -92,132 +56,82 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -92,132 +56,82 @@ namespace Titanium.Web.Proxy.EventArguments
/// <summary> /// <summary>
/// Read request body content as bytes[] for current session /// Read request body content as bytes[] for current session
/// </summary> /// </summary>
private void ReadRequestBody() private async Task ReadRequestBody()
{ {
//GET request don't have a request body to read //GET request don't have a request body to read
if ((ProxySession.Request.Method.ToUpper() != "POST" && ProxySession.Request.Method.ToUpper() != "PUT")) if ((WebSession.Request.Method.ToUpper() != "POST" && WebSession.Request.Method.ToUpper() != "PUT"))
{ {
throw new BodyNotFoundException("Request don't have a body." + throw new BodyNotFoundException("Request don't have a body." +
"Please verify that this request is a Http POST/PUT and request content length is greater than zero before accessing the body."); "Please verify that this request is a Http POST/PUT and request content length is greater than zero before accessing the body.");
} }
//Caching check //Caching check
if (ProxySession.Request.RequestBody == null) if (WebSession.Request.RequestBody == null)
{ {
var isChunked = false;
string requestContentEncoding = null;
//get compression method (gzip, zlib etc) //If chunked then its easy just read the whole body with the content length mentioned in the request header
if (ProxySession.Request.RequestHeaders.Any(x => x.Name.ToLower() == "content-encoding"))
{
requestContentEncoding = ProxySession.Request.RequestHeaders.First(x => x.Name.ToLower() == "content-encoding").Value;
}
//check if the request have chunked body (body send chunck by chunck without a fixed length) using (var requestBodyStream = new MemoryStream())
if (ProxySession.Request.RequestHeaders.Any(x => x.Name.ToLower() == "transfer-encoding"))
{ {
var transferEncoding = //For chunked request we need to read data as they arrive, until we reach a chunk end symbol
ProxySession.Request.RequestHeaders.First(x => x.Name.ToLower() == "transfer-encoding").Value.ToLower(); if (WebSession.Request.IsChunked)
if (transferEncoding.Contains("chunked"))
{ {
isChunked = true; await this.Client.ClientStreamReader.CopyBytesToStreamChunked(requestBodyStream).ConfigureAwait(false);
} }
} else
//If not chunked then its easy just read the whole body with the content length mentioned in the request header
if (requestContentEncoding == null && !isChunked)
ProxySession.Request.RequestBody = this.Client.ClientStreamReader.ReadBytes(RequestContentLength);
else
{
using (var requestBodyStream = new MemoryStream())
{ {
//For chunked request we need to read data as they arrive, until we reach a chunk end symbol //If not chunked then its easy just read the whole body with the content length mentioned in the request header
if (isChunked) if (WebSession.Request.ContentLength > 0)
{ {
while (true) //If not chunked then its easy just read the amount of bytes mentioned in content length header of response
{ await this.Client.ClientStreamReader.CopyBytesToStream(requestBodyStream, WebSession.Request.ContentLength).ConfigureAwait(false);
var chuchkHead = this.Client.ClientStreamReader.ReadLine();
var chunkSize = int.Parse(chuchkHead, NumberStyles.HexNumber);
if (chunkSize != 0)
{
var buffer = this.Client.ClientStreamReader.ReadBytes(chunkSize);
requestBodyStream.Write(buffer, 0, buffer.Length);
//chunk trail
this.Client.ClientStreamReader.ReadLine();
}
else
{
//chunk end
this.Client.ClientStreamReader.ReadLine();
break;
}
}
}
try
{
ProxySession.Request.RequestBody = GetDecompressedResponseBody(requestContentEncoding, requestBodyStream.ToArray());
}
catch
{
//if decompression fails, just assign the body stream as it it
//Not a safe option
ProxySession.Request.RequestBody = requestBodyStream.ToArray();
} }
else if (WebSession.Request.HttpVersion.ToLower().Trim().Equals("http/1.0"))
await WebSession.ServerConnection.StreamReader.CopyBytesToStream(requestBodyStream, long.MaxValue).ConfigureAwait(false);
} }
WebSession.Request.RequestBody = await GetDecompressedResponseBody(WebSession.Request.ContentEncoding, requestBodyStream.ToArray()).ConfigureAwait(false);
} }
//Now set the flag to true
//So that next time we can deliver body from cache
WebSession.Request.RequestBodyRead = true;
} }
//Now set the flag to true
//So that next time we can deliver body from cache
ProxySession.Request.RequestBodyRead = true;
} }
/// <summary> /// <summary>
/// Read response body as byte[] for current response /// Read response body as byte[] for current response
/// </summary> /// </summary>
private void ReadResponseBody() private async Task ReadResponseBody()
{ {
//If not already read (not cached yet) //If not already read (not cached yet)
if (ProxySession.Response.ResponseBody == null) if (WebSession.Response.ResponseBody == null)
{ {
using (var responseBodyStream = new MemoryStream()) using (var responseBodyStream = new MemoryStream())
{ {
//If chuncked the read chunk by chunk until we hit chunk end symbol //If chuncked the read chunk by chunk until we hit chunk end symbol
if (ProxySession.Response.IsChunked) if (WebSession.Response.IsChunked)
{ {
while (true) await WebSession.ServerConnection.StreamReader.CopyBytesToStreamChunked(responseBodyStream).ConfigureAwait(false);
{
var chuchkHead = ProxySession.ProxyClient.ServerStreamReader.ReadLine();
var chunkSize = int.Parse(chuchkHead, NumberStyles.HexNumber);
if (chunkSize != 0)
{
var buffer = ProxySession.ProxyClient.ServerStreamReader.ReadBytes(chunkSize);
responseBodyStream.Write(buffer, 0, buffer.Length);
//chunk trail
ProxySession.ProxyClient.ServerStreamReader.ReadLine();
}
else
{
//chuck end
ProxySession.ProxyClient.ServerStreamReader.ReadLine();
break;
}
}
} }
else else
{ {
//If not chunked then its easy just read the amount of bytes mentioned in content length header of response if (WebSession.Response.ContentLength > 0)
var buffer = ProxySession.ProxyClient.ServerStreamReader.ReadBytes(ProxySession.Response.ContentLength); {
responseBodyStream.Write(buffer, 0, buffer.Length); //If not chunked then its easy just read the amount of bytes mentioned in content length header of response
await WebSession.ServerConnection.StreamReader.CopyBytesToStream(responseBodyStream, WebSession.Response.ContentLength).ConfigureAwait(false);
}
else if(WebSession.Response.HttpVersion.ToLower().Trim().Equals("http/1.0"))
await WebSession.ServerConnection.StreamReader.CopyBytesToStream(responseBodyStream, long.MaxValue).ConfigureAwait(false);
} }
ProxySession.Response.ResponseBody = GetDecompressedResponseBody(ProxySession.Response.ContentEncoding, responseBodyStream.ToArray()); WebSession.Response.ResponseBody = await GetDecompressedResponseBody(WebSession.Response.ContentEncoding, responseBodyStream.ToArray()).ConfigureAwait(false);
} }
//set this to true for caching //set this to true for caching
ProxySession.Response.ResponseBodyRead = true; WebSession.Response.ResponseBodyRead = true;
} }
} }
...@@ -225,130 +139,149 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -225,130 +139,149 @@ namespace Titanium.Web.Proxy.EventArguments
/// Gets the request body as bytes /// Gets the request body as bytes
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public byte[] GetRequestBody() public async Task<byte[]> GetRequestBody()
{ {
if (ProxySession.Request.RequestLocked) throw new Exception("You cannot call this function after request is made to server."); if (WebSession.Request.RequestLocked)
throw new Exception("You cannot call this function after request is made to server.");
ReadRequestBody(); await ReadRequestBody().ConfigureAwait(false);
return ProxySession.Request.RequestBody; return WebSession.Request.RequestBody;
} }
/// <summary> /// <summary>
/// Gets the request body as string /// Gets the request body as string
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public string GetRequestBodyAsString() public async Task<string> GetRequestBodyAsString()
{ {
if (ProxySession.Request.RequestLocked) throw new Exception("You cannot call this function after request is made to server."); if (WebSession.Request.RequestLocked)
throw new Exception("You cannot call this function after request is made to server.");
ReadRequestBody(); await ReadRequestBody().ConfigureAwait(false);
//Use the encoding specified in request to decode the byte[] data to string //Use the encoding specified in request to decode the byte[] data to string
return ProxySession.Request.RequestBodyString ?? (ProxySession.Request.RequestBodyString = ProxySession.Request.Encoding.GetString(ProxySession.Request.RequestBody)); return WebSession.Request.RequestBodyString ?? (WebSession.Request.RequestBodyString = WebSession.Request.Encoding.GetString(WebSession.Request.RequestBody));
} }
/// <summary> /// <summary>
/// Sets the request body /// Sets the request body
/// </summary> /// </summary>
/// <param name="body"></param> /// <param name="body"></param>
public void SetRequestBody(byte[] body) public async Task SetRequestBody(byte[] body)
{ {
if (ProxySession.Request.RequestLocked) throw new Exception("You cannot call this function after request is made to server."); if (WebSession.Request.RequestLocked)
throw new Exception("You cannot call this function after request is made to server.");
//syphon out the request body from client before setting the new body //syphon out the request body from client before setting the new body
if (!ProxySession.Request.RequestBodyRead) if (!WebSession.Request.RequestBodyRead)
{ {
ReadRequestBody(); await ReadRequestBody().ConfigureAwait(false);
} }
ProxySession.Request.RequestBody = body; WebSession.Request.RequestBody = body;
ProxySession.Request.RequestBodyRead = true;
if (WebSession.Request.IsChunked == false)
WebSession.Request.ContentLength = body.Length;
else
WebSession.Request.ContentLength = -1;
} }
/// <summary> /// <summary>
/// Sets the body with the specified string /// Sets the body with the specified string
/// </summary> /// </summary>
/// <param name="body"></param> /// <param name="body"></param>
public void SetRequestBodyString(string body) public async Task SetRequestBodyString(string body)
{ {
if (ProxySession.Request.RequestLocked) throw new Exception("You cannot call this function after request is made to server."); if (WebSession.Request.RequestLocked)
throw new Exception("You cannot call this function after request is made to server.");
//syphon out the request body from client before setting the new body //syphon out the request body from client before setting the new body
if (!ProxySession.Request.RequestBodyRead) if (!WebSession.Request.RequestBodyRead)
{ {
ReadRequestBody(); await ReadRequestBody().ConfigureAwait(false);
} }
ProxySession.Request.RequestBody = ProxySession.Request.Encoding.GetBytes(body); await SetRequestBody(WebSession.Request.Encoding.GetBytes(body));
ProxySession.Request.RequestBodyRead = true;
} }
/// <summary> /// <summary>
/// Gets the response body as byte array /// Gets the response body as byte array
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public byte[] GetResponseBody() public async Task<byte[]> GetResponseBody()
{ {
if (!ProxySession.Request.RequestLocked) throw new Exception("You cannot call this function before request is made to server."); if (!WebSession.Request.RequestLocked)
throw new Exception("You cannot call this function before request is made to server.");
ReadResponseBody(); await ReadResponseBody().ConfigureAwait(false);
return ProxySession.Response.ResponseBody; return WebSession.Response.ResponseBody;
} }
/// <summary> /// <summary>
/// Gets the response body as string /// Gets the response body as string
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public string GetResponseBodyAsString() public async Task<string> GetResponseBodyAsString()
{ {
if (!ProxySession.Request.RequestLocked) throw new Exception("You cannot call this function before request is made to server."); if (!WebSession.Request.RequestLocked)
throw new Exception("You cannot call this function before request is made to server.");
GetResponseBody(); await GetResponseBody().ConfigureAwait(false);
return ProxySession.Response.ResponseBodyString ?? (ProxySession.Response.ResponseBodyString = ProxySession.Response.Encoding.GetString(ProxySession.Response.ResponseBody)); return WebSession.Response.ResponseBodyString ?? (WebSession.Response.ResponseBodyString = WebSession.Response.Encoding.GetString(WebSession.Response.ResponseBody));
} }
/// <summary> /// <summary>
/// Set the response body bytes /// Set the response body bytes
/// </summary> /// </summary>
/// <param name="body"></param> /// <param name="body"></param>
public void SetResponseBody(byte[] body) public async Task SetResponseBody(byte[] body)
{ {
if (!ProxySession.Request.RequestLocked) throw new Exception("You cannot call this function before request is made to server."); if (!WebSession.Request.RequestLocked)
throw new Exception("You cannot call this function before request is made to server.");
//syphon out the response body from server before setting the new body //syphon out the response body from server before setting the new body
if (ProxySession.Response.ResponseBody == null) if (WebSession.Response.ResponseBody == null)
{ {
GetResponseBody(); await GetResponseBody().ConfigureAwait(false);
} }
ProxySession.Response.ResponseBody = body; WebSession.Response.ResponseBody = body;
//If there is a content length header update it
if (WebSession.Response.IsChunked == false)
WebSession.Response.ContentLength = body.Length;
else
WebSession.Response.ContentLength = -1;
} }
/// <summary> /// <summary>
/// Replace the response body with the specified string /// Replace the response body with the specified string
/// </summary> /// </summary>
/// <param name="body"></param> /// <param name="body"></param>
public void SetResponseBodyString(string body) public async Task SetResponseBodyString(string body)
{ {
if (!ProxySession.Request.RequestLocked) throw new Exception("You cannot call this function before request is made to server."); if (!WebSession.Request.RequestLocked)
throw new Exception("You cannot call this function before request is made to server.");
//syphon out the response body from server before setting the new body //syphon out the response body from server before setting the new body
if (ProxySession.Response.ResponseBody == null) if (WebSession.Response.ResponseBody == null)
{ {
GetResponseBody(); await GetResponseBody().ConfigureAwait(false);
} }
var bodyBytes = ProxySession.Response.Encoding.GetBytes(body); var bodyBytes = WebSession.Response.Encoding.GetBytes(body);
SetResponseBody(bodyBytes);
}
private byte[] GetDecompressedResponseBody(string encodingType, byte[] responseBodyStream) await SetResponseBody(bodyBytes).ConfigureAwait(false);
}
private async Task<byte[]> GetDecompressedResponseBody(string encodingType, byte[] responseBodyStream)
{ {
var decompressionFactory = new DecompressionFactory(); var decompressionFactory = new DecompressionFactory();
var decompressor = decompressionFactory.Create(encodingType); var decompressor = decompressionFactory.Create(encodingType);
return decompressor.Decompress(responseBodyStream); return await decompressor.Decompress(responseBodyStream).ConfigureAwait(false);
} }
...@@ -358,16 +291,17 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -358,16 +291,17 @@ namespace Titanium.Web.Proxy.EventArguments
/// and ignore the request /// and ignore the request
/// </summary> /// </summary>
/// <param name="html"></param> /// <param name="html"></param>
public void Ok(string html) public async Task Ok(string html)
{ {
if (ProxySession.Request.RequestLocked) throw new Exception("You cannot call this function after request is made to server."); if (WebSession.Request.RequestLocked)
throw new Exception("You cannot call this function after request is made to server.");
if (html == null) if (html == null)
html = string.Empty; html = string.Empty;
var result = Encoding.Default.GetBytes(html); var result = Encoding.Default.GetBytes(html);
Ok(result); await Ok(result).ConfigureAwait(false);
} }
/// <summary> /// <summary>
...@@ -376,43 +310,43 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -376,43 +310,43 @@ namespace Titanium.Web.Proxy.EventArguments
/// and ignore the request /// and ignore the request
/// </summary> /// </summary>
/// <param name="body"></param> /// <param name="body"></param>
public void Ok(byte[] result) public async Task Ok(byte[] result)
{ {
var response = new OkResponse(); var response = new OkResponse();
response.HttpVersion = ProxySession.Request.HttpVersion; response.HttpVersion = WebSession.Request.HttpVersion;
response.ResponseBody = result; response.ResponseBody = result;
Respond(response); await Respond(response).ConfigureAwait(false);
ProxySession.Request.CancelRequest = true; WebSession.Request.CancelRequest = true;
} }
public void Redirect(string url) public async Task Redirect(string url)
{ {
var response = new RedirectResponse(); var response = new RedirectResponse();
response.HttpVersion = ProxySession.Request.HttpVersion; response.HttpVersion = WebSession.Request.HttpVersion;
response.ResponseHeaders.Add(new Models.HttpHeader("Location", url)); response.ResponseHeaders.Add(new Models.HttpHeader("Location", url));
response.ResponseBody = Encoding.ASCII.GetBytes(string.Empty); response.ResponseBody = Encoding.ASCII.GetBytes(string.Empty);
Respond(response); await Respond(response).ConfigureAwait(false);
ProxySession.Request.CancelRequest = true; WebSession.Request.CancelRequest = true;
} }
/// a generic responder method /// a generic responder method
public void Respond(Response response) public async Task Respond(Response response)
{ {
ProxySession.Request.RequestLocked = true; WebSession.Request.RequestLocked = true;
response.ResponseLocked = true; response.ResponseLocked = true;
response.ResponseBodyRead = true; response.ResponseBodyRead = true;
ProxySession.Response = response; WebSession.Response = response;
ProxyServer.HandleHttpSessionResponse(this); await ProxyServer.HandleHttpSessionResponse(this).ConfigureAwait(false);
} }
} }
} }
\ No newline at end of file
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))
return Encoding.GetEncoding("ISO-8859-1");
try 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;
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;
{
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;
while (true) long bytesToRead;
{ if (totalBytesToRead < Constants.BUFFER_SIZE)
// wait for the read operation to complete {
read.AsyncWaitHandle.WaitOne(); bytesToRead = totalBytesToRead;
bufl[bufno] = input.EndRead(read); }
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 while (totalbytesRead < totalBytesToRead)
// the only time one won't exist is after the very first read operation completes {
if (write != null) var buffer = await streamReader.ReadBytesAsync(bytesToRead);
{
write.AsyncWaitHandle.WaitOne();
output.EndWrite(write);
}
// start the new write operation if (buffer.Length == 0)
write = output.BeginWrite(buf[bufno], 0, bufl[bufno], null, null); break;
// toggle the current, in-use buffer totalbytesRead += buffer.Length;
// 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 var remainingBytes = totalBytesToRead - totalbytesRead;
// the only time one won't exist is if the input stream is empty. if (remainingBytes < bytesToRead)
if (write != null)
{ {
write.AsyncWaitHandle.WaitOne(); bytesToRead = remainingBytes;
output.EndWrite(write);
} }
await stream.WriteAsync(buffer, 0, buffer.Length);
output.Flush();
} }
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.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,15 +4,18 @@ using System.Diagnostics; ...@@ -4,15 +4,18 @@ 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
{ {
public class CertificateManager : IDisposable public class CertificateManager : IDisposable
{ {
private const string CertCreateFormat = 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}"; "-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,103 +67,126 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -64,103 +67,126 @@ 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;
try
{ {
X509Certificate2 certificate = null; store.Open(OpenFlags.ReadWrite);
try 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 = var certificates =
FindCertificates(store, certificateSubject); FindCertificates(store, certificateSubject);
if (certificates != null) if (certificates != null)
certificate = certificates[0]; certificate = certificates[0];
if (certificate == null) if (certificate == null)
{ {
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();
if (certificate != null && !_certificateCache.ContainsKey(certificateName))
_certificateCache.Add(certificateName, certificate);
} }
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)) // there is no non-generic TaskCompletionSource
throw new Exception("Unable to locate 'makecert.exe'."); var tcs = new TaskCompletionSource<int>();
var process = new Process();
process.StartInfo.Verb = "runas"; string file = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "makecert.exe");
process.StartInfo.Arguments = args != null ? args[0] : string.Empty;
process.StartInfo.CreateNoWindow = true;
process.StartInfo.UseShellExecute = false;
process.StartInfo.FileName = file;
process.Start(); if (!File.Exists(file))
process.WaitForExit(); 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; store.Open(OpenFlags.ReadWrite);
try 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); 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(); _certificateCache.Remove(certificateName);
if (certificates == null &&
_certificateCache.ContainsKey(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;
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 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;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.Linq;
using System.Net;
using System.Net.Security;
using System.Net.Sockets;
using System.Text; using System.Text;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Models; using Titanium.Web.Proxy.Models;
using System.Linq;
using Titanium.Web.Proxy.Extensions; using Titanium.Web.Proxy.Extensions;
namespace Titanium.Web.Proxy.Network namespace Titanium.Web.Proxy.Http
{ {
public class Request public class Request
{ {
...@@ -39,30 +32,56 @@ namespace Titanium.Web.Proxy.Network ...@@ -39,30 +32,56 @@ namespace Titanium.Web.Proxy.Network
} }
} }
public int ContentLength internal string ContentEncoding
{
get
{
var header = this.RequestHeaders.FirstOrDefault(x => x.Name.ToLower().Equals("content-encoding"));
if (header != null)
{
return header.Value.Trim();
}
return null;
}
}
public long ContentLength
{ {
get get
{ {
var header = RequestHeaders.FirstOrDefault(x => x.Name.ToLower() == "content-length"); var header = RequestHeaders.FirstOrDefault(x => x.Name.ToLower() == "content-length");
if (header == null) if (header == null)
return 0; return -1;
int contentLen; long contentLen;
int.TryParse(header.Value, out contentLen); long.TryParse(header.Value, out contentLen);
if (contentLen != 0) if (contentLen >=0)
return contentLen; return contentLen;
return 0; return -1;
} }
set set
{ {
var header = RequestHeaders.FirstOrDefault(x => x.Name.ToLower() == "content-length"); var header = RequestHeaders.FirstOrDefault(x => x.Name.ToLower() == "content-length");
if (header != null) if (value >= 0)
header.Value = value.ToString(); {
if (header != null)
header.Value = value.ToString();
else
RequestHeaders.Add(new HttpHeader("content-length", value.ToString()));
IsChunked = false;
}
else else
RequestHeaders.Add(new HttpHeader("content-length", value.ToString())); {
if (header != null)
this.RequestHeaders.Remove(header);
}
} }
} }
...@@ -87,7 +106,7 @@ namespace Titanium.Web.Proxy.Network ...@@ -87,7 +106,7 @@ namespace Titanium.Web.Proxy.Network
} }
public bool SendChunked public bool IsChunked
{ {
get get
{ {
...@@ -95,6 +114,27 @@ namespace Titanium.Web.Proxy.Network ...@@ -95,6 +114,27 @@ namespace Titanium.Web.Proxy.Network
if (header != null) return header.Value.ToLower().Contains("chunked"); if (header != null) return header.Value.ToLower().Contains("chunked");
return false; return false;
} }
set
{
var header = RequestHeaders.FirstOrDefault(x => x.Name.ToLower() == "transfer-encoding");
if (value)
{
if (header != null)
{
header.Value = "chunked";
}
else
RequestHeaders.Add(new HttpHeader("transfer-encoding", "chunked"));
this.ContentLength = -1;
}
else
{
if (header != null)
RequestHeaders.Remove(header);
}
}
} }
public bool ExpectContinue public bool ExpectContinue
...@@ -146,253 +186,4 @@ namespace Titanium.Web.Proxy.Network ...@@ -146,253 +186,4 @@ namespace Titanium.Web.Proxy.Network
} }
} }
public class Response
{
public string ResponseStatusCode { get; set; }
public string ResponseStatusDescription { get; set; }
internal Encoding Encoding { get { return this.GetResponseEncoding(); } }
internal string CharacterSet
{
get
{
if (this.ContentType.Contains(";"))
{
return this.ContentType.Split(';')[1].Substring(9).Trim();
}
return null;
}
}
internal string ContentEncoding
{
get
{
var header = this.ResponseHeaders.FirstOrDefault(x => x.Name.ToLower().Equals("content-encoding"));
if (header != null)
{
return header.Value.Trim().ToLower();
}
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)
{
if (header.Value.Contains(";"))
{
return header.Value.Split(';')[0].Trim();
}
else
return header.Value.ToLower().Trim();
}
return null;
}
}
internal int ContentLength
{
get
{
var header = this.ResponseHeaders.FirstOrDefault(x => x.Name.ToLower().Equals("content-length"));
if (header != null)
{
return int.Parse(header.Value.Trim());
}
return -1;
}
}
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;
}
}
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>();
}
}
public class HttpWebSession
{
private const string Space = " ";
public bool IsSecure
{
get
{
return this.Request.RequestUri.Scheme == Uri.UriSchemeHttps;
}
}
public Request Request { get; set; }
public Response Response { get; set; }
internal TcpConnection ProxyClient { get; set; }
public void SetConnection(TcpConnection Connection)
{
Connection.LastAccess = DateTime.Now;
ProxyClient = Connection;
}
public HttpWebSession()
{
this.Request = new Request();
this.Response = new Response();
}
public void SendRequest()
{
Stream stream = ProxyClient.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);
stream.Write(requestBytes, 0, requestBytes.Length);
stream.Flush();
if (ProxyServer.Enable100ContinueBehaviour)
if (this.Request.ExpectContinue)
{
var httpResult = ProxyClient.ServerStreamReader.ReadLine().Split(new char[] { ' ' }, 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;
ProxyClient.ServerStreamReader.ReadLine();
}
else if (responseStatusCode.Equals("417")
&& responseStatusDescription.ToLower().Equals("expectation failed"))
{
this.Request.ExpectationFailed = true;
ProxyClient.ServerStreamReader.ReadLine();
}
}
}
public void ReceiveResponse()
{
//return if this is already read
if (this.Response.ResponseStatusCode != null) return;
var httpResult = ProxyClient.ServerStreamReader.ReadLine().Split(new char[] { ' ' }, 3);
if (string.IsNullOrEmpty(httpResult[0]))
{
var s = ProxyClient.ServerStreamReader.ReadLine();
}
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;
ProxyClient.ServerStreamReader.ReadLine();
ReceiveResponse();
return;
}
else if (this.Response.ResponseStatusCode.Equals("417")
&& this.Response.ResponseStatusDescription.ToLower().Equals("expectation failed"))
{
this.Response.ExpectationFailed = true;
this.Response.ResponseStatusCode = null;
ProxyClient.ServerStreamReader.ReadLine();
ReceiveResponse();
return;
}
List<string> responseLines = ProxyClient.ServerStreamReader.ReadAllLines();
for (int index = 0; index < responseLines.Count; ++index)
{
string[] strArray = responseLines[index].Split(new char[] { ':' }, 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)
{ {
var client = new TcpClient(Hostname, port); TcpClient client;
var stream = (Stream)client.GetStream(); 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 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
{ {
...@@ -21,36 +18,15 @@ namespace Titanium.Web.Proxy ...@@ -21,36 +18,15 @@ namespace Titanium.Web.Proxy
/// </summary> /// </summary>
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)
{ {
...@@ -228,25 +217,27 @@ namespace Titanium.Web.Proxy ...@@ -228,25 +217,27 @@ namespace Titanium.Web.Proxy
private static void OnAcceptConnection(IAsyncResult asyn) private static void OnAcceptConnection(IAsyncResult asyn)
{ {
var endPoint = (ProxyEndPoint)asyn.AsyncState; var endPoint = (ProxyEndPoint)asyn.AsyncState;
try try
{ {
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;
} }
} }
} }
} }
\ No newline at end of file
...@@ -3,29 +3,30 @@ using System.Collections.Generic; ...@@ -3,29 +3,30 @@ using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net;
using System.Net.Security; using System.Net.Security;
using System.Net.Sockets; using System.Net.Sockets;
using System.Security.Authentication; using System.Security.Authentication;
using System.Text; using System.Text;
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.Extensions;
using Titanium.Web.Proxy.Helpers; using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Network; using Titanium.Web.Proxy.Network;
using Titanium.Web.Proxy.Models; using Titanium.Web.Proxy.Models;
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
using Titanium.Web.Proxy.Shared;
using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Extensions;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy namespace Titanium.Web.Proxy
{ {
partial class ProxyServer partial class ProxyServer
{ {
//This is called when client is aware of proxy //This is called when client is aware of proxy
private static void HandleClient(ExplicitProxyEndPoint endPoint, TcpClient client) private static async void HandleClient(ExplicitProxyEndPoint endPoint, TcpClient client)
{ {
Stream clientStream = client.GetStream(); Stream clientStream = client.GetStream();
var clientStreamReader = new CustomBinaryReader(clientStream, Encoding.ASCII); var clientStreamReader = new CustomBinaryReader(clientStream);
var clientStreamWriter = new StreamWriter(clientStream); var clientStreamWriter = new StreamWriter(clientStream);
Uri httpRemoteUri; Uri httpRemoteUri;
...@@ -33,7 +34,7 @@ namespace Titanium.Web.Proxy ...@@ -33,7 +34,7 @@ namespace Titanium.Web.Proxy
{ {
//read the first line HTTP command //read the first line HTTP command
var httpCmd = clientStreamReader.ReadLine(); var httpCmd = await clientStreamReader.ReadLineAsync().ConfigureAwait(false);
if (string.IsNullOrEmpty(httpCmd)) if (string.IsNullOrEmpty(httpCmd))
{ {
...@@ -42,7 +43,7 @@ namespace Titanium.Web.Proxy ...@@ -42,7 +43,7 @@ namespace Titanium.Web.Proxy
} }
//break up the line into three components (method, remote URL & Http Version) //break up the line into three components (method, remote URL & Http Version)
var httpCmdSplit = httpCmd.Split(SpaceSplit, 3); var httpCmdSplit = httpCmd.Split(Constants.SpaceSplit, 3);
var httpVerb = httpCmdSplit[0]; var httpVerb = httpCmdSplit[0];
...@@ -51,7 +52,10 @@ namespace Titanium.Web.Proxy ...@@ -51,7 +52,10 @@ namespace Titanium.Web.Proxy
else else
httpRemoteUri = new Uri(httpCmdSplit[1]); httpRemoteUri = new Uri(httpCmdSplit[1]);
var httpVersion = httpCmdSplit[2]; string httpVersion = "HTTP/1.1";
if (httpCmdSplit.Length == 3)
httpVersion = httpCmdSplit[2];
var excluded = endPoint.ExcludedHttpsHostNameRegex != null ? endPoint.ExcludedHttpsHostNameRegex.Any(x => Regex.IsMatch(httpRemoteUri.Host, x)) : false; var excluded = endPoint.ExcludedHttpsHostNameRegex != null ? endPoint.ExcludedHttpsHostNameRegex.Any(x => Regex.IsMatch(httpRemoteUri.Host, x)) : false;
...@@ -59,11 +63,11 @@ namespace Titanium.Web.Proxy ...@@ -59,11 +63,11 @@ namespace Titanium.Web.Proxy
if (httpVerb.ToUpper() == "CONNECT" && !excluded && httpRemoteUri.Port != 80) if (httpVerb.ToUpper() == "CONNECT" && !excluded && httpRemoteUri.Port != 80)
{ {
httpRemoteUri = new Uri("https://" + httpCmdSplit[1]); httpRemoteUri = new Uri("https://" + httpCmdSplit[1]);
clientStreamReader.ReadAllLines(); await clientStreamReader.ReadAllLinesAsync().ConfigureAwait(false);
WriteConnectResponse(clientStreamWriter, httpVersion); await WriteConnectResponse(clientStreamWriter, httpVersion).ConfigureAwait(false);
var certificate = CertManager.CreateCertificate(httpRemoteUri.Host); var certificate = await CertManager.CreateCertificate(httpRemoteUri.Host);
SslStream sslStream = null; SslStream sslStream = null;
...@@ -72,10 +76,10 @@ namespace Titanium.Web.Proxy ...@@ -72,10 +76,10 @@ namespace Titanium.Web.Proxy
sslStream = new SslStream(clientStream, true); sslStream = new SslStream(clientStream, true);
//Successfully managed to authenticate the client using the fake certificate //Successfully managed to authenticate the client using the fake certificate
sslStream.AuthenticateAsServer(certificate, false, await sslStream.AuthenticateAsServerAsync(certificate, false,
SupportedProtocols, false); Constants.SupportedProtocols, false).ConfigureAwait(false);
clientStreamReader = new CustomBinaryReader(sslStream, Encoding.ASCII); clientStreamReader = new CustomBinaryReader(sslStream);
clientStreamWriter = new StreamWriter(sslStream); clientStreamWriter = new StreamWriter(sslStream);
//HTTPS server created - we can now decrypt the client's traffic //HTTPS server created - we can now decrypt the client's traffic
clientStream = sslStream; clientStream = sslStream;
...@@ -91,16 +95,16 @@ namespace Titanium.Web.Proxy ...@@ -91,16 +95,16 @@ namespace Titanium.Web.Proxy
} }
httpCmd = clientStreamReader.ReadLine(); httpCmd = await clientStreamReader.ReadLineAsync().ConfigureAwait(false);
} }
else if (httpVerb.ToUpper() == "CONNECT") else if (httpVerb.ToUpper() == "CONNECT")
{ {
clientStreamReader.ReadAllLines(); await clientStreamReader.ReadAllLinesAsync().ConfigureAwait(false);
WriteConnectResponse(clientStreamWriter, httpVersion); await WriteConnectResponse(clientStreamWriter, httpVersion).ConfigureAwait(false);
TcpHelper.SendRaw(clientStream, null, null, httpRemoteUri.Host, httpRemoteUri.Port, await TcpHelper.SendRaw(clientStream, null, null, httpRemoteUri.Host, httpRemoteUri.Port,
false); false).ConfigureAwait(false);
Dispose(client, clientStream, clientStreamReader, clientStreamWriter, null); Dispose(client, clientStream, clientStreamReader, clientStreamWriter, null);
return; return;
...@@ -108,8 +112,8 @@ namespace Titanium.Web.Proxy ...@@ -108,8 +112,8 @@ namespace Titanium.Web.Proxy
//Now create the request //Now create the request
HandleHttpSessionRequest(client, httpCmd, clientStream, clientStreamReader, clientStreamWriter, await HandleHttpSessionRequest(client, httpCmd, clientStream, clientStreamReader, clientStreamWriter,
httpRemoteUri.Scheme == Uri.UriSchemeHttps ? true : false); httpRemoteUri.Scheme == Uri.UriSchemeHttps ? true : false).ConfigureAwait(false);
} }
catch catch
{ {
...@@ -119,7 +123,7 @@ namespace Titanium.Web.Proxy ...@@ -119,7 +123,7 @@ namespace Titanium.Web.Proxy
//This is called when requests are routed through router to this endpoint //This is called when requests are routed through router to this endpoint
//For ssl requests //For ssl requests
private static void HandleClient(TransparentProxyEndPoint endPoint, TcpClient tcpClient) private static async void HandleClient(TransparentProxyEndPoint endPoint, TcpClient tcpClient)
{ {
Stream clientStream = tcpClient.GetStream(); Stream clientStream = tcpClient.GetStream();
CustomBinaryReader clientStreamReader = null; CustomBinaryReader clientStreamReader = null;
...@@ -135,15 +139,15 @@ namespace Titanium.Web.Proxy ...@@ -135,15 +139,15 @@ namespace Titanium.Web.Proxy
// certificate = CertManager.CreateCertificate(hostName); // certificate = CertManager.CreateCertificate(hostName);
//} //}
//else //else
certificate = CertManager.CreateCertificate(endPoint.GenericCertificateName); certificate = await CertManager.CreateCertificate(endPoint.GenericCertificateName);
try try
{ {
//Successfully managed to authenticate the client using the fake certificate //Successfully managed to authenticate the client using the fake certificate
sslStream.AuthenticateAsServer(certificate, false, await sslStream.AuthenticateAsServerAsync(certificate, false,
SslProtocols.Tls, false); SslProtocols.Tls, false).ConfigureAwait(false);
clientStreamReader = new CustomBinaryReader(sslStream, Encoding.ASCII); clientStreamReader = new CustomBinaryReader(sslStream);
clientStreamWriter = new StreamWriter(sslStream); clientStreamWriter = new StreamWriter(sslStream);
//HTTPS server created - we can now decrypt the client's traffic //HTTPS server created - we can now decrypt the client's traffic
...@@ -160,17 +164,17 @@ namespace Titanium.Web.Proxy ...@@ -160,17 +164,17 @@ namespace Titanium.Web.Proxy
} }
else else
{ {
clientStreamReader = new CustomBinaryReader(clientStream, Encoding.ASCII); clientStreamReader = new CustomBinaryReader(clientStream);
} }
var httpCmd = clientStreamReader.ReadLine(); var httpCmd = await clientStreamReader.ReadLineAsync().ConfigureAwait(false);
//Now create the request //Now create the request
HandleHttpSessionRequest(tcpClient, httpCmd, clientStream, clientStreamReader, clientStreamWriter, await HandleHttpSessionRequest(tcpClient, httpCmd, clientStream, clientStreamReader, clientStreamWriter,
true); true).ConfigureAwait(false);
} }
private static void HandleHttpSessionRequest(TcpClient client, string httpCmd, Stream clientStream, private static async Task HandleHttpSessionRequest(TcpClient client, string httpCmd, Stream clientStream,
CustomBinaryReader clientStreamReader, StreamWriter clientStreamWriter, bool IsHttps) CustomBinaryReader clientStreamReader, StreamWriter clientStreamWriter, bool IsHttps)
{ {
TcpConnection connection = null; TcpConnection connection = null;
...@@ -190,11 +194,9 @@ namespace Titanium.Web.Proxy ...@@ -190,11 +194,9 @@ namespace Titanium.Web.Proxy
try try
{ {
//break up the line into three components (method, remote URL & Http Version) //break up the line into three components (method, remote URL & Http Version)
var httpCmdSplit = httpCmd.Split(SpaceSplit, 3); var httpCmdSplit = httpCmd.Split(Constants.SpaceSplit, 3);
var httpMethod = httpCmdSplit[0]; var httpMethod = httpCmdSplit[0];
var httpVersion = httpCmdSplit[2]; var httpVersion = httpCmdSplit[2];
Version version; Version version;
...@@ -207,111 +209,125 @@ namespace Titanium.Web.Proxy ...@@ -207,111 +209,125 @@ namespace Titanium.Web.Proxy
version = new Version(1, 0); version = new Version(1, 0);
} }
args.ProxySession.Request.RequestHeaders = new List<HttpHeader>(); args.WebSession.Request.RequestHeaders = new List<HttpHeader>();
string tmpLine; string tmpLine;
while (!string.IsNullOrEmpty(tmpLine = clientStreamReader.ReadLine())) while (!string.IsNullOrEmpty(tmpLine = await clientStreamReader.ReadLineAsync().ConfigureAwait(false)))
{ {
var header = tmpLine.Split(new char[] { ':' }, 2); var header = tmpLine.Split(new char[] { ':' }, 2);
args.ProxySession.Request.RequestHeaders.Add(new HttpHeader(header[0], header[1])); args.WebSession.Request.RequestHeaders.Add(new HttpHeader(header[0], header[1]));
} }
var httpRemoteUri = new Uri(!IsHttps ? httpCmdSplit[1] : (string.Concat("https://", args.ProxySession.Request.Host, httpCmdSplit[1]))); var httpRemoteUri = new Uri(!IsHttps ? httpCmdSplit[1] : (string.Concat("https://", args.WebSession.Request.Host, httpCmdSplit[1])));
args.IsHttps = IsHttps; args.IsHttps = IsHttps;
args.ProxySession.Request.RequestUri = httpRemoteUri; args.WebSession.Request.RequestUri = httpRemoteUri;
args.ProxySession.Request.Method = httpMethod; args.WebSession.Request.Method = httpMethod;
args.ProxySession.Request.HttpVersion = httpVersion; args.WebSession.Request.HttpVersion = httpVersion;
args.Client.ClientStream = clientStream; args.Client.ClientStream = clientStream;
args.Client.ClientStreamReader = clientStreamReader; args.Client.ClientStreamReader = clientStreamReader;
args.Client.ClientStreamWriter = clientStreamWriter; args.Client.ClientStreamWriter = clientStreamWriter;
if (args.ProxySession.Request.UpgradeToWebSocket) if (args.WebSession.Request.UpgradeToWebSocket)
{ {
TcpHelper.SendRaw(clientStream, httpCmd, args.ProxySession.Request.RequestHeaders, await TcpHelper.SendRaw(clientStream, httpCmd, args.WebSession.Request.RequestHeaders,
httpRemoteUri.Host, httpRemoteUri.Port, args.IsHttps); httpRemoteUri.Host, httpRemoteUri.Port, args.IsHttps).ConfigureAwait(false);
Dispose(client, clientStream, clientStreamReader, clientStreamWriter, args); Dispose(client, clientStream, clientStreamReader, clientStreamWriter, args);
return; return;
} }
PrepareRequestHeaders(args.ProxySession.Request.RequestHeaders, args.ProxySession); PrepareRequestHeaders(args.WebSession.Request.RequestHeaders, args.WebSession);
args.WebSession.Request.Host = args.WebSession.Request.RequestUri.Host;
//If requested interception
if (BeforeRequest != null)
{
Delegate[] invocationList = BeforeRequest.GetInvocationList();
Task[] handlerTasks = new Task[invocationList.Length];
for (int i = 0; i < invocationList.Length; i++)
{
handlerTasks[i] = ((Func<object, SessionEventArgs, Task>)invocationList[i])(null, args);
}
await Task.WhenAll(handlerTasks).ConfigureAwait(false);
}
//construct the web request that we are going to issue on behalf of the client. //construct the web request that we are going to issue on behalf of the client.
connection = connection == null ? connection = connection == null ?
TcpConnectionManager.GetClient(args.ProxySession.Request.RequestUri.Host, args.ProxySession.Request.RequestUri.Port, args.IsHttps) await TcpConnectionManager.GetClient(args, args.WebSession.Request.RequestUri.Host, args.WebSession.Request.RequestUri.Port, args.IsHttps, version).ConfigureAwait(false)
: lastRequestHostName != args.ProxySession.Request.RequestUri.Host ? TcpConnectionManager.GetClient(args.ProxySession.Request.RequestUri.Host, args.ProxySession.Request.RequestUri.Port, args.IsHttps) : lastRequestHostName != args.WebSession.Request.RequestUri.Host ? await TcpConnectionManager.GetClient(args, args.WebSession.Request.RequestUri.Host, args.WebSession.Request.RequestUri.Port, args.IsHttps, version).ConfigureAwait(false)
: connection; : connection;
lastRequestHostName = args.ProxySession.Request.RequestUri.Host; lastRequestHostName = args.WebSession.Request.RequestUri.Host;
args.ProxySession.Request.Host = args.ProxySession.Request.RequestUri.Host;
args.WebSession.Request.RequestLocked = true;
//If requested interception
if (BeforeRequest != null) if (args.WebSession.Request.CancelRequest)
{ {
BeforeRequest(null, args); Dispose(client, clientStream, clientStreamReader, clientStreamWriter, args);
break;
} }
args.ProxySession.Request.RequestLocked = true; if (args.WebSession.Request.ExpectContinue)
if (args.ProxySession.Request.ExpectContinue)
{ {
args.ProxySession.SetConnection(connection); args.WebSession.SetConnection(connection);
args.ProxySession.SendRequest(); await args.WebSession.SendRequest().ConfigureAwait(false);
} }
if (Enable100ContinueBehaviour) if (Enable100ContinueBehaviour)
if (args.ProxySession.Request.Is100Continue) if (args.WebSession.Request.Is100Continue)
{ {
WriteResponseStatus(args.ProxySession.Response.HttpVersion, "100", WriteResponseStatus(args.WebSession.Response.HttpVersion, "100",
"Continue", args.Client.ClientStreamWriter); "Continue", args.Client.ClientStreamWriter);
args.Client.ClientStreamWriter.WriteLine(); await args.Client.ClientStreamWriter.WriteLineAsync();
} }
else if (args.ProxySession.Request.ExpectationFailed) else if (args.WebSession.Request.ExpectationFailed)
{ {
WriteResponseStatus(args.ProxySession.Response.HttpVersion, "417", WriteResponseStatus(args.WebSession.Response.HttpVersion, "417",
"Expectation Failed", args.Client.ClientStreamWriter); "Expectation Failed", args.Client.ClientStreamWriter);
args.Client.ClientStreamWriter.WriteLine(); await args.Client.ClientStreamWriter.WriteLineAsync();
} }
if (args.ProxySession.Request.CancelRequest) if (!args.WebSession.Request.ExpectContinue)
{
Dispose(client, clientStream, clientStreamReader, clientStreamWriter, args);
break;
}
if (!args.ProxySession.Request.ExpectContinue)
{ {
args.ProxySession.SetConnection(connection); args.WebSession.SetConnection(connection);
args.ProxySession.SendRequest(); await args.WebSession.SendRequest().ConfigureAwait(false);
} }
//If request was modified by user //If request was modified by user
if (args.ProxySession.Request.RequestBodyRead) if (args.WebSession.Request.RequestBodyRead)
{ {
args.ProxySession.Request.ContentLength = args.ProxySession.Request.RequestBody.Length; if(args.WebSession.Request.ContentEncoding!=null)
var newStream = args.ProxySession.ProxyClient.ServerStreamReader.BaseStream; {
newStream.Write(args.ProxySession.Request.RequestBody, 0, args.ProxySession.Request.RequestBody.Length); args.WebSession.Request.RequestBody = await GetCompressedResponseBody(args.WebSession.Request.ContentEncoding, args.WebSession.Request.RequestBody);
}
//chunked send is not supported as of now
args.WebSession.Request.ContentLength = args.WebSession.Request.RequestBody.Length;
var newStream = args.WebSession.ServerConnection.Stream;
await newStream.WriteAsync(args.WebSession.Request.RequestBody, 0, args.WebSession.Request.RequestBody.Length).ConfigureAwait(false);
} }
else else
{ {
if (!args.ProxySession.Request.ExpectationFailed) if (!args.WebSession.Request.ExpectationFailed)
{ {
//If its a post/put request, then read the client html body and send it to server //If its a post/put request, then read the client html body and send it to server
if (httpMethod.ToUpper() == "POST" || httpMethod.ToUpper() == "PUT") if (httpMethod.ToUpper() == "POST" || httpMethod.ToUpper() == "PUT")
{ {
SendClientRequestBody(args); await SendClientRequestBody(args).ConfigureAwait(false);
} }
} }
} }
if (!args.ProxySession.Request.ExpectationFailed) if (!args.WebSession.Request.ExpectationFailed)
{ {
HandleHttpSessionResponse(args); await HandleHttpSessionResponse(args).ConfigureAwait(false);
} }
//if connection is closing exit //if connection is closing exit
if (args.ProxySession.Response.ResponseKeepAlive == false) if (args.WebSession.Response.ResponseKeepAlive == false)
{ {
connection.TcpClient.Close(); connection.TcpClient.Close();
Dispose(client, clientStream, clientStreamReader, clientStreamWriter, args); Dispose(client, clientStream, clientStreamReader, clientStreamWriter, args);
...@@ -319,7 +335,7 @@ namespace Titanium.Web.Proxy ...@@ -319,7 +335,7 @@ namespace Titanium.Web.Proxy
} }
// read the next request // read the next request
httpCmd = clientStreamReader.ReadLine(); httpCmd = await clientStreamReader.ReadLineAsync().ConfigureAwait(false);
} }
catch catch
...@@ -334,12 +350,12 @@ namespace Titanium.Web.Proxy ...@@ -334,12 +350,12 @@ namespace Titanium.Web.Proxy
TcpConnectionManager.ReleaseClient(connection); TcpConnectionManager.ReleaseClient(connection);
} }
private static void WriteConnectResponse(StreamWriter clientStreamWriter, string httpVersion) private static async Task WriteConnectResponse(StreamWriter clientStreamWriter, string httpVersion)
{ {
clientStreamWriter.WriteLine(httpVersion + " 200 Connection established"); await clientStreamWriter.WriteLineAsync(httpVersion + " 200 Connection established").ConfigureAwait(false);
clientStreamWriter.WriteLine("Timestamp: {0}", DateTime.Now); await clientStreamWriter.WriteLineAsync(string.Format("Timestamp: {0}", DateTime.Now)).ConfigureAwait(false);
clientStreamWriter.WriteLine(); await clientStreamWriter.WriteLineAsync().ConfigureAwait(false);
clientStreamWriter.Flush(); await clientStreamWriter.FlushAsync().ConfigureAwait(false);
} }
private static void PrepareRequestHeaders(List<HttpHeader> requestHeaders, HttpWebSession webRequest) private static void PrepareRequestHeaders(List<HttpHeader> requestHeaders, HttpWebSession webRequest)
...@@ -379,40 +395,16 @@ namespace Titanium.Web.Proxy ...@@ -379,40 +395,16 @@ namespace Titanium.Web.Proxy
headers.RemoveAll(x => x.Name.ToLower() == "proxy-connection"); headers.RemoveAll(x => x.Name.ToLower() == "proxy-connection");
} }
//This is called when the request is PUT/POST to read the body //This is called when the request is PUT/POST to read the body
private static void SendClientRequestBody(SessionEventArgs args) private static async Task SendClientRequestBody(SessionEventArgs args)
{ {
// End the operation // End the operation
var postStream = args.ProxySession.ProxyClient.Stream; var postStream = args.WebSession.ServerConnection.Stream;
if (args.ProxySession.Request.ContentLength > 0) if (args.WebSession.Request.ContentLength > 0)
{ {
//args.ProxyRequest.AllowWriteStreamBuffering = true;
try try
{ {
var totalbytesRead = 0; await args.Client.ClientStreamReader.CopyBytesToStream(postStream, args.WebSession.Request.ContentLength).ConfigureAwait(false);
int bytesToRead;
if (args.ProxySession.Request.ContentLength < BUFFER_SIZE)
{
bytesToRead = (int)args.ProxySession.Request.ContentLength;
}
else
bytesToRead = BUFFER_SIZE;
while (totalbytesRead < (int)args.ProxySession.Request.ContentLength)
{
var buffer = args.Client.ClientStreamReader.ReadBytes(bytesToRead);
totalbytesRead += buffer.Length;
var remainingBytes = (int)args.ProxySession.Request.ContentLength - totalbytesRead;
if (remainingBytes < bytesToRead)
{
bytesToRead = remainingBytes;
}
postStream.Write(buffer, 0, buffer.Length);
}
} }
catch catch
{ {
...@@ -420,29 +412,11 @@ namespace Titanium.Web.Proxy ...@@ -420,29 +412,11 @@ namespace Titanium.Web.Proxy
} }
} }
//Need to revist, find any potential bugs //Need to revist, find any potential bugs
else if (args.ProxySession.Request.SendChunked) else if (args.WebSession.Request.IsChunked)
{ {
try try
{ {
while (true) await args.Client.ClientStreamReader.CopyBytesToStreamChunked(postStream).ConfigureAwait(false);
{
var chuchkHead = args.Client.ClientStreamReader.ReadLine();
var chunkSize = int.Parse(chuchkHead, NumberStyles.HexNumber);
if (chunkSize != 0)
{
var buffer = args.Client.ClientStreamReader.ReadBytes(chunkSize);
postStream.Write(buffer, 0, buffer.Length);
//chunk trail
args.Client.ClientStreamReader.ReadLine();
}
else
{
args.Client.ClientStreamReader.ReadLine();
break;
}
}
} }
catch catch
{ {
...@@ -450,5 +424,59 @@ namespace Titanium.Web.Proxy ...@@ -450,5 +424,59 @@ namespace Titanium.Web.Proxy
} }
} }
} }
/// <summary>
/// Call back to override server certificate validation
/// </summary>
/// <param name="sender"></param>
/// <param name="certificate"></param>
/// <param name="chain"></param>
/// <param name="sslPolicyErrors"></param>
/// <returns></returns>
internal static bool ValidateServerCertificate(
object sender,
X509Certificate certificate,
X509Chain chain,
SslPolicyErrors sslPolicyErrors)
{
var param = sender as CustomSslStream;
if (ServerCertificateValidationCallback != null)
{
var args = new CertificateValidationEventArgs();
args.Session = param.Session;
args.Certificate = certificate;
args.Chain = chain;
args.SslPolicyErrors = sslPolicyErrors;
Delegate[] invocationList = ServerCertificateValidationCallback.GetInvocationList();
Task[] handlerTasks = new Task[invocationList.Length];
for (int i = 0; i < invocationList.Length; i++)
{
handlerTasks[i] = ((Func<object, CertificateValidationEventArgs, Task>)invocationList[i])(null, args);
}
Task.WhenAll(handlerTasks).Wait();
if (!args.IsValid)
{
param.Session.WebSession.Request.CancelRequest = true;
}
return args.IsValid;
}
if (sslPolicyErrors == SslPolicyErrors.None)
return true;
//By default
//do not allow this client to communicate with unauthenticated servers.
return false;
}
} }
} }
\ No newline at end of file
...@@ -3,78 +3,88 @@ using System.Collections.Generic; ...@@ -3,78 +3,88 @@ using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net;
using System.Net.Sockets; using System.Net.Sockets;
using System.Text; using System.Text;
using System.Threading.Tasks;
using Titanium.Web.Proxy.EventArguments; using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Helpers; using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Network;
using Titanium.Web.Proxy.Models; using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Compression; using Titanium.Web.Proxy.Compression;
using Titanium.Web.Proxy.Shared;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy namespace Titanium.Web.Proxy
{ {
partial class ProxyServer partial class ProxyServer
{ {
//Called asynchronously when a request was successfully and we received the response //Called asynchronously when a request was successfully and we received the response
public static void HandleHttpSessionResponse(SessionEventArgs args) public static async Task HandleHttpSessionResponse(SessionEventArgs args)
{ {
args.ProxySession.ReceiveResponse(); await args.WebSession.ReceiveResponse().ConfigureAwait(false);
try try
{ {
if (!args.ProxySession.Response.ResponseBodyRead) if (!args.WebSession.Response.ResponseBodyRead)
args.ProxySession.Response.ResponseStream = args.ProxySession.ProxyClient.ServerStreamReader.BaseStream; args.WebSession.Response.ResponseStream = args.WebSession.ServerConnection.Stream;
if (BeforeResponse != null && !args.WebSession.Response.ResponseLocked)
{
Delegate[] invocationList = BeforeResponse.GetInvocationList();
Task[] handlerTasks = new Task[invocationList.Length];
for (int i = 0; i < invocationList.Length; i++)
{
handlerTasks[i] = ((Func<object, SessionEventArgs, Task>)invocationList[i])(null, args);
}
if (BeforeResponse != null && !args.ProxySession.Response.ResponseLocked) await Task.WhenAll(handlerTasks).ConfigureAwait(false);
{
BeforeResponse(null, args);
} }
args.ProxySession.Response.ResponseLocked = true; args.WebSession.Response.ResponseLocked = true;
if (args.ProxySession.Response.Is100Continue) if (args.WebSession.Response.Is100Continue)
{ {
WriteResponseStatus(args.ProxySession.Response.HttpVersion, "100", WriteResponseStatus(args.WebSession.Response.HttpVersion, "100",
"Continue", args.Client.ClientStreamWriter); "Continue", args.Client.ClientStreamWriter);
args.Client.ClientStreamWriter.WriteLine(); await args.Client.ClientStreamWriter.WriteLineAsync();
} }
else if (args.ProxySession.Response.ExpectationFailed) else if (args.WebSession.Response.ExpectationFailed)
{ {
WriteResponseStatus(args.ProxySession.Response.HttpVersion, "417", WriteResponseStatus(args.WebSession.Response.HttpVersion, "417",
"Expectation Failed", args.Client.ClientStreamWriter); "Expectation Failed", args.Client.ClientStreamWriter);
args.Client.ClientStreamWriter.WriteLine(); await args.Client.ClientStreamWriter.WriteLineAsync();
} }
WriteResponseStatus(args.ProxySession.Response.HttpVersion, args.ProxySession.Response.ResponseStatusCode, WriteResponseStatus(args.WebSession.Response.HttpVersion, args.WebSession.Response.ResponseStatusCode,
args.ProxySession.Response.ResponseStatusDescription, args.Client.ClientStreamWriter); args.WebSession.Response.ResponseStatusDescription, args.Client.ClientStreamWriter);
if (args.ProxySession.Response.ResponseBodyRead) if (args.WebSession.Response.ResponseBodyRead)
{ {
var isChunked = args.ProxySession.Response.IsChunked; var isChunked = args.WebSession.Response.IsChunked;
var contentEncoding = args.ProxySession.Response.ContentEncoding; var contentEncoding = args.WebSession.Response.ContentEncoding;
if (contentEncoding != null) if (contentEncoding != null)
{ {
args.ProxySession.Response.ResponseBody = GetCompressedResponseBody(contentEncoding, args.ProxySession.Response.ResponseBody); args.WebSession.Response.ResponseBody = await GetCompressedResponseBody(contentEncoding, args.WebSession.Response.ResponseBody).ConfigureAwait(false);
if (isChunked == false)
args.WebSession.Response.ContentLength = args.WebSession.Response.ResponseBody.Length;
else
args.WebSession.Response.ContentLength = -1;
} }
WriteResponseHeaders(args.Client.ClientStreamWriter, args.ProxySession.Response.ResponseHeaders, args.ProxySession.Response.ResponseBody.Length, await WriteResponseHeaders(args.Client.ClientStreamWriter, args.WebSession.Response.ResponseHeaders).ConfigureAwait(false);
isChunked); await WriteResponseBody(args.Client.ClientStream, args.WebSession.Response.ResponseBody, isChunked).ConfigureAwait(false);
WriteResponseBody(args.Client.ClientStream, args.ProxySession.Response.ResponseBody, isChunked);
} }
else else
{ {
WriteResponseHeaders(args.Client.ClientStreamWriter, args.ProxySession.Response.ResponseHeaders); await WriteResponseHeaders(args.Client.ClientStreamWriter, args.WebSession.Response.ResponseHeaders);
if (args.ProxySession.Response.IsChunked || args.ProxySession.Response.ContentLength > 0) if (args.WebSession.Response.IsChunked || args.WebSession.Response.ContentLength > 0 || args.WebSession.Response.HttpVersion.ToLower().Trim() == "http/1.0")
WriteResponseBody(args.ProxySession.ProxyClient.ServerStreamReader, args.Client.ClientStream, args.ProxySession.Response.IsChunked, args.ProxySession.Response.ContentLength); await WriteResponseBody(args.WebSession.ServerConnection.StreamReader, args.Client.ClientStream, args.WebSession.Response.IsChunked, args.WebSession.Response.ContentLength).ConfigureAwait(false);
} }
args.Client.ClientStream.Flush(); await args.Client.ClientStream.FlushAsync();
} }
catch catch
...@@ -87,21 +97,21 @@ namespace Titanium.Web.Proxy ...@@ -87,21 +97,21 @@ namespace Titanium.Web.Proxy
} }
} }
private static byte[] GetCompressedResponseBody(string encodingType, byte[] responseBodyStream) private static async Task<byte[]> GetCompressedResponseBody(string encodingType, byte[] responseBodyStream)
{ {
var compressionFactory = new CompressionFactory(); var compressionFactory = new CompressionFactory();
var compressor = compressionFactory.Create(encodingType); var compressor = compressionFactory.Create(encodingType);
return compressor.Compress(responseBodyStream); return await compressor.Compress(responseBodyStream).ConfigureAwait(false);
} }
private static void WriteResponseStatus(string version, string code, string description, private static void WriteResponseStatus(string version, string code, string description,
StreamWriter responseWriter) StreamWriter responseWriter)
{ {
responseWriter.WriteLine(string.Format("{0} {1} {2}", version, code, description)); responseWriter.WriteLineAsync(string.Format("{0} {1} {2}", version, code, description));
} }
private static void WriteResponseHeaders(StreamWriter responseWriter, List<HttpHeader> headers) private static async Task WriteResponseHeaders(StreamWriter responseWriter, List<HttpHeader> headers)
{ {
if (headers != null) if (headers != null)
{ {
...@@ -109,12 +119,12 @@ namespace Titanium.Web.Proxy ...@@ -109,12 +119,12 @@ namespace Titanium.Web.Proxy
foreach (var header in headers) foreach (var header in headers)
{ {
responseWriter.WriteLine(header.ToString()); await responseWriter.WriteLineAsync(header.ToString());
} }
} }
responseWriter.WriteLine(); await responseWriter.WriteLineAsync();
responseWriter.Flush(); await responseWriter.FlushAsync();
} }
private static void FixResponseProxyHeaders(List<HttpHeader> headers) private static void FixResponseProxyHeaders(List<HttpHeader> headers)
{ {
...@@ -135,118 +145,92 @@ namespace Titanium.Web.Proxy ...@@ -135,118 +145,92 @@ namespace Titanium.Web.Proxy
headers.RemoveAll(x => x.Name.ToLower() == "proxy-connection"); headers.RemoveAll(x => x.Name.ToLower() == "proxy-connection");
} }
private static void WriteResponseHeaders(StreamWriter responseWriter, List<HttpHeader> headers, int length, private static async Task WriteResponseBody(Stream clientStream, byte[] data, bool isChunked)
bool isChunked)
{ {
FixResponseProxyHeaders(headers);
if (!isChunked) if (!isChunked)
{ {
if (headers.Any(x => x.Name.ToLower() == "content-length") == false) await clientStream.WriteAsync(data, 0, data.Length).ConfigureAwait(false);
{
headers.Add(new HttpHeader("Content-Length", length.ToString()));
}
}
if (headers != null)
{
foreach (var header in headers)
{
if (!isChunked && header.Name.ToLower() == "content-length")
header.Value = length.ToString();
responseWriter.WriteLine(header.ToString());
}
}
responseWriter.WriteLine();
responseWriter.Flush();
}
private static void WriteResponseBody(Stream clientStream, byte[] data, bool isChunked)
{
if (!isChunked)
{
clientStream.Write(data, 0, data.Length);
} }
else else
WriteResponseBodyChunked(data, clientStream); await WriteResponseBodyChunked(data, clientStream).ConfigureAwait(false);
} }
private static void WriteResponseBody(CustomBinaryReader inStreamReader, Stream outStream, bool isChunked, int BodyLength) private static async Task WriteResponseBody(CustomBinaryReader inStreamReader, Stream outStream, bool isChunked, long ContentLength)
{ {
if (!isChunked) if (!isChunked)
{ {
int bytesToRead = BUFFER_SIZE; //http 1.0
if (ContentLength == -1)
ContentLength = long.MaxValue;
if (BodyLength < BUFFER_SIZE) int bytesToRead = Constants.BUFFER_SIZE;
bytesToRead = BodyLength;
var buffer = new byte[BUFFER_SIZE]; if (ContentLength < Constants.BUFFER_SIZE)
bytesToRead = (int)ContentLength;
var buffer = new byte[Constants.BUFFER_SIZE];
var bytesRead = 0; var bytesRead = 0;
var totalBytesRead = 0; var totalBytesRead = 0;
while ((bytesRead += inStreamReader.BaseStream.Read(buffer, 0, bytesToRead)) > 0) while ((bytesRead += await inStreamReader.BaseStream.ReadAsync(buffer, 0, bytesToRead).ConfigureAwait(false)) > 0)
{ {
outStream.Write(buffer, 0, bytesRead); await outStream.WriteAsync(buffer, 0, bytesRead).ConfigureAwait(false);
totalBytesRead += bytesRead; totalBytesRead += bytesRead;
if (totalBytesRead == BodyLength) if (totalBytesRead == ContentLength)
break; break;
bytesRead = 0; bytesRead = 0;
var remainingBytes = (BodyLength - totalBytesRead); var remainingBytes = (ContentLength - totalBytesRead);
bytesToRead = remainingBytes > BUFFER_SIZE ? BUFFER_SIZE : remainingBytes; bytesToRead = remainingBytes > (long)Constants.BUFFER_SIZE ? Constants.BUFFER_SIZE : (int)remainingBytes;
} }
} }
else else
WriteResponseBodyChunked(inStreamReader, outStream); await WriteResponseBodyChunked(inStreamReader, outStream).ConfigureAwait(false);
} }
//Send chunked response //Send chunked response
private static void WriteResponseBodyChunked(CustomBinaryReader inStreamReader, Stream outStream) private static async Task WriteResponseBodyChunked(CustomBinaryReader inStreamReader, Stream outStream)
{ {
while (true) while (true)
{ {
var chuchkHead = inStreamReader.ReadLine(); var chunkHead = await inStreamReader.ReadLineAsync().ConfigureAwait(false);
var chunkSize = int.Parse(chuchkHead, NumberStyles.HexNumber); var chunkSize = int.Parse(chunkHead, NumberStyles.HexNumber);
if (chunkSize != 0) if (chunkSize != 0)
{ {
var buffer = inStreamReader.ReadBytes(chunkSize); var buffer = await inStreamReader.ReadBytesAsync(chunkSize).ConfigureAwait(false);
var chunkHead = Encoding.ASCII.GetBytes(chunkSize.ToString("x2")); var chunkHeadBytes = Encoding.ASCII.GetBytes(chunkSize.ToString("x2"));
outStream.Write(chunkHead, 0, chunkHead.Length); await outStream.WriteAsync(chunkHeadBytes, 0, chunkHeadBytes.Length).ConfigureAwait(false);
outStream.Write(NewLineBytes, 0, NewLineBytes.Length); await outStream.WriteAsync(Constants.NewLineBytes, 0, Constants.NewLineBytes.Length).ConfigureAwait(false);
outStream.Write(buffer, 0, chunkSize); await outStream.WriteAsync(buffer, 0, chunkSize).ConfigureAwait(false);
outStream.Write(NewLineBytes, 0, NewLineBytes.Length); await outStream.WriteAsync(Constants.NewLineBytes, 0, Constants.NewLineBytes.Length).ConfigureAwait(false);
inStreamReader.ReadLine(); await inStreamReader.ReadLineAsync().ConfigureAwait(false);
} }
else else
{ {
inStreamReader.ReadLine(); await inStreamReader.ReadLineAsync().ConfigureAwait(false);
outStream.Write(ChunkEnd, 0, ChunkEnd.Length); await outStream.WriteAsync(Constants.ChunkEnd, 0, Constants.ChunkEnd.Length).ConfigureAwait(false);
break; break;
} }
} }
} }
private static void WriteResponseBodyChunked(byte[] data, Stream outStream) private static async Task WriteResponseBodyChunked(byte[] data, Stream outStream)
{ {
var chunkHead = Encoding.ASCII.GetBytes(data.Length.ToString("x2")); var chunkHead = Encoding.ASCII.GetBytes(data.Length.ToString("x2"));
outStream.Write(chunkHead, 0, chunkHead.Length); await outStream.WriteAsync(chunkHead, 0, chunkHead.Length).ConfigureAwait(false);
outStream.Write(NewLineBytes, 0, NewLineBytes.Length); await outStream.WriteAsync(Constants.NewLineBytes, 0, Constants.NewLineBytes.Length).ConfigureAwait(false);
outStream.Write(data, 0, data.Length); await outStream.WriteAsync(data, 0, data.Length).ConfigureAwait(false);
outStream.Write(NewLineBytes, 0, NewLineBytes.Length); await outStream.WriteAsync(Constants.NewLineBytes, 0, Constants.NewLineBytes.Length).ConfigureAwait(false);
outStream.Write(ChunkEnd, 0, ChunkEnd.Length); await outStream.WriteAsync(Constants.ChunkEnd, 0, Constants.ChunkEnd.Length).ConfigureAwait(false);
} }
......
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