Commit 95877cde authored by titanium007's avatar titanium007

issues #67 #69 #70 #71 #72

parent e4c68b0b
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
{ {
......
...@@ -5,7 +5,7 @@ using System.Text.RegularExpressions; ...@@ -5,7 +5,7 @@ using System.Text.RegularExpressions;
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 +13,7 @@ namespace Titanium.Web.Proxy.Test ...@@ -13,6 +13,7 @@ namespace Titanium.Web.Proxy.Test
{ {
ProxyServer.BeforeRequest += OnRequest; ProxyServer.BeforeRequest += OnRequest;
ProxyServer.BeforeResponse += OnResponse; ProxyServer.BeforeResponse += OnResponse;
ProxyServer.RemoteCertificateValidationCallback += 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 +35,14 @@ namespace Titanium.Web.Proxy.Test ...@@ -34,13 +35,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} ",
...@@ -63,12 +65,12 @@ namespace Titanium.Web.Proxy.Test ...@@ -63,12 +65,12 @@ namespace Titanium.Web.Proxy.Test
//Read browser URL send back to proxy by the injection script in OnResponse event //Read browser URL send back to proxy by the injection script in OnResponse event
public void 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 = e.GetRequestBody();
...@@ -82,7 +84,7 @@ namespace Titanium.Web.Proxy.Test ...@@ -82,7 +84,7 @@ namespace Titanium.Web.Proxy.Test
//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>" + e.Ok("<!DOCTYPE html>" +
"<html><body><h1>" + "<html><body><h1>" +
...@@ -93,7 +95,7 @@ namespace Titanium.Web.Proxy.Test ...@@ -93,7 +95,7 @@ namespace Titanium.Web.Proxy.Test
"</html>"); "</html>");
} }
//Redirect example //Redirect example
if (e.ProxySession.Request.RequestUri.AbsoluteUri.Contains("wikipedia.org")) if (e.WebSession.Request.RequestUri.AbsoluteUri.Contains("wikipedia.org"))
{ {
e.Redirect("https://www.paypal.com"); e.Redirect("https://www.paypal.com");
} }
...@@ -105,14 +107,14 @@ namespace Titanium.Web.Proxy.Test ...@@ -105,14 +107,14 @@ namespace Titanium.Web.Proxy.Test
{ {
//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.Trim().ToLower().Contains("text/html"))
{ {
byte[] bodyBytes = e.GetResponseBody(); byte[] bodyBytes = e.GetResponseBody();
e.SetResponseBody(bodyBytes); e.SetResponseBody(bodyBytes);
...@@ -123,5 +125,19 @@ namespace Titanium.Web.Proxy.Test ...@@ -123,5 +125,19 @@ namespace Titanium.Web.Proxy.Test
} }
} }
} }
/// <summary>
/// Allows overriding default certificate validation logic
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
public void 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
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/titanium007/Titanium/master/Titanium-Web-Proxy/Examples/Titanium.Web.Proxy.Examples.Basic/Capture.PNG)
Features Features
======== ========
...@@ -32,52 +32,69 @@ After installing nuget package mark following files to be copied to app director ...@@ -32,52 +32,69 @@ 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.RemoteCertificateValidationCallback += 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)
//A transparent endpoint usually requires a network router port forwarding HTTP(S) packets to this endpoint
//Currently do not support Server Name Indication (It is not currently supported by SslStream class)
//That means that the transparent endpoint will always provide the same Generic Certificate to all HTTPS requests
//In this example only google.com will work for HTTPS requests
//Other sites will receive a certificate mismatch warning on browser
var transparentEndPoint = new TransparentProxyEndPoint(IPAddress.Any, 8001, true)
{
GenericCertificateName = "google.com"
};
ProxyServer.AddEndPoint(transparentEndPoint);
foreach (var endPoint in ProxyServer.ProxyEndPoints) //ProxyServer.UpStreamHttpProxy = new ExternalProxy() { HostName = "localhost", Port = 8888 };
Console.WriteLine("Listening on '{0}' endpoint at Ip {1} and port: {2} ", //ProxyServer.UpStreamHttpsProxy = new ExternalProxy() { HostName = "localhost", Port = 8888 };
endPoint.GetType().Name, endPoint.IpAddress, endPoint.Port);
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);
//wait here (You can use something else as a wait function, I am using this as a demo) //Only explicit proxies can be set as system proxy!
Console.Read(); 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
//Read browser URL send back to proxy by the injection script in OnResponse event
public void 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 = e.GetRequestBody();
...@@ -91,43 +108,58 @@ Sample request and response event handlers ...@@ -91,43 +108,58 @@ Sample request and response event handlers
//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>" +
"<html><body><h1>" +
"Website Blocked" +
"</h1>" +
"<p>Blocked by titanium web proxy.</p>" +
"</body>" +
"</html>");
}
//Redirect example
if (e.WebSession.Request.RequestUri.AbsoluteUri.Contains("wikipedia.org"))
{ {
e.Ok("<!DOCTYPE html>"+ e.Redirect("https://www.paypal.com");
"<html><body><h1>"+
"Website Blocked"+
"</h1>"+
"<p>Blocked by titanium web proxy.</p>"+
"</body>"+
"</html>");
} }
} }
//Test script injection
//Insert script to read the Browser URL and send it back to proxy
public void 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.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.Trim().ToLower().Contains("text/html"))
{ {
byte[] bodyBytes = e.GetResponseBody();
e.SetResponseBody(bodyBytes);
string body = e.GetResponseBodyAsString(); string body = e.GetResponseBodyAsString();
e.SetResponseBodyString(body);
} }
} }
} }
} }
// Allows overriding default certificate validation logic
public void 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
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 Ionic.Zlib; using Ionic.Zlib;
using System.IO; using System.IO;
using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Decompression namespace Titanium.Web.Proxy.Decompression
{ {
...@@ -11,7 +12,7 @@ namespace Titanium.Web.Proxy.Decompression ...@@ -11,7 +12,7 @@ namespace Titanium.Web.Proxy.Decompression
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())
{ {
......
using System.IO; using System.IO;
using System.IO.Compression; using System.IO.Compression;
using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Decompression namespace Titanium.Web.Proxy.Decompression
{ {
...@@ -9,7 +10,7 @@ namespace Titanium.Web.Proxy.Decompression ...@@ -9,7 +10,7 @@ namespace Titanium.Web.Proxy.Decompression
{ {
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;
......
using Ionic.Zlib; using Ionic.Zlib;
using System.IO; using System.IO;
using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Decompression namespace Titanium.Web.Proxy.Decompression
{ {
...@@ -10,7 +11,7 @@ namespace Titanium.Web.Proxy.Decompression ...@@ -10,7 +11,7 @@ namespace Titanium.Web.Proxy.Decompression
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())
{ {
......
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.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.EventArguments
......
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;
namespace Titanium.Web.Proxy.EventArguments namespace Titanium.Web.Proxy.EventArguments
{ {
...@@ -24,7 +23,7 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -24,7 +23,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 +40,8 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -41,45 +40,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
...@@ -95,82 +57,42 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -95,82 +57,42 @@ namespace Titanium.Web.Proxy.EventArguments
private void ReadRequestBody() private void 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; this.Client.ClientStreamReader.CopyBytesToStreamChunked(requestBodyStream);
} }
} 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
{ this.Client.ClientStreamReader.CopyBytesToStream(requestBodyStream, WebSession.Request.ContentLength);
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();
} }
} }
WebSession.Request.RequestBody = GetDecompressedResponseBody(WebSession.Request.ContentEncoding, requestBodyStream.ToArray());
} }
} }
//Now set the flag to true //Now set the flag to true
//So that next time we can deliver body from cache //So that next time we can deliver body from cache
ProxySession.Request.RequestBodyRead = true; WebSession.Request.RequestBodyRead = true;
} }
/// <summary> /// <summary>
...@@ -179,45 +101,30 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -179,45 +101,30 @@ namespace Titanium.Web.Proxy.EventArguments
private void ReadResponseBody() private void 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) WebSession.ProxyClient.ServerStreamReader.CopyBytesToStreamChunked(responseBodyStream);
{
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
WebSession.ProxyClient.ServerStreamReader.CopyBytesToStream(responseBodyStream, WebSession.Response.ContentLength);
}
} }
ProxySession.Response.ResponseBody = GetDecompressedResponseBody(ProxySession.Response.ContentEncoding, responseBodyStream.ToArray()); WebSession.Response.ResponseBody = GetDecompressedResponseBody(WebSession.Response.ContentEncoding, responseBodyStream.ToArray());
} }
//set this to true for caching //set this to true for caching
ProxySession.Response.ResponseBodyRead = true; WebSession.Response.ResponseBodyRead = true;
} }
} }
...@@ -227,10 +134,11 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -227,10 +134,11 @@ namespace Titanium.Web.Proxy.EventArguments
/// <returns></returns> /// <returns></returns>
public byte[] GetRequestBody() public 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(); ReadRequestBody();
return ProxySession.Request.RequestBody; return WebSession.Request.RequestBody;
} }
/// <summary> /// <summary>
/// Gets the request body as string /// Gets the request body as string
...@@ -238,13 +146,14 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -238,13 +146,14 @@ namespace Titanium.Web.Proxy.EventArguments
/// <returns></returns> /// <returns></returns>
public string GetRequestBodyAsString() public 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(); ReadRequestBody();
//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>
...@@ -253,16 +162,17 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -253,16 +162,17 @@ namespace Titanium.Web.Proxy.EventArguments
/// <param name="body"></param> /// <param name="body"></param>
public void SetRequestBody(byte[] body) public void 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(); ReadRequestBody();
} }
ProxySession.Request.RequestBody = body; WebSession.Request.RequestBody = body;
ProxySession.Request.RequestBodyRead = true; WebSession.Request.RequestBodyRead = true;
} }
/// <summary> /// <summary>
...@@ -271,16 +181,22 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -271,16 +181,22 @@ namespace Titanium.Web.Proxy.EventArguments
/// <param name="body"></param> /// <param name="body"></param>
public void SetRequestBodyString(string body) public void 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(); ReadRequestBody();
} }
ProxySession.Request.RequestBody = ProxySession.Request.Encoding.GetBytes(body); WebSession.Request.RequestBody = WebSession.Request.Encoding.GetBytes(body);
ProxySession.Request.RequestBodyRead = true;
//If there is a content length header update it
if (!WebSession.Request.IsChunked)
WebSession.Request.ContentLength = body.Length;
WebSession.Request.RequestBodyRead = true;
} }
/// <summary> /// <summary>
...@@ -289,10 +205,11 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -289,10 +205,11 @@ namespace Titanium.Web.Proxy.EventArguments
/// <returns></returns> /// <returns></returns>
public byte[] GetResponseBody() public 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(); ReadResponseBody();
return ProxySession.Response.ResponseBody; return WebSession.Response.ResponseBody;
} }
/// <summary> /// <summary>
...@@ -301,11 +218,12 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -301,11 +218,12 @@ namespace Titanium.Web.Proxy.EventArguments
/// <returns></returns> /// <returns></returns>
public string GetResponseBodyAsString() public 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(); GetResponseBody();
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>
...@@ -314,15 +232,21 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -314,15 +232,21 @@ namespace Titanium.Web.Proxy.EventArguments
/// <param name="body"></param> /// <param name="body"></param>
public void SetResponseBody(byte[] body) public void 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(); GetResponseBody();
} }
ProxySession.Response.ResponseBody = body; WebSession.Response.ResponseBody = body;
//If there is a content length header update it
if (!WebSession.Response.IsChunked)
WebSession.Response.ContentLength = body.Length;
} }
/// <summary> /// <summary>
...@@ -331,15 +255,16 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -331,15 +255,16 @@ namespace Titanium.Web.Proxy.EventArguments
/// <param name="body"></param> /// <param name="body"></param>
public void SetResponseBodyString(string body) public void 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(); GetResponseBody();
} }
var bodyBytes = ProxySession.Response.Encoding.GetBytes(body); var bodyBytes = WebSession.Response.Encoding.GetBytes(body);
SetResponseBody(bodyBytes); SetResponseBody(bodyBytes);
} }
...@@ -360,7 +285,8 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -360,7 +285,8 @@ namespace Titanium.Web.Proxy.EventArguments
/// <param name="html"></param> /// <param name="html"></param>
public void Ok(string html) public void 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;
...@@ -380,39 +306,39 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -380,39 +306,39 @@ namespace Titanium.Web.Proxy.EventArguments
{ {
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); Respond(response);
ProxySession.Request.CancelRequest = true; WebSession.Request.CancelRequest = true;
} }
public void Redirect(string url) public void 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); Respond(response);
ProxySession.Request.CancelRequest = true; WebSession.Request.CancelRequest = true;
} }
/// a generic responder method /// a generic responder method
public void Respond(Response response) public void 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); ProxyServer.HandleHttpSessionResponse(this);
} }
} }
} }
\ 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
{ {
...@@ -18,7 +18,7 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -18,7 +18,7 @@ namespace Titanium.Web.Proxy.Extensions
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;
namespace Titanium.Web.Proxy.Extensions namespace Titanium.Web.Proxy.Extensions
{ {
......
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); output.Write(bytes, 0, bytes.Length);
} }
await input.CopyToAsync(output);
CopyToAsync(input, output, bufferSize);
} }
//http://stackoverflow.com/questions/1540658/net-asynchronous-stream-read-write internal static void CopyBytesToStream(this CustomBinaryReader clientStreamReader, 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)
{
// wait for the read operation to complete
read.AsyncWaitHandle.WaitOne();
bufl[bufno] = input.EndRead(read);
// 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 int bytesToRead;
// the only time one won't exist is after the very first read operation completes if (totalBytesToRead < Constants.BUFFER_SIZE)
if (write != null) {
{ bytesToRead = (int)totalBytesToRead;
write.AsyncWaitHandle.WaitOne(); }
output.EndWrite(write); else
} bytesToRead = Constants.BUFFER_SIZE;
// start the new write operation
write = output.BeginWrite(buf[bufno], 0, bufl[bufno], null, null);
// toggle the current, in-use buffer while (totalbytesRead < (int)totalBytesToRead)
// and start the read operation on the new buffer. {
// var buffer = clientStreamReader.ReadBytes(bytesToRead);
// Changed to use XOR to toggle between 0 and 1. totalbytesRead += buffer.Length;
// 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 = (int)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);
} }
stream.Write(buffer, 0, buffer.Length);
output.Flush();
} }
catch }
internal static void CopyBytesToStreamChunked(this CustomBinaryReader clientStreamReader, Stream stream)
{
while (true)
{ {
// ignored var chuchkHead = clientStreamReader.ReadLine();
var chunkSize = int.Parse(chuchkHead, NumberStyles.HexNumber);
if (chunkSize != 0)
{
var buffer = clientStreamReader.ReadBytes(chunkSize);
stream.Write(buffer, 0, buffer.Length);
//chunk trail
clientStreamReader.ReadLine();
}
else
{
clientStreamReader.ReadLine();
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
......
using System; using System.Collections.Generic;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Text; using System.Text;
......
...@@ -8,13 +8,12 @@ using System.Text; ...@@ -8,13 +8,12 @@ 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 static void 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)
{ {
...@@ -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); sslStream.AuthenticateAsClient(hostName, null, Constants.SupportedProtocols, false);
tunnelStream = sslStream; tunnelStream = sslStream;
} }
catch catch
...@@ -63,16 +62,15 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -63,16 +62,15 @@ namespace Titanium.Web.Proxy.Helpers
} }
} }
var sendRelay = Task.Factory.StartNew(() => var sendRelay = Task.Factory.StartNew(() =>
{ {
if (sb != null) if (sb != null)
clientStream.CopyToAsync(sb.ToString(), tunnelStream, BUFFER_SIZE); clientStream.CopyToAsync(sb.ToString(), tunnelStream).Wait();
else else
clientStream.CopyToAsync(string.Empty, tunnelStream, BUFFER_SIZE); clientStream.CopyToAsync(string.Empty, tunnelStream).Wait();
}); });
var receiveRelay = Task.Factory.StartNew(() => tunnelStream.CopyToAsync(string.Empty, clientStream, BUFFER_SIZE)); var receiveRelay = Task.Factory.StartNew(() =>tunnelStream.CopyToAsync(string.Empty, clientStream).Wait());
Task.WaitAll(sendRelay, receiveRelay); Task.WaitAll(sendRelay, receiveRelay);
} }
......
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
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.Network;
using Titanium.Web.Proxy.Extensions; using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Network namespace Titanium.Web.Proxy.Http
{ {
public class Request
{
public string Method { get; set; }
public Uri RequestUri { get; set; }
public string HttpVersion { get; set; }
internal string Host
{
get
{
var host = RequestHeaders.FirstOrDefault(x => x.Name.ToLower() == "host");
if (host != null)
return host.Value;
return null;
}
set
{
var host = RequestHeaders.FirstOrDefault(x => x.Name.ToLower() == "host");
if (host != null)
host.Value = value;
else
RequestHeaders.Add(new HttpHeader("Host", value));
}
}
public int ContentLength
{
get
{
var header = RequestHeaders.FirstOrDefault(x => x.Name.ToLower() == "content-length");
if (header == null)
return 0;
int contentLen;
int.TryParse(header.Value, out contentLen);
if (contentLen != 0)
return contentLen;
return 0;
}
set
{
var header = RequestHeaders.FirstOrDefault(x => x.Name.ToLower() == "content-length");
if (header != null)
header.Value = value.ToString();
else
RequestHeaders.Add(new HttpHeader("content-length", value.ToString()));
}
}
public string ContentType
{
get
{
var header = RequestHeaders.FirstOrDefault(x => x.Name.ToLower() == "content-type");
if (header != null)
return header.Value;
return null;
}
set
{
var header = RequestHeaders.FirstOrDefault(x => x.Name.ToLower() == "content-type");
if (header != null)
header.Value = value.ToString();
else
RequestHeaders.Add(new HttpHeader("content-type", value.ToString()));
}
}
public bool SendChunked
{
get
{
var header = RequestHeaders.FirstOrDefault(x => x.Name.ToLower() == "transfer-encoding");
if (header != null) return header.Value.ToLower().Contains("chunked");
return false;
}
}
public bool ExpectContinue
{
get
{
var header = RequestHeaders.FirstOrDefault(x => x.Name.ToLower() == "expect");
if (header != null) return header.Value.Equals("100-continue");
return false;
}
}
public string Url { get { return RequestUri.OriginalString; } }
internal Encoding Encoding { get { return this.GetEncoding(); } }
/// <summary>
/// Terminates the underlying Tcp Connection to client after current request
/// </summary>
internal bool CancelRequest { get; set; }
internal byte[] RequestBody { get; set; }
internal string RequestBodyString { get; set; }
internal bool RequestBodyRead { get; set; }
internal bool RequestLocked { get; set; }
internal bool UpgradeToWebSocket
{
get
{
var header = RequestHeaders.FirstOrDefault(x => x.Name.ToLower() == "upgrade");
if (header == null)
return false;
if (header.Value.ToLower() == "websocket")
return true;
return false;
}
}
public List<HttpHeader> RequestHeaders { get; set; }
public bool Is100Continue { get; internal set; }
public bool ExpectationFailed { get; internal set; }
public Request()
{
this.RequestHeaders = new List<HttpHeader>();
}
}
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 public class HttpWebSession
{ {
private const string Space = " "; internal TcpConnection ProxyClient { get; set; }
public Request Request { get; set; }
public Response Response { get; set; }
public bool IsSecure public bool IsSecure
{ {
...@@ -282,24 +23,19 @@ namespace Titanium.Web.Proxy.Network ...@@ -282,24 +23,19 @@ namespace Titanium.Web.Proxy.Network
} }
} }
public Request Request { get; set; } internal void SetConnection(TcpConnection Connection)
public Response Response { get; set; }
internal TcpConnection ProxyClient { get; set; }
public void SetConnection(TcpConnection Connection)
{ {
Connection.LastAccess = DateTime.Now; Connection.LastAccess = DateTime.Now;
ProxyClient = Connection; ProxyClient = Connection;
} }
public HttpWebSession() internal HttpWebSession()
{ {
this.Request = new Request(); this.Request = new Request();
this.Response = new Response(); this.Response = new Response();
} }
public void SendRequest() internal void SendRequest()
{ {
Stream stream = ProxyClient.Stream; Stream stream = ProxyClient.Stream;
...@@ -327,7 +63,7 @@ namespace Titanium.Web.Proxy.Network ...@@ -327,7 +63,7 @@ namespace Titanium.Web.Proxy.Network
if (ProxyServer.Enable100ContinueBehaviour) if (ProxyServer.Enable100ContinueBehaviour)
if (this.Request.ExpectContinue) if (this.Request.ExpectContinue)
{ {
var httpResult = ProxyClient.ServerStreamReader.ReadLine().Split(new char[] { ' ' }, 3); var httpResult = ProxyClient.ServerStreamReader.ReadLine().Split(Constants.SpaceSplit, 3);
var responseStatusCode = httpResult[1].Trim(); var responseStatusCode = httpResult[1].Trim();
var responseStatusDescription = httpResult[2].Trim(); var responseStatusDescription = httpResult[2].Trim();
...@@ -347,12 +83,12 @@ namespace Titanium.Web.Proxy.Network ...@@ -347,12 +83,12 @@ namespace Titanium.Web.Proxy.Network
} }
} }
public void ReceiveResponse() internal void ReceiveResponse()
{ {
//return if this is already read //return if this is already read
if (this.Response.ResponseStatusCode != null) return; if (this.Response.ResponseStatusCode != null) return;
var httpResult = ProxyClient.ServerStreamReader.ReadLine().Split(new char[] { ' ' }, 3); var httpResult = ProxyClient.ServerStreamReader.ReadLine().Split(Constants.SpaceSplit, 3);
if (string.IsNullOrEmpty(httpResult[0])) if (string.IsNullOrEmpty(httpResult[0]))
{ {
...@@ -387,12 +123,10 @@ namespace Titanium.Web.Proxy.Network ...@@ -387,12 +123,10 @@ namespace Titanium.Web.Proxy.Network
for (int index = 0; index < responseLines.Count; ++index) for (int index = 0; index < responseLines.Count; ++index)
{ {
string[] strArray = responseLines[index].Split(new char[] { ':' }, 2); string[] strArray = responseLines[index].Split(Constants.ColonSplit, 2);
this.Response.ResponseHeaders.Add(new HttpHeader(strArray[0], strArray[1])); this.Response.ResponseHeaders.Add(new HttpHeader(strArray[0], strArray[1]));
} }
} }
} }
} }
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Extensions;
namespace Titanium.Web.Proxy.Http
{
public class Request
{
public string Method { get; set; }
public Uri RequestUri { get; set; }
public string HttpVersion { get; set; }
internal string Host
{
get
{
var host = RequestHeaders.FirstOrDefault(x => x.Name.ToLower() == "host");
if (host != null)
return host.Value;
return null;
}
set
{
var host = RequestHeaders.FirstOrDefault(x => x.Name.ToLower() == "host");
if (host != null)
host.Value = value;
else
RequestHeaders.Add(new HttpHeader("Host", value));
}
}
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
{
var header = RequestHeaders.FirstOrDefault(x => x.Name.ToLower() == "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 = RequestHeaders.FirstOrDefault(x => x.Name.ToLower() == "content-length");
if (value >= 0)
{
if (header != null)
header.Value = value.ToString();
else
RequestHeaders.Add(new HttpHeader("content-length", value.ToString()));
}
else
{
if (header != null)
this.RequestHeaders.Remove(header);
}
}
}
public string ContentType
{
get
{
var header = RequestHeaders.FirstOrDefault(x => x.Name.ToLower() == "content-type");
if (header != null)
return header.Value;
return null;
}
set
{
var header = RequestHeaders.FirstOrDefault(x => x.Name.ToLower() == "content-type");
if (header != null)
header.Value = value.ToString();
else
RequestHeaders.Add(new HttpHeader("content-type", value.ToString()));
}
}
public bool IsChunked
{
get
{
var header = RequestHeaders.FirstOrDefault(x => x.Name.ToLower() == "transfer-encoding");
if (header != null) return header.Value.ToLower().Contains("chunked");
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
{
get
{
var header = RequestHeaders.FirstOrDefault(x => x.Name.ToLower() == "expect");
if (header != null) return header.Value.Equals("100-continue");
return false;
}
}
public string Url { get { return RequestUri.OriginalString; } }
internal Encoding Encoding { get { return this.GetEncoding(); } }
/// <summary>
/// Terminates the underlying Tcp Connection to client after current request
/// </summary>
internal bool CancelRequest { get; set; }
internal byte[] RequestBody { get; set; }
internal string RequestBodyString { get; set; }
internal bool RequestBodyRead { get; set; }
internal bool RequestLocked { get; set; }
internal bool UpgradeToWebSocket
{
get
{
var header = RequestHeaders.FirstOrDefault(x => x.Name.ToLower() == "upgrade");
if (header == null)
return false;
if (header.Value.ToLower() == "websocket")
return true;
return false;
}
}
public List<HttpHeader> RequestHeaders { get; set; }
public bool Is100Continue { get; internal set; }
public bool ExpectationFailed { get; internal set; }
public Request()
{
this.RequestHeaders = new List<HttpHeader>();
}
}
}
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.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();
}
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 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()));
}
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)
{
}
}
}
...@@ -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 ServerStreamReader { 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 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,58 @@ namespace Titanium.Web.Proxy.Network ...@@ -57,28 +61,58 @@ namespace Titanium.Web.Proxy.Network
} }
if (cached == null) if (cached == null)
cached = CreateClient(Hostname, port, IsSecure); cached = CreateClient(sessionArgs,hostname, port, isSecure, version);
if (ConnectionCache.Where(x => x.HostName == Hostname && x.port == port && x.IsSecure == IsSecure && x.TcpClient.Connected).Count() < 2) 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))); Task.Factory.StartNew(() => CreateClient(sessionArgs, hostname, port, isSecure, version));
} }
return cached; return cached;
} }
private static TcpConnection CreateClient(string Hostname, int port, bool IsSecure) private static 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();
var writer = new StreamWriter(stream);
writer.WriteLine(string.Format("CONNECT {0}:{1} {2}", sessionArgs.WebSession.Request.RequestUri.Host, sessionArgs.WebSession.Request.RequestUri.Port, sessionArgs.WebSession.Request.HttpVersion));
writer.WriteLine(string.Format("Host: {0}:{1}", sessionArgs.WebSession.Request.RequestUri.Host, sessionArgs.WebSession.Request.RequestUri.Port));
writer.WriteLine("Connection: Keep-Alive");
writer.WriteLine();
writer.Flush();
var reader = new CustomBinaryReader(stream, Encoding.ASCII);
var result = reader.ReadLine();
if (!result.ToLower().Contains("200 connection established"))
throw new Exception("Upstream proxy failed to create a secure tunnel");
reader.ReadAllLines();
}
else
{
client = new TcpClient(hostname, port);
stream = (Stream)client.GetStream();
}
try try
{ {
sslStream = new SslStream(stream); sslStream = new CustomSslStream(stream, true, ProxyServer.ValidateServerCertificate);
sslStream.AuthenticateAsClient(Hostname, null, ProxyServer.SupportedProtocols , false); sslStream.Session = sessionArgs;
sslStream.AuthenticateAsClient(hostname, null, Constants.SupportedProtocols, false);
stream = (Stream)sslStream; stream = (Stream)sslStream;
} }
catch catch
...@@ -88,25 +122,40 @@ namespace Titanium.Web.Proxy.Network ...@@ -88,25 +122,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), ServerStreamReader = new CustomBinaryReader(stream, Encoding.ASCII),
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 static void ClearIdleConnections()
{ {
while (true) while (true)
{ {
......
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; }
...@@ -61,6 +37,22 @@ namespace Titanium.Web.Proxy ...@@ -61,6 +37,22 @@ namespace Titanium.Web.Proxy
public static event EventHandler<SessionEventArgs> BeforeRequest; public static event EventHandler<SessionEventArgs> BeforeRequest;
public static event EventHandler<SessionEventArgs> BeforeResponse; public static event EventHandler<SessionEventArgs> 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 EventHandler<CertificateValidationEventArgs> RemoteCertificateValidationCallback;
public static List<ProxyEndPoint> ProxyEndPoints { get; set; } public static List<ProxyEndPoint> ProxyEndPoints { get; set; }
public static void Initialize() public static void Initialize()
...@@ -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();
if (EnableSsl)
certTrusted = CertManager.CreateTrustedRootCertificate();
foreach (var endPoint in ProxyEndPoints) foreach (var endPoint in ProxyEndPoints)
{ {
...@@ -228,7 +217,7 @@ namespace Titanium.Web.Proxy ...@@ -228,7 +217,7 @@ 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);
...@@ -245,8 +234,52 @@ namespace Titanium.Web.Proxy ...@@ -245,8 +234,52 @@ namespace Titanium.Web.Proxy
// ignored // ignored
} }
}
/// <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>
public static bool ValidateServerCertificate(
object sender,
X509Certificate certificate,
X509Chain chain,
SslPolicyErrors sslPolicyErrors)
{
var param = sender as CustomSslStream;
if (RemoteCertificateValidationCallback != null)
{
var args = new CertificateValidationEventArgs();
args.Session = param.Session;
args.Certificate = certificate;
args.Chain = chain;
args.SslPolicyErrors = sslPolicyErrors;
RemoteCertificateValidationCallback.Invoke(null, args);
if(!args.IsValid)
{
param.Session.WebSession.Request.CancelRequest = true;
}
return args.IsValid;
}
if (sslPolicyErrors == SslPolicyErrors.None)
return true;
Console.WriteLine("Certificate error: {0}", sslPolicyErrors);
//By default
//do not allow this client to communicate with unauthenticated servers.
return false;
} }
} }
} }
\ No newline at end of file
...@@ -3,19 +3,19 @@ using System.Collections.Generic; ...@@ -3,19 +3,19 @@ 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;
namespace Titanium.Web.Proxy namespace Titanium.Web.Proxy
{ {
...@@ -42,7 +42,7 @@ namespace Titanium.Web.Proxy ...@@ -42,7 +42,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 +51,10 @@ namespace Titanium.Web.Proxy ...@@ -51,7 +51,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;
...@@ -73,7 +76,7 @@ namespace Titanium.Web.Proxy ...@@ -73,7 +76,7 @@ namespace Titanium.Web.Proxy
//Successfully managed to authenticate the client using the fake certificate //Successfully managed to authenticate the client using the fake certificate
sslStream.AuthenticateAsServer(certificate, false, sslStream.AuthenticateAsServer(certificate, false,
SupportedProtocols, false); Constants.SupportedProtocols, false);
clientStreamReader = new CustomBinaryReader(sslStream, Encoding.ASCII); clientStreamReader = new CustomBinaryReader(sslStream, Encoding.ASCII);
clientStreamWriter = new StreamWriter(sslStream); clientStreamWriter = new StreamWriter(sslStream);
...@@ -190,11 +193,9 @@ namespace Titanium.Web.Proxy ...@@ -190,11 +193,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,95 +208,92 @@ namespace Titanium.Web.Proxy ...@@ -207,95 +208,92 @@ 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 = clientStreamReader.ReadLine()))
{ {
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, TcpHelper.SendRaw(clientStream, httpCmd, args.WebSession.Request.RequestHeaders,
httpRemoteUri.Host, httpRemoteUri.Port, args.IsHttps); httpRemoteUri.Host, httpRemoteUri.Port, args.IsHttps);
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
BeforeRequest?.Invoke(null, args);
//construct the web request that we are going to issue on behalf of the client. //construct the web request that we are going to issue on behalf of the client.
connection = connection == null ? connection = connection == null ?
TcpConnectionManager.GetClient(args.ProxySession.Request.RequestUri.Host, args.ProxySession.Request.RequestUri.Port, args.IsHttps) TcpConnectionManager.GetClient(args, args.WebSession.Request.RequestUri.Host, args.WebSession.Request.RequestUri.Port, args.IsHttps, version)
: 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 ? TcpConnectionManager.GetClient(args, args.WebSession.Request.RequestUri.Host, args.WebSession.Request.RequestUri.Port, args.IsHttps, version)
: 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(); args.WebSession.SendRequest();
} }
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(); args.Client.ClientStreamWriter.WriteLine();
} }
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(); args.Client.ClientStreamWriter.WriteLine();
} }
if (args.ProxySession.Request.CancelRequest) if (!args.WebSession.Request.ExpectContinue)
{ {
Dispose(client, clientStream, clientStreamReader, clientStreamWriter, args); args.WebSession.SetConnection(connection);
break; args.WebSession.SendRequest();
}
if (!args.ProxySession.Request.ExpectContinue)
{
args.ProxySession.SetConnection(connection);
args.ProxySession.SendRequest();
} }
//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; args.WebSession.Request.ContentLength = args.WebSession.Request.RequestBody.Length;
var newStream = args.ProxySession.ProxyClient.ServerStreamReader.BaseStream; var newStream = args.WebSession.ProxyClient.ServerStreamReader.BaseStream;
newStream.Write(args.ProxySession.Request.RequestBody, 0, args.ProxySession.Request.RequestBody.Length); newStream.Write(args.WebSession.Request.RequestBody, 0, args.WebSession.Request.RequestBody.Length);
} }
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")
...@@ -305,13 +303,13 @@ namespace Titanium.Web.Proxy ...@@ -305,13 +303,13 @@ namespace Titanium.Web.Proxy
} }
} }
if (!args.ProxySession.Request.ExpectationFailed) if (!args.WebSession.Request.ExpectationFailed)
{ {
HandleHttpSessionResponse(args); HandleHttpSessionResponse(args);
} }
//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);
...@@ -382,37 +380,14 @@ namespace Titanium.Web.Proxy ...@@ -382,37 +380,14 @@ namespace Titanium.Web.Proxy
private static void SendClientRequestBody(SessionEventArgs args) private static void SendClientRequestBody(SessionEventArgs args)
{ {
// End the operation // End the operation
var postStream = args.ProxySession.ProxyClient.Stream; var postStream = args.WebSession.ProxyClient.Stream;
if (args.ProxySession.Request.ContentLength > 0) if (args.WebSession.Request.ContentLength > 0)
{ {
//args.ProxyRequest.AllowWriteStreamBuffering = true;
try try
{ {
var totalbytesRead = 0; args.Client.ClientStreamReader.CopyBytesToStream(postStream, args.WebSession.Request.ContentLength);
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 +395,11 @@ namespace Titanium.Web.Proxy ...@@ -420,29 +395,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) args.Client.ClientStreamReader.CopyBytesToStreamChunked(postStream);
{
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 +407,7 @@ namespace Titanium.Web.Proxy ...@@ -450,5 +407,7 @@ namespace Titanium.Web.Proxy
} }
} }
} }
} }
} }
\ No newline at end of file
...@@ -3,16 +3,13 @@ using System.Collections.Generic; ...@@ -3,16 +3,13 @@ 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;
namespace Titanium.Web.Proxy namespace Titanium.Web.Proxy
{ {
...@@ -21,57 +18,57 @@ namespace Titanium.Web.Proxy ...@@ -21,57 +18,57 @@ namespace Titanium.Web.Proxy
//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 void HandleHttpSessionResponse(SessionEventArgs args)
{ {
args.ProxySession.ReceiveResponse(); args.WebSession.ReceiveResponse();
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.ProxyClient.ServerStreamReader.BaseStream;
if (BeforeResponse != null && !args.ProxySession.Response.ResponseLocked) if (BeforeResponse != null && !args.WebSession.Response.ResponseLocked)
{ {
BeforeResponse(null, args); 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(); args.Client.ClientStreamWriter.WriteLine();
} }
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(); args.Client.ClientStreamWriter.WriteLine();
} }
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 = GetCompressedResponseBody(contentEncoding, args.WebSession.Response.ResponseBody);
} }
WriteResponseHeaders(args.Client.ClientStreamWriter, args.ProxySession.Response.ResponseHeaders, args.ProxySession.Response.ResponseBody.Length, WriteResponseHeaders(args.Client.ClientStreamWriter, args.WebSession.Response.ResponseHeaders, args.WebSession.Response.ResponseBody.Length,
isChunked); isChunked);
WriteResponseBody(args.Client.ClientStream, args.ProxySession.Response.ResponseBody, isChunked); WriteResponseBody(args.Client.ClientStream, args.WebSession.Response.ResponseBody, isChunked);
} }
else else
{ {
WriteResponseHeaders(args.Client.ClientStreamWriter, args.ProxySession.Response.ResponseHeaders); 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)
WriteResponseBody(args.ProxySession.ProxyClient.ServerStreamReader, args.Client.ClientStream, args.ProxySession.Response.IsChunked, args.ProxySession.Response.ContentLength); WriteResponseBody(args.WebSession.ProxyClient.ServerStreamReader, args.Client.ClientStream, args.WebSession.Response.IsChunked, args.WebSession.Response.ContentLength);
} }
args.Client.ClientStream.Flush(); args.Client.ClientStream.Flush();
...@@ -173,16 +170,16 @@ namespace Titanium.Web.Proxy ...@@ -173,16 +170,16 @@ namespace Titanium.Web.Proxy
WriteResponseBodyChunked(data, clientStream); WriteResponseBodyChunked(data, clientStream);
} }
private static void WriteResponseBody(CustomBinaryReader inStreamReader, Stream outStream, bool isChunked, int BodyLength) private static void WriteResponseBody(CustomBinaryReader inStreamReader, Stream outStream, bool isChunked, long ContentLength)
{ {
if (!isChunked) if (!isChunked)
{ {
int bytesToRead = BUFFER_SIZE; int bytesToRead = Constants.BUFFER_SIZE;
if (BodyLength < BUFFER_SIZE) if (ContentLength < Constants.BUFFER_SIZE)
bytesToRead = BodyLength; bytesToRead = (int)ContentLength;
var buffer = new byte[BUFFER_SIZE]; var buffer = new byte[Constants.BUFFER_SIZE];
var bytesRead = 0; var bytesRead = 0;
var totalBytesRead = 0; var totalBytesRead = 0;
...@@ -192,12 +189,12 @@ namespace Titanium.Web.Proxy ...@@ -192,12 +189,12 @@ namespace Titanium.Web.Proxy
outStream.Write(buffer, 0, bytesRead); outStream.Write(buffer, 0, bytesRead);
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
...@@ -219,22 +216,20 @@ namespace Titanium.Web.Proxy ...@@ -219,22 +216,20 @@ namespace Titanium.Web.Proxy
var chunkHead = Encoding.ASCII.GetBytes(chunkSize.ToString("x2")); var chunkHead = Encoding.ASCII.GetBytes(chunkSize.ToString("x2"));
outStream.Write(chunkHead, 0, chunkHead.Length); outStream.Write(chunkHead, 0, chunkHead.Length);
outStream.Write(NewLineBytes, 0, NewLineBytes.Length); outStream.Write(Constants.NewLineBytes, 0, Constants.NewLineBytes.Length);
outStream.Write(buffer, 0, chunkSize); outStream.Write(buffer, 0, chunkSize);
outStream.Write(NewLineBytes, 0, NewLineBytes.Length); outStream.Write(Constants.NewLineBytes, 0, Constants.NewLineBytes.Length);
inStreamReader.ReadLine(); inStreamReader.ReadLine();
} }
else else
{ {
inStreamReader.ReadLine(); inStreamReader.ReadLine();
outStream.Write(ChunkEnd, 0, ChunkEnd.Length); outStream.Write(Constants.ChunkEnd, 0, Constants.ChunkEnd.Length);
break; break;
} }
} }
} }
private static void WriteResponseBodyChunked(byte[] data, Stream outStream) private static void WriteResponseBodyChunked(byte[] data, Stream outStream)
...@@ -242,11 +237,11 @@ namespace Titanium.Web.Proxy ...@@ -242,11 +237,11 @@ namespace Titanium.Web.Proxy
var chunkHead = Encoding.ASCII.GetBytes(data.Length.ToString("x2")); var chunkHead = Encoding.ASCII.GetBytes(data.Length.ToString("x2"));
outStream.Write(chunkHead, 0, chunkHead.Length); outStream.Write(chunkHead, 0, chunkHead.Length);
outStream.Write(NewLineBytes, 0, NewLineBytes.Length); outStream.Write(Constants.NewLineBytes, 0, Constants.NewLineBytes.Length);
outStream.Write(data, 0, data.Length); outStream.Write(data, 0, data.Length);
outStream.Write(NewLineBytes, 0, NewLineBytes.Length); outStream.Write(Constants.NewLineBytes, 0, Constants.NewLineBytes.Length);
outStream.Write(ChunkEnd, 0, ChunkEnd.Length); outStream.Write(Constants.ChunkEnd, 0, Constants.ChunkEnd.Length);
} }
......
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,6 +60,7 @@ ...@@ -60,6 +60,7 @@
<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\CertificateValidationEventArgs.cs" />
<Compile Include="EventArguments\ProxyClient.cs" /> <Compile Include="EventArguments\ProxyClient.cs" />
<Compile Include="Exceptions\BodyNotFoundException.cs" /> <Compile Include="Exceptions\BodyNotFoundException.cs" />
<Compile Include="Extensions\HttpWebResponseExtensions.cs" /> <Compile Include="Extensions\HttpWebResponseExtensions.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>
......
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