Commit 4186c54c authored by Jehonathan Thomas's avatar Jehonathan Thomas Committed by GitHub

Merge pull request #304 from justcoding121/develop

Beta release
parents c44cc790 e16db4d5
...@@ -6,39 +6,42 @@ $SolutionRoot = (Split-Path -parent $Here) ...@@ -6,39 +6,42 @@ $SolutionRoot = (Split-Path -parent $Here)
$ProjectName = "Titanium.Web.Proxy" $ProjectName = "Titanium.Web.Proxy"
$SolutionFile = "$SolutionRoot\$ProjectName.sln" $SolutionFile14 = "$SolutionRoot\$ProjectName.sln"
$SolutionFile = "$SolutionRoot\$ProjectName.Standard.sln"
## This comes from the build server iteration ## This comes from the build server iteration
if(!$BuildNumber) { $BuildNumber = $env:APPVEYOR_BUILD_NUMBER } if(!$BuildNumber) { $BuildNumber = $env:APPVEYOR_BUILD_NUMBER }
if(!$BuildNumber) { $BuildNumber = "1"} if(!$BuildNumber) { $BuildNumber = "1"}
## This comes from the Hg commit hash used to build
if(!$CommitHash) { $CommitHash = $env:APPVEYOR_REPO_COMMIT }
if(!$CommitHash) { $CommitHash = "local-build" }
## The build configuration, i.e. Debug/Release ## The build configuration, i.e. Debug/Release
if(!$Configuration) { $Configuration = $env:Configuration } if(!$Configuration) { $Configuration = $env:Configuration }
if(!$Configuration) { $Configuration = "Release" } if(!$Configuration) { $Configuration = "Release" }
if(!$Version) { $Version = $env:APPVEYOR_BUILD_VERSION } if(!$Version) { $Version = $env:APPVEYOR_BUILD_VERSION }
if(!$Version) { $Version = "2.0.$BuildNumber" } if(!$Version) { $Version = "1.0.$BuildNumber" }
if(!$Branch) { $Branch = $env:APPVEYOR_REPO_BRANCH } if(!$Branch) { $Branch = $env:APPVEYOR_REPO_BRANCH }
if(!$Branch) { $Branch = "local" } if(!$Branch) { $Branch = "local" }
if($Branch -eq "beta" ) { $Version = "$Version-beta" } if($Branch -eq "beta" ) { $Version = "$Version-beta" }
Import-Module "$Here\Common" -DisableNameChecking Import-Module "$Here\Common" -DisableNameChecking
$NuGet = Join-Path $SolutionRoot ".nuget\nuget.exe" $NuGet = Join-Path $SolutionRoot ".nuget\nuget.exe"
$MSBuild ="${env:ProgramFiles(x86)}\MSBuild\14.0\Bin\msbuild.exe" $MSBuild14 = "${env:ProgramFiles(x86)}\MSBuild\14.0\Bin\msbuild.exe"
$MSBuild = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\2017\Community\MSBuild\15.0\Bin\msbuild.exe"
$MSBuild -replace ' ', '` '
FormatTaskName (("-"*25) + "[{0}]" + ("-"*25)) FormatTaskName (("-"*25) + "[{0}]" + ("-"*25))
Task default -depends Clean, Build, Package Task default -depends Clean, Build, Package
Task Build -depends Restore-Packages { Task Build -depends Restore-Packages{
exec { . $MSBuild $SolutionFile /t:Build /v:normal /p:Configuration=$Configuration } exec { . $MSBuild14 $SolutionFile14 /t:Build /v:normal /p:Configuration=$Configuration }
exec { . $MSBuild $SolutionFile /t:Build /v:normal /p:Configuration=$Configuration /t:restore }
} }
Task Package -depends Build { Task Package -depends Build {
...@@ -46,20 +49,23 @@ Task Package -depends Build { ...@@ -46,20 +49,23 @@ Task Package -depends Build {
} }
Task Clean -depends Install-BuildTools { Task Clean -depends Install-BuildTools {
Remove-Item -Path "$SolutionRoot\packages\*" -Exclude repositories.config -Recurse -Force
Get-ChildItem .\ -include bin,obj -Recurse | foreach ($_) { Remove-Item $_.fullname -Force -Recurse } Get-ChildItem .\ -include bin,obj -Recurse | foreach ($_) { Remove-Item $_.fullname -Force -Recurse }
exec { . $MSBuild14 $SolutionFile14 /t:Clean /v:quiet }
exec { . $MSBuild $SolutionFile /t:Clean /v:quiet } exec { . $MSBuild $SolutionFile /t:Clean /v:quiet }
} }
Task Restore-Packages { Task Restore-Packages {
exec { . $NuGet restore $SolutionFile } exec { . dotnet restore "$SolutionRoot\Titanium.Web.Proxy.sln" }
exec { . dotnet restore "$SolutionRoot\Titanium.Web.Proxy.Standard.sln" }
} }
Task Install-MSBuild { Task Install-MSBuild {
if(!(Test-Path "${env:ProgramFiles(x86)}\MSBuild\14.0\Bin\msbuild.exe")) if(!(Test-Path $MSBuild14))
{ {
cinst microsoft-build-tools -y cinst microsoft-build-tools -y
} }
} }
Task Install-BuildTools -depends Install-MSBuild Task Install-BuildTools -depends Install-MSBuild
\ No newline at end of file
using System;
using System.Runtime.InteropServices;
namespace Titanium.Web.Proxy.Examples.Basic.Helpers
{
/// <summary>
/// Adapated from
/// http://stackoverflow.com/questions/13656846/how-to-programmatic-disable-c-sharp-console-applications-quick-edit-mode
/// </summary>
internal static class ConsoleHelper
{
const uint ENABLE_QUICK_EDIT = 0x0040;
// STD_INPUT_HANDLE (DWORD): -10 is the standard input device.
const int STD_INPUT_HANDLE = -10;
[DllImport("kernel32.dll", SetLastError = true)]
static extern IntPtr GetStdHandle(int nStdHandle);
[DllImport("kernel32.dll")]
static extern bool GetConsoleMode(IntPtr hConsoleHandle, out uint lpMode);
[DllImport("kernel32.dll")]
static extern bool SetConsoleMode(IntPtr hConsoleHandle, uint dwMode);
internal static bool DisableQuickEditMode()
{
var consoleHandle = GetStdHandle(STD_INPUT_HANDLE);
// get current console mode
uint consoleMode;
if (!GetConsoleMode(consoleHandle, out consoleMode))
{
// ERROR: Unable to get console mode.
return false;
}
// Clear the quick edit bit in the mode flags
consoleMode &= ~ENABLE_QUICK_EDIT;
// set the new mode
if (!SetConsoleMode(consoleHandle, consoleMode))
{
// ERROR: Unable to set console mode
return false;
}
return true;
}
}
}
using System;
using Titanium.Web.Proxy.Examples.Basic.Helpers;
namespace Titanium.Web.Proxy.Examples.Basic.Standard
{
class Program
{
private static readonly ProxyTestController controller = new ProxyTestController();
static void Main(string[] args)
{
//fix console hang due to QuickEdit mode
ConsoleHelper.DisableQuickEditMode();
//Start proxy controller
controller.StartProxy();
Console.WriteLine("Hit any key to exit..");
Console.WriteLine();
Console.Read();
controller.Stop();
}
}
}
\ No newline at end of file
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Security;
using System.Threading.Tasks;
using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Models;
namespace Titanium.Web.Proxy.Examples.Basic
{
public class ProxyTestController
{
private readonly ProxyServer proxyServer;
//share requestBody outside handlers
//Using a dictionary is not a good idea since it can cause memory overflow
//ideally the data should be moved out of memory
//private readonly IDictionary<Guid, string> requestBodyHistory = new ConcurrentDictionary<Guid, string>();
public ProxyTestController()
{
proxyServer = new ProxyServer();
//generate root certificate without storing it in file system
//proxyServer.CertificateEngine = Network.CertificateEngine.BouncyCastle;
//proxyServer.CertificateManager.CreateTrustedRootCertificate(false);
//proxyServer.CertificateManager.TrustRootCertificate();
proxyServer.ExceptionFunc = exception => Console.WriteLine(exception.Message);
proxyServer.TrustRootCertificate = true;
proxyServer.ForwardToUpstreamGateway = true;
//optionally set the Certificate Engine
//Under Mono only BouncyCastle will be supported
//proxyServer.CertificateEngine = Network.CertificateEngine.BouncyCastle;
//optionally set the Root Certificate
//proxyServer.RootCertificate = new X509Certificate2("myCert.pfx", string.Empty, X509KeyStorageFlags.Exportable);
}
public void StartProxy()
{
proxyServer.BeforeRequest += OnRequest;
proxyServer.BeforeResponse += OnResponse;
proxyServer.TunnelConnectRequest += OnTunnelConnectRequest;
proxyServer.TunnelConnectResponse += OnTunnelConnectResponse;
proxyServer.ServerCertificateValidationCallback += OnCertificateValidation;
proxyServer.ClientCertificateSelectionCallback += OnCertificateSelection;
//proxyServer.EnableWinAuth = true;
var explicitEndPoint = new ExplicitProxyEndPoint(IPAddress.Any, 8000, true)
{
//Exclude Https addresses you don't want to proxy
//Useful for clients that use certificate pinning
//for example google.com and dropbox.com
ExcludedHttpsHostNameRegex = new List<string>
{
"dropbox.com"
},
//Include Https addresses you want to proxy (others will be excluded)
//for example github.com
//IncludedHttpsHostNameRegex = new List<string>
//{
// "github.com"
//},
//You can set only one of the ExcludedHttpsHostNameRegex and IncludedHttpsHostNameRegex properties, otherwise ArgumentException will be thrown
//Use self-issued generic certificate on all https requests
//Optimizes performance by not creating a certificate for each https-enabled domain
//Useful when certificate trust is not required by proxy clients
//GenericCertificate = new X509Certificate2(Path.Combine(System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), "genericcert.pfx"), "password")
};
//An explicit endpoint is where the client knows about the existence of a proxy
//So client sends request in a proxy friendly manner
proxyServer.AddEndPoint(explicitEndPoint);
proxyServer.Start();
//Transparent endpoint is useful for reverse proxy (client is not aware of the existence of proxy)
//A transparent endpoint usually requires a network router port forwarding HTTP(S) packets or DNS
//to send data to this endPoint
//var transparentEndPoint = new TransparentProxyEndPoint(IPAddress.Any, 443, true)
//{
// //Generic Certificate hostname to use
// //When SNI is disabled by client
// GenericCertificateName = "google.com"
//};
//proxyServer.AddEndPoint(transparentEndPoint);
//proxyServer.UpStreamHttpProxy = new ExternalProxy() { HostName = "localhost", Port = 8888 };
//proxyServer.UpStreamHttpsProxy = new ExternalProxy() { HostName = "localhost", Port = 8888 };
foreach (var endPoint in proxyServer.ProxyEndPoints)
Console.WriteLine("Listening on '{0}' endpoint at Ip {1} and port: {2} ", endPoint.GetType().Name, endPoint.IpAddress, endPoint.Port);
}
public void Stop()
{
proxyServer.TunnelConnectRequest -= OnTunnelConnectRequest;
proxyServer.TunnelConnectResponse -= OnTunnelConnectResponse;
proxyServer.BeforeRequest -= OnRequest;
proxyServer.BeforeResponse -= OnResponse;
proxyServer.ServerCertificateValidationCallback -= OnCertificateValidation;
proxyServer.ClientCertificateSelectionCallback -= OnCertificateSelection;
proxyServer.Stop();
//remove the generated certificates
//proxyServer.CertificateManager.RemoveTrustedRootCertificates();
}
private async Task OnTunnelConnectRequest(object sender, TunnelConnectSessionEventArgs e)
{
Console.WriteLine("Tunnel to: " + e.WebSession.Request.Host);
}
private async Task OnTunnelConnectResponse(object sender, TunnelConnectSessionEventArgs e)
{
}
//intecept & cancel redirect or update requests
public async Task OnRequest(object sender, SessionEventArgs e)
{
Console.WriteLine("Active Client Connections:" + ((ProxyServer)sender).ClientConnectionCount);
Console.WriteLine(e.WebSession.Request.Url);
//read request headers
var requestHeaders = e.WebSession.Request.RequestHeaders;
if (e.WebSession.Request.HasBody)
{
//Get/Set request body bytes
var bodyBytes = await e.GetRequestBody();
await e.SetRequestBody(bodyBytes);
//Get/Set request body as string
string bodyString = await e.GetRequestBodyAsString();
await e.SetRequestBodyString(bodyString);
//requestBodyHistory[e.Id] = bodyString;
}
////To cancel a request with a custom HTML content
////Filter URL
//if (e.WebSession.Request.RequestUri.AbsoluteUri.Contains("google.com"))
//{
// await e.Ok("<!DOCTYPE html>" +
// "<html><body><h1>" +
// "Website Blocked" +
// "</h1>" +
// "<p>Blocked by titanium web proxy.</p>" +
// "</body>" +
// "</html>");
//}
////Redirect example
//if (e.WebSession.Request.RequestUri.AbsoluteUri.Contains("wikipedia.org"))
//{
// await e.Redirect("https://www.paypal.com");
//}
}
//Modify response
public async Task OnResponse(object sender, SessionEventArgs e)
{
Console.WriteLine("Active Server Connections:" + ((ProxyServer)sender).ServerConnectionCount);
//if (requestBodyHistory.ContainsKey(e.Id))
//{
// //access request body by looking up the shared dictionary using requestId
// var requestBody = requestBodyHistory[e.Id];
//}
//read response headers
var responseHeaders = e.WebSession.Response.ResponseHeaders;
// print out process id of current session
//Console.WriteLine($"PID: {e.WebSession.ProcessId.Value}");
//if (!e.ProxySession.Request.Host.Equals("medeczane.sgk.gov.tr")) return;
if (e.WebSession.Request.Method == "GET" || e.WebSession.Request.Method == "POST")
{
if (e.WebSession.Response.ResponseStatusCode == (int)HttpStatusCode.OK)
{
if (e.WebSession.Response.ContentType != null && e.WebSession.Response.ContentType.Trim().ToLower().Contains("text/html"))
{
var bodyBytes = await e.GetResponseBody();
await e.SetResponseBody(bodyBytes);
string body = await e.GetResponseBodyAsString();
await e.SetResponseBodyString(body);
}
}
}
}
/// <summary>
/// Allows overriding default certificate validation logic
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
public Task OnCertificateValidation(object sender, CertificateValidationEventArgs e)
{
//set IsValid to true/false based on Certificate Errors
if (e.SslPolicyErrors == SslPolicyErrors.None)
{
e.IsValid = true;
}
return Task.FromResult(0);
}
/// <summary>
/// Allows overriding default client certificate selection logic during mutual authentication
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
public Task OnCertificateSelection(object sender, CertificateSelectionEventArgs e)
{
//set e.clientCertificate to override
return Task.FromResult(0);
}
}
}
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp1.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\Titanium.Web.Proxy\Titanium.Web.Proxy.Standard.csproj" />
</ItemGroup>
</Project>
\ No newline at end of file
using System; using System;
using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Net; using System.Net;
using System.Net.Security; using System.Net.Security;
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.Http;
using Titanium.Web.Proxy.Models; using Titanium.Web.Proxy.Models;
namespace Titanium.Web.Proxy.Examples.Basic namespace Titanium.Web.Proxy.Examples.Basic
...@@ -13,6 +15,13 @@ namespace Titanium.Web.Proxy.Examples.Basic ...@@ -13,6 +15,13 @@ namespace Titanium.Web.Proxy.Examples.Basic
{ {
private readonly ProxyServer proxyServer; private readonly ProxyServer proxyServer;
//keep track of request headers
private readonly IDictionary<Guid, HeaderCollection> requestHeaderHistory = new ConcurrentDictionary<Guid, HeaderCollection>();
//keep track of response headers
private readonly IDictionary<Guid, HeaderCollection> responseHeaderHistory = new ConcurrentDictionary<Guid, HeaderCollection>();
//share requestBody outside handlers //share requestBody outside handlers
//Using a dictionary is not a good idea since it can cause memory overflow //Using a dictionary is not a good idea since it can cause memory overflow
//ideally the data should be moved out of memory //ideally the data should be moved out of memory
...@@ -136,7 +145,7 @@ namespace Titanium.Web.Proxy.Examples.Basic ...@@ -136,7 +145,7 @@ namespace Titanium.Web.Proxy.Examples.Basic
Console.WriteLine(e.WebSession.Request.Url); Console.WriteLine(e.WebSession.Request.Url);
//read request headers //read request headers
var requestHeaders = e.WebSession.Request.RequestHeaders; requestHeaderHistory[e.Id] = e.WebSession.Request.RequestHeaders;
if (e.WebSession.Request.HasBody) if (e.WebSession.Request.HasBody)
{ {
...@@ -183,7 +192,7 @@ namespace Titanium.Web.Proxy.Examples.Basic ...@@ -183,7 +192,7 @@ namespace Titanium.Web.Proxy.Examples.Basic
//} //}
//read response headers //read response headers
var responseHeaders = e.WebSession.Response.ResponseHeaders; responseHeaderHistory[e.Id] = e.WebSession.Response.ResponseHeaders;
// print out process id of current session // print out process id of current session
//Console.WriteLine($"PID: {e.WebSession.ProcessId.Value}"); //Console.WriteLine($"PID: {e.WebSession.ProcessId.Value}");
......
using System; using System.Windows;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
namespace Titanium.Web.Proxy.Examples.Wpf namespace Titanium.Web.Proxy.Examples.Wpf
{ {
......
...@@ -3,17 +3,10 @@ using System.Collections.Generic; ...@@ -3,17 +3,10 @@ using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input; using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using Titanium.Web.Proxy.EventArguments; using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Helpers; using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Http; using Titanium.Web.Proxy.Http;
......
using System; using System;
using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using Titanium.Web.Proxy.EventArguments; using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Examples.Wpf.Annotations; using Titanium.Web.Proxy.Examples.Wpf.Annotations;
using Titanium.Web.Proxy.Http; using Titanium.Web.Proxy.Http;
......
...@@ -51,6 +51,10 @@ ...@@ -51,6 +51,10 @@
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="StreamExtended, Version=1.0.1.0, Culture=neutral, PublicKeyToken=bbfa0f1d54f50043, processorArchitecture=MSIL">
<HintPath>..\..\packages\StreamExtended.1.0.18\lib\net45\StreamExtended.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="System.Data" /> <Reference Include="System.Data" />
<Reference Include="System.Xml" /> <Reference Include="System.Xml" />
...@@ -104,6 +108,7 @@ ...@@ -104,6 +108,7 @@
<Generator>ResXFileCodeGenerator</Generator> <Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput> <LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource> </EmbeddedResource>
<None Include="packages.config" />
<None Include="Properties\Settings.settings"> <None Include="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator> <Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput> <LastGenOutput>Settings.Designer.cs</LastGenOutput>
......
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="StreamExtended" version="1.0.18" targetFramework="net45" />
</packages>
\ No newline at end of file
...@@ -27,7 +27,7 @@ Usage ...@@ -27,7 +27,7 @@ Usage
Refer the HTTP Proxy Server library in your project, look up Test project to learn usage. Refer the HTTP Proxy Server library in your project, look up Test project to learn usage.
Install by nuget: Install by [nuget](https://www.nuget.org/packages/Titanium.Web.Proxy)
For beta releases on [beta branch](https://github.com/justcoding121/Titanium-Web-Proxy/tree/beta) For beta releases on [beta branch](https://github.com/justcoding121/Titanium-Web-Proxy/tree/beta)
...@@ -206,7 +206,6 @@ public Task OnCertificateSelection(object sender, CertificateSelectionEventArgs ...@@ -206,7 +206,6 @@ public Task OnCertificateSelection(object sender, CertificateSelectionEventArgs
``` ```
Future road map (Pull requests are welcome!) Future road map (Pull requests are welcome!)
============ ============
* Support Server Name Indication (SNI) for transparent endpoints
* Support HTTP 2.0 * Support HTTP 2.0
* Support SOCKS protocol * Support SOCKS protocol

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26430.14
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Examples", "Examples", "{B6DBABDC-C985-4872-9C38-B4E5079CBC4B}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{6FD3B84B-9283-4E9C-8C43-A234E9AA3EAA}"
ProjectSection(SolutionItems) = preProject
.nuget\NuGet.Config = .nuget\NuGet.Config
.nuget\NuGet.exe = .nuget\NuGet.exe
.nuget\NuGet.targets = .nuget\NuGet.targets
EndProjectSection
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
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{BC1E0789-D348-49CF-8B67-5E99D50EDF64}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Titanium.Web.Proxy.Standard", "Titanium.Web.Proxy\Titanium.Web.Proxy.Standard.csproj", "{9F983536-C482-4BA0-BEE9-C59FC4221F76}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Titanium.Web.Proxy.Examples.Basic.Standard", "Examples\Titanium.Web.Proxy.Examples.Basic.Standard\Titanium.Web.Proxy.Examples.Basic.Standard.csproj", "{EEE97BBC-6898-41DE-B97B-BE0157A816CD}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{9F983536-C482-4BA0-BEE9-C59FC4221F76}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9F983536-C482-4BA0-BEE9-C59FC4221F76}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9F983536-C482-4BA0-BEE9-C59FC4221F76}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9F983536-C482-4BA0-BEE9-C59FC4221F76}.Release|Any CPU.Build.0 = Release|Any CPU
{EEE97BBC-6898-41DE-B97B-BE0157A816CD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EEE97BBC-6898-41DE-B97B-BE0157A816CD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EEE97BBC-6898-41DE-B97B-BE0157A816CD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EEE97BBC-6898-41DE-B97B-BE0157A816CD}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{EEE97BBC-6898-41DE-B97B-BE0157A816CD} = {B6DBABDC-C985-4872-9C38-B4E5079CBC4B}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
EnterpriseLibraryConfigurationToolBinariesPath = .1.505.2\lib\NET35
EndGlobalSection
EndGlobal
using System.IO; using StreamExtended.Helpers;
using System.IO;
using System.IO.Compression; using System.IO.Compression;
using System.Threading.Tasks; using System.Threading.Tasks;
using Titanium.Web.Proxy.Helpers; using Titanium.Web.Proxy.Helpers;
......
using System.IO; using StreamExtended.Helpers;
using System.IO;
using System.IO.Compression; using System.IO.Compression;
using System.Threading.Tasks; using System.Threading.Tasks;
using Titanium.Web.Proxy.Helpers; using Titanium.Web.Proxy.Helpers;
......
...@@ -2,7 +2,6 @@ ...@@ -2,7 +2,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Net; using System.Net;
using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Titanium.Web.Proxy.Decompression; using Titanium.Web.Proxy.Decompression;
using Titanium.Web.Proxy.Exceptions; using Titanium.Web.Proxy.Exceptions;
......
using System; using Titanium.Web.Proxy.Models;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Models;
namespace Titanium.Web.Proxy.EventArguments namespace Titanium.Web.Proxy.EventArguments
{ {
......
using System; using System.Net.Sockets;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Extensions namespace Titanium.Web.Proxy.Extensions
{ {
......
using System; using StreamExtended.Network;
using System;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Titanium.Web.Proxy.Helpers; using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Extensions namespace Titanium.Web.Proxy.Extensions
{ {
...@@ -14,23 +13,11 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -14,23 +13,11 @@ namespace Titanium.Web.Proxy.Extensions
internal static class StreamExtensions internal static class StreamExtensions
{ {
/// <summary> /// <summary>
/// Copy streams asynchronously with an initial data inserted to the beginning of stream /// Copy streams asynchronously
/// </summary> /// </summary>
/// <param name="input"></param> /// <param name="input"></param>
/// <param name="initialData"></param>
/// <param name="output"></param> /// <param name="output"></param>
/// <returns></returns> /// <param name="onCopy"></param>
internal static async Task CopyToAsync(this Stream input, string initialData, Stream output)
{
if (!string.IsNullOrEmpty(initialData))
{
var bytes = Encoding.ASCII.GetBytes(initialData);
await output.WriteAsync(bytes, 0, bytes.Length);
}
await input.CopyToAsync(output);
}
internal static async Task CopyToAsync(this Stream input, Stream output, Action<byte[], int, int> onCopy) internal static async Task CopyToAsync(this Stream input, Stream output, Action<byte[], int, int> onCopy)
{ {
byte[] buffer = new byte[81920]; byte[] buffer = new byte[81920];
...@@ -109,104 +96,5 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -109,104 +96,5 @@ namespace Titanium.Web.Proxy.Extensions
} }
} }
} }
/// <summary>
/// Writes the byte array body to the given stream; optionally chunked
/// </summary>
/// <param name="clientStream"></param>
/// <param name="data"></param>
/// <param name="isChunked"></param>
/// <returns></returns>
internal static async Task WriteResponseBody(this Stream clientStream, byte[] data, bool isChunked)
{
if (!isChunked)
{
await clientStream.WriteAsync(data, 0, data.Length);
}
else
{
await WriteResponseBodyChunked(data, clientStream);
}
}
/// <summary>
/// Copies the specified content length number of bytes to the output stream from the given inputs stream
/// optionally chunked
/// </summary>
/// <param name="inStreamReader"></param>
/// <param name="bufferSize"></param>
/// <param name="outStream"></param>
/// <param name="isChunked"></param>
/// <param name="contentLength"></param>
/// <returns></returns>
internal static async Task WriteResponseBody(this CustomBinaryReader inStreamReader, int bufferSize, Stream outStream, bool isChunked,
long contentLength)
{
if (!isChunked)
{
//http 1.0
if (contentLength == -1)
{
contentLength = long.MaxValue;
}
await CopyBytesToStream(inStreamReader, outStream, contentLength);
}
else
{
await WriteResponseBodyChunked(inStreamReader, outStream);
}
}
/// <summary>
/// Copies the streams chunked
/// </summary>
/// <param name="inStreamReader"></param>
/// <param name="outStream"></param>
/// <returns></returns>
internal static async Task WriteResponseBodyChunked(this CustomBinaryReader inStreamReader, Stream outStream)
{
while (true)
{
string chunkHead = await inStreamReader.ReadLineAsync();
int chunkSize = int.Parse(chunkHead, NumberStyles.HexNumber);
if (chunkSize != 0)
{
var chunkHeadBytes = Encoding.ASCII.GetBytes(chunkSize.ToString("x2"));
await outStream.WriteAsync(chunkHeadBytes, 0, chunkHeadBytes.Length);
await outStream.WriteAsync(ProxyConstants.NewLineBytes, 0, ProxyConstants.NewLineBytes.Length);
await CopyBytesToStream(inStreamReader, outStream, chunkSize);
await outStream.WriteAsync(ProxyConstants.NewLineBytes, 0, ProxyConstants.NewLineBytes.Length);
await inStreamReader.ReadLineAsync();
}
else
{
await inStreamReader.ReadLineAsync();
await outStream.WriteAsync(ProxyConstants.ChunkEnd, 0, ProxyConstants.ChunkEnd.Length);
break;
}
}
}
/// <summary>
/// Copies the given input bytes to output stream chunked
/// </summary>
/// <param name="data"></param>
/// <param name="outStream"></param>
/// <returns></returns>
internal static async Task WriteResponseBodyChunked(this byte[] data, Stream outStream)
{
var chunkHead = Encoding.ASCII.GetBytes(data.Length.ToString("x2"));
await outStream.WriteAsync(chunkHead, 0, chunkHead.Length);
await outStream.WriteAsync(ProxyConstants.NewLineBytes, 0, ProxyConstants.NewLineBytes.Length);
await outStream.WriteAsync(data, 0, data.Length);
await outStream.WriteAsync(ProxyConstants.NewLineBytes, 0, ProxyConstants.NewLineBytes.Length);
await outStream.WriteAsync(ProxyConstants.ChunkEnd, 0, ProxyConstants.ChunkEnd.Length);
}
} }
} }
using System.Collections.Concurrent;
namespace Titanium.Web.Proxy.Helpers
{
internal static class BufferPool
{
private static readonly ConcurrentQueue<byte[]> buffers = new ConcurrentQueue<byte[]>();
internal static byte[] GetBuffer(int bufferSize)
{
byte[] buffer;
if (!buffers.TryDequeue(out buffer) || buffer.Length != bufferSize)
{
buffer = new byte[bufferSize];
}
return buffer;
}
internal static void ReturnBuffer(byte[] buffer)
{
if (buffer != null)
{
buffers.Enqueue(buffer);
}
}
}
}
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Helpers
{
/// <summary>
/// A custom binary reader that would allo us to read string line by line
/// using the specified encoding
/// as well as to read bytes as required
/// </summary>
internal class CustomBinaryReader : IDisposable
{
private readonly CustomBufferedStream stream;
private readonly Encoding encoding;
private bool disposed;
internal byte[] Buffer { get; }
internal CustomBinaryReader(CustomBufferedStream stream, int bufferSize)
{
this.stream = stream;
Buffer = BufferPool.GetBuffer(bufferSize);
//default to UTF-8
encoding = Encoding.UTF8;
}
/// <summary>
/// Read a line from the byte stream
/// </summary>
/// <returns></returns>
internal async Task<string> ReadLineAsync()
{
byte lastChar = default(byte);
int bufferDataLength = 0;
// try to use the thread static buffer, usually it is enough
var buffer = Buffer;
while (stream.DataAvailable || await stream.FillBufferAsync())
{
byte newChar = stream.ReadByteFromBuffer();
buffer[bufferDataLength] = newChar;
//if new line
if (lastChar == '\r' && newChar == '\n')
{
return encoding.GetString(buffer, 0, bufferDataLength - 1);
}
//end of stream
if (newChar == '\0')
{
return encoding.GetString(buffer, 0, bufferDataLength);
}
bufferDataLength++;
//store last char for new line comparison
lastChar = newChar;
if (bufferDataLength == buffer.Length)
{
ResizeBuffer(ref buffer, bufferDataLength * 2);
}
}
if (bufferDataLength == 0)
{
return null;
}
return encoding.GetString(buffer, 0, bufferDataLength);
}
/// <summary>
/// Read until the last new line
/// </summary>
/// <returns></returns>
internal async Task<List<string>> ReadAllLinesAsync()
{
string tmpLine;
var requestLines = new List<string>();
while (!string.IsNullOrEmpty(tmpLine = await ReadLineAsync()))
{
requestLines.Add(tmpLine);
}
return requestLines;
}
/// <summary>
/// Read until the last new line, ignores the result
/// </summary>
/// <returns></returns>
internal async Task ReadAndIgnoreAllLinesAsync()
{
while (!string.IsNullOrEmpty(await ReadLineAsync()))
{
}
}
/// <summary>
/// Read the specified number (or less) of raw bytes from the base stream to the given buffer
/// </summary>
/// <param name="buffer"></param>
/// <param name="bytesToRead"></param>
/// <returns>The number of bytes read</returns>
internal Task<int> ReadBytesAsync(byte[] buffer, int bytesToRead)
{
return stream.ReadAsync(buffer, 0, bytesToRead);
}
public void Dispose()
{
if (!disposed)
{
disposed = true;
BufferPool.ReturnBuffer(Buffer);
}
}
/// <summary>
/// Increase size of buffer and copy existing content to new buffer
/// </summary>
/// <param name="buffer"></param>
/// <param name="size"></param>
private void ResizeBuffer(ref byte[] buffer, long size)
{
var newBuffer = new byte[size];
System.Buffer.BlockCopy(buffer, 0, newBuffer, 0, buffer.Length);
buffer = newBuffer;
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Helpers
{
class CustomBufferedPeekStream
{
private readonly CustomBufferedStream baseStream;
private int position;
public CustomBufferedPeekStream(CustomBufferedStream baseStream, int startPosition = 0)
{
this.baseStream = baseStream;
position = startPosition;
}
public int Available => baseStream.Available - position;
public async Task<bool> EnsureBufferLength(int length)
{
var val = await baseStream.PeekByteAsync(position + length - 1);
return val != -1;
}
public byte ReadByte()
{
return baseStream.PeekByteFromBuffer(position++);
}
public int ReadInt16()
{
int i1 = ReadByte();
int i2 = ReadByte();
return (i1 << 8) + i2;
}
public int ReadInt24()
{
int i1 = ReadByte();
int i2 = ReadByte();
int i3 = ReadByte();
return (i1 << 16) + (i2 << 8) + i3;
}
public byte[] ReadBytes(int length)
{
var buffer = new byte[length];
for (int i = 0; i < buffer.Length; i++)
{
buffer[i] = ReadByte();
}
return buffer;
}
}
}
This diff is collapsed.
using System.IO;
namespace Titanium.Web.Proxy.Helpers
{
sealed class HttpRequestWriter : HttpWriter
{
public HttpRequestWriter(Stream stream) : base(stream, true)
{
}
}
}
using StreamExtended.Network;
using System;
using System.Globalization;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Helpers
{
sealed class HttpResponseWriter : HttpWriter
{
public HttpResponseWriter(Stream stream) : base(stream, true)
{
}
/// <summary>
/// Writes the response.
/// </summary>
/// <param name="response"></param>
/// <param name="flush"></param>
/// <returns></returns>
public async Task WriteResponseAsync(Response response, bool flush = true)
{
await WriteResponseStatusAsync(response.HttpVersion, response.ResponseStatusCode, response.ResponseStatusDescription);
response.ResponseHeaders.FixProxyHeaders();
await WriteHeadersAsync(response.ResponseHeaders, flush);
}
/// <summary>
/// Write response status
/// </summary>
/// <param name="version"></param>
/// <param name="code"></param>
/// <param name="description"></param>
/// <returns></returns>
public Task WriteResponseStatusAsync(Version version, int code, string description)
{
return WriteLineAsync($"HTTP/{version.Major}.{version.Minor} {code} {description}");
}
/// <summary>
/// Writes the byte array body to the stream; optionally chunked
/// </summary>
/// <param name="data"></param>
/// <param name="isChunked"></param>
/// <returns></returns>
internal async Task WriteResponseBodyAsync(byte[] data, bool isChunked)
{
if (!isChunked)
{
await BaseStream.WriteAsync(data, 0, data.Length);
}
else
{
await WriteResponseBodyChunkedAsync(data);
}
}
/// <summary>
/// Copies the specified content length number of bytes to the output stream from the given inputs stream
/// optionally chunked
/// </summary>
/// <param name="bufferSize"></param>
/// <param name="inStreamReader"></param>
/// <param name="isChunked"></param>
/// <param name="contentLength"></param>
/// <returns></returns>
internal async Task WriteResponseBodyAsync(int bufferSize, CustomBinaryReader inStreamReader, bool isChunked,
long contentLength)
{
if (!isChunked)
{
//http 1.0
if (contentLength == -1)
{
contentLength = long.MaxValue;
}
await inStreamReader.CopyBytesToStream(BaseStream, contentLength);
}
else
{
await WriteResponseBodyChunkedAsync(inStreamReader);
}
}
/// <summary>
/// Copies the streams chunked
/// </summary>
/// <param name="inStreamReader"></param>
/// <returns></returns>
internal async Task WriteResponseBodyChunkedAsync(CustomBinaryReader inStreamReader)
{
while (true)
{
string chunkHead = await inStreamReader.ReadLineAsync();
int chunkSize = int.Parse(chunkHead, NumberStyles.HexNumber);
if (chunkSize != 0)
{
var chunkHeadBytes = Encoding.ASCII.GetBytes(chunkSize.ToString("x2"));
await BaseStream.WriteAsync(chunkHeadBytes, 0, chunkHeadBytes.Length);
await BaseStream.WriteAsync(ProxyConstants.NewLineBytes, 0, ProxyConstants.NewLineBytes.Length);
await inStreamReader.CopyBytesToStream(BaseStream, chunkSize);
await BaseStream.WriteAsync(ProxyConstants.NewLineBytes, 0, ProxyConstants.NewLineBytes.Length);
await inStreamReader.ReadLineAsync();
}
else
{
await inStreamReader.ReadLineAsync();
await BaseStream.WriteAsync(ProxyConstants.ChunkEnd, 0, ProxyConstants.ChunkEnd.Length);
break;
}
}
}
/// <summary>
/// Copies the given input bytes to output stream chunked
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
internal async Task WriteResponseBodyChunkedAsync(byte[] data)
{
var chunkHead = Encoding.ASCII.GetBytes(data.Length.ToString("x2"));
await BaseStream.WriteAsync(chunkHead, 0, chunkHead.Length);
await BaseStream.WriteAsync(ProxyConstants.NewLineBytes, 0, ProxyConstants.NewLineBytes.Length);
await BaseStream.WriteAsync(data, 0, data.Length);
await BaseStream.WriteAsync(ProxyConstants.NewLineBytes, 0, ProxyConstants.NewLineBytes.Length);
await BaseStream.WriteAsync(ProxyConstants.ChunkEnd, 0, ProxyConstants.ChunkEnd.Length);
}
}
}
using System.IO;
using System.Text;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Helpers
{
abstract class HttpWriter : StreamWriter
{
protected HttpWriter(Stream stream, bool leaveOpen) : base(stream, Encoding.ASCII, 1024, leaveOpen)
{
NewLine = ProxyConstants.NewLine;
}
public void WriteHeaders(HeaderCollection headers, bool flush = true)
{
if (headers != null)
{
foreach (var header in headers)
{
header.WriteToStream(this);
}
}
WriteLine();
if (flush)
{
Flush();
}
}
/// <summary>
/// Write the headers to client
/// </summary>
/// <param name="headers"></param>
/// <param name="flush"></param>
/// <returns></returns>
public async Task WriteHeadersAsync(HeaderCollection headers, bool flush = true)
{
if (headers != null)
{
foreach (var header in headers)
{
await header.WriteToStreamAsync(this);
}
}
await WriteLineAsync();
if (flush)
{
await FlushAsync();
}
}
}
}
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
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.Network.Tcp; using Titanium.Web.Proxy.Network.Tcp;
...@@ -114,18 +112,16 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -114,18 +112,16 @@ namespace Titanium.Web.Proxy.Helpers
/// Usefull for websocket requests /// Usefull for websocket requests
/// </summary> /// </summary>
/// <param name="clientStream"></param> /// <param name="clientStream"></param>
/// <param name="connection"></param> /// <param name="serverStream"></param>
/// <param name="onDataSend"></param> /// <param name="onDataSend"></param>
/// <param name="onDataReceive"></param> /// <param name="onDataReceive"></param>
/// <returns></returns> /// <returns></returns>
internal static async Task SendRaw(Stream clientStream, TcpConnection connection, Action<byte[], int, int> onDataSend, Action<byte[], int, int> onDataReceive) internal static async Task SendRaw(Stream clientStream, Stream serverStream, Action<byte[], int, int> onDataSend, Action<byte[], int, int> onDataReceive)
{ {
var tunnelStream = connection.Stream;
//Now async relay all server=>client & client=>server data //Now async relay all server=>client & client=>server data
var sendRelay = clientStream.CopyToAsync(tunnelStream, onDataSend); var sendRelay = clientStream.CopyToAsync(serverStream, onDataSend);
var receiveRelay = tunnelStream.CopyToAsync(clientStream, onDataReceive); var receiveRelay = serverStream.CopyToAsync(clientStream, onDataReceive);
await Task.WhenAll(sendRelay, receiveRelay); await Task.WhenAll(sendRelay, receiveRelay);
} }
......
using System.Threading.Tasks; using StreamExtended;
using Titanium.Web.Proxy.Ssl;
namespace Titanium.Web.Proxy.Http namespace Titanium.Web.Proxy.Http
{ {
......
using System; using StreamExtended;
using System.Collections.Generic; using System;
using System.Linq; using System.Net;
using System.Text;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Http namespace Titanium.Web.Proxy.Http
{ {
public class ConnectResponse : Response public class ConnectResponse : Response
{ {
public string ServerHelloInfo { get; set; } public ServerHelloInfo ServerHelloInfo { get; set; }
/// <summary>
/// Creates a successfull CONNECT response
/// </summary>
/// <param name="httpVersion"></param>
/// <returns></returns>
internal static ConnectResponse CreateSuccessfullConnectResponse(Version httpVersion)
{
var response = new ConnectResponse
{
HttpVersion = httpVersion,
ResponseStatusCode = (int)HttpStatusCode.OK,
ResponseStatusDescription = "Connection Established"
};
response.ResponseHeaders.AddHeader("Timestamp", DateTime.Now.ToString());
return response;
}
} }
} }
...@@ -2,8 +2,6 @@ ...@@ -2,8 +2,6 @@
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Models; using Titanium.Web.Proxy.Models;
namespace Titanium.Web.Proxy.Http namespace Titanium.Web.Proxy.Http
...@@ -250,6 +248,21 @@ namespace Titanium.Web.Proxy.Http ...@@ -250,6 +248,21 @@ namespace Titanium.Web.Proxy.Http
return null; return null;
} }
/// <summary>
/// Fix proxy specific headers
/// </summary>
internal void FixProxyHeaders()
{
//If proxy-connection close was returned inform to close the connection
string proxyHeader = GetHeaderValueOrNull("proxy-connection");
RemoveHeader("proxy-connection");
if (proxyHeader != null)
{
SetOrAddHeaderValue("connection", proxyHeader);
}
}
/// <summary> /// <summary>
/// Returns an enumerator that iterates through the collection. /// Returns an enumerator that iterates through the collection.
/// </summary> /// </summary>
......
using System.Collections.Generic; using StreamExtended.Network;
using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Titanium.Web.Proxy.Helpers; using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Models; using Titanium.Web.Proxy.Models;
......
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Net; using System.Net;
using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Models; using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Network.Tcp; using Titanium.Web.Proxy.Network.Tcp;
using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Http namespace Titanium.Web.Proxy.Http
{ {
...@@ -77,35 +75,39 @@ namespace Titanium.Web.Proxy.Http ...@@ -77,35 +75,39 @@ namespace Titanium.Web.Proxy.Http
{ {
var stream = ServerConnection.Stream; var stream = ServerConnection.Stream;
var requestLines = new StringBuilder(); byte[] requestBytes;
using (var ms = new MemoryStream())
using (var writer = new HttpRequestWriter(ms))
{
//prepare the request & headers
writer.WriteLine($"{Request.Method} {Request.RequestUri.PathAndQuery} HTTP/{Request.HttpVersion.Major}.{Request.HttpVersion.Minor}");
//prepare the request & headers var upstreamProxy = ServerConnection.UpStreamHttpProxy;
requestLines.AppendLine($"{Request.Method} {Request.RequestUri.PathAndQuery} HTTP/{Request.HttpVersion.Major}.{Request.HttpVersion.Minor}");
//Send Authentication to Upstream proxy if needed
if (upstreamProxy != null
&& ServerConnection.IsHttps == false
&& !string.IsNullOrEmpty(upstreamProxy.UserName)
&& upstreamProxy.Password != null)
{
HttpHeader.ProxyConnectionKeepAlive.WriteToStream(writer);
HttpHeader.GetProxyAuthorizationHeader(upstreamProxy.UserName, upstreamProxy.Password).WriteToStream(writer);
}
//Send Authentication to Upstream proxy if needed //write request headers
if (ServerConnection.UpStreamHttpProxy != null foreach (var header in Request.RequestHeaders)
&& ServerConnection.IsHttps == false
&& !string.IsNullOrEmpty(ServerConnection.UpStreamHttpProxy.UserName)
&& ServerConnection.UpStreamHttpProxy.Password != null)
{
requestLines.AppendLine("Proxy-Connection: keep-alive");
requestLines.AppendLine("Proxy-Authorization" + ": Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes(
$"{ServerConnection.UpStreamHttpProxy.UserName}:{ServerConnection.UpStreamHttpProxy.Password}")));
}
//write request headers
foreach (var header in Request.RequestHeaders)
{
if (header.Name != "Proxy-Authorization")
{ {
requestLines.AppendLine($"{header.Name}: {header.Value}"); if (header.Name != "Proxy-Authorization")
{
header.WriteToStream(writer);
}
} }
}
requestLines.AppendLine(); writer.WriteLine();
writer.Flush();
string request = requestLines.ToString(); requestBytes = ms.ToArray();
var requestBytes = Encoding.ASCII.GetBytes(request); }
await stream.WriteAsync(requestBytes, 0, requestBytes.Length); await stream.WriteAsync(requestBytes, 0, requestBytes.Length);
await stream.FlushAsync(); await stream.FlushAsync();
......
using System; using System;
using System.Collections.Generic;
using System.Linq;
using System.Text; using System.Text;
using Titanium.Web.Proxy.Extensions; using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Models; using Titanium.Web.Proxy.Models;
......
using System; using System;
using System.Collections.Generic;
using System.Linq;
using System.Text; using System.Text;
using Titanium.Web.Proxy.Extensions; using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Models; using Titanium.Web.Proxy.Models;
......
using System; using System;
using System.IO; using System.IO;
using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Models namespace Titanium.Web.Proxy.Models
...@@ -13,6 +14,8 @@ namespace Titanium.Web.Proxy.Models ...@@ -13,6 +14,8 @@ namespace Titanium.Web.Proxy.Models
internal static readonly Version Version11 = new Version(1, 1); internal static readonly Version Version11 = new Version(1, 1);
internal static HttpHeader ProxyConnectionKeepAlive = new HttpHeader("Proxy-Connection", "keep-alive");
/// <summary> /// <summary>
/// Constructor. /// Constructor.
/// </summary> /// </summary>
...@@ -49,7 +52,21 @@ namespace Titanium.Web.Proxy.Models ...@@ -49,7 +52,21 @@ namespace Titanium.Web.Proxy.Models
return $"{Name}: {Value}"; return $"{Name}: {Value}";
} }
internal async Task WriteToStream(StreamWriter writer) internal static HttpHeader GetProxyAuthorizationHeader(string userName, string password)
{
var result = new HttpHeader("Proxy-Authorization",
"Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes($"{userName}:{password}")));
return result;
}
internal void WriteToStream(StreamWriter writer)
{
writer.Write(Name);
writer.Write(": ");
writer.WriteLine(Value);
}
internal async Task WriteToStreamAsync(StreamWriter writer)
{ {
await writer.WriteAsync(Name); await writer.WriteAsync(Name);
await writer.WriteAsync(": "); await writer.WriteAsync(": ");
......
using System.IO; using StreamExtended.Network;
using System.Net.Sockets; using System.Net.Sockets;
using Titanium.Web.Proxy.Helpers; using Titanium.Web.Proxy.Helpers;
...@@ -17,7 +17,7 @@ namespace Titanium.Web.Proxy.Network ...@@ -17,7 +17,7 @@ namespace Titanium.Web.Proxy.Network
/// <summary> /// <summary>
/// holds the stream to client /// holds the stream to client
/// </summary> /// </summary>
internal Stream ClientStream { get; set; } internal CustomBufferedStream ClientStream { get; set; }
/// <summary> /// <summary>
/// Used to read line by line from client /// Used to read line by line from client
...@@ -27,6 +27,6 @@ namespace Titanium.Web.Proxy.Network ...@@ -27,6 +27,6 @@ namespace Titanium.Web.Proxy.Network
/// <summary> /// <summary>
/// used to write line by line to client /// used to write line by line to client
/// </summary> /// </summary>
internal StreamWriter ClientStreamWriter { get; set; } internal HttpResponseWriter ClientStreamWriter { get; set; }
} }
} }
using System; using StreamExtended.Network;
using System.IO; using System;
using System.Net.Sockets; using System.Net.Sockets;
using Titanium.Web.Proxy.Extensions; using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Models; using Titanium.Web.Proxy.Models;
namespace Titanium.Web.Proxy.Network.Tcp namespace Titanium.Web.Proxy.Network.Tcp
...@@ -41,7 +40,7 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -41,7 +40,7 @@ namespace Titanium.Web.Proxy.Network.Tcp
/// <summary> /// <summary>
/// Server stream /// Server stream
/// </summary> /// </summary>
internal Stream Stream { get; set; } internal CustomBufferedStream Stream { get; set; }
/// <summary> /// <summary>
/// Last time this connection was used /// Last time this connection was used
......
using System; using StreamExtended.Network;
using System;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net.Security; using System.Net.Security;
using System.Net.Sockets; using System.Net.Sockets;
using System.Text; using System.Text;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Titanium.Web.Proxy.Extensions; using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Helpers; using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Models; using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Network.Tcp namespace Titanium.Web.Proxy.Network.Tcp
{ {
...@@ -25,12 +24,13 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -25,12 +24,13 @@ namespace Titanium.Web.Proxy.Network.Tcp
/// <param name="remoteHostName"></param> /// <param name="remoteHostName"></param>
/// <param name="remotePort"></param> /// <param name="remotePort"></param>
/// <param name="httpVersion"></param> /// <param name="httpVersion"></param>
/// <param name="isConnect"></param>
/// <param name="isHttps"></param> /// <param name="isHttps"></param>
/// <param name="externalHttpProxy"></param> /// <param name="externalHttpProxy"></param>
/// <param name="externalHttpsProxy"></param> /// <param name="externalHttpsProxy"></param>
/// <returns></returns> /// <returns></returns>
internal async Task<TcpConnection> CreateClient(ProxyServer server, string remoteHostName, int remotePort, Version httpVersion, bool isHttps, internal async Task<TcpConnection> CreateClient(ProxyServer server, string remoteHostName, int remotePort, Version httpVersion, bool isHttps,
ExternalProxy externalHttpProxy, ExternalProxy externalHttpsProxy) bool isConnect, ExternalProxy externalHttpProxy, ExternalProxy externalHttpsProxy)
{ {
bool useUpstreamProxy = false; bool useUpstreamProxy = false;
var externalProxy = isHttps ? externalHttpsProxy : externalHttpProxy; var externalProxy = isHttps ? externalHttpsProxy : externalHttpProxy;
...@@ -52,65 +52,61 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -52,65 +52,61 @@ namespace Titanium.Web.Proxy.Network.Tcp
try try
{ {
//If this proxy uses another external proxy then create a tunnel request for HTTP/HTTPS connections
if (useUpstreamProxy)
{
#if NET45 #if NET45
client = new TcpClient(server.UpStreamEndPoint); client = new TcpClient(server.UpStreamEndPoint);
#else #else
client = new TcpClient(server.UpStreamEndPoint.AddressFamily); client = new TcpClient();
client.Client.Bind(server.UpStreamEndPoint);
#endif #endif
//If this proxy uses another external proxy then create a tunnel request for HTTP/HTTPS connections
if (useUpstreamProxy)
{
await client.ConnectAsync(externalProxy.HostName, externalProxy.Port); await client.ConnectAsync(externalProxy.HostName, externalProxy.Port);
stream = new CustomBufferedStream(client.GetStream(), server.BufferSize); }
else
{
await client.ConnectAsync(remoteHostName, remotePort);
}
if (isHttps) stream = new CustomBufferedStream(client.GetStream(), server.BufferSize);
if (useUpstreamProxy && (isConnect || isHttps))
{
using (var writer = new HttpRequestWriter(stream))
{ {
using (var writer = new StreamWriter(stream, Encoding.ASCII, server.BufferSize, true) await writer.WriteLineAsync($"CONNECT {remoteHostName}:{remotePort} HTTP/{httpVersion}");
{ await writer.WriteLineAsync($"Host: {remoteHostName}:{remotePort}");
NewLine = ProxyConstants.NewLine await writer.WriteLineAsync("Connection: Keep-Alive");
})
if (!string.IsNullOrEmpty(externalProxy.UserName) && externalProxy.Password != null)
{ {
await writer.WriteLineAsync($"CONNECT {remoteHostName}:{remotePort} HTTP/{httpVersion}"); await HttpHeader.ProxyConnectionKeepAlive.WriteToStreamAsync(writer);
await writer.WriteLineAsync($"Host: {remoteHostName}:{remotePort}"); await writer.WriteLineAsync("Proxy-Authorization" + ": Basic " +
await writer.WriteLineAsync("Connection: Keep-Alive"); Convert.ToBase64String(Encoding.UTF8.GetBytes(
externalProxy.UserName + ":" + externalProxy.Password)));
if (!string.IsNullOrEmpty(externalProxy.UserName) && externalProxy.Password != null)
{
await writer.WriteLineAsync("Proxy-Connection: keep-alive");
await writer.WriteLineAsync("Proxy-Authorization" + ": Basic " +
Convert.ToBase64String(Encoding.UTF8.GetBytes(
externalProxy.UserName + ":" + externalProxy.Password)));
}
await writer.WriteLineAsync();
await writer.FlushAsync();
} }
using (var reader = new CustomBinaryReader(stream, server.BufferSize)) await writer.WriteLineAsync();
{ await writer.FlushAsync();
string result = await reader.ReadLineAsync(); }
if (!new[] { "200 OK", "connection established" }.Any(s => result.ContainsIgnoreCase(s))) using (var reader = new CustomBinaryReader(stream, server.BufferSize))
{ {
throw new Exception("Upstream proxy failed to create a secure tunnel"); string result = await reader.ReadLineAsync();
}
await reader.ReadAndIgnoreAllLinesAsync(); if (!new[] { "200 OK", "connection established" }.Any(s => result.ContainsIgnoreCase(s)))
{
throw new Exception("Upstream proxy failed to create a secure tunnel");
} }
await reader.ReadAndIgnoreAllLinesAsync();
} }
} }
else
{
#if NET45
client = new TcpClient(server.UpStreamEndPoint);
#else
client = new TcpClient(server.UpStreamEndPoint.AddressFamily);
#endif
await client.ConnectAsync(remoteHostName, remotePort);
stream = new CustomBufferedStream(client.GetStream(), server.BufferSize);
}
if (isHttps) if (isHttps)
{ {
var sslStream = new SslStream(stream, false, server.ValidateServerCertificate, server.SelectClientCertificate); var sslStream = new SslStream(stream, false, server.ValidateServerCertificate, server.SelectClientCertificate);
stream = new CustomBufferedStream(sslStream, server.BufferSize); stream = new CustomBufferedStream(sslStream, server.BufferSize);
......
using System; using System;
using System.Collections.Generic;
using System.IO;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Titanium.Web.Proxy.EventArguments; using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Exceptions; using Titanium.Web.Proxy.Exceptions;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Http; using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Models; using Titanium.Web.Proxy.Models;
...@@ -14,7 +13,7 @@ namespace Titanium.Web.Proxy ...@@ -14,7 +13,7 @@ namespace Titanium.Web.Proxy
{ {
public partial class ProxyServer public partial class ProxyServer
{ {
private async Task<bool> CheckAuthorization(StreamWriter clientStreamWriter, SessionEventArgs session) private async Task<bool> CheckAuthorization(HttpResponseWriter clientStreamWriter, SessionEventArgs session)
{ {
if (AuthenticateUserFunc == null) if (AuthenticateUserFunc == null)
{ {
...@@ -64,7 +63,7 @@ namespace Titanium.Web.Proxy ...@@ -64,7 +63,7 @@ namespace Titanium.Web.Proxy
} }
} }
private async Task<Response> SendAuthentication407Response(StreamWriter clientStreamWriter, string description) private async Task<Response> SendAuthentication407Response(HttpResponseWriter clientStreamWriter, string description)
{ {
var response = new Response var response = new Response
{ {
...@@ -76,7 +75,7 @@ namespace Titanium.Web.Proxy ...@@ -76,7 +75,7 @@ namespace Titanium.Web.Proxy
response.ResponseHeaders.AddHeader("Proxy-Authenticate", $"Basic realm=\"{ProxyRealm}\""); response.ResponseHeaders.AddHeader("Proxy-Authenticate", $"Basic realm=\"{ProxyRealm}\"");
response.ResponseHeaders.AddHeader("Proxy-Connection", "close"); response.ResponseHeaders.AddHeader("Proxy-Connection", "close");
await WriteResponse(response, clientStreamWriter); await clientStreamWriter.WriteResponseAsync(response);
return response; return response;
} }
} }
......
using System; using StreamExtended.Network;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
...@@ -35,6 +36,11 @@ namespace Titanium.Web.Proxy ...@@ -35,6 +36,11 @@ namespace Titanium.Web.Proxy
internal const string UriSchemeHttps = "https"; internal const string UriSchemeHttps = "https";
#endif #endif
/// <summary>
/// Enable the experimental ALPN adder streams
/// </summary>
internal static bool AlpnEnabled = false;
/// <summary> /// <summary>
/// Is the proxy currently running /// Is the proxy currently running
/// </summary> /// </summary>
...@@ -50,10 +56,6 @@ namespace Titanium.Web.Proxy ...@@ -50,10 +56,6 @@ namespace Titanium.Web.Proxy
/// </summary> /// </summary>
private Action<Exception> exceptionFunc; private Action<Exception> exceptionFunc;
#if NET45
private WinHttpWebProxyFinder systemProxyResolver;
#endif
/// <summary> /// <summary>
/// Backing field for corresponding public property /// Backing field for corresponding public property
/// </summary> /// </summary>
...@@ -75,6 +77,8 @@ namespace Titanium.Web.Proxy ...@@ -75,6 +77,8 @@ namespace Titanium.Web.Proxy
private TcpConnectionFactory tcpConnectionFactory { get; } private TcpConnectionFactory tcpConnectionFactory { get; }
#if NET45 #if NET45
private WinHttpWebProxyFinder systemProxyResolver;
/// <summary> /// <summary>
/// Manage system proxy settings /// Manage system proxy settings
/// </summary> /// </summary>
...@@ -274,6 +278,7 @@ namespace Titanium.Web.Proxy ...@@ -274,6 +278,7 @@ namespace Titanium.Web.Proxy
/// Realm used during Proxy Basic Authentication /// Realm used during Proxy Basic Authentication
/// </summary> /// </summary>
public string ProxyRealm { get; set; } = "TitaniumProxy"; public string ProxyRealm { get; set; } = "TitaniumProxy";
/// <summary> /// <summary>
/// A callback to provide authentication credentials for up stream proxy this proxy is using for HTTP requests /// A callback to provide authentication credentials for up stream proxy this proxy is using for HTTP requests
/// return the ExternalProxy object with valid credentials /// return the ExternalProxy object with valid credentials
...@@ -294,7 +299,11 @@ namespace Titanium.Web.Proxy ...@@ -294,7 +299,11 @@ namespace Titanium.Web.Proxy
/// <summary> /// <summary>
/// List of supported Ssl versions /// List of supported Ssl versions
/// </summary> /// </summary>
#if NET45
public SslProtocols SupportedSslProtocols { get; set; } = SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12 | SslProtocols.Ssl3; public SslProtocols SupportedSslProtocols { get; set; } = SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12 | SslProtocols.Ssl3;
#else
public SslProtocols SupportedSslProtocols { get; set; } = SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12;
#endif
/// <summary> /// <summary>
/// Total number of active client connections /// Total number of active client connections
...@@ -633,6 +642,27 @@ namespace Titanium.Web.Proxy ...@@ -633,6 +642,27 @@ namespace Titanium.Web.Proxy
proxyRunning = false; proxyRunning = false;
} }
/// <summary>
/// Handle dispose of a client/server session
/// </summary>
/// <param name="clientStream"></param>
/// <param name="clientStreamReader"></param>
/// <param name="clientStreamWriter"></param>
/// <param name="serverConnection"></param>
private void Dispose(CustomBufferedStream clientStream, CustomBinaryReader clientStreamReader, HttpResponseWriter clientStreamWriter, TcpConnection serverConnection)
{
clientStream?.Dispose();
clientStreamReader?.Dispose();
clientStreamWriter?.Dispose();
if (serverConnection != null)
{
serverConnection.Dispose();
UpdateServerConnectionCount(false);
}
}
/// <summary> /// <summary>
/// Dispose Proxy. /// Dispose Proxy.
/// </summary> /// </summary>
......
This diff is collapsed.
using System; using System;
using System.Collections.Generic;
using System.IO;
using System.Net; using System.Net;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Titanium.Web.Proxy.Compression; using Titanium.Web.Proxy.Compression;
using Titanium.Web.Proxy.EventArguments; using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Exceptions; using Titanium.Web.Proxy.Exceptions;
using Titanium.Web.Proxy.Extensions; using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Helpers; using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Network.Tcp;
namespace Titanium.Web.Proxy namespace Titanium.Web.Proxy
{ {
...@@ -69,21 +63,24 @@ namespace Titanium.Web.Proxy ...@@ -69,21 +63,24 @@ namespace Titanium.Web.Proxy
response.ResponseLocked = true; response.ResponseLocked = true;
var clientStreamWriter = args.ProxyClient.ClientStreamWriter;
//Write back to client 100-conitinue response if that's what server returned //Write back to client 100-conitinue response if that's what server returned
if (response.Is100Continue) if (response.Is100Continue)
{ {
await WriteResponseStatus(response.HttpVersion, (int)HttpStatusCode.Continue, "Continue", args.ProxyClient.ClientStreamWriter); await clientStreamWriter.WriteResponseStatusAsync(response.HttpVersion, (int)HttpStatusCode.Continue, "Continue");
await args.ProxyClient.ClientStreamWriter.WriteLineAsync(); await clientStreamWriter.WriteLineAsync();
} }
else if (response.ExpectationFailed) else if (response.ExpectationFailed)
{ {
await WriteResponseStatus(response.HttpVersion, (int)HttpStatusCode.ExpectationFailed, "Expectation Failed", args.ProxyClient.ClientStreamWriter); await clientStreamWriter.WriteResponseStatusAsync(response.HttpVersion, (int)HttpStatusCode.ExpectationFailed, "Expectation Failed");
await args.ProxyClient.ClientStreamWriter.WriteLineAsync(); await clientStreamWriter.WriteLineAsync();
} }
//Write back response status to client //Write back response status to client
await WriteResponseStatus(response.HttpVersion, response.ResponseStatusCode, response.ResponseStatusDescription, args.ProxyClient.ClientStreamWriter); await clientStreamWriter.WriteResponseStatusAsync(response.HttpVersion, response.ResponseStatusCode, response.ResponseStatusDescription);
response.ResponseHeaders.FixProxyHeaders();
if (response.ResponseBodyRead) if (response.ResponseBodyRead)
{ {
bool isChunked = response.IsChunked; bool isChunked = response.IsChunked;
...@@ -103,22 +100,22 @@ namespace Titanium.Web.Proxy ...@@ -103,22 +100,22 @@ namespace Titanium.Web.Proxy
} }
} }
await WriteResponseHeaders(args.ProxyClient.ClientStreamWriter, response); await clientStreamWriter.WriteHeadersAsync(response.ResponseHeaders);
await args.ProxyClient.ClientStream.WriteResponseBody(response.ResponseBody, isChunked); await clientStreamWriter.WriteResponseBodyAsync(response.ResponseBody, isChunked);
} }
else else
{ {
await WriteResponseHeaders(args.ProxyClient.ClientStreamWriter, response); await clientStreamWriter.WriteHeadersAsync(response.ResponseHeaders);
//Write body if exists //Write body if exists
if (response.HasBody) if (response.HasBody)
{ {
await args.WebSession.ServerConnection.StreamReader.WriteResponseBody(BufferSize, args.ProxyClient.ClientStream, await clientStreamWriter.WriteResponseBodyAsync(BufferSize, args.WebSession.ServerConnection.StreamReader,
response.IsChunked, response.ContentLength); response.IsChunked, response.ContentLength);
} }
} }
await args.ProxyClient.ClientStream.FlushAsync(); await clientStreamWriter.FlushAsync();
} }
catch (Exception e) catch (Exception e)
{ {
...@@ -144,91 +141,5 @@ namespace Titanium.Web.Proxy ...@@ -144,91 +141,5 @@ namespace Titanium.Web.Proxy
var compressor = compressionFactory.Create(encodingType); var compressor = compressionFactory.Create(encodingType);
return await compressor.Compress(responseBodyStream); return await compressor.Compress(responseBodyStream);
} }
/// <summary>
/// Writes the response.
/// </summary>
/// <param name="response"></param>
/// <param name="responseWriter"></param>
/// <param name="flush"></param>
/// <returns></returns>
private async Task WriteResponse(Response response, StreamWriter responseWriter, bool flush = true)
{
await WriteResponseStatus(response.HttpVersion, response.ResponseStatusCode, response.ResponseStatusDescription, responseWriter);
await WriteResponseHeaders(responseWriter, response, flush);
}
/// <summary>
/// Write response status
/// </summary>
/// <param name="version"></param>
/// <param name="code"></param>
/// <param name="description"></param>
/// <param name="responseWriter"></param>
/// <returns></returns>
private async Task WriteResponseStatus(Version version, int code, string description, StreamWriter responseWriter)
{
await responseWriter.WriteLineAsync($"HTTP/{version.Major}.{version.Minor} {code} {description}");
}
/// <summary>
/// Write response headers to client
/// </summary>
/// <param name="responseWriter"></param>
/// <param name="response"></param>
/// <param name="flush"></param>
/// <returns></returns>
private async Task WriteResponseHeaders(StreamWriter responseWriter, Response response, bool flush = true)
{
FixProxyHeaders(response.ResponseHeaders);
foreach (var header in response.ResponseHeaders)
{
await header.WriteToStream(responseWriter);
}
await responseWriter.WriteLineAsync();
if (flush)
{
await responseWriter.FlushAsync();
}
}
/// <summary>
/// Fix proxy specific headers
/// </summary>
/// <param name="headers"></param>
private void FixProxyHeaders(HeaderCollection headers)
{
//If proxy-connection close was returned inform to close the connection
string proxyHeader = headers.GetHeaderValueOrNull("proxy-connection");
headers.RemoveHeader("proxy-connection");
if (proxyHeader != null)
{
headers.SetOrAddHeaderValue("connection", proxyHeader);
}
}
/// <summary>
/// Handle dispose of a client/server session
/// </summary>
/// <param name="clientStream"></param>
/// <param name="clientStreamReader"></param>
/// <param name="clientStreamWriter"></param>
/// <param name="serverConnection"></param>
private void Dispose(Stream clientStream, CustomBinaryReader clientStreamReader, StreamWriter clientStreamWriter, TcpConnection serverConnection)
{
clientStream?.Dispose();
clientStreamReader?.Dispose();
clientStreamWriter?.Dispose();
if (serverConnection != null)
{
serverConnection.Dispose();
UpdateServerConnectionCount(false);
}
}
} }
} }
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Titanium.Web.Proxy.Ssl
{
public class ClientHelloInfo
{
private static readonly string[] compressions = {
"null",
"DEFLATE"
};
public int MajorVersion { get; set; }
public int MinorVersion { get; set; }
public byte[] Random { get; set; }
public DateTime Time
{
get
{
DateTime time = DateTime.MinValue;
if (Random.Length > 3)
{
time = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc)
.AddSeconds(((uint)Random[3] << 24) + ((uint)Random[2] << 16) + ((uint)Random[1] << 8) + (uint)Random[0]).ToLocalTime();
}
return time;
}
}
public byte[] SessionId { get; set; }
public int[] Ciphers { get; set; }
public byte[] CompressionData { get; set; }
public List<SslExtension> Extensions { get; set; }
private static string SslVersionToString(int major, int minor)
{
string str = "Unknown";
if (major == 3 && minor == 3)
str = "TLS/1.2";
else if (major == 3 && minor == 2)
str = "TLS/1.1";
else if (major == 3 && minor == 1)
str = "TLS/1.0";
else if (major == 3 && minor == 0)
str = "SSL/3.0";
else if (major == 2 && minor == 0)
str = "SSL/2.0";
return $"{major}.{minor} ({str})";
}
/// <summary>
/// Returns a <see cref="System.String" /> that represents this instance.
/// </summary>
/// <returns>
/// A <see cref="System.String" /> that represents this instance.
/// </returns>
public override string ToString()
{
var sb = new StringBuilder();
sb.AppendLine("A SSLv3-compatible ClientHello handshake was found. Titanium extracted the parameters below.");
sb.AppendLine();
sb.AppendLine($"Version: {SslVersionToString(MajorVersion, MinorVersion)}");
sb.AppendLine($"Random: {string.Join(" ", Random.Select(x => x.ToString("X2")))}");
sb.AppendLine($"\"Time\": {Time}");
sb.AppendLine($"SessionID: {string.Join(" ", SessionId.Select(x => x.ToString("X2")))}");
if (Extensions != null)
{
sb.AppendLine("Extensions:");
foreach (var extension in Extensions)
{
sb.AppendLine($"{extension.Name}: {extension.Data}");
}
}
if (CompressionData.Length > 0)
{
int id = CompressionData[0];
string compression = null;
compression = compressions.Length > id ? compressions[id] : $"unknown [0x{id:X2}]";
sb.AppendLine($"Compression: {compression}");
}
if (Ciphers.Length > 0)
{
sb.AppendLine($"Ciphers:");
foreach (int cipher in Ciphers)
{
string cipherStr;
if (!SslCiphers.Ciphers.TryGetValue(cipher, out cipherStr))
{
cipherStr = $"unknown";
}
sb.AppendLine($"[0x{cipher:X4}] {cipherStr}");
}
}
return sb.ToString();
}
}
}
\ No newline at end of file
This diff is collapsed.
namespace Titanium.Web.Proxy.Ssl
{
public class SslExtension
{
public int Value { get; set; }
public string Name { get; set; }
public string Data { get; set; }
public SslExtension(int value, string name, string data)
{
Value = value;
Name = name;
Data = data;
}
}
}
\ No newline at end of file
This diff is collapsed.
using System.Collections.Generic;
using System.Threading.Tasks;
using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Http;
namespace Titanium.Web.Proxy.Ssl
{
class HttpsTools
{
public static async Task<bool> IsClientHello(CustomBufferedStream clientStream)
{
var clientHello = await GetClientHelloInfo(clientStream);
return clientHello != null;
}
public static async Task<ClientHelloInfo> GetClientHelloInfo(CustomBufferedStream clientStream)
{
//detects the HTTPS ClientHello message as it is described in the following url:
//https://stackoverflow.com/questions/3897883/how-to-detect-an-incoming-ssl-https-handshake-ssl-wire-format
int recordType = await clientStream.PeekByteAsync(0);
if (recordType == 0x80)
{
var peekStream = new CustomBufferedPeekStream(clientStream, 1);
//SSL 2
int length = peekStream.ReadByte();
if (length < 9)
{
// Message body too short.
return null;
}
if (peekStream.ReadByte() != 0x01)
{
// should be ClientHello
return null;
}
int majorVersion = clientStream.ReadByte();
int minorVersion = clientStream.ReadByte();
return new ClientHelloInfo();
}
else if (recordType == 0x16)
{
var peekStream = new CustomBufferedPeekStream(clientStream, 1);
//should contain at least 43 bytes
// 2 version + 2 length + 1 type + 3 length(?) + 2 version + 32 random + 1 sessionid length
if (!await peekStream.EnsureBufferLength(43))
{
return null;
}
//SSL 3.0 or TLS 1.0, 1.1 and 1.2
int majorVersion = peekStream.ReadByte();
int minorVersion = peekStream.ReadByte();
int length = peekStream.ReadInt16();
if (peekStream.ReadByte() != 0x01)
{
// should be ClientHello
return null;
}
length = peekStream.ReadInt24();
majorVersion = peekStream.ReadByte();
minorVersion = peekStream.ReadByte();
byte[] random = peekStream.ReadBytes(32);
length = peekStream.ReadByte();
// sessionid + 2 ciphersData length
if (!await peekStream.EnsureBufferLength(length + 2))
{
return null;
}
byte[] sessionId = peekStream.ReadBytes(length);
length = peekStream.ReadInt16();
// ciphersData + compressionData length
if (!await peekStream.EnsureBufferLength(length + 1))
{
return null;
}
byte[] ciphersData = peekStream.ReadBytes(length);
int[] ciphers = new int[ciphersData.Length / 2];
for (int i = 0; i < ciphers.Length; i++)
{
ciphers[i] = (ciphersData[2 * i] << 8) + ciphersData[2 * i + 1];
}
length = peekStream.ReadByte();
if (length < 1)
{
return null;
}
// compressionData
if (!await peekStream.EnsureBufferLength(length))
{
return null;
}
byte[] compressionData = peekStream.ReadBytes(length);
List<SslExtension> extensions = null;
if (majorVersion > 3 || majorVersion == 3 && minorVersion >= 1)
{
if (await peekStream.EnsureBufferLength(2))
{
length = peekStream.ReadInt16();
if (await peekStream.EnsureBufferLength(length))
{
extensions = new List<SslExtension>();
while (peekStream.Available > 3)
{
int id = peekStream.ReadInt16();
length = peekStream.ReadInt16();
byte[] data = peekStream.ReadBytes(length);
extensions.Add(SslExtensions.GetExtension(id, data));
}
}
}
}
var clientHelloInfo = new ClientHelloInfo
{
MajorVersion = majorVersion,
MinorVersion = minorVersion,
Random = random,
SessionId = sessionId,
Ciphers = ciphers,
CompressionData = compressionData,
Extensions = extensions,
};
return clientHelloInfo;
}
return null;
}
}
}
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>netstandard1.5</TargetFramework> <TargetFramework>netstandard1.6</TargetFramework>
<RootNamespace>Titanium.Web.Proxy</RootNamespace> <RootNamespace>Titanium.Web.Proxy</RootNamespace>
</PropertyGroup> </PropertyGroup>
...@@ -34,6 +34,7 @@ ...@@ -34,6 +34,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Portable.BouncyCastle" Version="1.8.1.2" /> <PackageReference Include="Portable.BouncyCastle" Version="1.8.1.2" />
<PackageReference Include="StreamExtended" Version="1.0.18" />
<PackageReference Include="System.Net.NameResolution" Version="4.3.0" /> <PackageReference Include="System.Net.NameResolution" Version="4.3.0" />
<PackageReference Include="System.Net.Security" Version="4.3.1" /> <PackageReference Include="System.Net.Security" Version="4.3.1" />
<PackageReference Include="System.Runtime.Serialization.Formatters" Version="4.3.0" /> <PackageReference Include="System.Runtime.Serialization.Formatters" Version="4.3.0" />
......
...@@ -50,6 +50,10 @@ ...@@ -50,6 +50,10 @@
<HintPath>..\packages\BouncyCastle.1.8.1\lib\BouncyCastle.Crypto.dll</HintPath> <HintPath>..\packages\BouncyCastle.1.8.1\lib\BouncyCastle.Crypto.dll</HintPath>
<Private>True</Private> <Private>True</Private>
</Reference> </Reference>
<Reference Include="StreamExtended, Version=1.0.1.0, Culture=neutral, PublicKeyToken=bbfa0f1d54f50043, processorArchitecture=MSIL">
<HintPath>..\packages\StreamExtended.1.0.18\lib\net45\StreamExtended.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="System.Configuration" /> <Reference Include="System.Configuration" />
<Reference Include="System.Core" /> <Reference Include="System.Core" />
...@@ -87,23 +91,17 @@ ...@@ -87,23 +91,17 @@
<Compile Include="Extensions\StreamExtensions.cs" /> <Compile Include="Extensions\StreamExtensions.cs" />
<Compile Include="Extensions\StringExtensions.cs" /> <Compile Include="Extensions\StringExtensions.cs" />
<Compile Include="Extensions\TcpExtensions.cs" /> <Compile Include="Extensions\TcpExtensions.cs" />
<Compile Include="Helpers\BufferPool.cs" />
<Compile Include="Helpers\CustomBinaryReader.cs" />
<Compile Include="Helpers\CustomBufferedPeekStream.cs" />
<Compile Include="Helpers\CustomBufferedStream.cs" />
<Compile Include="Helpers\Firefox.cs" /> <Compile Include="Helpers\Firefox.cs" />
<Compile Include="Helpers\HttpRequestWriter.cs" />
<Compile Include="Helpers\HttpResponseWriter.cs" />
<Compile Include="Helpers\HttpWriter.cs" />
<Compile Include="Helpers\HttpHelper.cs" /> <Compile Include="Helpers\HttpHelper.cs" />
<Compile Include="Helpers\Network.cs" /> <Compile Include="Helpers\Network.cs" />
<Compile Include="Helpers\Tcp.cs" /> <Compile Include="Helpers\Tcp.cs" />
<Compile Include="Ssl\ClientHelloInfo.cs" />
<Compile Include="Http\ConnectRequest.cs" /> <Compile Include="Http\ConnectRequest.cs" />
<Compile Include="Http\ConnectResponse.cs" /> <Compile Include="Http\ConnectResponse.cs" />
<Compile Include="Http\HeaderCollection.cs" /> <Compile Include="Http\HeaderCollection.cs" />
<Compile Include="Http\HeaderParser.cs" /> <Compile Include="Http\HeaderParser.cs" />
<Compile Include="Ssl\SslCiphers.cs" />
<Compile Include="Ssl\SslExtension.cs" />
<Compile Include="Ssl\SslExtensions.cs" />
<Compile Include="Ssl\SslTools.cs" />
<Compile Include="Http\HttpWebClient.cs" /> <Compile Include="Http\HttpWebClient.cs" />
<Compile Include="Http\Request.cs" /> <Compile Include="Http\Request.cs" />
<Compile Include="Http\Response.cs" /> <Compile Include="Http\Response.cs" />
......
...@@ -14,10 +14,19 @@ ...@@ -14,10 +14,19 @@
<copyright>Copyright &#x00A9; Titanium. All rights reserved.</copyright> <copyright>Copyright &#x00A9; Titanium. All rights reserved.</copyright>
<tags></tags> <tags></tags>
<dependencies> <dependencies>
<dependency id="BouncyCastle" version="1.8.1" /> <group>
<dependency id="StreamExtended" version="1.0.18" />
</group>
<group targetFramework="net45">
<dependency id="BouncyCastle" version="1.8.1" />
</group>
<group targetFramework="netstandard1.6">
<dependency id="Portable.BouncyCastle" version="1.8.1.2" />
</group>
</dependencies> </dependencies>
</metadata> </metadata>
<files> <files>
<file src="bin\$configuration$\Titanium.Web.Proxy.dll" target="lib\net45" /> <file src="bin\$configuration$\Titanium.Web.Proxy.dll" target="lib\net45" />
<file src="bin\$configuration$\netstandard1.6\Titanium.Web.Proxy.*" target="lib\netstandard1.6" />
</files> </files>
</package> </package>
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<packages> <packages>
<package id="BouncyCastle" version="1.8.1" targetFramework="net45" /> <package id="BouncyCastle" version="1.8.1" targetFramework="net45" />
<package id="StreamExtended" version="1.0.18" targetFramework="net45" />
</packages> </packages>
\ No newline at end of file
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
# version format # version format
version: 3.0.{build} version: 3.0.{build}
image: Visual Studio 2017
shallow_clone: true shallow_clone: true
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment