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,17 +49,20 @@ Task Package -depends Build { ...@@ -46,17 +49,20 @@ 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
} }
......
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;
}
}
}
using System;
using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Helpers
{
/// <summary>
/// A custom network stream inherited from stream
/// with an underlying buffer
/// </summary>
/// <seealso cref="System.IO.Stream" />
internal class CustomBufferedStream : Stream
{
#if NET45
private AsyncCallback readCallback;
#endif
private readonly Stream baseStream;
private byte[] streamBuffer;
private readonly byte[] oneByteBuffer = new byte[1];
private int bufferLength;
private int bufferPos;
private bool disposed;
/// <summary>
/// Initializes a new instance of the <see cref="CustomBufferedStream"/> class.
/// </summary>
/// <param name="baseStream">The base stream.</param>
/// <param name="bufferSize">Size of the buffer.</param>
public CustomBufferedStream(Stream baseStream, int bufferSize)
{
#if NET45
readCallback = ReadCallback;
#endif
this.baseStream = baseStream;
streamBuffer = BufferPool.GetBuffer(bufferSize);
}
/// <summary>
/// When overridden in a derived class, clears all buffers for this stream and causes any buffered data to be written to the underlying device.
/// </summary>
public override void Flush()
{
baseStream.Flush();
}
/// <summary>
/// When overridden in a derived class, sets the position within the current stream.
/// </summary>
/// <param name="offset">A byte offset relative to the <paramref name="origin" /> parameter.</param>
/// <param name="origin">A value of type <see cref="T:System.IO.SeekOrigin" /> indicating the reference point used to obtain the new position.</param>
/// <returns>
/// The new position within the current stream.
/// </returns>
public override long Seek(long offset, SeekOrigin origin)
{
bufferLength = 0;
bufferPos = 0;
return baseStream.Seek(offset, origin);
}
/// <summary>
/// When overridden in a derived class, sets the length of the current stream.
/// </summary>
/// <param name="value">The desired length of the current stream in bytes.</param>
public override void SetLength(long value)
{
baseStream.SetLength(value);
}
/// <summary>
/// When overridden in a derived class, reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read.
/// </summary>
/// <param name="buffer">An array of bytes. When this method returns, the buffer contains the specified byte array with the values between <paramref name="offset" /> and (<paramref name="offset" /> + <paramref name="count" /> - 1) replaced by the bytes read from the current source.</param>
/// <param name="offset">The zero-based byte offset in <paramref name="buffer" /> at which to begin storing the data read from the current stream.</param>
/// <param name="count">The maximum number of bytes to be read from the current stream.</param>
/// <returns>
/// The total number of bytes read into the buffer. This can be less than the number of bytes requested if that many bytes are not currently available, or zero (0) if the end of the stream has been reached.
/// </returns>
public override int Read(byte[] buffer, int offset, int count)
{
if (bufferLength == 0)
{
FillBuffer();
}
int available = Math.Min(bufferLength, count);
if (available > 0)
{
Buffer.BlockCopy(streamBuffer, bufferPos, buffer, offset, available);
bufferPos += available;
bufferLength -= available;
}
return available;
}
/// <summary>
/// When overridden in a derived class, writes a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written.
/// </summary>
/// <param name="buffer">An array of bytes. This method copies <paramref name="count" /> bytes from <paramref name="buffer" /> to the current stream.</param>
/// <param name="offset">The zero-based byte offset in <paramref name="buffer" /> at which to begin copying bytes to the current stream.</param>
/// <param name="count">The number of bytes to be written to the current stream.</param>
public override void Write(byte[] buffer, int offset, int count)
{
OnDataSent(buffer, offset, count);
baseStream.Write(buffer, offset, count);
}
#if NET45
/// <summary>
/// Begins an asynchronous read operation. (Consider using <see cref="M:System.IO.Stream.ReadAsync(System.Byte[],System.Int32,System.Int32)" /> instead; see the Remarks section.)
/// </summary>
/// <param name="buffer">The buffer to read the data into.</param>
/// <param name="offset">The byte offset in <paramref name="buffer" /> at which to begin writing data read from the stream.</param>
/// <param name="count">The maximum number of bytes to read.</param>
/// <param name="callback">An optional asynchronous callback, to be called when the read is complete.</param>
/// <param name="state">A user-provided object that distinguishes this particular asynchronous read request from other requests.</param>
/// <returns>
/// An <see cref="T:System.IAsyncResult" /> that represents the asynchronous read, which could still be pending.
/// </returns>
[DebuggerStepThrough]
public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
{
if (bufferLength > 0)
{
int available = Math.Min(bufferLength, count);
Buffer.BlockCopy(streamBuffer, bufferPos, buffer, offset, available);
bufferPos += available;
bufferLength -= available;
return new ReadAsyncResult(buffer, offset, available, state, callback);
}
var result = new ReadAsyncResult(buffer, offset, 0, state, callback);
result.BaseResult = baseStream.BeginRead(buffer, offset, count, readCallback, result);
return result;
}
private void ReadCallback(IAsyncResult ar)
{
var readResult = (ReadAsyncResult)ar.AsyncState;
readResult.BaseResult = ar;
readResult.Callback(readResult);
}
/// <summary>
/// Begins an asynchronous write operation. (Consider using <see cref="M:System.IO.Stream.WriteAsync(System.Byte[],System.Int32,System.Int32)" /> instead; see the Remarks section.)
/// </summary>
/// <param name="buffer">The buffer to write data from.</param>
/// <param name="offset">The byte offset in <paramref name="buffer" /> from which to begin writing.</param>
/// <param name="count">The maximum number of bytes to write.</param>
/// <param name="callback">An optional asynchronous callback, to be called when the write is complete.</param>
/// <param name="state">A user-provided object that distinguishes this particular asynchronous write request from other requests.</param>
/// <returns>
/// An IAsyncResult that represents the asynchronous write, which could still be pending.
/// </returns>
[DebuggerStepThrough]
public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
{
OnDataSent(buffer, offset, count);
return baseStream.BeginWrite(buffer, offset, count, callback, state);
}
#endif
/// <summary>
/// Asynchronously reads the bytes from the current stream and writes them to another stream, using a specified buffer size and cancellation token.
/// </summary>
/// <param name="destination">The stream to which the contents of the current stream will be copied.</param>
/// <param name="bufferSize">The size, in bytes, of the buffer. This value must be greater than zero. The default size is 81920.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is <see cref="P:System.Threading.CancellationToken.None" />.</param>
/// <returns>
/// A task that represents the asynchronous copy operation.
/// </returns>
public override async Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken)
{
if (bufferLength > 0)
{
await destination.WriteAsync(streamBuffer, bufferPos, bufferLength, cancellationToken);
bufferLength = 0;
}
await base.CopyToAsync(destination, bufferSize, cancellationToken);
}
#if NET45
/// <summary>
/// Waits for the pending asynchronous read to complete. (Consider using <see cref="M:System.IO.Stream.ReadAsync(System.Byte[],System.Int32,System.Int32)" /> instead; see the Remarks section.)
/// </summary>
/// <param name="asyncResult">The reference to the pending asynchronous request to finish.</param>
/// <returns>
/// The number of bytes read from the stream, between zero (0) and the number of bytes you requested. Streams return zero (0) only at the end of the stream, otherwise, they should block until at least one byte is available.
/// </returns>
[DebuggerStepThrough]
public override int EndRead(IAsyncResult asyncResult)
{
var readResult = (ReadAsyncResult)asyncResult;
int result = readResult.BaseResult == null ? readResult.ReadBytes : baseStream.EndRead(readResult.BaseResult);
OnDataReceived(readResult.Buffer, readResult.Offset, result);
return result;
}
/// <summary>
/// Ends an asynchronous write operation. (Consider using <see cref="M:System.IO.Stream.WriteAsync(System.Byte[],System.Int32,System.Int32)" /> instead; see the Remarks section.)
/// </summary>
/// <param name="asyncResult">A reference to the outstanding asynchronous I/O request.</param>
[DebuggerStepThrough]
public override void EndWrite(IAsyncResult asyncResult)
{
baseStream.EndWrite(asyncResult);
}
#endif
/// <summary>
/// Asynchronously clears all buffers for this stream, causes any buffered data to be written to the underlying device, and monitors cancellation requests.
/// </summary>
/// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is <see cref="P:System.Threading.CancellationToken.None" />.</param>
/// <returns>
/// A task that represents the asynchronous flush operation.
/// </returns>
public override Task FlushAsync(CancellationToken cancellationToken)
{
return baseStream.FlushAsync(cancellationToken);
}
/// <summary>
/// Asynchronously reads a sequence of bytes from the current stream,
/// advances the position within the stream by the number of bytes read,
/// and monitors cancellation requests.
/// </summary>
/// <param name="buffer">The buffer to write the data into.</param>
/// <param name="offset">The byte offset in <paramref name="buffer" /> at which
/// to begin writing data from the stream.</param>
/// <param name="count">The maximum number of bytes to read.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.
/// The default value is <see cref="P:System.Threading.CancellationToken.None" />.</param>
/// <returns>
/// A task that represents the asynchronous read operation.
/// The value of the parameter contains the total
/// number of bytes read into the buffer.
/// The result value can be less than the number of bytes
/// requested if the number of bytes currently available is
/// less than the requested number, or it can be 0 (zero)
/// if the end of the stream has been reached.
/// </returns>
public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
if (bufferLength == 0)
{
await FillBufferAsync(cancellationToken);
}
int available = Math.Min(bufferLength, count);
if (available > 0)
{
Buffer.BlockCopy(streamBuffer, bufferPos, buffer, offset, available);
bufferPos += available;
bufferLength -= available;
}
return available;
}
/// <summary>
/// Reads a byte from the stream and advances the position within the stream by one byte, or returns -1 if at the end of the stream.
/// </summary>
/// <returns>
/// The unsigned byte cast to an Int32, or -1 if at the end of the stream.
/// </returns>
public override int ReadByte()
{
if (bufferLength == 0)
{
FillBuffer();
}
if (bufferLength == 0)
{
return -1;
}
bufferLength--;
return streamBuffer[bufferPos++];
}
public async Task<int> PeekByteAsync(int index)
{
if (Available <= index)
{
await FillBufferAsync();
}
if (Available <= index)
{
return -1;
}
return streamBuffer[bufferPos + index];
}
public byte PeekByteFromBuffer(int index)
{
if (bufferLength <= index)
{
throw new Exception("Index is out of buffer size");
}
return streamBuffer[bufferPos + index];
}
public byte ReadByteFromBuffer()
{
if (bufferLength == 0)
{
throw new Exception("Buffer is empty");
}
bufferLength--;
return streamBuffer[bufferPos++];
}
/// <summary>
/// Asynchronously writes a sequence of bytes to the current stream, advances the current position within this stream by the number of bytes written, and monitors cancellation requests.
/// </summary>
/// <param name="buffer">The buffer to write data from.</param>
/// <param name="offset">The zero-based byte offset in <paramref name="buffer" /> from which to begin copying bytes to the stream.</param>
/// <param name="count">The maximum number of bytes to write.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is <see cref="P:System.Threading.CancellationToken.None" />.</param>
/// <returns>
/// A task that represents the asynchronous write operation.
/// </returns>
[DebuggerStepThrough]
public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
OnDataSent(buffer, offset, count);
return baseStream.WriteAsync(buffer, offset, count, cancellationToken);
}
/// <summary>
/// Writes a byte to the current position in the stream and advances the position within the stream by one byte.
/// </summary>
/// <param name="value">The byte to write to the stream.</param>
public override void WriteByte(byte value)
{
oneByteBuffer[0] = value;
OnDataSent(oneByteBuffer, 0, 1);
baseStream.Write(oneByteBuffer, 0, 1);
}
private void OnDataSent(byte[] buffer, int offset, int count)
{
}
private void OnDataReceived(byte[] buffer, int offset, int count)
{
}
/// <summary>
/// Releases the unmanaged resources used by the <see cref="T:System.IO.Stream" /> and optionally releases the managed resources.
/// </summary>
/// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param>
protected override void Dispose(bool disposing)
{
if (!disposed)
{
disposed = true;
baseStream.Dispose();
BufferPool.ReturnBuffer(streamBuffer);
streamBuffer = null;
#if NET45
readCallback = null;
#endif
}
}
/// <summary>
/// When overridden in a derived class, gets a value indicating whether the current stream supports reading.
/// </summary>
public override bool CanRead => baseStream.CanRead;
/// <summary>
/// When overridden in a derived class, gets a value indicating whether the current stream supports seeking.
/// </summary>
public override bool CanSeek => baseStream.CanSeek;
/// <summary>
/// When overridden in a derived class, gets a value indicating whether the current stream supports writing.
/// </summary>
public override bool CanWrite => baseStream.CanWrite;
/// <summary>
/// Gets a value that determines whether the current stream can time out.
/// </summary>
public override bool CanTimeout => baseStream.CanTimeout;
/// <summary>
/// When overridden in a derived class, gets the length in bytes of the stream.
/// </summary>
public override long Length => baseStream.Length;
public bool DataAvailable => bufferLength > 0;
public int Available => bufferLength;
/// <summary>
/// When overridden in a derived class, gets or sets the position within the current stream.
/// </summary>
public override long Position
{
get { return baseStream.Position; }
set { baseStream.Position = value; }
}
/// <summary>
/// Gets or sets a value, in miliseconds, that determines how long the stream will attempt to read before timing out.
/// </summary>
public override int ReadTimeout
{
get { return baseStream.ReadTimeout; }
set { baseStream.ReadTimeout = value; }
}
/// <summary>
/// Gets or sets a value, in miliseconds, that determines how long the stream will attempt to write before timing out.
/// </summary>
public override int WriteTimeout
{
get { return baseStream.WriteTimeout; }
set { baseStream.WriteTimeout = value; }
}
/// <summary>
/// Fills the buffer.
/// </summary>
public bool FillBuffer()
{
if (bufferLength > 0)
{
//normally we fill the buffer only when it is empty, but sometimes we need more data
//move the remanining data to the beginning of the buffer
Buffer.BlockCopy(streamBuffer, bufferPos, streamBuffer, 0, bufferLength);
}
bufferPos = 0;
int readBytes = baseStream.Read(streamBuffer, bufferLength, streamBuffer.Length - bufferLength);
if (readBytes > 0)
{
OnDataReceived(streamBuffer, bufferLength, readBytes);
bufferLength += readBytes;
}
return readBytes > 0;
}
/// <summary>
/// Fills the buffer asynchronous.
/// </summary>
/// <returns></returns>
public Task<bool> FillBufferAsync()
{
return FillBufferAsync(CancellationToken.None);
}
/// <summary>
/// Fills the buffer asynchronous.
/// </summary>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns></returns>
public async Task<bool> FillBufferAsync(CancellationToken cancellationToken)
{
if (bufferLength > 0)
{
//normally we fill the buffer only when it is empty, but sometimes we need more data
//move the remanining data to the beginning of the buffer
Buffer.BlockCopy(streamBuffer, bufferPos, streamBuffer, 0, bufferLength);
}
bufferPos = 0;
int readBytes = await baseStream.ReadAsync(streamBuffer, bufferLength, streamBuffer.Length - bufferLength, cancellationToken);
if (readBytes > 0)
{
OnDataReceived(streamBuffer, bufferLength, readBytes);
bufferLength += readBytes;
}
return readBytes > 0;
}
private class ReadAsyncResult : IAsyncResult
{
public byte[] Buffer { get; }
public int Offset { get; }
public IAsyncResult BaseResult { get; set; }
public int ReadBytes { get; }
public object AsyncState { get; }
public AsyncCallback Callback { get; }
public bool IsCompleted => CompletedSynchronously || BaseResult.IsCompleted;
public WaitHandle AsyncWaitHandle => BaseResult?.AsyncWaitHandle;
public bool CompletedSynchronously => BaseResult == null || BaseResult.CompletedSynchronously;
public ReadAsyncResult(byte[] buffer, int offset, int readBytes, object state, AsyncCallback callback)
{
Buffer = buffer;
Offset = offset;
ReadBytes = readBytes;
AsyncState = state;
Callback = callback;
}
}
}
}
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 //prepare the request & headers
requestLines.AppendLine($"{Request.Method} {Request.RequestUri.PathAndQuery} HTTP/{Request.HttpVersion.Major}.{Request.HttpVersion.Minor}"); writer.WriteLine($"{Request.Method} {Request.RequestUri.PathAndQuery} HTTP/{Request.HttpVersion.Major}.{Request.HttpVersion.Minor}");
var upstreamProxy = ServerConnection.UpStreamHttpProxy;
//Send Authentication to Upstream proxy if needed //Send Authentication to Upstream proxy if needed
if (ServerConnection.UpStreamHttpProxy != null if (upstreamProxy != null
&& ServerConnection.IsHttps == false && ServerConnection.IsHttps == false
&& !string.IsNullOrEmpty(ServerConnection.UpStreamHttpProxy.UserName) && !string.IsNullOrEmpty(upstreamProxy.UserName)
&& ServerConnection.UpStreamHttpProxy.Password != null) && upstreamProxy.Password != null)
{ {
requestLines.AppendLine("Proxy-Connection: keep-alive"); HttpHeader.ProxyConnectionKeepAlive.WriteToStream(writer);
requestLines.AppendLine("Proxy-Authorization" + ": Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes( HttpHeader.GetProxyAuthorizationHeader(upstreamProxy.UserName, upstreamProxy.Password).WriteToStream(writer);
$"{ServerConnection.UpStreamHttpProxy.UserName}:{ServerConnection.UpStreamHttpProxy.Password}")));
} }
//write request headers //write request headers
foreach (var header in Request.RequestHeaders) foreach (var header in Request.RequestHeaders)
{ {
if (header.Name != "Proxy-Authorization") if (header.Name != "Proxy-Authorization")
{ {
requestLines.AppendLine($"{header.Name}: {header.Value}"); 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,23 +52,28 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -52,23 +52,28 @@ 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);
}
else
{
await client.ConnectAsync(remoteHostName, remotePort);
}
stream = new CustomBufferedStream(client.GetStream(), server.BufferSize); stream = new CustomBufferedStream(client.GetStream(), server.BufferSize);
if (isHttps) if (useUpstreamProxy && (isConnect || isHttps))
{ {
using (var writer = new StreamWriter(stream, Encoding.ASCII, server.BufferSize, true) using (var writer = new HttpRequestWriter(stream))
{
NewLine = ProxyConstants.NewLine
})
{ {
await writer.WriteLineAsync($"CONNECT {remoteHostName}:{remotePort} HTTP/{httpVersion}"); await writer.WriteLineAsync($"CONNECT {remoteHostName}:{remotePort} HTTP/{httpVersion}");
await writer.WriteLineAsync($"Host: {remoteHostName}:{remotePort}"); await writer.WriteLineAsync($"Host: {remoteHostName}:{remotePort}");
...@@ -76,11 +81,12 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -76,11 +81,12 @@ namespace Titanium.Web.Proxy.Network.Tcp
if (!string.IsNullOrEmpty(externalProxy.UserName) && externalProxy.Password != null) if (!string.IsNullOrEmpty(externalProxy.UserName) && externalProxy.Password != null)
{ {
await writer.WriteLineAsync("Proxy-Connection: keep-alive"); await HttpHeader.ProxyConnectionKeepAlive.WriteToStreamAsync(writer);
await writer.WriteLineAsync("Proxy-Authorization" + ": Basic " + await writer.WriteLineAsync("Proxy-Authorization" + ": Basic " +
Convert.ToBase64String(Encoding.UTF8.GetBytes( Convert.ToBase64String(Encoding.UTF8.GetBytes(
externalProxy.UserName + ":" + externalProxy.Password))); externalProxy.UserName + ":" + externalProxy.Password)));
} }
await writer.WriteLineAsync(); await writer.WriteLineAsync();
await writer.FlushAsync(); await writer.FlushAsync();
} }
...@@ -97,20 +103,10 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -97,20 +103,10 @@ namespace Titanium.Web.Proxy.Network.Tcp
await reader.ReadAndIgnoreAllLinesAsync(); 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>
......
using System; using StreamExtended;
using System.Collections.Generic; using StreamExtended.Network;
using System;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net; 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.Threading;
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;
...@@ -16,8 +15,6 @@ using Titanium.Web.Proxy.Helpers; ...@@ -16,8 +15,6 @@ 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;
using Titanium.Web.Proxy.Network.Tcp; using Titanium.Web.Proxy.Network.Tcp;
using Titanium.Web.Proxy.Shared;
using Titanium.Web.Proxy.Ssl;
namespace Titanium.Web.Proxy namespace Titanium.Web.Proxy
{ {
...@@ -40,10 +37,7 @@ namespace Titanium.Web.Proxy ...@@ -40,10 +37,7 @@ namespace Titanium.Web.Proxy
var clientStream = new CustomBufferedStream(tcpClient.GetStream(), BufferSize); var clientStream = new CustomBufferedStream(tcpClient.GetStream(), BufferSize);
var clientStreamReader = new CustomBinaryReader(clientStream, BufferSize); var clientStreamReader = new CustomBinaryReader(clientStream, BufferSize);
var clientStreamWriter = new StreamWriter(clientStream) var clientStreamWriter = new HttpResponseWriter(clientStream);
{
NewLine = ProxyConstants.NewLine
};
Uri httpRemoteUri; Uri httpRemoteUri;
...@@ -96,8 +90,6 @@ namespace Titanium.Web.Proxy ...@@ -96,8 +90,6 @@ namespace Titanium.Web.Proxy
connectArgs.WebSession.Request = connectRequest; connectArgs.WebSession.Request = connectRequest;
connectArgs.ProxyClient.TcpClient = tcpClient; connectArgs.ProxyClient.TcpClient = tcpClient;
connectArgs.ProxyClient.ClientStream = clientStream; connectArgs.ProxyClient.ClientStream = clientStream;
connectArgs.ProxyClient.ClientStreamReader = clientStreamReader;
connectArgs.ProxyClient.ClientStreamWriter = clientStreamWriter;
if (TunnelConnectRequest != null) if (TunnelConnectRequest != null)
{ {
...@@ -115,10 +107,10 @@ namespace Titanium.Web.Proxy ...@@ -115,10 +107,10 @@ namespace Titanium.Web.Proxy
} }
//write back successfull CONNECT response //write back successfull CONNECT response
connectArgs.WebSession.Response = CreateConnectResponse(version); connectArgs.WebSession.Response = ConnectResponse.CreateSuccessfullConnectResponse(version);
await WriteResponse(connectArgs.WebSession.Response, clientStreamWriter); await clientStreamWriter.WriteResponseAsync(connectArgs.WebSession.Response);
var clientHelloInfo = await HttpsTools.GetClientHelloInfo(clientStream); var clientHelloInfo = await SslTools.PeekClientHello(clientStream);
bool isClientHello = clientHelloInfo != null; bool isClientHello = clientHelloInfo != null;
if (isClientHello) if (isClientHello)
{ {
...@@ -134,11 +126,13 @@ namespace Titanium.Web.Proxy ...@@ -134,11 +126,13 @@ namespace Titanium.Web.Proxy
if (!excluded && isClientHello) if (!excluded && isClientHello)
{ {
httpRemoteUri = new Uri("https://" + httpUrl); httpRemoteUri = new Uri("https://" + httpUrl);
connectRequest.RequestUri = httpRemoteUri;
SslStream sslStream = null; SslStream sslStream = null;
try try
{ {
sslStream = new SslStream(clientStream); sslStream = new SslStream(clientStream);
string certName = HttpHelper.GetWildCardDomainName(httpRemoteUri.Host); string certName = HttpHelper.GetWildCardDomainName(httpRemoteUri.Host);
...@@ -153,10 +147,7 @@ namespace Titanium.Web.Proxy ...@@ -153,10 +147,7 @@ namespace Titanium.Web.Proxy
clientStreamReader.Dispose(); clientStreamReader.Dispose();
clientStreamReader = new CustomBinaryReader(clientStream, BufferSize); clientStreamReader = new CustomBinaryReader(clientStream, BufferSize);
clientStreamWriter = new StreamWriter(clientStream) clientStreamWriter = new HttpResponseWriter(clientStream);
{
NewLine = ProxyConstants.NewLine
};
} }
catch catch
{ {
...@@ -167,13 +158,28 @@ namespace Titanium.Web.Proxy ...@@ -167,13 +158,28 @@ namespace Titanium.Web.Proxy
//Now read the actual HTTPS request line //Now read the actual HTTPS request line
httpCmd = await clientStreamReader.ReadLineAsync(); httpCmd = await clientStreamReader.ReadLineAsync();
} }
//Sorry cannot do a HTTPS request decrypt to port 80 at this time //Hostname is excluded or it is not an HTTPS connect
else else
{ {
//create new connection //create new connection
using (var connection = await GetServerConnection(connectArgs)) using (var connection = await GetServerConnection(connectArgs, true))
{ {
await TcpHelper.SendRaw(clientStream, connection, if (isClientHello)
{
if (clientStream.Available > 0)
{
//send the buffered data
var data = new byte[clientStream.Available];
await clientStream.ReadAsync(data, 0, data.Length);
await connection.Stream.WriteAsync(data, 0, data.Length);
await connection.Stream.FlushAsync();
}
var serverHelloInfo = await SslTools.PeekServerHello(connection.Stream);
((ConnectResponse)connectArgs.WebSession.Response).ServerHelloInfo = serverHelloInfo;
}
await TcpHelper.SendRaw(clientStream, connection.Stream,
(buffer, offset, count) => { connectArgs.OnDataSent(buffer, offset, count); }, (buffer, offset, count) => { connectArgs.OnDataReceived(buffer, offset, count); }); (buffer, offset, count) => { connectArgs.OnDataSent(buffer, offset, count); }, (buffer, offset, count) => { connectArgs.OnDataReceived(buffer, offset, count); });
UpdateServerConnectionCount(false); UpdateServerConnectionCount(false);
} }
...@@ -212,22 +218,22 @@ namespace Titanium.Web.Proxy ...@@ -212,22 +218,22 @@ namespace Titanium.Web.Proxy
var clientStream = new CustomBufferedStream(tcpClient.GetStream(), BufferSize); var clientStream = new CustomBufferedStream(tcpClient.GetStream(), BufferSize);
CustomBinaryReader clientStreamReader = null; CustomBinaryReader clientStreamReader = null;
StreamWriter clientStreamWriter = null; HttpResponseWriter clientStreamWriter = null;
try try
{ {
if (endPoint.EnableSsl) if (endPoint.EnableSsl)
{ {
var clientSslHelloInfo = await HttpsTools.GetClientHelloInfo(clientStream); var clientSslHelloInfo = await SslTools.PeekClientHello(clientStream);
if (clientSslHelloInfo != null) if (clientSslHelloInfo != null)
{ {
var sslStream = new SslStream(clientStream); var sslStream = new SslStream(clientStream);
clientStream = new CustomBufferedStream(sslStream, BufferSize); clientStream = new CustomBufferedStream(sslStream, BufferSize);
string sniHostName = clientSslHelloInfo.Extensions.FirstOrDefault(x => x.Name == "server_name")?.Data; string sniHostName = clientSslHelloInfo.Extensions?.FirstOrDefault(x => x.Name == "server_name")?.Data;
string certName = HttpHelper.GetWildCardDomainName(sniHostName ?? endPoint.GenericCertificateName);
var certificate = CertificateManager.CreateCertificate(sniHostName ?? endPoint.GenericCertificateName, false); var certificate = CertificateManager.CreateCertificate(certName, false);
//Successfully managed to authenticate the client using the fake certificate //Successfully managed to authenticate the client using the fake certificate
await sslStream.AuthenticateAsServerAsync(certificate, false, SslProtocols.Tls, false); await sslStream.AuthenticateAsServerAsync(certificate, false, SslProtocols.Tls, false);
...@@ -237,10 +243,7 @@ namespace Titanium.Web.Proxy ...@@ -237,10 +243,7 @@ namespace Titanium.Web.Proxy
} }
clientStreamReader = new CustomBinaryReader(clientStream, BufferSize); clientStreamReader = new CustomBinaryReader(clientStream, BufferSize);
clientStreamWriter = new StreamWriter(clientStream) clientStreamWriter = new HttpResponseWriter(clientStream);
{
NewLine = ProxyConstants.NewLine
};
//now read the request line //now read the request line
string httpCmd = await clientStreamReader.ReadLineAsync(); string httpCmd = await clientStreamReader.ReadLineAsync();
...@@ -273,8 +276,8 @@ namespace Titanium.Web.Proxy ...@@ -273,8 +276,8 @@ namespace Titanium.Web.Proxy
/// <param name="connectRequest"></param> /// <param name="connectRequest"></param>
/// <param name="isTransparentEndPoint"></param> /// <param name="isTransparentEndPoint"></param>
/// <returns></returns> /// <returns></returns>
private async Task<bool> HandleHttpSessionRequest(TcpClient client, string httpCmd, Stream clientStream, private async Task<bool> HandleHttpSessionRequest(TcpClient client, string httpCmd, CustomBufferedStream clientStream,
CustomBinaryReader clientStreamReader, StreamWriter clientStreamWriter, string httpsConnectHostname, CustomBinaryReader clientStreamReader, HttpResponseWriter clientStreamWriter, string httpsConnectHostname,
ProxyEndPoint endPoint, ConnectRequest connectRequest, bool isTransparentEndPoint = false) ProxyEndPoint endPoint, ConnectRequest connectRequest, bool isTransparentEndPoint = false)
{ {
bool disposed = false; bool disposed = false;
...@@ -351,16 +354,17 @@ namespace Titanium.Web.Proxy ...@@ -351,16 +354,17 @@ namespace Titanium.Web.Proxy
break; break;
} }
if (connection == null)
{
connection = await GetServerConnection(args);
}
//create a new connection if hostname changes //create a new connection if hostname changes
else if (!connection.HostName.Equals(args.WebSession.Request.RequestUri.Host, StringComparison.OrdinalIgnoreCase)) if (connection != null && !connection.HostName.Equals(args.WebSession.Request.RequestUri.Host, StringComparison.OrdinalIgnoreCase))
{ {
connection.Dispose(); connection.Dispose();
UpdateServerConnectionCount(false); UpdateServerConnectionCount(false);
connection = await GetServerConnection(args); connection = null;
}
if (connection == null)
{
connection = await GetServerConnection(args, false);
} }
//if upgrading to websocket then relay the requet without reading the contents //if upgrading to websocket then relay the requet without reading the contents
...@@ -368,26 +372,16 @@ namespace Titanium.Web.Proxy ...@@ -368,26 +372,16 @@ namespace Titanium.Web.Proxy
{ {
//prepare the prefix content //prepare the prefix content
var requestHeaders = args.WebSession.Request.RequestHeaders; var requestHeaders = args.WebSession.Request.RequestHeaders;
byte[] requestBytes;
using (var ms = new MemoryStream()) using (var ms = new MemoryStream())
using (var writer = new StreamWriter(ms, Encoding.ASCII) { NewLine = ProxyConstants.NewLine }) using (var writer = new HttpRequestWriter(ms))
{ {
writer.WriteLine(httpCmd); writer.WriteLine(httpCmd);
writer.WriteHeaders(requestHeaders);
if (requestHeaders != null) requestBytes = ms.ToArray();
{
foreach (string header in requestHeaders.Select(t => t.ToString()))
{
writer.WriteLine(header);
}
}
writer.WriteLine();
writer.Flush();
var data = ms.ToArray();
await connection.Stream.WriteAsync(data, 0, data.Length);
} }
await connection.Stream.WriteAsync(requestBytes, 0, requestBytes.Length);
string httpStatus = await connection.StreamReader.ReadLineAsync(); string httpStatus = await connection.StreamReader.ReadLineAsync();
Version responseVersion; Version responseVersion;
...@@ -400,7 +394,7 @@ namespace Titanium.Web.Proxy ...@@ -400,7 +394,7 @@ namespace Titanium.Web.Proxy
await HeaderParser.ReadHeaders(connection.StreamReader, args.WebSession.Response.ResponseHeaders); await HeaderParser.ReadHeaders(connection.StreamReader, args.WebSession.Response.ResponseHeaders);
await WriteResponse(args.WebSession.Response, clientStreamWriter); await clientStreamWriter.WriteResponseAsync(args.WebSession.Response);
//If user requested call back then do it //If user requested call back then do it
if (BeforeResponse != null && !args.WebSession.Response.ResponseLocked) if (BeforeResponse != null && !args.WebSession.Response.ResponseLocked)
...@@ -408,7 +402,7 @@ namespace Titanium.Web.Proxy ...@@ -408,7 +402,7 @@ namespace Titanium.Web.Proxy
await BeforeResponse.InvokeParallelAsync(this, args, ExceptionFunc); await BeforeResponse.InvokeParallelAsync(this, args, ExceptionFunc);
} }
await TcpHelper.SendRaw(clientStream, connection, await TcpHelper.SendRaw(clientStream, connection.Stream,
(buffer, offset, count) => { args.OnDataSent(buffer, offset, count); }, (buffer, offset, count) => { args.OnDataReceived(buffer, offset, count); }); (buffer, offset, count) => { args.OnDataSent(buffer, offset, count); }, (buffer, offset, count) => { args.OnDataReceived(buffer, offset, count); });
args.Dispose(); args.Dispose();
...@@ -481,12 +475,12 @@ namespace Titanium.Web.Proxy ...@@ -481,12 +475,12 @@ namespace Titanium.Web.Proxy
{ {
if (args.WebSession.Request.Is100Continue) if (args.WebSession.Request.Is100Continue)
{ {
await WriteResponseStatus(args.WebSession.Response.HttpVersion, (int)HttpStatusCode.Continue, "Continue", args.ProxyClient.ClientStreamWriter); await args.ProxyClient.ClientStreamWriter.WriteResponseStatusAsync(args.WebSession.Response.HttpVersion, (int)HttpStatusCode.Continue, "Continue");
await args.ProxyClient.ClientStreamWriter.WriteLineAsync(); await args.ProxyClient.ClientStreamWriter.WriteLineAsync();
} }
else if (args.WebSession.Request.ExpectationFailed) else if (args.WebSession.Request.ExpectationFailed)
{ {
await WriteResponseStatus(args.WebSession.Response.HttpVersion, (int)HttpStatusCode.ExpectationFailed, "Expectation Failed", args.ProxyClient.ClientStreamWriter); await args.ProxyClient.ClientStreamWriter.WriteResponseStatusAsync(args.WebSession.Response.HttpVersion, (int)HttpStatusCode.ExpectationFailed, "Expectation Failed");
await args.ProxyClient.ClientStreamWriter.WriteLineAsync(); await args.ProxyClient.ClientStreamWriter.WriteLineAsync();
} }
} }
...@@ -575,8 +569,9 @@ namespace Titanium.Web.Proxy ...@@ -575,8 +569,9 @@ namespace Titanium.Web.Proxy
/// Create a Server Connection /// Create a Server Connection
/// </summary> /// </summary>
/// <param name="args"></param> /// <param name="args"></param>
/// <param name="isConnect"></param>
/// <returns></returns> /// <returns></returns>
private async Task<TcpConnection> GetServerConnection(SessionEventArgs args) private async Task<TcpConnection> GetServerConnection(SessionEventArgs args, bool isConnect)
{ {
ExternalProxy customUpStreamHttpProxy = null; ExternalProxy customUpStreamHttpProxy = null;
ExternalProxy customUpStreamHttpsProxy = null; ExternalProxy customUpStreamHttpsProxy = null;
...@@ -603,30 +598,11 @@ namespace Titanium.Web.Proxy ...@@ -603,30 +598,11 @@ namespace Titanium.Web.Proxy
args.WebSession.Request.RequestUri.Host, args.WebSession.Request.RequestUri.Host,
args.WebSession.Request.RequestUri.Port, args.WebSession.Request.RequestUri.Port,
args.WebSession.Request.HttpVersion, args.WebSession.Request.HttpVersion,
args.IsHttps, args.IsHttps, isConnect,
customUpStreamHttpProxy ?? UpStreamHttpProxy, customUpStreamHttpProxy ?? UpStreamHttpProxy,
customUpStreamHttpsProxy ?? UpStreamHttpsProxy); customUpStreamHttpsProxy ?? UpStreamHttpsProxy);
} }
/// <summary>
/// Write successfull CONNECT response to client
/// </summary>
/// <param name="httpVersion"></param>
/// <returns></returns>
private ConnectResponse CreateConnectResponse(Version httpVersion)
{
var response = new ConnectResponse
{
HttpVersion = httpVersion,
ResponseStatusCode = (int)HttpStatusCode.OK,
ResponseStatusDescription = "Connection established"
};
response.ResponseHeaders.AddHeader("Timestamp", DateTime.Now.ToString());
return response;
}
/// <summary> /// <summary>
/// prepare the request headers so that we can avoid encodings not parsable by this proxy /// prepare the request headers so that we can avoid encodings not parsable by this proxy
/// </summary> /// </summary>
...@@ -644,7 +620,7 @@ namespace Titanium.Web.Proxy ...@@ -644,7 +620,7 @@ namespace Titanium.Web.Proxy
} }
} }
FixProxyHeaders(requestHeaders); requestHeaders.FixProxyHeaders();
} }
/// <summary> /// <summary>
......
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
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Ssl
{
static class SslCiphers
{
public static readonly Dictionary<int, string> Ciphers = new Dictionary<int, string>
{
{ 0x0000, "TLS_NULL_WITH_NULL_NULL" },
{ 0x0001, "TLS_RSA_WITH_NULL_MD5" },
{ 0x0002, "TLS_RSA_WITH_NULL_SHA" },
{ 0x0003, "TLS_RSA_EXPORT_WITH_RC4_40_MD5" },
{ 0x0004, "TLS_RSA_WITH_RC4_128_MD5" },
{ 0x0005, "TLS_RSA_WITH_RC4_128_SHA" },
{ 0x0006, "TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5" },
{ 0x0007, "TLS_RSA_WITH_IDEA_CBC_SHA" },
{ 0x0008, "TLS_RSA_EXPORT_WITH_DES40_CBC_SHA" },
{ 0x0009, "TLS_RSA_WITH_DES_CBC_SHA" },
{ 0x000A, "TLS_RSA_WITH_3DES_EDE_CBC_SHA" },
{ 0x000B, "TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA" },
{ 0x000C, "TLS_DH_DSS_WITH_DES_CBC_SHA" },
{ 0x000D, "TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA" },
{ 0x000E, "TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA" },
{ 0x000F, "TLS_DH_RSA_WITH_DES_CBC_SHA" },
{ 0x0010, "TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA" },
{ 0x0011, "TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA" },
{ 0x0012, "TLS_DHE_DSS_WITH_DES_CBC_SHA" },
{ 0x0013, "TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA" },
{ 0x0014, "TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA" },
{ 0x0015, "TLS_DHE_RSA_WITH_DES_CBC_SHA" },
{ 0x0016, "TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA" },
{ 0x0017, "TLS_DH_anon_EXPORT_WITH_RC4_40_MD5" },
{ 0x0018, "TLS_DH_anon_WITH_RC4_128_MD5" },
{ 0x0019, "TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA" },
{ 0x001A, "TLS_DH_anon_WITH_DES_CBC_SHA" },
{ 0x001B, "TLS_DH_anon_WITH_3DES_EDE_CBC_SHA" },
{ 0x001E, "TLS_KRB5_WITH_DES_CBC_SHA" },
{ 0x001F, "TLS_KRB5_WITH_3DES_EDE_CBC_SHA" },
{ 0x0020, "TLS_KRB5_WITH_RC4_128_SHA" },
{ 0x0021, "TLS_KRB5_WITH_IDEA_CBC_SHA" },
{ 0x0022, "TLS_KRB5_WITH_DES_CBC_MD5" },
{ 0x0023, "TLS_KRB5_WITH_3DES_EDE_CBC_MD5" },
{ 0x0024, "TLS_KRB5_WITH_RC4_128_MD5" },
{ 0x0025, "TLS_KRB5_WITH_IDEA_CBC_MD5" },
{ 0x0026, "TLS_KRB5_EXPORT_WITH_DES_CBC_40_SHA" },
{ 0x0027, "TLS_KRB5_EXPORT_WITH_RC2_CBC_40_SHA" },
{ 0x0028, "TLS_KRB5_EXPORT_WITH_RC4_40_SHA" },
{ 0x0029, "TLS_KRB5_EXPORT_WITH_DES_CBC_40_MD5" },
{ 0x002A, "TLS_KRB5_EXPORT_WITH_RC2_CBC_40_MD5" },
{ 0x002B, "TLS_KRB5_EXPORT_WITH_RC4_40_MD5" },
{ 0x002C, "TLS_PSK_WITH_NULL_SHA" },
{ 0x002D, "TLS_DHE_PSK_WITH_NULL_SHA" },
{ 0x002E, "TLS_RSA_PSK_WITH_NULL_SHA" },
{ 0x002F, "TLS_RSA_WITH_AES_128_CBC_SHA" },
{ 0x0030, "TLS_DH_DSS_WITH_AES_128_CBC_SHA" },
{ 0x0031, "TLS_DH_RSA_WITH_AES_128_CBC_SHA" },
{ 0x0032, "TLS_DHE_DSS_WITH_AES_128_CBC_SHA" },
{ 0x0033, "TLS_DHE_RSA_WITH_AES_128_CBC_SHA" },
{ 0x0034, "TLS_DH_anon_WITH_AES_128_CBC_SHA" },
{ 0x0035, "TLS_RSA_WITH_AES_256_CBC_SHA" },
{ 0x0036, "TLS_DH_DSS_WITH_AES_256_CBC_SHA" },
{ 0x0037, "TLS_DH_RSA_WITH_AES_256_CBC_SHA" },
{ 0x0038, "TLS_DHE_DSS_WITH_AES_256_CBC_SHA" },
{ 0x0039, "TLS_DHE_RSA_WITH_AES_256_CBC_SHA" },
{ 0x003A, "TLS_DH_anon_WITH_AES_256_CBC_SHA" },
{ 0x003B, "TLS_RSA_WITH_NULL_SHA256" },
{ 0x003C, "TLS_RSA_WITH_AES_128_CBC_SHA256" },
{ 0x003D, "TLS_RSA_WITH_AES_256_CBC_SHA256" },
{ 0x003E, "TLS_DH_DSS_WITH_AES_128_CBC_SHA256" },
{ 0x003F, "TLS_DH_RSA_WITH_AES_128_CBC_SHA256" },
{ 0x0040, "TLS_DHE_DSS_WITH_AES_128_CBC_SHA256" },
{ 0x0041, "TLS_RSA_WITH_CAMELLIA_128_CBC_SHA" },
{ 0x0042, "TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA" },
{ 0x0043, "TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA" },
{ 0x0044, "TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA" },
{ 0x0045, "TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA" },
{ 0x0046, "TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA" },
{ 0x0067, "TLS_DHE_RSA_WITH_AES_128_CBC_SHA256" },
{ 0x0068, "TLS_DH_DSS_WITH_AES_256_CBC_SHA256" },
{ 0x0069, "TLS_DH_RSA_WITH_AES_256_CBC_SHA256" },
{ 0x006A, "TLS_DHE_DSS_WITH_AES_256_CBC_SHA256" },
{ 0x006B, "TLS_DHE_RSA_WITH_AES_256_CBC_SHA256" },
{ 0x006C, "TLS_DH_anon_WITH_AES_128_CBC_SHA256" },
{ 0x006D, "TLS_DH_anon_WITH_AES_256_CBC_SHA256" },
{ 0x0084, "TLS_RSA_WITH_CAMELLIA_256_CBC_SHA" },
{ 0x0085, "TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA" },
{ 0x0086, "TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA" },
{ 0x0087, "TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA" },
{ 0x0088, "TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA" },
{ 0x0089, "TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA" },
{ 0x008A, "TLS_PSK_WITH_RC4_128_SHA" },
{ 0x008B, "TLS_PSK_WITH_3DES_EDE_CBC_SHA" },
{ 0x008C, "TLS_PSK_WITH_AES_128_CBC_SHA" },
{ 0x008D, "TLS_PSK_WITH_AES_256_CBC_SHA" },
{ 0x008E, "TLS_DHE_PSK_WITH_RC4_128_SHA" },
{ 0x008F, "TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA" },
{ 0x0090, "TLS_DHE_PSK_WITH_AES_128_CBC_SHA" },
{ 0x0091, "TLS_DHE_PSK_WITH_AES_256_CBC_SHA" },
{ 0x0092, "TLS_RSA_PSK_WITH_RC4_128_SHA" },
{ 0x0093, "TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA" },
{ 0x0094, "TLS_RSA_PSK_WITH_AES_128_CBC_SHA" },
{ 0x0095, "TLS_RSA_PSK_WITH_AES_256_CBC_SHA" },
{ 0x0096, "TLS_RSA_WITH_SEED_CBC_SHA" },
{ 0x0097, "TLS_DH_DSS_WITH_SEED_CBC_SHA" },
{ 0x0098, "TLS_DH_RSA_WITH_SEED_CBC_SHA" },
{ 0x0099, "TLS_DHE_DSS_WITH_SEED_CBC_SHA" },
{ 0x009A, "TLS_DHE_RSA_WITH_SEED_CBC_SHA" },
{ 0x009B, "TLS_DH_anon_WITH_SEED_CBC_SHA" },
{ 0x009C, "TLS_RSA_WITH_AES_128_GCM_SHA256" },
{ 0x009D, "TLS_RSA_WITH_AES_256_GCM_SHA384" },
{ 0x009E, "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256" },
{ 0x009F, "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384" },
{ 0x00A0, "TLS_DH_RSA_WITH_AES_128_GCM_SHA256" },
{ 0x00A1, "TLS_DH_RSA_WITH_AES_256_GCM_SHA384" },
{ 0x00A2, "TLS_DHE_DSS_WITH_AES_128_GCM_SHA256" },
{ 0x00A3, "TLS_DHE_DSS_WITH_AES_256_GCM_SHA384" },
{ 0x00A4, "TLS_DH_DSS_WITH_AES_128_GCM_SHA256" },
{ 0x00A5, "TLS_DH_DSS_WITH_AES_256_GCM_SHA384" },
{ 0x00A6, "TLS_DH_anon_WITH_AES_128_GCM_SHA256" },
{ 0x00A7, "TLS_DH_anon_WITH_AES_256_GCM_SHA384" },
{ 0x00A8, "TLS_PSK_WITH_AES_128_GCM_SHA256" },
{ 0x00A9, "TLS_PSK_WITH_AES_256_GCM_SHA384" },
{ 0x00AA, "TLS_DHE_PSK_WITH_AES_128_GCM_SHA256" },
{ 0x00AB, "TLS_DHE_PSK_WITH_AES_256_GCM_SHA384" },
{ 0x00AC, "TLS_RSA_PSK_WITH_AES_128_GCM_SHA256" },
{ 0x00AD, "TLS_RSA_PSK_WITH_AES_256_GCM_SHA384" },
{ 0x00AE, "TLS_PSK_WITH_AES_128_CBC_SHA256" },
{ 0x00AF, "TLS_PSK_WITH_AES_256_CBC_SHA384" },
{ 0x00B0, "TLS_PSK_WITH_NULL_SHA256" },
{ 0x00B1, "TLS_PSK_WITH_NULL_SHA384" },
{ 0x00B2, "TLS_DHE_PSK_WITH_AES_128_CBC_SHA256" },
{ 0x00B3, "TLS_DHE_PSK_WITH_AES_256_CBC_SHA384" },
{ 0x00B4, "TLS_DHE_PSK_WITH_NULL_SHA256" },
{ 0x00B5, "TLS_DHE_PSK_WITH_NULL_SHA384" },
{ 0x00B6, "TLS_RSA_PSK_WITH_AES_128_CBC_SHA256" },
{ 0x00B7, "TLS_RSA_PSK_WITH_AES_256_CBC_SHA384" },
{ 0x00B8, "TLS_RSA_PSK_WITH_NULL_SHA256" },
{ 0x00B9, "TLS_RSA_PSK_WITH_NULL_SHA384" },
{ 0x00BA, "TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256" },
{ 0x00BB, "TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA256" },
{ 0x00BC, "TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256" },
{ 0x00BD, "TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256" },
{ 0x00BE, "TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256" },
{ 0x00BF, "TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA256" },
{ 0x00C0, "TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256" },
{ 0x00C1, "TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA256" },
{ 0x00C2, "TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256" },
{ 0x00C3, "TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256" },
{ 0x00C4, "TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256" },
{ 0x00C5, "TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA256" },
{ 0x00FF, "TLS_EMPTY_RENEGOTIATION_INFO_SCSV" },
{ 0x5600, "TLS_FALLBACK_SCSV" },
{ 0xC001, "TLS_ECDH_ECDSA_WITH_NULL_SHA" },
{ 0xC002, "TLS_ECDH_ECDSA_WITH_RC4_128_SHA" },
{ 0xC003, "TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA" },
{ 0xC004, "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA" },
{ 0xC005, "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA" },
{ 0xC006, "TLS_ECDHE_ECDSA_WITH_NULL_SHA" },
{ 0xC007, "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA" },
{ 0xC008, "TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA" },
{ 0xC009, "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA" },
{ 0xC00A, "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA" },
{ 0xC00B, "TLS_ECDH_RSA_WITH_NULL_SHA" },
{ 0xC00C, "TLS_ECDH_RSA_WITH_RC4_128_SHA" },
{ 0xC00D, "TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA" },
{ 0xC00E, "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA" },
{ 0xC00F, "TLS_ECDH_RSA_WITH_AES_256_CBC_SHA" },
{ 0xC010, "TLS_ECDHE_RSA_WITH_NULL_SHA" },
{ 0xC011, "TLS_ECDHE_RSA_WITH_RC4_128_SHA" },
{ 0xC012, "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA" },
{ 0xC013, "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA" },
{ 0xC014, "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA" },
{ 0xC015, "TLS_ECDH_anon_WITH_NULL_SHA" },
{ 0xC016, "TLS_ECDH_anon_WITH_RC4_128_SHA" },
{ 0xC017, "TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA" },
{ 0xC018, "TLS_ECDH_anon_WITH_AES_128_CBC_SHA" },
{ 0xC019, "TLS_ECDH_anon_WITH_AES_256_CBC_SHA" },
{ 0xC01A, "TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA" },
{ 0xC01B, "TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA" },
{ 0xC01C, "TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA" },
{ 0xC01D, "TLS_SRP_SHA_WITH_AES_128_CBC_SHA" },
{ 0xC01E, "TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA" },
{ 0xC01F, "TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA" },
{ 0xC020, "TLS_SRP_SHA_WITH_AES_256_CBC_SHA" },
{ 0xC021, "TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA" },
{ 0xC022, "TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA" },
{ 0xC023, "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256" },
{ 0xC024, "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384" },
{ 0xC025, "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256" },
{ 0xC026, "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384" },
{ 0xC027, "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256" },
{ 0xC028, "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384" },
{ 0xC029, "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256" },
{ 0xC02A, "TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384" },
{ 0xC02B, "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256" },
{ 0xC02C, "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384" },
{ 0xC02D, "TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256" },
{ 0xC02E, "TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384" },
{ 0xC02F, "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256" },
{ 0xC030, "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384" },
{ 0xC031, "TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256" },
{ 0xC032, "TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384" },
{ 0xC033, "TLS_ECDHE_PSK_WITH_RC4_128_SHA" },
{ 0xC034, "TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA" },
{ 0xC035, "TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA" },
{ 0xC036, "TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA" },
{ 0xC037, "TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256" },
{ 0xC038, "TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384" },
{ 0xC039, "TLS_ECDHE_PSK_WITH_NULL_SHA" },
{ 0xC03A, "TLS_ECDHE_PSK_WITH_NULL_SHA256" },
{ 0xC03B, "TLS_ECDHE_PSK_WITH_NULL_SHA384" },
{ 0xC03C, "TLS_RSA_WITH_ARIA_128_CBC_SHA256" },
{ 0xC03D, "TLS_RSA_WITH_ARIA_256_CBC_SHA384" },
{ 0xC03E, "TLS_DH_DSS_WITH_ARIA_128_CBC_SHA256" },
{ 0xC03F, "TLS_DH_DSS_WITH_ARIA_256_CBC_SHA384" },
{ 0xC040, "TLS_DH_RSA_WITH_ARIA_128_CBC_SHA256" },
{ 0xC041, "TLS_DH_RSA_WITH_ARIA_256_CBC_SHA384" },
{ 0xC042, "TLS_DHE_DSS_WITH_ARIA_128_CBC_SHA256" },
{ 0xC043, "TLS_DHE_DSS_WITH_ARIA_256_CBC_SHA384" },
{ 0xC044, "TLS_DHE_RSA_WITH_ARIA_128_CBC_SHA256" },
{ 0xC045, "TLS_DHE_RSA_WITH_ARIA_256_CBC_SHA384" },
{ 0xC046, "TLS_DH_anon_WITH_ARIA_128_CBC_SHA256" },
{ 0xC047, "TLS_DH_anon_WITH_ARIA_256_CBC_SHA384" },
{ 0xC048, "TLS_ECDHE_ECDSA_WITH_ARIA_128_CBC_SHA256" },
{ 0xC049, "TLS_ECDHE_ECDSA_WITH_ARIA_256_CBC_SHA384" },
{ 0xC04A, "TLS_ECDH_ECDSA_WITH_ARIA_128_CBC_SHA256" },
{ 0xC04B, "TLS_ECDH_ECDSA_WITH_ARIA_256_CBC_SHA384" },
{ 0xC04C, "TLS_ECDHE_RSA_WITH_ARIA_128_CBC_SHA256" },
{ 0xC04D, "TLS_ECDHE_RSA_WITH_ARIA_256_CBC_SHA384" },
{ 0xC04E, "TLS_ECDH_RSA_WITH_ARIA_128_CBC_SHA256" },
{ 0xC04F, "TLS_ECDH_RSA_WITH_ARIA_256_CBC_SHA384" },
{ 0xC050, "TLS_RSA_WITH_ARIA_128_GCM_SHA256" },
{ 0xC051, "TLS_RSA_WITH_ARIA_256_GCM_SHA384" },
{ 0xC052, "TLS_DHE_RSA_WITH_ARIA_128_GCM_SHA256" },
{ 0xC053, "TLS_DHE_RSA_WITH_ARIA_256_GCM_SHA384" },
{ 0xC054, "TLS_DH_RSA_WITH_ARIA_128_GCM_SHA256" },
{ 0xC055, "TLS_DH_RSA_WITH_ARIA_256_GCM_SHA384" },
{ 0xC056, "TLS_DHE_DSS_WITH_ARIA_128_GCM_SHA256" },
{ 0xC057, "TLS_DHE_DSS_WITH_ARIA_256_GCM_SHA384" },
{ 0xC058, "TLS_DH_DSS_WITH_ARIA_128_GCM_SHA256" },
{ 0xC059, "TLS_DH_DSS_WITH_ARIA_256_GCM_SHA384" },
{ 0xC05A, "TLS_DH_anon_WITH_ARIA_128_GCM_SHA256" },
{ 0xC05B, "TLS_DH_anon_WITH_ARIA_256_GCM_SHA384" },
{ 0xC05C, "TLS_ECDHE_ECDSA_WITH_ARIA_128_GCM_SHA256" },
{ 0xC05D, "TLS_ECDHE_ECDSA_WITH_ARIA_256_GCM_SHA384" },
{ 0xC05E, "TLS_ECDH_ECDSA_WITH_ARIA_128_GCM_SHA256" },
{ 0xC05F, "TLS_ECDH_ECDSA_WITH_ARIA_256_GCM_SHA384" },
{ 0xC060, "TLS_ECDHE_RSA_WITH_ARIA_128_GCM_SHA256" },
{ 0xC061, "TLS_ECDHE_RSA_WITH_ARIA_256_GCM_SHA384" },
{ 0xC062, "TLS_ECDH_RSA_WITH_ARIA_128_GCM_SHA256" },
{ 0xC063, "TLS_ECDH_RSA_WITH_ARIA_256_GCM_SHA384" },
{ 0xC064, "TLS_PSK_WITH_ARIA_128_CBC_SHA256" },
{ 0xC065, "TLS_PSK_WITH_ARIA_256_CBC_SHA384" },
{ 0xC066, "TLS_DHE_PSK_WITH_ARIA_128_CBC_SHA256" },
{ 0xC067, "TLS_DHE_PSK_WITH_ARIA_256_CBC_SHA384" },
{ 0xC068, "TLS_RSA_PSK_WITH_ARIA_128_CBC_SHA256" },
{ 0xC069, "TLS_RSA_PSK_WITH_ARIA_256_CBC_SHA384" },
{ 0xC06A, "TLS_PSK_WITH_ARIA_128_GCM_SHA256" },
{ 0xC06B, "TLS_PSK_WITH_ARIA_256_GCM_SHA384" },
{ 0xC06C, "TLS_DHE_PSK_WITH_ARIA_128_GCM_SHA256" },
{ 0xC06D, "TLS_DHE_PSK_WITH_ARIA_256_GCM_SHA384" },
{ 0xC06E, "TLS_RSA_PSK_WITH_ARIA_128_GCM_SHA256" },
{ 0xC06F, "TLS_RSA_PSK_WITH_ARIA_256_GCM_SHA384" },
{ 0xC070, "TLS_ECDHE_PSK_WITH_ARIA_128_CBC_SHA256" },
{ 0xC071, "TLS_ECDHE_PSK_WITH_ARIA_256_CBC_SHA384" },
{ 0xC072, "TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256" },
{ 0xC073, "TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384" },
{ 0xC074, "TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256" },
{ 0xC075, "TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384" },
{ 0xC076, "TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256" },
{ 0xC077, "TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384" },
{ 0xC078, "TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256" },
{ 0xC079, "TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384" },
{ 0xC07A, "TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256" },
{ 0xC07B, "TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384" },
{ 0xC07C, "TLS_DHE_RSA_WITH_CAMELLIA_128_GCM_SHA256" },
{ 0xC07D, "TLS_DHE_RSA_WITH_CAMELLIA_256_GCM_SHA384" },
{ 0xC07E, "TLS_DH_RSA_WITH_CAMELLIA_128_GCM_SHA256" },
{ 0xC07F, "TLS_DH_RSA_WITH_CAMELLIA_256_GCM_SHA384" },
{ 0xC080, "TLS_DHE_DSS_WITH_CAMELLIA_128_GCM_SHA256" },
{ 0xC081, "TLS_DHE_DSS_WITH_CAMELLIA_256_GCM_SHA384" },
{ 0xC082, "TLS_DH_DSS_WITH_CAMELLIA_128_GCM_SHA256" },
{ 0xC083, "TLS_DH_DSS_WITH_CAMELLIA_256_GCM_SHA384" },
{ 0xC084, "TLS_DH_anon_WITH_CAMELLIA_128_GCM_SHA256" },
{ 0xC085, "TLS_DH_anon_WITH_CAMELLIA_256_GCM_SHA384" },
{ 0xC086, "TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_GCM_SHA256" },
{ 0xC087, "TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_GCM_SHA384" },
{ 0xC088, "TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256" },
{ 0xC089, "TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384" },
{ 0xC08A, "TLS_ECDHE_RSA_WITH_CAMELLIA_128_GCM_SHA256" },
{ 0xC08B, "TLS_ECDHE_RSA_WITH_CAMELLIA_256_GCM_SHA384" },
{ 0xC08C, "TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256" },
{ 0xC08D, "TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384" },
{ 0xC08E, "TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256" },
{ 0xC08F, "TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384" },
{ 0xC090, "TLS_DHE_PSK_WITH_CAMELLIA_128_GCM_SHA256" },
{ 0xC091, "TLS_DHE_PSK_WITH_CAMELLIA_256_GCM_SHA384" },
{ 0xC092, "TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256" },
{ 0xC093, "TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384" },
{ 0xC094, "TLS_PSK_WITH_CAMELLIA_128_CBC_SHA256" },
{ 0xC095, "TLS_PSK_WITH_CAMELLIA_256_CBC_SHA384" },
{ 0xC096, "TLS_DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256" },
{ 0xC097, "TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384" },
{ 0xC098, "TLS_RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256" },
{ 0xC099, "TLS_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384" },
{ 0xC09A, "TLS_ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256" },
{ 0xC09B, "TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384" },
{ 0xC09C, "TLS_RSA_WITH_AES_128_CCM" },
{ 0xC09D, "TLS_RSA_WITH_AES_256_CCM" },
{ 0xC09E, "TLS_DHE_RSA_WITH_AES_128_CCM" },
{ 0xC09F, "TLS_DHE_RSA_WITH_AES_256_CCM" },
{ 0xC0A0, "TLS_RSA_WITH_AES_128_CCM_8" },
{ 0xC0A1, "TLS_RSA_WITH_AES_256_CCM_8" },
{ 0xC0A2, "TLS_DHE_RSA_WITH_AES_128_CCM_8" },
{ 0xC0A3, "TLS_DHE_RSA_WITH_AES_256_CCM_8" },
{ 0xC0A4, "TLS_PSK_WITH_AES_128_CCM" },
{ 0xC0A5, "TLS_PSK_WITH_AES_256_CCM" },
{ 0xC0A6, "TLS_DHE_PSK_WITH_AES_128_CCM" },
{ 0xC0A7, "TLS_DHE_PSK_WITH_AES_256_CCM" },
{ 0xC0A8, "TLS_PSK_WITH_AES_128_CCM_8" },
{ 0xC0A9, "TLS_PSK_WITH_AES_256_CCM_8" },
{ 0xC0AA, "TLS_PSK_DHE_WITH_AES_128_CCM_8" },
{ 0xC0AB, "TLS_PSK_DHE_WITH_AES_256_CCM_8" },
{ 0xC0AC, "TLS_ECDHE_ECDSA_WITH_AES_128_CCM" },
{ 0xC0AD, "TLS_ECDHE_ECDSA_WITH_AES_256_CCM" },
{ 0xC0AE, "TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8" },
{ 0xC0AF, "TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8" },
{ 0xCCA8, "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256" },
{ 0xCCA9, "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256" },
{ 0xCCAA, "TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256" },
{ 0xCCAB, "TLS_PSK_WITH_CHACHA20_POLY1305_SHA256" },
{ 0xCCAC, "TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256" },
{ 0xCCAD, "TLS_DHE_PSK_WITH_CHACHA20_POLY1305_SHA256" },
{ 0xCCAE, "TLS_RSA_PSK_WITH_CHACHA20_POLY1305_SHA256" },
};
}
}
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
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Ssl
{
internal class SslExtensions
{
internal static SslExtension GetExtension(int value, byte[] data)
{
string name = GetExtensionName(value);
string dataStr = GetExtensionData(value, data);
return new SslExtension(value, name, dataStr);
}
private static string GetExtensionData(int value, byte[] data)
{
//https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml
switch (value)
{
case 0:
var stringBuilder = new StringBuilder();
int index = 2;
while (index < data.Length)
{
int nameType = data[index];
int count = (data[index + 1] << 8) + data[index + 2];
string str = Encoding.ASCII.GetString(data, index + 3, count);
if (nameType == 0)
{
stringBuilder.AppendFormat("{0}{1}", stringBuilder.Length > 1 ? "; " : string.Empty, str);
}
index += 3 + count;
}
return stringBuilder.ToString();
case 5:
if (data.Length == 5 && data[0] == 1 && data[1] == 0 && data[2] == 0 && data[3] == 0 && data[4] == 0)
{
return "OCSP - Implicit Responder";
}
return ByteArrayToString(data);
case 10:
return GetSupportedGroup(data);
case 11:
return GetEcPointFormats(data);
case 13:
return GetSignatureAlgorithms(data);
case 16:
return GetApplicationLayerProtocolNegotiation(data);
case 35655:
return $"{data.Length} bytes";
default:
return ByteArrayToString(data);
}
}
private static string GetSupportedGroup(byte[] data)
{
//https://datatracker.ietf.org/doc/draft-ietf-tls-rfc4492bis/?include_text=1
List<string> list = new List<string>();
if (data.Length < 2)
{
return string.Empty;
}
int i = 2;
while (i < data.Length - 1)
{
int namedCurve = (data[i] << 8) + data[i + 1];
switch (namedCurve)
{
case 1:
list.Add("sect163k1 [0x1]"); //deprecated
break;
case 2:
list.Add("sect163r1 [0x2]"); //deprecated
break;
case 3:
list.Add("sect163r2 [0x3]"); //deprecated
break;
case 4:
list.Add("sect193r1 [0x4]"); //deprecated
break;
case 5:
list.Add("sect193r2 [0x5]"); //deprecated
break;
case 6:
list.Add("sect233k1 [0x6]"); //deprecated
break;
case 7:
list.Add("sect233r1 [0x7]"); //deprecated
break;
case 8:
list.Add("sect239k1 [0x8]"); //deprecated
break;
case 9:
list.Add("sect283k1 [0x9]"); //deprecated
break;
case 10:
list.Add("sect283r1 [0xA]"); //deprecated
break;
case 11:
list.Add("sect409k1 [0xB]"); //deprecated
break;
case 12:
list.Add("sect409r1 [0xC]"); //deprecated
break;
case 13:
list.Add("sect571k1 [0xD]"); //deprecated
break;
case 14:
list.Add("sect571r1 [0xE]"); //deprecated
break;
case 15:
list.Add("secp160k1 [0xF]"); //deprecated
break;
case 16:
list.Add("secp160r1 [0x10]"); //deprecated
break;
case 17:
list.Add("secp160r2 [0x11]"); //deprecated
break;
case 18:
list.Add("secp192k1 [0x12]"); //deprecated
break;
case 19:
list.Add("secp192r1 [0x13]"); //deprecated
break;
case 20:
list.Add("secp224k1 [0x14]"); //deprecated
break;
case 21:
list.Add("secp224r1 [0x15]"); //deprecated
break;
case 22:
list.Add("secp256k1 [0x16]"); //deprecated
break;
case 23:
list.Add("secp256r1 [0x17]");
break;
case 24:
list.Add("secp384r1 [0x18]");
break;
case 25:
list.Add("secp521r1 [0x19]");
break;
case 26:
list.Add("brainpoolP256r1 [0x1A]");
break;
case 27:
list.Add("brainpoolP384r1 [0x1B]");
break;
case 28:
list.Add("brainpoolP512r1 [0x1C]");
break;
case 29:
list.Add("x25519 [0x1D]");
break;
case 30:
list.Add("x448 [0x1E]");
break;
case 256:
list.Add("ffdhe2048 [0x0100]");
break;
case 257:
list.Add("ffdhe3072 [0x0101]");
break;
case 258:
list.Add("ffdhe4096 [0x0102]");
break;
case 259:
list.Add("ffdhe6144 [0x0103]");
break;
case 260:
list.Add("ffdhe8192 [0x0104]");
break;
case 65281:
list.Add("arbitrary_explicit_prime_curves [0xFF01]"); //deprecated
break;
case 65282:
list.Add("arbitrary_explicit_char2_curves [0xFF02]"); //deprecated
break;
default:
list.Add($"unknown [0x{namedCurve:X4}]");
break;
}
i += 2;
}
return string.Join(", ", list.ToArray());
}
private static string GetEcPointFormats(byte[] data)
{
List<string> list = new List<string>();
if (data.Length < 1)
{
return string.Empty;
}
int i = 1;
while (i < data.Length)
{
switch (data[i])
{
case 0:
list.Add("uncompressed [0x0]");
break;
case 1:
list.Add("ansiX962_compressed_prime [0x1]");
break;
case 2:
list.Add("ansiX962_compressed_char2 [0x2]");
break;
default:
list.Add($"unknown [0x{data[i]:X2}]");
break;
}
i += 2;
}
return string.Join(", ", list.ToArray());
}
private static string GetSignatureAlgorithms(byte[] data)
{
// https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml
int num = (data[0] << 8) + data[1];
var sb = new StringBuilder();
int index = 2;
while (index < num + 2)
{
switch (data[index])
{
case 0:
sb.Append("none");
break;
case 1:
sb.Append("md5");
break;
case 2:
sb.Append("sha1");
break;
case 3:
sb.Append("sha224");
break;
case 4:
sb.Append("sha256");
break;
case 5:
sb.Append("sha384");
break;
case 6:
sb.Append("sha512");
break;
case 8:
sb.Append("Intrinsic");
break;
default:
sb.AppendFormat("Unknown[0x{0:X2}]", data[index]);
break;
}
sb.AppendFormat("_");
switch (data[index + 1])
{
case 0:
sb.Append("anonymous");
break;
case 1:
sb.Append("rsa");
break;
case 2:
sb.Append("dsa");
break;
case 3:
sb.Append("ecdsa");
break;
case 7:
sb.Append("ed25519");
break;
case 8:
sb.Append("ed448");
break;
default:
sb.AppendFormat("Unknown[0x{0:X2}]", data[index + 1]);
break;
}
sb.AppendFormat(", ");
index += 2;
}
if (sb.Length > 1)
sb.Length -= 2;
return sb.ToString();
}
private static string GetApplicationLayerProtocolNegotiation(byte[] data)
{
List<string> stringList = new List<string>();
int index = 2;
while (index < data.Length)
{
int count = data[index];
stringList.Add(Encoding.ASCII.GetString(data, index + 1, count));
index += 1 + count;
}
return string.Join(", ", stringList.ToArray());
}
private static string GetExtensionName(int value)
{
//https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml
switch (value)
{
case 0:
return "server_name";
case 1:
return "max_fragment_length";
case 2:
return "client_certificate_url";
case 3:
return "trusted_ca_keys";
case 4:
return "truncated_hmac";
case 5:
return "status_request";
case 6:
return "user_mapping";
case 7:
return "client_authz";
case 8:
return "server_authz";
case 9:
return "cert_type";
case 10:
return "supported_groups"; // renamed from "elliptic_curves"
case 11:
return "ec_point_formats";
case 12:
return "srp";
case 13:
return "signature_algorithms";
case 14:
return "use_srtp";
case 15:
return "heartbeat";
case 16:
return "ALPN"; // application_layer_protocol_negotiation
case 17:
return "status_request_v2";
case 18:
return "signed_certificate_timestamp";
case 19:
return "client_certificate_type";
case 20:
return "server_certificate_type";
case 21:
return "padding";
case 22:
return "encrypt_then_mac";
case 23:
return "extended_master_secret";
case 24:
return "token_binding"; // TEMPORARY - registered 2016-02-04, extension registered 2017-01-12, expires 2018-02-04
case 25:
return "cached_info";
case 35:
return "SessionTicket TLS";
case 13172:
return "next_protocol_negotiation";
case 30031:
case 30032:
return "channel_id"; // Google
case 35655:
return "draft-agl-tls-padding";
case 65281:
return "renegotiation_info";
default:
return "unknown";
}
}
private static string ByteArrayToString(byte[] data)
{
return string.Join(" ", data.Select(x => x.ToString("X2")));
}
}
}
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>
<group>
<dependency id="StreamExtended" version="1.0.18" />
</group>
<group targetFramework="net45">
<dependency id="BouncyCastle" version="1.8.1" /> <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