Commit 75a10f90 authored by justcoding121's avatar justcoding121

move source under src\ directory

parent 67bfa3bc
...@@ -2,7 +2,7 @@ $PSake.use_exit_on_error = $true ...@@ -2,7 +2,7 @@ $PSake.use_exit_on_error = $true
$Here = "$(Split-Path -parent $MyInvocation.MyCommand.Definition)" $Here = "$(Split-Path -parent $MyInvocation.MyCommand.Definition)"
$SolutionRoot = (Split-Path -parent $Here) $SolutionRoot = "$(Split-Path -parent $Here)\src"
$ProjectName = "Titanium.Web.Proxy" $ProjectName = "Titanium.Web.Proxy"
$GitHubProjectName = "Titanium-Web-Proxy" $GitHubProjectName = "Titanium-Web-Proxy"
......
...@@ -6,7 +6,7 @@ param ( ...@@ -6,7 +6,7 @@ param (
function Install-Chocolatey() function Install-Chocolatey()
{ {
if(-not $env:ChocolateyInstall -or -not (Test-Path "$env:ChocolateyInstall")) if(-not $env:ChocolateyInstall -or -not (Test-Path "$env:ChocolateyInstall\*"))
{ {
Write-Output "Chocolatey Not Found, Installing..." Write-Output "Chocolatey Not Found, Installing..."
iex ((new-object net.webclient).DownloadString('http://chocolatey.org/install.ps1')) iex ((new-object net.webclient).DownloadString('http://chocolatey.org/install.ps1'))
......
baseurl: /Titanium-Web-Proxy baseurl: /src
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\Titanium.Web.Proxy\Titanium.Web.Proxy.csproj" /> <ProjectReference Include="..\..\src\Titanium.Web.Proxy\Titanium.Web.Proxy.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>
\ No newline at end of file
...@@ -116,12 +116,6 @@ ...@@ -116,12 +116,6 @@
<ItemGroup> <ItemGroup>
<None Include="App.config" /> <None Include="App.config" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Titanium.Web.Proxy\Titanium.Web.Proxy.csproj">
<Project>{8d73a1be-868c-42d2-9ece-f32cc1a02906}</Project>
<Name>Titanium.Web.Proxy</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup> <ItemGroup>
<BootstrapperPackage Include="Microsoft.Net.Framework.3.5.SP1"> <BootstrapperPackage Include="Microsoft.Net.Framework.3.5.SP1">
<Visible>False</Visible> <Visible>False</Visible>
...@@ -129,5 +123,11 @@ ...@@ -129,5 +123,11 @@
<Install>false</Install> <Install>false</Install>
</BootstrapperPackage> </BootstrapperPackage>
</ItemGroup> </ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Titanium.Web.Proxy\Titanium.Web.Proxy.csproj">
<Project>{91018b6d-a7a9-45be-9cb3-79cbb8b169a6}</Project>
<Name>Titanium.Web.Proxy</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project> </Project>
\ No newline at end of file
...@@ -5,8 +5,6 @@ VisualStudioVersion = 15.0.26906.1 ...@@ -5,8 +5,6 @@ VisualStudioVersion = 15.0.26906.1
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Examples", "Examples", "{B6DBABDC-C985-4872-9C38-B4E5079CBC4B}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Examples", "Examples", "{B6DBABDC-C985-4872-9C38-B4E5079CBC4B}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Titanium.Web.Proxy", "Titanium.Web.Proxy\Titanium.Web.Proxy.csproj", "{8D73A1BE-868C-42D2-9ECE-F32CC1A02906}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{6FD3B84B-9283-4E9C-8C43-A234E9AA3EAA}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{6FD3B84B-9283-4E9C-8C43-A234E9AA3EAA}"
ProjectSection(SolutionItems) = preProject ProjectSection(SolutionItems) = preProject
.nuget\NuGet.Config = .nuget\NuGet.Config .nuget\NuGet.Config = .nuget\NuGet.Config
...@@ -29,13 +27,15 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{AC9AE37A ...@@ -29,13 +27,15 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{AC9AE37A
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{BC1E0789-D348-49CF-8B67-5E99D50EDF64}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{BC1E0789-D348-49CF-8B67-5E99D50EDF64}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Titanium.Web.Proxy.UnitTests", "Tests\Titanium.Web.Proxy.UnitTests\Titanium.Web.Proxy.UnitTests.csproj", "{B517E3D0-D03B-436F-AB03-34BA0D5321AF}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Titanium.Web.Proxy", "Titanium.Web.Proxy\Titanium.Web.Proxy.csproj", "{91018B6D-A7A9-45BE-9CB3-79CBB8B169A6}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Titanium.Web.Proxy.UnitTests", "..\tests\Titanium.Web.Proxy.UnitTests\Titanium.Web.Proxy.UnitTests.csproj", "{B517E3D0-D03B-436F-AB03-34BA0D5321AF}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Titanium.Web.Proxy.IntegrationTests", "Tests\Titanium.Web.Proxy.IntegrationTests\Titanium.Web.Proxy.IntegrationTests.csproj", "{32231301-B0FB-4F9E-98DF-B3E8A88F4C16}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Titanium.Web.Proxy.IntegrationTests", "..\tests\Titanium.Web.Proxy.IntegrationTests\Titanium.Web.Proxy.IntegrationTests.csproj", "{32231301-B0FB-4F9E-98DF-B3E8A88F4C16}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Titanium.Web.Proxy.Examples.Wpf", "Examples\Titanium.Web.Proxy.Examples.Wpf\Titanium.Web.Proxy.Examples.Wpf.csproj", "{4406CE17-9A39-4F28-8363-6169A4F799C1}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Titanium.Web.Proxy.Examples.Basic", "..\examples\Titanium.Web.Proxy.Examples.Basic\Titanium.Web.Proxy.Examples.Basic.csproj", "{1FAC4205-4445-4F2B-BB8F-618E8A0C15FD}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Titanium.Web.Proxy.Examples.Basic", "Examples\Titanium.Web.Proxy.Examples.Basic\Titanium.Web.Proxy.Examples.Basic.csproj", "{9A2C6980-90D1-4082-AD60-B2428F3D6197}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Titanium.Web.Proxy.Examples.Wpf", "..\examples\Titanium.Web.Proxy.Examples.Wpf\Titanium.Web.Proxy.Examples.Wpf.csproj", "{4406CE17-9A39-4F28-8363-6169A4F799C1}"
EndProject EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
...@@ -43,10 +43,10 @@ Global ...@@ -43,10 +43,10 @@ Global
Release|Any CPU = Release|Any CPU Release|Any CPU = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution GlobalSection(ProjectConfigurationPlatforms) = postSolution
{8D73A1BE-868C-42D2-9ECE-F32CC1A02906}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {91018B6D-A7A9-45BE-9CB3-79CBB8B169A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8D73A1BE-868C-42D2-9ECE-F32CC1A02906}.Debug|Any CPU.Build.0 = Debug|Any CPU {91018B6D-A7A9-45BE-9CB3-79CBB8B169A6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8D73A1BE-868C-42D2-9ECE-F32CC1A02906}.Release|Any CPU.ActiveCfg = Release|Any CPU {91018B6D-A7A9-45BE-9CB3-79CBB8B169A6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8D73A1BE-868C-42D2-9ECE-F32CC1A02906}.Release|Any CPU.Build.0 = Release|Any CPU {91018B6D-A7A9-45BE-9CB3-79CBB8B169A6}.Release|Any CPU.Build.0 = Release|Any CPU
{B517E3D0-D03B-436F-AB03-34BA0D5321AF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B517E3D0-D03B-436F-AB03-34BA0D5321AF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B517E3D0-D03B-436F-AB03-34BA0D5321AF}.Debug|Any CPU.Build.0 = Debug|Any CPU {B517E3D0-D03B-436F-AB03-34BA0D5321AF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B517E3D0-D03B-436F-AB03-34BA0D5321AF}.Release|Any CPU.ActiveCfg = Release|Any CPU {B517E3D0-D03B-436F-AB03-34BA0D5321AF}.Release|Any CPU.ActiveCfg = Release|Any CPU
...@@ -55,14 +55,14 @@ Global ...@@ -55,14 +55,14 @@ Global
{32231301-B0FB-4F9E-98DF-B3E8A88F4C16}.Debug|Any CPU.Build.0 = Debug|Any CPU {32231301-B0FB-4F9E-98DF-B3E8A88F4C16}.Debug|Any CPU.Build.0 = Debug|Any CPU
{32231301-B0FB-4F9E-98DF-B3E8A88F4C16}.Release|Any CPU.ActiveCfg = Release|Any CPU {32231301-B0FB-4F9E-98DF-B3E8A88F4C16}.Release|Any CPU.ActiveCfg = Release|Any CPU
{32231301-B0FB-4F9E-98DF-B3E8A88F4C16}.Release|Any CPU.Build.0 = Release|Any CPU {32231301-B0FB-4F9E-98DF-B3E8A88F4C16}.Release|Any CPU.Build.0 = Release|Any CPU
{1FAC4205-4445-4F2B-BB8F-618E8A0C15FD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1FAC4205-4445-4F2B-BB8F-618E8A0C15FD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1FAC4205-4445-4F2B-BB8F-618E8A0C15FD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1FAC4205-4445-4F2B-BB8F-618E8A0C15FD}.Release|Any CPU.Build.0 = Release|Any CPU
{4406CE17-9A39-4F28-8363-6169A4F799C1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {4406CE17-9A39-4F28-8363-6169A4F799C1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4406CE17-9A39-4F28-8363-6169A4F799C1}.Debug|Any CPU.Build.0 = Debug|Any CPU {4406CE17-9A39-4F28-8363-6169A4F799C1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4406CE17-9A39-4F28-8363-6169A4F799C1}.Release|Any CPU.ActiveCfg = Release|Any CPU {4406CE17-9A39-4F28-8363-6169A4F799C1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4406CE17-9A39-4F28-8363-6169A4F799C1}.Release|Any CPU.Build.0 = Release|Any CPU {4406CE17-9A39-4F28-8363-6169A4F799C1}.Release|Any CPU.Build.0 = Release|Any CPU
{9A2C6980-90D1-4082-AD60-B2428F3D6197}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9A2C6980-90D1-4082-AD60-B2428F3D6197}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9A2C6980-90D1-4082-AD60-B2428F3D6197}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9A2C6980-90D1-4082-AD60-B2428F3D6197}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
...@@ -70,8 +70,8 @@ Global ...@@ -70,8 +70,8 @@ Global
GlobalSection(NestedProjects) = preSolution GlobalSection(NestedProjects) = preSolution
{B517E3D0-D03B-436F-AB03-34BA0D5321AF} = {BC1E0789-D348-49CF-8B67-5E99D50EDF64} {B517E3D0-D03B-436F-AB03-34BA0D5321AF} = {BC1E0789-D348-49CF-8B67-5E99D50EDF64}
{32231301-B0FB-4F9E-98DF-B3E8A88F4C16} = {BC1E0789-D348-49CF-8B67-5E99D50EDF64} {32231301-B0FB-4F9E-98DF-B3E8A88F4C16} = {BC1E0789-D348-49CF-8B67-5E99D50EDF64}
{1FAC4205-4445-4F2B-BB8F-618E8A0C15FD} = {B6DBABDC-C985-4872-9C38-B4E5079CBC4B}
{4406CE17-9A39-4F28-8363-6169A4F799C1} = {B6DBABDC-C985-4872-9C38-B4E5079CBC4B} {4406CE17-9A39-4F28-8363-6169A4F799C1} = {B6DBABDC-C985-4872-9C38-B4E5079CBC4B}
{9A2C6980-90D1-4082-AD60-B2428F3D6197} = {B6DBABDC-C985-4872-9C38-B4E5079CBC4B}
EndGlobalSection EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
EnterpriseLibraryConfigurationToolBinariesPath = .1.505.2\lib\NET35 EnterpriseLibraryConfigurationToolBinariesPath = .1.505.2\lib\NET35
......
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Net; using System.Net;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using StreamExtended.Network; using StreamExtended.Network;
using Titanium.Web.Proxy.Compression; using Titanium.Web.Proxy.Compression;
using Titanium.Web.Proxy.Helpers; using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Http; using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Http.Responses; using Titanium.Web.Proxy.Http.Responses;
using Titanium.Web.Proxy.Models; using Titanium.Web.Proxy.Models;
namespace Titanium.Web.Proxy.EventArguments namespace Titanium.Web.Proxy.EventArguments
{ {
/// <summary> /// <summary>
/// Holds info related to a single proxy session (single request/response sequence). /// Holds info related to a single proxy session (single request/response sequence).
/// A proxy session is bounded to a single connection from client. /// A proxy session is bounded to a single connection from client.
/// A proxy session ends when client terminates connection to proxy /// A proxy session ends when client terminates connection to proxy
/// or when server terminates connection from proxy. /// or when server terminates connection from proxy.
/// </summary> /// </summary>
public class SessionEventArgs : SessionEventArgsBase public class SessionEventArgs : SessionEventArgsBase
{ {
private static readonly byte[] emptyData = new byte[0]; private static readonly byte[] emptyData = new byte[0];
/// <summary> /// <summary>
/// Backing field for corresponding public property /// Backing field for corresponding public property
/// </summary> /// </summary>
private bool reRequest; private bool reRequest;
/// <summary> /// <summary>
/// Constructor to initialize the proxy /// Constructor to initialize the proxy
/// </summary> /// </summary>
internal SessionEventArgs(ProxyServer server, ProxyEndPoint endPoint, internal SessionEventArgs(ProxyServer server, ProxyEndPoint endPoint,
CancellationTokenSource cancellationTokenSource) CancellationTokenSource cancellationTokenSource)
: this(server, endPoint, null, cancellationTokenSource) : this(server, endPoint, null, cancellationTokenSource)
{ {
} }
protected SessionEventArgs(ProxyServer server, ProxyEndPoint endPoint, protected SessionEventArgs(ProxyServer server, ProxyEndPoint endPoint,
Request request, CancellationTokenSource cancellationTokenSource) Request request, CancellationTokenSource cancellationTokenSource)
: base(server, endPoint, cancellationTokenSource, request) : base(server, endPoint, cancellationTokenSource, request)
{ {
} }
private bool hasMulipartEventSubscribers => MultipartRequestPartSent != null; private bool hasMulipartEventSubscribers => MultipartRequestPartSent != null;
/// <summary> /// <summary>
/// Should we send the request again ? /// Should we send the request again ?
/// </summary> /// </summary>
public bool ReRequest public bool ReRequest
{ {
get => reRequest; get => reRequest;
set set
{ {
if (WebSession.Response.StatusCode == 0) if (WebSession.Response.StatusCode == 0)
{ {
throw new Exception("Response status code is empty. Cannot request again a request " + "which was never send to server."); throw new Exception("Response status code is empty. Cannot request again a request " + "which was never send to server.");
} }
reRequest = value; reRequest = value;
} }
} }
/// <summary> /// <summary>
/// Occurs when multipart request part sent. /// Occurs when multipart request part sent.
/// </summary> /// </summary>
public event EventHandler<MultipartRequestPartSentEventArgs> MultipartRequestPartSent; public event EventHandler<MultipartRequestPartSentEventArgs> MultipartRequestPartSent;
private ICustomStreamReader getStreamReader(bool isRequest) private ICustomStreamReader getStreamReader(bool isRequest)
{ {
return isRequest ? ProxyClient.ClientStream : WebSession.ServerConnection.Stream; return isRequest ? ProxyClient.ClientStream : WebSession.ServerConnection.Stream;
} }
private HttpWriter getStreamWriter(bool isRequest) private HttpWriter getStreamWriter(bool isRequest)
{ {
return isRequest ? (HttpWriter)ProxyClient.ClientStreamWriter : WebSession.ServerConnection.StreamWriter; return isRequest ? (HttpWriter)ProxyClient.ClientStreamWriter : WebSession.ServerConnection.StreamWriter;
} }
/// <summary> /// <summary>
/// Read request body content as bytes[] for current session /// Read request body content as bytes[] for current session
/// </summary> /// </summary>
private async Task readRequestBodyAsync(CancellationToken cancellationToken) private async Task readRequestBodyAsync(CancellationToken cancellationToken)
{ {
WebSession.Request.EnsureBodyAvailable(false); WebSession.Request.EnsureBodyAvailable(false);
var request = WebSession.Request; var request = WebSession.Request;
// If not already read (not cached yet) // If not already read (not cached yet)
if (!request.IsBodyRead) if (!request.IsBodyRead)
{ {
var body = await readBodyAsync(true, cancellationToken); var body = await readBodyAsync(true, cancellationToken);
request.Body = body; request.Body = body;
// Now set the flag to true // Now set the flag to true
// So that next time we can deliver body from cache // So that next time we can deliver body from cache
request.IsBodyRead = true; request.IsBodyRead = true;
OnDataSent(body, 0, body.Length); OnDataSent(body, 0, body.Length);
} }
} }
/// <summary> /// <summary>
/// reinit response object /// reinit response object
/// </summary> /// </summary>
internal async Task ClearResponse(CancellationToken cancellationToken) internal async Task ClearResponse(CancellationToken cancellationToken)
{ {
// syphon out the response body from server // syphon out the response body from server
await SyphonOutBodyAsync(false, cancellationToken); await SyphonOutBodyAsync(false, cancellationToken);
WebSession.Response = new Response(); WebSession.Response = new Response();
} }
internal void OnMultipartRequestPartSent(string boundary, HeaderCollection headers) internal void OnMultipartRequestPartSent(string boundary, HeaderCollection headers)
{ {
try try
{ {
MultipartRequestPartSent?.Invoke(this, new MultipartRequestPartSentEventArgs(boundary, headers)); MultipartRequestPartSent?.Invoke(this, new MultipartRequestPartSentEventArgs(boundary, headers));
} }
catch (Exception ex) catch (Exception ex)
{ {
exceptionFunc(new Exception("Exception thrown in user event", ex)); exceptionFunc(new Exception("Exception thrown in user event", ex));
} }
} }
/// <summary> /// <summary>
/// Read response body as byte[] for current response /// Read response body as byte[] for current response
/// </summary> /// </summary>
private async Task readResponseBodyAsync(CancellationToken cancellationToken) private async Task readResponseBodyAsync(CancellationToken cancellationToken)
{ {
if (!WebSession.Request.Locked) if (!WebSession.Request.Locked)
{ {
throw new Exception("You cannot read the response body before request is made to server."); throw new Exception("You cannot read the response body before request is made to server.");
} }
var response = WebSession.Response; var response = WebSession.Response;
if (!response.HasBody) if (!response.HasBody)
{ {
return; return;
} }
// If not already read (not cached yet) // If not already read (not cached yet)
if (!response.IsBodyRead) if (!response.IsBodyRead)
{ {
var body = await readBodyAsync(false, cancellationToken); var body = await readBodyAsync(false, cancellationToken);
response.Body = body; response.Body = body;
// Now set the flag to true // Now set the flag to true
// So that next time we can deliver body from cache // So that next time we can deliver body from cache
response.IsBodyRead = true; response.IsBodyRead = true;
OnDataReceived(body, 0, body.Length); OnDataReceived(body, 0, body.Length);
} }
} }
private async Task<byte[]> readBodyAsync(bool isRequest, CancellationToken cancellationToken) private async Task<byte[]> readBodyAsync(bool isRequest, CancellationToken cancellationToken)
{ {
using (var bodyStream = new MemoryStream()) using (var bodyStream = new MemoryStream())
{ {
var writer = new HttpWriter(bodyStream, bufferPool, bufferSize); var writer = new HttpWriter(bodyStream, bufferPool, bufferSize);
if (isRequest) if (isRequest)
{ {
await CopyRequestBodyAsync(writer, TransformationMode.Uncompress, cancellationToken); await CopyRequestBodyAsync(writer, TransformationMode.Uncompress, cancellationToken);
} }
else else
{ {
await CopyResponseBodyAsync(writer, TransformationMode.Uncompress, cancellationToken); await CopyResponseBodyAsync(writer, TransformationMode.Uncompress, cancellationToken);
} }
return bodyStream.ToArray(); return bodyStream.ToArray();
} }
} }
/// <summary> /// <summary>
/// Syphon out any left over data in given request/response from backing tcp connection. /// Syphon out any left over data in given request/response from backing tcp connection.
/// When user modifies the response/request we need to do this to reuse tcp connections. /// When user modifies the response/request we need to do this to reuse tcp connections.
/// </summary> /// </summary>
/// <param name="isRequest"></param> /// <param name="isRequest"></param>
/// <param name="cancellationToken"></param> /// <param name="cancellationToken"></param>
/// <returns></returns> /// <returns></returns>
internal async Task SyphonOutBodyAsync(bool isRequest, CancellationToken cancellationToken) internal async Task SyphonOutBodyAsync(bool isRequest, CancellationToken cancellationToken)
{ {
var requestResponse = isRequest ? (RequestResponseBase)WebSession.Request : WebSession.Response; var requestResponse = isRequest ? (RequestResponseBase)WebSession.Request : WebSession.Response;
if (requestResponse.OriginalIsBodyRead || !requestResponse.OriginalHasBody) if (requestResponse.OriginalIsBodyRead || !requestResponse.OriginalHasBody)
{ {
return; return;
} }
using (var bodyStream = new MemoryStream()) using (var bodyStream = new MemoryStream())
{ {
var writer = new HttpWriter(bodyStream, bufferPool, bufferSize); var writer = new HttpWriter(bodyStream, bufferPool, bufferSize);
await copyBodyAsync(isRequest, true, writer, TransformationMode.None, null, cancellationToken); await copyBodyAsync(isRequest, true, writer, TransformationMode.None, null, cancellationToken);
} }
} }
/// <summary> /// <summary>
/// This is called when the request is PUT/POST/PATCH to read the body /// This is called when the request is PUT/POST/PATCH to read the body
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
internal async Task CopyRequestBodyAsync(HttpWriter writer, TransformationMode transformation, CancellationToken cancellationToken) internal async Task CopyRequestBodyAsync(HttpWriter writer, TransformationMode transformation, CancellationToken cancellationToken)
{ {
var request = WebSession.Request; var request = WebSession.Request;
long contentLength = request.ContentLength; long contentLength = request.ContentLength;
// send the request body bytes to server // send the request body bytes to server
if (contentLength > 0 && hasMulipartEventSubscribers && request.IsMultipartFormData) if (contentLength > 0 && hasMulipartEventSubscribers && request.IsMultipartFormData)
{ {
var reader = getStreamReader(true); var reader = getStreamReader(true);
string boundary = HttpHelper.GetBoundaryFromContentType(request.ContentType); string boundary = HttpHelper.GetBoundaryFromContentType(request.ContentType);
using (var copyStream = new CopyStream(reader, writer, bufferPool, bufferSize)) using (var copyStream = new CopyStream(reader, writer, bufferPool, bufferSize))
{ {
while (contentLength > copyStream.ReadBytes) while (contentLength > copyStream.ReadBytes)
{ {
long read = await readUntilBoundaryAsync(copyStream, contentLength, boundary, cancellationToken); long read = await readUntilBoundaryAsync(copyStream, contentLength, boundary, cancellationToken);
if (read == 0) if (read == 0)
{ {
break; break;
} }
if (contentLength > copyStream.ReadBytes) if (contentLength > copyStream.ReadBytes)
{ {
var headers = new HeaderCollection(); var headers = new HeaderCollection();
await HeaderParser.ReadHeaders(copyStream, headers, cancellationToken); await HeaderParser.ReadHeaders(copyStream, headers, cancellationToken);
OnMultipartRequestPartSent(boundary, headers); OnMultipartRequestPartSent(boundary, headers);
} }
} }
await copyStream.FlushAsync(cancellationToken); await copyStream.FlushAsync(cancellationToken);
} }
} }
else else
{ {
await copyBodyAsync(true, false, writer, transformation, OnDataSent, cancellationToken); await copyBodyAsync(true, false, writer, transformation, OnDataSent, cancellationToken);
} }
} }
internal async Task CopyResponseBodyAsync(HttpWriter writer, TransformationMode transformation, CancellationToken cancellationToken) internal async Task CopyResponseBodyAsync(HttpWriter writer, TransformationMode transformation, CancellationToken cancellationToken)
{ {
await copyBodyAsync(false, false, writer, transformation, OnDataReceived, cancellationToken); await copyBodyAsync(false, false, writer, transformation, OnDataReceived, cancellationToken);
} }
private async Task copyBodyAsync(bool isRequest, bool useOriginalHeaderValues, HttpWriter writer, TransformationMode transformation, Action<byte[], int, int> onCopy, CancellationToken cancellationToken) private async Task copyBodyAsync(bool isRequest, bool useOriginalHeaderValues, HttpWriter writer, TransformationMode transformation, Action<byte[], int, int> onCopy, CancellationToken cancellationToken)
{ {
var stream = getStreamReader(isRequest); var stream = getStreamReader(isRequest);
var requestResponse = isRequest ? (RequestResponseBase)WebSession.Request : WebSession.Response; var requestResponse = isRequest ? (RequestResponseBase)WebSession.Request : WebSession.Response;
bool isChunked = useOriginalHeaderValues? requestResponse.OriginalIsChunked : requestResponse.IsChunked; bool isChunked = useOriginalHeaderValues? requestResponse.OriginalIsChunked : requestResponse.IsChunked;
long contentLength = useOriginalHeaderValues ? requestResponse.OriginalContentLength : requestResponse.ContentLength; long contentLength = useOriginalHeaderValues ? requestResponse.OriginalContentLength : requestResponse.ContentLength;
if (transformation == TransformationMode.None) if (transformation == TransformationMode.None)
{ {
await writer.CopyBodyAsync(stream, isChunked, contentLength, onCopy, cancellationToken); await writer.CopyBodyAsync(stream, isChunked, contentLength, onCopy, cancellationToken);
return; return;
} }
LimitedStream limitedStream; LimitedStream limitedStream;
Stream decompressStream = null; Stream decompressStream = null;
string contentEncoding = useOriginalHeaderValues ? requestResponse.OriginalContentEncoding : requestResponse.ContentEncoding; string contentEncoding = useOriginalHeaderValues ? requestResponse.OriginalContentEncoding : requestResponse.ContentEncoding;
Stream s = limitedStream = new LimitedStream(stream, bufferPool, isChunked, contentLength); Stream s = limitedStream = new LimitedStream(stream, bufferPool, isChunked, contentLength);
if (transformation == TransformationMode.Uncompress && contentEncoding != null) if (transformation == TransformationMode.Uncompress && contentEncoding != null)
{ {
s = decompressStream = DecompressionFactory.Create(contentEncoding, s); s = decompressStream = DecompressionFactory.Create(contentEncoding, s);
} }
try try
{ {
using (var bufStream = new CustomBufferedStream(s, bufferPool, bufferSize, true)) using (var bufStream = new CustomBufferedStream(s, bufferPool, bufferSize, true))
{ {
await writer.CopyBodyAsync(bufStream, false, -1, onCopy, cancellationToken); await writer.CopyBodyAsync(bufStream, false, -1, onCopy, cancellationToken);
} }
} }
finally finally
{ {
decompressStream?.Dispose(); decompressStream?.Dispose();
await limitedStream.Finish(); await limitedStream.Finish();
limitedStream.Dispose(); limitedStream.Dispose();
} }
} }
/// <summary> /// <summary>
/// Read a line from the byte stream /// Read a line from the byte stream
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
private async Task<long> readUntilBoundaryAsync(ICustomStreamReader reader, long totalBytesToRead, string boundary, CancellationToken cancellationToken) private async Task<long> readUntilBoundaryAsync(ICustomStreamReader reader, long totalBytesToRead, string boundary, CancellationToken cancellationToken)
{ {
int bufferDataLength = 0; int bufferDataLength = 0;
var buffer = bufferPool.GetBuffer(bufferSize); var buffer = bufferPool.GetBuffer(bufferSize);
try try
{ {
int boundaryLength = boundary.Length + 4; int boundaryLength = boundary.Length + 4;
long bytesRead = 0; long bytesRead = 0;
while (bytesRead < totalBytesToRead && (reader.DataAvailable || await reader.FillBufferAsync(cancellationToken))) while (bytesRead < totalBytesToRead && (reader.DataAvailable || await reader.FillBufferAsync(cancellationToken)))
{ {
byte newChar = reader.ReadByteFromBuffer(); byte newChar = reader.ReadByteFromBuffer();
buffer[bufferDataLength] = newChar; buffer[bufferDataLength] = newChar;
bufferDataLength++; bufferDataLength++;
bytesRead++; bytesRead++;
if (bufferDataLength >= boundaryLength) if (bufferDataLength >= boundaryLength)
{ {
int startIdx = bufferDataLength - boundaryLength; int startIdx = bufferDataLength - boundaryLength;
if (buffer[startIdx] == '-' && buffer[startIdx + 1] == '-') if (buffer[startIdx] == '-' && buffer[startIdx + 1] == '-')
{ {
startIdx += 2; startIdx += 2;
bool ok = true; bool ok = true;
for (int i = 0; i < boundary.Length; i++) for (int i = 0; i < boundary.Length; i++)
{ {
if (buffer[startIdx + i] != boundary[i]) if (buffer[startIdx + i] != boundary[i])
{ {
ok = false; ok = false;
break; break;
} }
} }
if (ok) if (ok)
{ {
break; break;
} }
} }
} }
if (bufferDataLength == buffer.Length) if (bufferDataLength == buffer.Length)
{ {
// boundary is not longer than 70 bytes according to the specification, so keeping the last 100 (minimum 74) bytes is enough // boundary is not longer than 70 bytes according to the specification, so keeping the last 100 (minimum 74) bytes is enough
const int bytesToKeep = 100; const int bytesToKeep = 100;
Buffer.BlockCopy(buffer, buffer.Length - bytesToKeep, buffer, 0, bytesToKeep); Buffer.BlockCopy(buffer, buffer.Length - bytesToKeep, buffer, 0, bytesToKeep);
bufferDataLength = bytesToKeep; bufferDataLength = bytesToKeep;
} }
} }
return bytesRead; return bytesRead;
} }
finally finally
{ {
bufferPool.ReturnBuffer(buffer); bufferPool.ReturnBuffer(buffer);
} }
} }
/// <summary> /// <summary>
/// Gets the request body as bytes. /// Gets the request body as bytes.
/// </summary> /// </summary>
/// <param name="cancellationToken">Optional cancellation token for this async task.</param> /// <param name="cancellationToken">Optional cancellation token for this async task.</param>
/// <returns>The body as bytes.</returns> /// <returns>The body as bytes.</returns>
public async Task<byte[]> GetRequestBody(CancellationToken cancellationToken = default) public async Task<byte[]> GetRequestBody(CancellationToken cancellationToken = default)
{ {
if (!WebSession.Request.IsBodyRead) if (!WebSession.Request.IsBodyRead)
{ {
await readRequestBodyAsync(cancellationToken); await readRequestBodyAsync(cancellationToken);
} }
return WebSession.Request.Body; return WebSession.Request.Body;
} }
/// <summary> /// <summary>
/// Gets the request body as string. /// Gets the request body as string.
/// </summary> /// </summary>
/// <param name="cancellationToken">Optional cancellation token for this async task.</param> /// <param name="cancellationToken">Optional cancellation token for this async task.</param>
/// <returns>The body as string.</returns> /// <returns>The body as string.</returns>
public async Task<string> GetRequestBodyAsString(CancellationToken cancellationToken = default) public async Task<string> GetRequestBodyAsString(CancellationToken cancellationToken = default)
{ {
if (!WebSession.Request.IsBodyRead) if (!WebSession.Request.IsBodyRead)
{ {
await readRequestBodyAsync(cancellationToken); await readRequestBodyAsync(cancellationToken);
} }
return WebSession.Request.BodyString; return WebSession.Request.BodyString;
} }
/// <summary> /// <summary>
/// Sets the request body. /// Sets the request body.
/// </summary> /// </summary>
/// <param name="body">The request body bytes.</param> /// <param name="body">The request body bytes.</param>
public void SetRequestBody(byte[] body) public void SetRequestBody(byte[] body)
{ {
var request = WebSession.Request; var request = WebSession.Request;
if (request.Locked) if (request.Locked)
{ {
throw new Exception("You cannot call this function after request is made to server."); throw new Exception("You cannot call this function after request is made to server.");
} }
request.Body = body; request.Body = body;
} }
/// <summary> /// <summary>
/// Sets the body with the specified string. /// Sets the body with the specified string.
/// </summary> /// </summary>
/// <param name="body">The request body string to set.</param> /// <param name="body">The request body string to set.</param>
public void SetRequestBodyString(string body) public void SetRequestBodyString(string body)
{ {
if (WebSession.Request.Locked) if (WebSession.Request.Locked)
{ {
throw new Exception("You cannot call this function after request is made to server."); throw new Exception("You cannot call this function after request is made to server.");
} }
SetRequestBody(WebSession.Request.Encoding.GetBytes(body)); SetRequestBody(WebSession.Request.Encoding.GetBytes(body));
} }
/// <summary> /// <summary>
/// Gets the response body as bytes. /// Gets the response body as bytes.
/// </summary> /// </summary>
/// <param name="cancellationToken">Optional cancellation token for this async task.</param> /// <param name="cancellationToken">Optional cancellation token for this async task.</param>
/// <returns>The resulting bytes.</returns> /// <returns>The resulting bytes.</returns>
public async Task<byte[]> GetResponseBody(CancellationToken cancellationToken = default) public async Task<byte[]> GetResponseBody(CancellationToken cancellationToken = default)
{ {
if (!WebSession.Response.IsBodyRead) if (!WebSession.Response.IsBodyRead)
{ {
await readResponseBodyAsync(cancellationToken); await readResponseBodyAsync(cancellationToken);
} }
return WebSession.Response.Body; return WebSession.Response.Body;
} }
/// <summary> /// <summary>
/// Gets the response body as string. /// Gets the response body as string.
/// </summary> /// </summary>
/// <param name="cancellationToken">Optional cancellation token for this async task.</param> /// <param name="cancellationToken">Optional cancellation token for this async task.</param>
/// <returns>The string body.</returns> /// <returns>The string body.</returns>
public async Task<string> GetResponseBodyAsString(CancellationToken cancellationToken = default) public async Task<string> GetResponseBodyAsString(CancellationToken cancellationToken = default)
{ {
if (!WebSession.Response.IsBodyRead) if (!WebSession.Response.IsBodyRead)
{ {
await readResponseBodyAsync(cancellationToken); await readResponseBodyAsync(cancellationToken);
} }
return WebSession.Response.BodyString; return WebSession.Response.BodyString;
} }
/// <summary> /// <summary>
/// Set the response body bytes. /// Set the response body bytes.
/// </summary> /// </summary>
/// <param name="body">The body bytes to set.</param> /// <param name="body">The body bytes to set.</param>
public void SetResponseBody(byte[] body) public void SetResponseBody(byte[] body)
{ {
if (!WebSession.Request.Locked) if (!WebSession.Request.Locked)
{ {
throw new Exception("You cannot call this function before request is made to server."); throw new Exception("You cannot call this function before request is made to server.");
} }
var response = WebSession.Response; var response = WebSession.Response;
response.Body = body; response.Body = body;
} }
/// <summary> /// <summary>
/// Replace the response body with the specified string. /// Replace the response body with the specified string.
/// </summary> /// </summary>
/// <param name="body">The body string to set.</param> /// <param name="body">The body string to set.</param>
public void SetResponseBodyString(string body) public void SetResponseBodyString(string body)
{ {
if (!WebSession.Request.Locked) if (!WebSession.Request.Locked)
{ {
throw new Exception("You cannot call this function before request is made to server."); throw new Exception("You cannot call this function before request is made to server.");
} }
var bodyBytes = WebSession.Response.Encoding.GetBytes(body); var bodyBytes = WebSession.Response.Encoding.GetBytes(body);
SetResponseBody(bodyBytes); SetResponseBody(bodyBytes);
} }
/// <summary> /// <summary>
/// Before request is made to server respond with the specified HTML string to client /// Before request is made to server respond with the specified HTML string to client
/// and ignore the request. /// and ignore the request.
/// </summary> /// </summary>
/// <param name="html">HTML content to sent.</param> /// <param name="html">HTML content to sent.</param>
/// <param name="headers">HTTP response headers.</param> /// <param name="headers">HTTP response headers.</param>
/// <param name="closeServerConnection">Close the server connection used by request if any?</param> /// <param name="closeServerConnection">Close the server connection used by request if any?</param>
public void Ok(string html, Dictionary<string, HttpHeader> headers = null, public void Ok(string html, Dictionary<string, HttpHeader> headers = null,
bool closeServerConnection = false) bool closeServerConnection = false)
{ {
var response = new OkResponse(); var response = new OkResponse();
if (headers != null) if (headers != null)
{ {
response.Headers.AddHeaders(headers); response.Headers.AddHeaders(headers);
} }
response.HttpVersion = WebSession.Request.HttpVersion; response.HttpVersion = WebSession.Request.HttpVersion;
response.Body = response.Encoding.GetBytes(html ?? string.Empty); response.Body = response.Encoding.GetBytes(html ?? string.Empty);
Respond(response, closeServerConnection); Respond(response, closeServerConnection);
} }
/// <summary> /// <summary>
/// Before request is made to server respond with the specified byte[] to client /// Before request is made to server respond with the specified byte[] to client
/// and ignore the request. /// and ignore the request.
/// </summary> /// </summary>
/// <param name="result">The html content bytes.</param> /// <param name="result">The html content bytes.</param>
/// <param name="headers">The HTTP headers.</param> /// <param name="headers">The HTTP headers.</param>
/// <param name="closeServerConnection">Close the server connection used by request if any?</param> /// <param name="closeServerConnection">Close the server connection used by request if any?</param>
public void Ok(byte[] result, Dictionary<string, HttpHeader> headers = null, public void Ok(byte[] result, Dictionary<string, HttpHeader> headers = null,
bool closeServerConnection = false) bool closeServerConnection = false)
{ {
var response = new OkResponse(); var response = new OkResponse();
response.Headers.AddHeaders(headers); response.Headers.AddHeaders(headers);
response.HttpVersion = WebSession.Request.HttpVersion; response.HttpVersion = WebSession.Request.HttpVersion;
response.Body = result; response.Body = result;
Respond(response, closeServerConnection); Respond(response, closeServerConnection);
} }
/// <summary> /// <summary>
/// Before request is made to server  /// Before request is made to server 
/// respond with the specified HTML string and the specified status to client. /// respond with the specified HTML string and the specified status to client.
/// And then ignore the request.  /// And then ignore the request. 
/// </summary> /// </summary>
/// <param name="html">The html content.</param> /// <param name="html">The html content.</param>
/// <param name="status">The HTTP status code.</param> /// <param name="status">The HTTP status code.</param>
/// <param name="headers">The HTTP headers.</param> /// <param name="headers">The HTTP headers.</param>
/// <param name="closeServerConnection">Close the server connection used by request if any?</param> /// <param name="closeServerConnection">Close the server connection used by request if any?</param>
public void GenericResponse(string html, HttpStatusCode status, public void GenericResponse(string html, HttpStatusCode status,
Dictionary<string, HttpHeader> headers = null, bool closeServerConnection = false) Dictionary<string, HttpHeader> headers = null, bool closeServerConnection = false)
{ {
var response = new GenericResponse(status); var response = new GenericResponse(status);
response.HttpVersion = WebSession.Request.HttpVersion; response.HttpVersion = WebSession.Request.HttpVersion;
response.Headers.AddHeaders(headers); response.Headers.AddHeaders(headers);
response.Body = response.Encoding.GetBytes(html ?? string.Empty); response.Body = response.Encoding.GetBytes(html ?? string.Empty);
Respond(response, closeServerConnection); Respond(response, closeServerConnection);
} }
/// <summary> /// <summary>
/// Before request is made to server respond with the specified byte[], /// Before request is made to server respond with the specified byte[],
/// the specified status to client. And then ignore the request. /// the specified status to client. And then ignore the request.
/// </summary> /// </summary>
/// <param name="result">The bytes to sent.</param> /// <param name="result">The bytes to sent.</param>
/// <param name="status">The HTTP status code.</param> /// <param name="status">The HTTP status code.</param>
/// <param name="headers">The HTTP headers.</param> /// <param name="headers">The HTTP headers.</param>
/// <param name="closeServerConnection">Close the server connection used by request if any?</param> /// <param name="closeServerConnection">Close the server connection used by request if any?</param>
public void GenericResponse(byte[] result, HttpStatusCode status, public void GenericResponse(byte[] result, HttpStatusCode status,
Dictionary<string, HttpHeader> headers, bool closeServerConnection = false) Dictionary<string, HttpHeader> headers, bool closeServerConnection = false)
{ {
var response = new GenericResponse(status); var response = new GenericResponse(status);
response.HttpVersion = WebSession.Request.HttpVersion; response.HttpVersion = WebSession.Request.HttpVersion;
response.Headers.AddHeaders(headers); response.Headers.AddHeaders(headers);
response.Body = result; response.Body = result;
Respond(response, closeServerConnection); Respond(response, closeServerConnection);
} }
/// <summary> /// <summary>
/// Redirect to provided URL. /// Redirect to provided URL.
/// </summary> /// </summary>
/// <param name="url">The URL to redirect.</param> /// <param name="url">The URL to redirect.</param>
/// <param name="closeServerConnection">Close the server connection used by request if any?</param> /// <param name="closeServerConnection">Close the server connection used by request if any?</param>
public void Redirect(string url, bool closeServerConnection = false) public void Redirect(string url, bool closeServerConnection = false)
{ {
var response = new RedirectResponse(); var response = new RedirectResponse();
response.HttpVersion = WebSession.Request.HttpVersion; response.HttpVersion = WebSession.Request.HttpVersion;
response.Headers.AddHeader(KnownHeaders.Location, url); response.Headers.AddHeader(KnownHeaders.Location, url);
response.Body = emptyData; response.Body = emptyData;
Respond(response, closeServerConnection); Respond(response, closeServerConnection);
} }
/// <summary> /// <summary>
/// Respond with given response object to client. /// Respond with given response object to client.
/// </summary> /// </summary>
/// <param name="response">The response object.</param> /// <param name="response">The response object.</param>
/// <param name="closeServerConnection">Close the server connection used by request if any?</param> /// <param name="closeServerConnection">Close the server connection used by request if any?</param>
public void Respond(Response response, bool closeServerConnection = false) public void Respond(Response response, bool closeServerConnection = false)
{ {
//request already send/ready to be sent. //request already send/ready to be sent.
if (WebSession.Request.Locked) if (WebSession.Request.Locked)
{ {
//response already received from server and ready to be sent to client. //response already received from server and ready to be sent to client.
if (WebSession.Response.Locked) if (WebSession.Response.Locked)
{ {
throw new Exception("You cannot call this function after response is sent to the client."); throw new Exception("You cannot call this function after response is sent to the client.");
} }
//cleanup original response. //cleanup original response.
if (closeServerConnection) if (closeServerConnection)
{ {
//no need to cleanup original connection. //no need to cleanup original connection.
//it will be closed any way. //it will be closed any way.
TerminateServerConnection(); TerminateServerConnection();
} }
response.SetOriginalHeaders(WebSession.Response); response.SetOriginalHeaders(WebSession.Response);
//response already received from server but not yet ready to sent to client. //response already received from server but not yet ready to sent to client.
WebSession.Response = response; WebSession.Response = response;
WebSession.Response.Locked = true; WebSession.Response.Locked = true;
} }
//request not yet sent/not yet ready to be sent. //request not yet sent/not yet ready to be sent.
else else
{ {
WebSession.Request.Locked = true; WebSession.Request.Locked = true;
WebSession.Request.CancelRequest = true; WebSession.Request.CancelRequest = true;
//set new response. //set new response.
WebSession.Response = response; WebSession.Response = response;
WebSession.Response.Locked = true; WebSession.Response.Locked = true;
} }
} }
/// <summary> /// <summary>
/// Terminate the connection to server at the end of this HTTP request/response session. /// Terminate the connection to server at the end of this HTTP request/response session.
/// </summary> /// </summary>
public void TerminateServerConnection() public void TerminateServerConnection()
{ {
WebSession.CloseServerConnection = true; WebSession.CloseServerConnection = true;
} }
/// <summary> /// <summary>
/// Implement any cleanup here /// Implement any cleanup here
/// </summary> /// </summary>
public override void Dispose() public override void Dispose()
{ {
MultipartRequestPartSent = null; MultipartRequestPartSent = null;
base.Dispose(); base.Dispose();
} }
} }
} }
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Titanium.Web.Proxy.EventArguments; using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Models; using Titanium.Web.Proxy.Models;
namespace Titanium.Web.Proxy.Exceptions namespace Titanium.Web.Proxy.Exceptions
{ {
/// <summary> /// <summary>
/// Proxy authorization exception. /// Proxy authorization exception.
/// </summary> /// </summary>
public class ProxyAuthorizationException : ProxyException public class ProxyAuthorizationException : ProxyException
{ {
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ProxyAuthorizationException" /> class. /// Initializes a new instance of the <see cref="ProxyAuthorizationException" /> class.
/// </summary> /// </summary>
/// <param name="message">Exception message.</param> /// <param name="message">Exception message.</param>
/// <param name="session">The <see cref="SessionEventArgs" /> instance containing the event data.</param> /// <param name="session">The <see cref="SessionEventArgs" /> instance containing the event data.</param>
/// <param name="innerException">Inner exception associated to upstream proxy authorization</param> /// <param name="innerException">Inner exception associated to upstream proxy authorization</param>
/// <param name="headers">Http's headers associated</param> /// <param name="headers">Http's headers associated</param>
internal ProxyAuthorizationException(string message, SessionEventArgsBase session, Exception innerException, internal ProxyAuthorizationException(string message, SessionEventArgsBase session, Exception innerException,
IEnumerable<HttpHeader> headers) : base(message, innerException) IEnumerable<HttpHeader> headers) : base(message, innerException)
{ {
Session = session; Session = session;
Headers = headers; Headers = headers;
} }
/// <summary> /// <summary>
/// The current session within which this error happened. /// The current session within which this error happened.
/// </summary> /// </summary>
public SessionEventArgsBase Session { get; } public SessionEventArgsBase Session { get; }
/// <summary> /// <summary>
/// Headers associated with the authorization exception. /// Headers associated with the authorization exception.
/// </summary> /// </summary>
public IEnumerable<HttpHeader> Headers { get; } public IEnumerable<HttpHeader> Headers { get; }
} }
} }
using System; using System;
namespace Titanium.Web.Proxy.Exceptions namespace Titanium.Web.Proxy.Exceptions
{ {
/// <summary> /// <summary>
/// Base class exception associated with this proxy server. /// Base class exception associated with this proxy server.
/// </summary> /// </summary>
public abstract class ProxyException : Exception public abstract class ProxyException : Exception
{ {
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ProxyException" /> class. /// Initializes a new instance of the <see cref="ProxyException" /> class.
/// - must be invoked by derived classes' constructors /// - must be invoked by derived classes' constructors
/// </summary> /// </summary>
/// <param name="message">Exception message</param> /// <param name="message">Exception message</param>
protected ProxyException(string message) : base(message) protected ProxyException(string message) : base(message)
{ {
} }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ProxyException" /> class. /// Initializes a new instance of the <see cref="ProxyException" /> class.
/// - must be invoked by derived classes' constructors /// - must be invoked by derived classes' constructors
/// </summary> /// </summary>
/// <param name="message">Excception message</param> /// <param name="message">Excception message</param>
/// <param name="innerException">Inner exception associated</param> /// <param name="innerException">Inner exception associated</param>
protected ProxyException(string message, Exception innerException) : base(message, innerException) protected ProxyException(string message, Exception innerException) : base(message, innerException)
{ {
} }
} }
} }
using System; using System;
using Titanium.Web.Proxy.EventArguments; using Titanium.Web.Proxy.EventArguments;
namespace Titanium.Web.Proxy.Exceptions namespace Titanium.Web.Proxy.Exceptions
{ {
/// <summary> /// <summary>
/// Proxy HTTP exception. /// Proxy HTTP exception.
/// </summary> /// </summary>
public class ProxyHttpException : ProxyException public class ProxyHttpException : ProxyException
{ {
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ProxyHttpException" /> class. /// Initializes a new instance of the <see cref="ProxyHttpException" /> class.
/// </summary> /// </summary>
/// <param name="message">Message for this exception</param> /// <param name="message">Message for this exception</param>
/// <param name="innerException">Associated inner exception</param> /// <param name="innerException">Associated inner exception</param>
/// <param name="sessionEventArgs">Instance of <see cref="EventArguments.SessionEventArgs" /> associated to the exception</param> /// <param name="sessionEventArgs">Instance of <see cref="EventArguments.SessionEventArgs" /> associated to the exception</param>
internal ProxyHttpException(string message, Exception innerException, SessionEventArgs sessionEventArgs) : base( internal ProxyHttpException(string message, Exception innerException, SessionEventArgs sessionEventArgs) : base(
message, innerException) message, innerException)
{ {
SessionEventArgs = sessionEventArgs; SessionEventArgs = sessionEventArgs;
} }
/// <summary> /// <summary>
/// Gets session info associated to the exception. /// Gets session info associated to the exception.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// This object properties should not be edited. /// This object properties should not be edited.
/// </remarks> /// </remarks>
public SessionEventArgs SessionEventArgs { get; } public SessionEventArgs SessionEventArgs { get; }
} }
} }
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Net.Sockets; using System.Net.Sockets;
namespace Titanium.Web.Proxy.Helpers namespace Titanium.Web.Proxy.Helpers
{ {
internal class NetworkHelper internal class NetworkHelper
{ {
/// <summary> /// <summary>
/// Adapated from below link /// Adapated from below link
/// http://stackoverflow.com/questions/11834091/how-to-check-if-localhost /// http://stackoverflow.com/questions/11834091/how-to-check-if-localhost
/// </summary> /// </summary>
/// <param name="address"></param> /// <param name="address"></param>
/// <returns></returns> /// <returns></returns>
internal static bool IsLocalIpAddress(IPAddress address) internal static bool IsLocalIpAddress(IPAddress address)
{ {
if (IPAddress.IsLoopback(address)) if (IPAddress.IsLoopback(address))
{ {
return true; return true;
} }
// get local IP addresses // get local IP addresses
var localIPs = Dns.GetHostAddresses(Dns.GetHostName()); var localIPs = Dns.GetHostAddresses(Dns.GetHostName());
// test if any host IP equals to any local IP or to localhost // test if any host IP equals to any local IP or to localhost
return localIPs.Contains(address); return localIPs.Contains(address);
} }
internal static bool IsLocalIpAddress(string hostName) internal static bool IsLocalIpAddress(string hostName)
{ {
hostName = hostName.ToLower(); hostName = hostName.ToLower();
if (hostName == "127.0.0.1" if (hostName == "127.0.0.1"
|| hostName == "localhost") || hostName == "localhost")
{ {
return true; return true;
} }
var localhostDnsName = Dns.GetHostName().ToLower(); var localhostDnsName = Dns.GetHostName().ToLower();
//if hostname matches current machine DNS name //if hostname matches current machine DNS name
if (hostName == localhostDnsName) if (hostName == localhostDnsName)
{ {
return true; return true;
} }
var isLocalhost = false; var isLocalhost = false;
IPHostEntry hostEntry = null; IPHostEntry hostEntry = null;
//check if parsable to an IP Address //check if parsable to an IP Address
if (IPAddress.TryParse(hostName, out var ipAddress)) if (IPAddress.TryParse(hostName, out var ipAddress))
{ {
hostEntry = Dns.GetHostEntry(localhostDnsName); hostEntry = Dns.GetHostEntry(localhostDnsName);
isLocalhost = hostEntry.AddressList.Any(x => x.Equals(ipAddress)); isLocalhost = hostEntry.AddressList.Any(x => x.Equals(ipAddress));
} }
if (!isLocalhost) if (!isLocalhost)
{ {
try try
{ {
hostEntry = Dns.GetHostEntry(hostName); hostEntry = Dns.GetHostEntry(hostName);
isLocalhost = hostEntry.AddressList.Any(x => hostEntry.AddressList.Any(x.Equals)); isLocalhost = hostEntry.AddressList.Any(x => hostEntry.AddressList.Any(x.Equals));
} }
catch (SocketException) catch (SocketException)
{ {
} }
} }
return isLocalhost; return isLocalhost;
} }
} }
} }
using System; using System;
using System.IO; using System.IO;
using System.Net; using System.Net;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Titanium.Web.Proxy.Exceptions; using Titanium.Web.Proxy.Exceptions;
using Titanium.Web.Proxy.Extensions; using Titanium.Web.Proxy.Extensions;
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.Shared;
namespace Titanium.Web.Proxy.Http namespace Titanium.Web.Proxy.Http
{ {
/// <summary> /// <summary>
/// Used to communicate with the server over HTTP(S) /// Used to communicate with the server over HTTP(S)
/// </summary> /// </summary>
public class HttpWebClient public class HttpWebClient
{ {
internal HttpWebClient(Request request = null, Response response = null) internal HttpWebClient(Request request = null, Response response = null)
{ {
Request = request ?? new Request(); Request = request ?? new Request();
Response = response ?? new Response(); Response = response ?? new Response();
} }
/// <summary> /// <summary>
/// Connection to server /// Connection to server
/// </summary> /// </summary>
internal TcpServerConnection ServerConnection { get; set; } internal TcpServerConnection ServerConnection { get; set; }
/// <summary> /// <summary>
/// Should we close the server connection at the end of this HTTP request/response session. /// Should we close the server connection at the end of this HTTP request/response session.
/// </summary> /// </summary>
internal bool CloseServerConnection { get; set; } internal bool CloseServerConnection { get; set; }
/// <summary> /// <summary>
/// Stores internal data for the session. /// Stores internal data for the session.
/// </summary> /// </summary>
internal InternalDataStore Data { get; } = new InternalDataStore(); internal InternalDataStore Data { get; } = new InternalDataStore();
/// <summary> /// <summary>
/// Gets or sets the user data. /// Gets or sets the user data.
/// </summary> /// </summary>
public object UserData { get; set; } public object UserData { get; set; }
/// <summary> /// <summary>
/// Override UpStreamEndPoint for this request; Local NIC via request is made /// Override UpStreamEndPoint for this request; Local NIC via request is made
/// </summary> /// </summary>
public IPEndPoint UpStreamEndPoint { get; set; } public IPEndPoint UpStreamEndPoint { get; set; }
/// <summary> /// <summary>
/// Headers passed with Connect. /// Headers passed with Connect.
/// </summary> /// </summary>
public ConnectRequest ConnectRequest { get; internal set; } public ConnectRequest ConnectRequest { get; internal set; }
/// <summary> /// <summary>
/// Web Request. /// Web Request.
/// </summary> /// </summary>
public Request Request { get; } public Request Request { get; }
/// <summary> /// <summary>
/// Web Response. /// Web Response.
/// </summary> /// </summary>
public Response Response { get; internal set; } public Response Response { get; internal set; }
/// <summary> /// <summary>
/// PID of the process that is created the current session when client is running in this machine /// PID of the process that is created the current session when client is running in this machine
/// If client is remote then this will return /// If client is remote then this will return
/// </summary> /// </summary>
public Lazy<int> ProcessId { get; internal set; } public Lazy<int> ProcessId { get; internal set; }
/// <summary> /// <summary>
/// Is Https? /// Is Https?
/// </summary> /// </summary>
public bool IsHttps => Request.IsHttps; public bool IsHttps => Request.IsHttps;
/// <summary> /// <summary>
/// Set the tcp connection to server used by this webclient /// Set the tcp connection to server used by this webclient
/// </summary> /// </summary>
/// <param name="serverConnection">Instance of <see cref="TcpServerConnection" /></param> /// <param name="serverConnection">Instance of <see cref="TcpServerConnection" /></param>
internal void SetConnection(TcpServerConnection serverConnection) internal void SetConnection(TcpServerConnection serverConnection)
{ {
serverConnection.LastAccess = DateTime.Now; serverConnection.LastAccess = DateTime.Now;
ServerConnection = serverConnection; ServerConnection = serverConnection;
} }
/// <summary> /// <summary>
/// Prepare and send the http(s) request /// Prepare and send the http(s) request
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
internal async Task SendRequest(bool enable100ContinueBehaviour, bool isTransparent, internal async Task SendRequest(bool enable100ContinueBehaviour, bool isTransparent,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
var upstreamProxy = ServerConnection.UpStreamProxy; var upstreamProxy = ServerConnection.UpStreamProxy;
bool useUpstreamProxy = upstreamProxy != null && ServerConnection.IsHttps == false; bool useUpstreamProxy = upstreamProxy != null && ServerConnection.IsHttps == false;
var writer = ServerConnection.StreamWriter; var writer = ServerConnection.StreamWriter;
// prepare the request & headers // prepare the request & headers
await writer.WriteLineAsync(Request.CreateRequestLine(Request.Method, await writer.WriteLineAsync(Request.CreateRequestLine(Request.Method,
useUpstreamProxy || isTransparent ? Request.OriginalUrl : Request.RequestUri.PathAndQuery, useUpstreamProxy || isTransparent ? Request.OriginalUrl : Request.RequestUri.PathAndQuery,
Request.HttpVersion), cancellationToken); Request.HttpVersion), cancellationToken);
var headerBuilder = new StringBuilder(); var headerBuilder = new StringBuilder();
// Send Authentication to Upstream proxy if needed // Send Authentication to Upstream proxy if needed
if (!isTransparent && upstreamProxy != null if (!isTransparent && upstreamProxy != null
&& ServerConnection.IsHttps == false && ServerConnection.IsHttps == false
&& !string.IsNullOrEmpty(upstreamProxy.UserName) && !string.IsNullOrEmpty(upstreamProxy.UserName)
&& upstreamProxy.Password != null) && upstreamProxy.Password != null)
{ {
headerBuilder.Append($"{HttpHeader.ProxyConnectionKeepAlive}{ProxyConstants.NewLine}"); headerBuilder.Append($"{HttpHeader.ProxyConnectionKeepAlive}{ProxyConstants.NewLine}");
headerBuilder.Append($"{HttpHeader.GetProxyAuthorizationHeader(upstreamProxy.UserName, upstreamProxy.Password)}{ProxyConstants.NewLine}"); headerBuilder.Append($"{HttpHeader.GetProxyAuthorizationHeader(upstreamProxy.UserName, upstreamProxy.Password)}{ProxyConstants.NewLine}");
} }
// write request headers // write request headers
foreach (var header in Request.Headers) foreach (var header in Request.Headers)
{ {
if (isTransparent || header.Name != KnownHeaders.ProxyAuthorization) if (isTransparent || header.Name != KnownHeaders.ProxyAuthorization)
{ {
headerBuilder.Append($"{header}{ProxyConstants.NewLine}"); headerBuilder.Append($"{header}{ProxyConstants.NewLine}");
} }
} }
headerBuilder.Append(ProxyConstants.NewLine); headerBuilder.Append(ProxyConstants.NewLine);
await writer.WriteAsync(headerBuilder.ToString(), cancellationToken); await writer.WriteAsync(headerBuilder.ToString(), cancellationToken);
if (enable100ContinueBehaviour) if (enable100ContinueBehaviour)
{ {
if (Request.ExpectContinue) if (Request.ExpectContinue)
{ {
string httpStatus; string httpStatus;
try try
{ {
httpStatus = await ServerConnection.Stream.ReadLineAsync(cancellationToken); httpStatus = await ServerConnection.Stream.ReadLineAsync(cancellationToken);
if (httpStatus == null) if (httpStatus == null)
{ {
throw new ServerConnectionException("Server connection was closed."); throw new ServerConnectionException("Server connection was closed.");
} }
} }
catch (Exception e) when (!(e is ServerConnectionException)) catch (Exception e) when (!(e is ServerConnectionException))
{ {
throw new ServerConnectionException("Server connection was closed."); throw new ServerConnectionException("Server connection was closed.");
} }
Response.ParseResponseLine(httpStatus, out _, out int responseStatusCode, Response.ParseResponseLine(httpStatus, out _, out int responseStatusCode,
out string responseStatusDescription); out string responseStatusDescription);
// find if server is willing for expect continue // find if server is willing for expect continue
if (responseStatusCode == (int)HttpStatusCode.Continue if (responseStatusCode == (int)HttpStatusCode.Continue
&& responseStatusDescription.EqualsIgnoreCase("continue")) && responseStatusDescription.EqualsIgnoreCase("continue"))
{ {
Request.Is100Continue = true; Request.Is100Continue = true;
await ServerConnection.Stream.ReadLineAsync(cancellationToken); await ServerConnection.Stream.ReadLineAsync(cancellationToken);
} }
else if (responseStatusCode == (int)HttpStatusCode.ExpectationFailed else if (responseStatusCode == (int)HttpStatusCode.ExpectationFailed
&& responseStatusDescription.EqualsIgnoreCase("expectation failed")) && responseStatusDescription.EqualsIgnoreCase("expectation failed"))
{ {
Request.ExpectationFailed = true; Request.ExpectationFailed = true;
await ServerConnection.Stream.ReadLineAsync(cancellationToken); await ServerConnection.Stream.ReadLineAsync(cancellationToken);
} }
} }
} }
} }
/// <summary> /// <summary>
/// Receive and parse the http response from server /// Receive and parse the http response from server
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
internal async Task ReceiveResponse(CancellationToken cancellationToken) internal async Task ReceiveResponse(CancellationToken cancellationToken)
{ {
// return if this is already read // return if this is already read
if (Response.StatusCode != 0) if (Response.StatusCode != 0)
{ {
return; return;
} }
string httpStatus; string httpStatus;
try try
{ {
httpStatus = await ServerConnection.Stream.ReadLineAsync(cancellationToken); httpStatus = await ServerConnection.Stream.ReadLineAsync(cancellationToken);
if (httpStatus == null) if (httpStatus == null)
{ {
throw new ServerConnectionException("Server connection was closed."); throw new ServerConnectionException("Server connection was closed.");
} }
} }
catch (Exception e) when (!(e is ServerConnectionException)) catch (Exception e) when (!(e is ServerConnectionException))
{ {
throw new ServerConnectionException("Server connection was closed."); throw new ServerConnectionException("Server connection was closed.");
} }
if (httpStatus == string.Empty) if (httpStatus == string.Empty)
{ {
httpStatus = await ServerConnection.Stream.ReadLineAsync(cancellationToken); httpStatus = await ServerConnection.Stream.ReadLineAsync(cancellationToken);
} }
Response.ParseResponseLine(httpStatus, out var version, out int statusCode, out string statusDescription); Response.ParseResponseLine(httpStatus, out var version, out int statusCode, out string statusDescription);
Response.HttpVersion = version; Response.HttpVersion = version;
Response.StatusCode = statusCode; Response.StatusCode = statusCode;
Response.StatusDescription = statusDescription; Response.StatusDescription = statusDescription;
// For HTTP 1.1 comptibility server may send expect-continue even if not asked for it in request // For HTTP 1.1 comptibility server may send expect-continue even if not asked for it in request
if (Response.StatusCode == (int)HttpStatusCode.Continue if (Response.StatusCode == (int)HttpStatusCode.Continue
&& Response.StatusDescription.EqualsIgnoreCase("continue")) && Response.StatusDescription.EqualsIgnoreCase("continue"))
{ {
// Read the next line after 100-continue // Read the next line after 100-continue
Response.Is100Continue = true; Response.Is100Continue = true;
Response.StatusCode = 0; Response.StatusCode = 0;
await ServerConnection.Stream.ReadLineAsync(cancellationToken); await ServerConnection.Stream.ReadLineAsync(cancellationToken);
// now receive response // now receive response
await ReceiveResponse(cancellationToken); await ReceiveResponse(cancellationToken);
return; return;
} }
if (Response.StatusCode == (int)HttpStatusCode.ExpectationFailed if (Response.StatusCode == (int)HttpStatusCode.ExpectationFailed
&& Response.StatusDescription.EqualsIgnoreCase("expectation failed")) && Response.StatusDescription.EqualsIgnoreCase("expectation failed"))
{ {
// read next line after expectation failed response // read next line after expectation failed response
Response.ExpectationFailed = true; Response.ExpectationFailed = true;
Response.StatusCode = 0; Response.StatusCode = 0;
await ServerConnection.Stream.ReadLineAsync(cancellationToken); await ServerConnection.Stream.ReadLineAsync(cancellationToken);
// now receive response // now receive response
await ReceiveResponse(cancellationToken); await ReceiveResponse(cancellationToken);
return; return;
} }
// Read the response headers in to unique and non-unique header collections // Read the response headers in to unique and non-unique header collections
await HeaderParser.ReadHeaders(ServerConnection.Stream, Response.Headers, cancellationToken); await HeaderParser.ReadHeaders(ServerConnection.Stream, Response.Headers, cancellationToken);
} }
/// <summary> /// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary> /// </summary>
internal void FinishSession() internal void FinishSession()
{ {
ServerConnection = null; ServerConnection = null;
ConnectRequest?.FinishSession(); ConnectRequest?.FinishSession();
Request?.FinishSession(); Request?.FinishSession();
Response?.FinishSession(); Response?.FinishSession();
Data.Clear(); Data.Clear();
UserData = null; UserData = null;
} }
} }
} }
using System; using System;
using System.Net; using System.Net;
namespace Titanium.Web.Proxy.Models namespace Titanium.Web.Proxy.Models
{ {
/// <summary> /// <summary>
/// An upstream proxy this proxy uses if any. /// An upstream proxy this proxy uses if any.
/// </summary> /// </summary>
public class ExternalProxy public class ExternalProxy
{ {
private static readonly Lazy<NetworkCredential> defaultCredentials = private static readonly Lazy<NetworkCredential> defaultCredentials =
new Lazy<NetworkCredential>(() => CredentialCache.DefaultNetworkCredentials); new Lazy<NetworkCredential>(() => CredentialCache.DefaultNetworkCredentials);
private string password; private string password;
private string userName; private string userName;
/// <summary> /// <summary>
/// Use default windows credentials? /// Use default windows credentials?
/// </summary> /// </summary>
public bool UseDefaultCredentials { get; set; } public bool UseDefaultCredentials { get; set; }
/// <summary> /// <summary>
/// Bypass this proxy for connections to localhost? /// Bypass this proxy for connections to localhost?
/// </summary> /// </summary>
public bool BypassLocalhost { get; set; } public bool BypassLocalhost { get; set; }
/// <summary> /// <summary>
/// Username. /// Username.
/// </summary> /// </summary>
public string UserName public string UserName
{ {
get => UseDefaultCredentials ? defaultCredentials.Value.UserName : userName; get => UseDefaultCredentials ? defaultCredentials.Value.UserName : userName;
set set
{ {
userName = value; userName = value;
if (defaultCredentials.Value.UserName != userName) if (defaultCredentials.Value.UserName != userName)
{ {
UseDefaultCredentials = false; UseDefaultCredentials = false;
} }
} }
} }
/// <summary> /// <summary>
/// Password. /// Password.
/// </summary> /// </summary>
public string Password public string Password
{ {
get => UseDefaultCredentials ? defaultCredentials.Value.Password : password; get => UseDefaultCredentials ? defaultCredentials.Value.Password : password;
set set
{ {
password = value; password = value;
if (defaultCredentials.Value.Password != password) if (defaultCredentials.Value.Password != password)
{ {
UseDefaultCredentials = false; UseDefaultCredentials = false;
} }
} }
} }
/// <summary> /// <summary>
/// Host name. /// Host name.
/// </summary> /// </summary>
public string HostName { get; set; } public string HostName { get; set; }
/// <summary> /// <summary>
/// Port. /// Port.
/// </summary> /// </summary>
public int Port { get; set; } public int Port { get; set; }
/// <summary> /// <summary>
/// Get cache key for Tcp connection cahe. /// Get cache key for Tcp connection cahe.
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
internal string GetCacheKey() internal string GetCacheKey()
{ {
return $"{HostName}-{Port}" + (UseDefaultCredentials ? $"-{UserName}-{Password}" : string.Empty); return $"{HostName}-{Port}" + (UseDefaultCredentials ? $"-{UserName}-{Password}" : string.Empty);
} }
/// <summary> /// <summary>
/// returns data in Hostname:port format. /// returns data in Hostname:port format.
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public override string ToString() public override string ToString()
{ {
return $"{HostName}:{Port}"; return $"{HostName}:{Port}";
} }
} }
} }
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
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.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using StreamExtended.Network; using StreamExtended.Network;
using Titanium.Web.Proxy.EventArguments; using Titanium.Web.Proxy.EventArguments;
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.Http;
using Titanium.Web.Proxy.Models; using Titanium.Web.Proxy.Models;
namespace Titanium.Web.Proxy.Network.Tcp namespace Titanium.Web.Proxy.Network.Tcp
{ {
/// <summary> /// <summary>
/// A class that manages Tcp Connection to server used by this proxy server. /// A class that manages Tcp Connection to server used by this proxy server.
/// </summary> /// </summary>
internal class TcpConnectionFactory : IDisposable internal class TcpConnectionFactory : IDisposable
{ {
//Tcp server connection pool cache //Tcp server connection pool cache
private readonly ConcurrentDictionary<string, ConcurrentQueue<TcpServerConnection>> cache private readonly ConcurrentDictionary<string, ConcurrentQueue<TcpServerConnection>> cache
= new ConcurrentDictionary<string, ConcurrentQueue<TcpServerConnection>>(); = new ConcurrentDictionary<string, ConcurrentQueue<TcpServerConnection>>();
//Tcp connections waiting to be disposed by cleanup task //Tcp connections waiting to be disposed by cleanup task
private readonly ConcurrentBag<TcpServerConnection> disposalBag = private readonly ConcurrentBag<TcpServerConnection> disposalBag =
new ConcurrentBag<TcpServerConnection>(); new ConcurrentBag<TcpServerConnection>();
//cache object race operations lock //cache object race operations lock
private readonly SemaphoreSlim @lock = new SemaphoreSlim(1); private readonly SemaphoreSlim @lock = new SemaphoreSlim(1);
private volatile bool runCleanUpTask = true; private volatile bool runCleanUpTask = true;
internal TcpConnectionFactory(ProxyServer server) internal TcpConnectionFactory(ProxyServer server)
{ {
this.server = server; this.server = server;
Task.Run(async () => await clearOutdatedConnections()); Task.Run(async () => await clearOutdatedConnections());
} }
internal ProxyServer server { get; set; } internal ProxyServer server { get; set; }
internal string GetConnectionCacheKey(string remoteHostName, int remotePort, internal string GetConnectionCacheKey(string remoteHostName, int remotePort,
bool isHttps, List<SslApplicationProtocol> applicationProtocols, bool isHttps, List<SslApplicationProtocol> applicationProtocols,
ProxyServer proxyServer, IPEndPoint upStreamEndPoint, ExternalProxy externalProxy) ProxyServer proxyServer, IPEndPoint upStreamEndPoint, ExternalProxy externalProxy)
{ {
//http version is ignored since its an application level decision b/w HTTP 1.0/1.1 //http version is ignored since its an application level decision b/w HTTP 1.0/1.1
//also when doing connect request MS Edge browser sends http 1.0 but uses 1.1 after server sends 1.1 its response. //also when doing connect request MS Edge browser sends http 1.0 but uses 1.1 after server sends 1.1 its response.
//That can create cache miss for same server connection unneccessarily expecially when prefetcing with Connect. //That can create cache miss for same server connection unneccessarily expecially when prefetcing with Connect.
//http version 2 is separated using applicationProtocols below. //http version 2 is separated using applicationProtocols below.
var cacheKeyBuilder = new StringBuilder($"{remoteHostName}-{remotePort}-" + var cacheKeyBuilder = new StringBuilder($"{remoteHostName}-{remotePort}-" +
//when creating Tcp client isConnect won't matter //when creating Tcp client isConnect won't matter
$"{isHttps}-"); $"{isHttps}-");
if (applicationProtocols != null) if (applicationProtocols != null)
{ {
foreach (var protocol in applicationProtocols.OrderBy(x => x)) foreach (var protocol in applicationProtocols.OrderBy(x => x))
{ {
cacheKeyBuilder.Append($"{protocol}-"); cacheKeyBuilder.Append($"{protocol}-");
} }
} }
cacheKeyBuilder.Append(upStreamEndPoint != null cacheKeyBuilder.Append(upStreamEndPoint != null
? $"{upStreamEndPoint.Address}-{upStreamEndPoint.Port}-" ? $"{upStreamEndPoint.Address}-{upStreamEndPoint.Port}-"
: string.Empty); : string.Empty);
cacheKeyBuilder.Append(externalProxy != null ? $"{externalProxy.GetCacheKey()}-" : string.Empty); cacheKeyBuilder.Append(externalProxy != null ? $"{externalProxy.GetCacheKey()}-" : string.Empty);
return cacheKeyBuilder.ToString(); return cacheKeyBuilder.ToString();
} }
/// <summary> /// <summary>
/// Gets the connection cache key. /// Gets the connection cache key.
/// </summary> /// </summary>
/// <param name="args">The session event arguments.</param> /// <param name="args">The session event arguments.</param>
/// <param name="applicationProtocol"></param> /// <param name="applicationProtocol"></param>
/// <returns></returns> /// <returns></returns>
internal async Task<string> GetConnectionCacheKey(ProxyServer server, SessionEventArgsBase args, internal async Task<string> GetConnectionCacheKey(ProxyServer server, SessionEventArgsBase args,
SslApplicationProtocol applicationProtocol) SslApplicationProtocol applicationProtocol)
{ {
List<SslApplicationProtocol> applicationProtocols = null; List<SslApplicationProtocol> applicationProtocols = null;
if (applicationProtocol != default) if (applicationProtocol != default)
{ {
applicationProtocols = new List<SslApplicationProtocol> { applicationProtocol }; applicationProtocols = new List<SslApplicationProtocol> { applicationProtocol };
} }
ExternalProxy customUpStreamProxy = null; ExternalProxy customUpStreamProxy = null;
bool isHttps = args.IsHttps; bool isHttps = args.IsHttps;
if (server.GetCustomUpStreamProxyFunc != null) if (server.GetCustomUpStreamProxyFunc != null)
{ {
customUpStreamProxy = await server.GetCustomUpStreamProxyFunc(args); customUpStreamProxy = await server.GetCustomUpStreamProxyFunc(args);
} }
args.CustomUpStreamProxyUsed = customUpStreamProxy; args.CustomUpStreamProxyUsed = customUpStreamProxy;
return GetConnectionCacheKey( return GetConnectionCacheKey(
args.WebSession.Request.RequestUri.Host, args.WebSession.Request.RequestUri.Host,
args.WebSession.Request.RequestUri.Port, args.WebSession.Request.RequestUri.Port,
isHttps, applicationProtocols, isHttps, applicationProtocols,
server, args.WebSession.UpStreamEndPoint ?? server.UpStreamEndPoint, server, args.WebSession.UpStreamEndPoint ?? server.UpStreamEndPoint,
customUpStreamProxy ?? (isHttps ? server.UpStreamHttpsProxy : server.UpStreamHttpProxy)); customUpStreamProxy ?? (isHttps ? server.UpStreamHttpsProxy : server.UpStreamHttpProxy));
} }
/// <summary> /// <summary>
/// Create a server connection. /// Create a server connection.
/// </summary> /// </summary>
/// <param name="args">The session event arguments.</param> /// <param name="args">The session event arguments.</param>
/// <param name="isConnect">Is this a CONNECT request.</param> /// <param name="isConnect">Is this a CONNECT request.</param>
/// <param name="applicationProtocol"></param> /// <param name="applicationProtocol"></param>
/// <param name="cancellationToken">The cancellation token for this async task.</param> /// <param name="cancellationToken">The cancellation token for this async task.</param>
/// <returns></returns> /// <returns></returns>
internal Task<TcpServerConnection> GetServerConnection(ProxyServer server, SessionEventArgsBase args, bool isConnect, internal Task<TcpServerConnection> GetServerConnection(ProxyServer server, SessionEventArgsBase args, bool isConnect,
SslApplicationProtocol applicationProtocol, bool noCache, CancellationToken cancellationToken) SslApplicationProtocol applicationProtocol, bool noCache, CancellationToken cancellationToken)
{ {
List<SslApplicationProtocol> applicationProtocols = null; List<SslApplicationProtocol> applicationProtocols = null;
if (applicationProtocol != default) if (applicationProtocol != default)
{ {
applicationProtocols = new List<SslApplicationProtocol> { applicationProtocol }; applicationProtocols = new List<SslApplicationProtocol> { applicationProtocol };
} }
return GetServerConnection(server, args, isConnect, applicationProtocols, noCache, cancellationToken); return GetServerConnection(server, args, isConnect, applicationProtocols, noCache, cancellationToken);
} }
/// <summary> /// <summary>
/// Create a server connection. /// Create a server connection.
/// </summary> /// </summary>
/// <param name="args">The session event arguments.</param> /// <param name="args">The session event arguments.</param>
/// <param name="isConnect">Is this a CONNECT request.</param> /// <param name="isConnect">Is this a CONNECT request.</param>
/// <param name="applicationProtocols"></param> /// <param name="applicationProtocols"></param>
/// <param name="cancellationToken">The cancellation token for this async task.</param> /// <param name="cancellationToken">The cancellation token for this async task.</param>
/// <returns></returns> /// <returns></returns>
internal async Task<TcpServerConnection> GetServerConnection(ProxyServer server, SessionEventArgsBase args, bool isConnect, internal async Task<TcpServerConnection> GetServerConnection(ProxyServer server, SessionEventArgsBase args, bool isConnect,
List<SslApplicationProtocol> applicationProtocols, bool noCache, CancellationToken cancellationToken) List<SslApplicationProtocol> applicationProtocols, bool noCache, CancellationToken cancellationToken)
{ {
ExternalProxy customUpStreamProxy = null; ExternalProxy customUpStreamProxy = null;
bool isHttps = args.IsHttps; bool isHttps = args.IsHttps;
if (server.GetCustomUpStreamProxyFunc != null) if (server.GetCustomUpStreamProxyFunc != null)
{ {
customUpStreamProxy = await server.GetCustomUpStreamProxyFunc(args); customUpStreamProxy = await server.GetCustomUpStreamProxyFunc(args);
} }
args.CustomUpStreamProxyUsed = customUpStreamProxy; args.CustomUpStreamProxyUsed = customUpStreamProxy;
return await GetServerConnection( return await GetServerConnection(
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,
isHttps, applicationProtocols, isConnect, isHttps, applicationProtocols, isConnect,
server, args.WebSession.UpStreamEndPoint ?? server.UpStreamEndPoint, server, args.WebSession.UpStreamEndPoint ?? server.UpStreamEndPoint,
customUpStreamProxy ?? (isHttps ? server.UpStreamHttpsProxy : server.UpStreamHttpProxy), customUpStreamProxy ?? (isHttps ? server.UpStreamHttpsProxy : server.UpStreamHttpProxy),
noCache, cancellationToken); noCache, cancellationToken);
} }
/// <summary> /// <summary>
/// Gets a TCP connection to server from connection pool. /// Gets a TCP connection to server from connection pool.
/// </summary> /// </summary>
/// <param name="remoteHostName">The remote hostname.</param> /// <param name="remoteHostName">The remote hostname.</param>
/// <param name="remotePort">The remote port.</param> /// <param name="remotePort">The remote port.</param>
/// <param name="httpVersion">The http version to use.</param> /// <param name="httpVersion">The http version to use.</param>
/// <param name="isHttps">Is this a HTTPS request.</param> /// <param name="isHttps">Is this a HTTPS request.</param>
/// <param name="applicationProtocols">The list of HTTPS application level protocol to negotiate if needed.</param> /// <param name="applicationProtocols">The list of HTTPS application level protocol to negotiate if needed.</param>
/// <param name="isConnect">Is this a CONNECT request.</param> /// <param name="isConnect">Is this a CONNECT request.</param>
/// <param name="proxyServer">The current ProxyServer instance.</param> /// <param name="proxyServer">The current ProxyServer instance.</param>
/// <param name="upStreamEndPoint">The local upstream endpoint to make request via.</param> /// <param name="upStreamEndPoint">The local upstream endpoint to make request via.</param>
/// <param name="externalProxy">The external proxy to make request via.</param> /// <param name="externalProxy">The external proxy to make request via.</param>
/// <param name="noCache">Not from cache/create new connection.</param> /// <param name="noCache">Not from cache/create new connection.</param>
/// <param name="cancellationToken">The cancellation token for this async task.</param> /// <param name="cancellationToken">The cancellation token for this async task.</param>
/// <returns></returns> /// <returns></returns>
internal async Task<TcpServerConnection> GetServerConnection(string remoteHostName, int remotePort, internal async Task<TcpServerConnection> GetServerConnection(string remoteHostName, int remotePort,
Version httpVersion, bool isHttps, List<SslApplicationProtocol> applicationProtocols, bool isConnect, Version httpVersion, bool isHttps, List<SslApplicationProtocol> applicationProtocols, bool isConnect,
ProxyServer proxyServer, IPEndPoint upStreamEndPoint, ExternalProxy externalProxy, ProxyServer proxyServer, IPEndPoint upStreamEndPoint, ExternalProxy externalProxy,
bool noCache, CancellationToken cancellationToken) bool noCache, CancellationToken cancellationToken)
{ {
var cacheKey = GetConnectionCacheKey(remoteHostName, remotePort, var cacheKey = GetConnectionCacheKey(remoteHostName, remotePort,
isHttps, applicationProtocols, isHttps, applicationProtocols,
proxyServer, upStreamEndPoint, externalProxy); proxyServer, upStreamEndPoint, externalProxy);
if (proxyServer.EnableConnectionPool && !noCache) if (proxyServer.EnableConnectionPool && !noCache)
{ {
if (cache.TryGetValue(cacheKey, out var existingConnections)) if (cache.TryGetValue(cacheKey, out var existingConnections))
{ {
while (existingConnections.Count > 0) while (existingConnections.Count > 0)
{ {
if (existingConnections.TryDequeue(out var recentConnection)) if (existingConnections.TryDequeue(out var recentConnection))
{ {
//+3 seconds for potential delay after getting connection //+3 seconds for potential delay after getting connection
var cutOff = DateTime.Now.AddSeconds(-1 * proxyServer.ConnectionTimeOutSeconds + 3); var cutOff = DateTime.Now.AddSeconds(-1 * proxyServer.ConnectionTimeOutSeconds + 3);
if (recentConnection.LastAccess > cutOff if (recentConnection.LastAccess > cutOff
&& recentConnection.TcpClient.IsGoodConnection()) && recentConnection.TcpClient.IsGoodConnection())
{ {
return recentConnection; return recentConnection;
} }
disposalBag.Add(recentConnection); disposalBag.Add(recentConnection);
} }
} }
} }
} }
var connection = await createServerConnection(remoteHostName, remotePort, httpVersion, isHttps, var connection = await createServerConnection(remoteHostName, remotePort, httpVersion, isHttps,
applicationProtocols, isConnect, proxyServer, upStreamEndPoint, externalProxy, cancellationToken); applicationProtocols, isConnect, proxyServer, upStreamEndPoint, externalProxy, cancellationToken);
connection.CacheKey = cacheKey; connection.CacheKey = cacheKey;
return connection; return connection;
} }
/// <summary> /// <summary>
/// Creates a TCP connection to server /// Creates a TCP connection to server
/// </summary> /// </summary>
/// <param name="remoteHostName">The remote hostname.</param> /// <param name="remoteHostName">The remote hostname.</param>
/// <param name="remotePort">The remote port.</param> /// <param name="remotePort">The remote port.</param>
/// <param name="httpVersion">The http version to use.</param> /// <param name="httpVersion">The http version to use.</param>
/// <param name="isHttps">Is this a HTTPS request.</param> /// <param name="isHttps">Is this a HTTPS request.</param>
/// <param name="applicationProtocols">The list of HTTPS application level protocol to negotiate if needed.</param> /// <param name="applicationProtocols">The list of HTTPS application level protocol to negotiate if needed.</param>
/// <param name="isConnect">Is this a CONNECT request.</param> /// <param name="isConnect">Is this a CONNECT request.</param>
/// <param name="proxyServer">The current ProxyServer instance.</param> /// <param name="proxyServer">The current ProxyServer instance.</param>
/// <param name="upStreamEndPoint">The local upstream endpoint to make request via.</param> /// <param name="upStreamEndPoint">The local upstream endpoint to make request via.</param>
/// <param name="externalProxy">The external proxy to make request via.</param> /// <param name="externalProxy">The external proxy to make request via.</param>
/// <param name="cancellationToken">The cancellation token for this async task.</param> /// <param name="cancellationToken">The cancellation token for this async task.</param>
/// <returns></returns> /// <returns></returns>
private async Task<TcpServerConnection> createServerConnection(string remoteHostName, int remotePort, private async Task<TcpServerConnection> createServerConnection(string remoteHostName, int remotePort,
Version httpVersion, bool isHttps, List<SslApplicationProtocol> applicationProtocols, bool isConnect, Version httpVersion, bool isHttps, List<SslApplicationProtocol> applicationProtocols, bool isConnect,
ProxyServer proxyServer, IPEndPoint upStreamEndPoint, ExternalProxy externalProxy, ProxyServer proxyServer, IPEndPoint upStreamEndPoint, ExternalProxy externalProxy,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
//deny connection to proxy end points to avoid infinite connection loop. //deny connection to proxy end points to avoid infinite connection loop.
if (server.ProxyEndPoints.Any(x => x.Port == remotePort) if (server.ProxyEndPoints.Any(x => x.Port == remotePort)
&& NetworkHelper.IsLocalIpAddress(remoteHostName)) && NetworkHelper.IsLocalIpAddress(remoteHostName))
{ {
throw new Exception($"A client is making HTTP request to one of the listening ports of this proxy {remoteHostName}:{remotePort}"); throw new Exception($"A client is making HTTP request to one of the listening ports of this proxy {remoteHostName}:{remotePort}");
} }
if (externalProxy != null) if (externalProxy != null)
{ {
if (server.ProxyEndPoints.Any(x => x.Port == externalProxy.Port) if (server.ProxyEndPoints.Any(x => x.Port == externalProxy.Port)
&& NetworkHelper.IsLocalIpAddress(externalProxy.HostName)) && NetworkHelper.IsLocalIpAddress(externalProxy.HostName))
{ {
throw new Exception($"A client is making HTTP request via external proxy to one of the listening ports of this proxy {remoteHostName}:{remotePort}"); throw new Exception($"A client is making HTTP request via external proxy to one of the listening ports of this proxy {remoteHostName}:{remotePort}");
} }
} }
bool useUpstreamProxy = false; bool useUpstreamProxy = false;
// check if external proxy is set for HTTP/HTTPS // check if external proxy is set for HTTP/HTTPS
if (externalProxy != null && if (externalProxy != null &&
!(externalProxy.HostName == remoteHostName && externalProxy.Port == remotePort)) !(externalProxy.HostName == remoteHostName && externalProxy.Port == remotePort))
{ {
useUpstreamProxy = true; useUpstreamProxy = true;
// check if we need to ByPass // check if we need to ByPass
if (externalProxy.BypassLocalhost && NetworkHelper.IsLocalIpAddress(remoteHostName)) if (externalProxy.BypassLocalhost && NetworkHelper.IsLocalIpAddress(remoteHostName))
{ {
useUpstreamProxy = false; useUpstreamProxy = false;
} }
} }
TcpClient tcpClient = null; TcpClient tcpClient = null;
CustomBufferedStream stream = null; CustomBufferedStream stream = null;
SslApplicationProtocol negotiatedApplicationProtocol = default; SslApplicationProtocol negotiatedApplicationProtocol = default;
try try
{ {
tcpClient = new TcpClient(upStreamEndPoint) tcpClient = new TcpClient(upStreamEndPoint)
{ {
ReceiveTimeout = proxyServer.ConnectionTimeOutSeconds * 1000, ReceiveTimeout = proxyServer.ConnectionTimeOutSeconds * 1000,
SendTimeout = proxyServer.ConnectionTimeOutSeconds * 1000, SendTimeout = proxyServer.ConnectionTimeOutSeconds * 1000,
SendBufferSize = proxyServer.BufferSize, SendBufferSize = proxyServer.BufferSize,
ReceiveBufferSize = proxyServer.BufferSize, ReceiveBufferSize = proxyServer.BufferSize,
LingerState = new LingerOption(true, proxyServer.TcpTimeWaitSeconds) LingerState = new LingerOption(true, proxyServer.TcpTimeWaitSeconds)
}; };
//linux has a bug with socket reuse in .net core. //linux has a bug with socket reuse in .net core.
if (proxyServer.ReuseSocket && RunTime.IsWindows || RunTime.IsRunningOnMono) if (proxyServer.ReuseSocket && RunTime.IsWindows || RunTime.IsRunningOnMono)
{ {
tcpClient.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); tcpClient.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
} }
// If this proxy uses another external proxy then create a tunnel request for HTTP/HTTPS connections // If this proxy uses another external proxy then create a tunnel request for HTTP/HTTPS connections
if (useUpstreamProxy) if (useUpstreamProxy)
{ {
await tcpClient.ConnectAsync(externalProxy.HostName, externalProxy.Port); await tcpClient.ConnectAsync(externalProxy.HostName, externalProxy.Port);
} }
else else
{ {
await tcpClient.ConnectAsync(remoteHostName, remotePort); await tcpClient.ConnectAsync(remoteHostName, remotePort);
} }
await proxyServer.InvokeConnectionCreateEvent(tcpClient, false); await proxyServer.InvokeConnectionCreateEvent(tcpClient, false);
stream = new CustomBufferedStream(tcpClient.GetStream(), proxyServer.BufferPool, proxyServer.BufferSize); stream = new CustomBufferedStream(tcpClient.GetStream(), proxyServer.BufferPool, proxyServer.BufferSize);
if (useUpstreamProxy && (isConnect || isHttps)) if (useUpstreamProxy && (isConnect || isHttps))
{ {
var writer = new HttpRequestWriter(stream, proxyServer.BufferPool, proxyServer.BufferSize); var writer = new HttpRequestWriter(stream, proxyServer.BufferPool, proxyServer.BufferSize);
var connectRequest = new ConnectRequest var connectRequest = new ConnectRequest
{ {
OriginalUrl = $"{remoteHostName}:{remotePort}", OriginalUrl = $"{remoteHostName}:{remotePort}",
HttpVersion = httpVersion HttpVersion = httpVersion
}; };
connectRequest.Headers.AddHeader(KnownHeaders.Connection, KnownHeaders.ConnectionKeepAlive); connectRequest.Headers.AddHeader(KnownHeaders.Connection, KnownHeaders.ConnectionKeepAlive);
if (!string.IsNullOrEmpty(externalProxy.UserName) && externalProxy.Password != null) if (!string.IsNullOrEmpty(externalProxy.UserName) && externalProxy.Password != null)
{ {
connectRequest.Headers.AddHeader(HttpHeader.ProxyConnectionKeepAlive); connectRequest.Headers.AddHeader(HttpHeader.ProxyConnectionKeepAlive);
connectRequest.Headers.AddHeader( connectRequest.Headers.AddHeader(
HttpHeader.GetProxyAuthorizationHeader(externalProxy.UserName, externalProxy.Password)); HttpHeader.GetProxyAuthorizationHeader(externalProxy.UserName, externalProxy.Password));
} }
await writer.WriteRequestAsync(connectRequest, cancellationToken: cancellationToken); await writer.WriteRequestAsync(connectRequest, cancellationToken: cancellationToken);
string httpStatus = await stream.ReadLineAsync(cancellationToken); string httpStatus = await stream.ReadLineAsync(cancellationToken);
Response.ParseResponseLine(httpStatus, out _, out int statusCode, out string statusDescription); Response.ParseResponseLine(httpStatus, out _, out int statusCode, out string statusDescription);
if (statusCode != 200 && !statusDescription.EqualsIgnoreCase("OK") if (statusCode != 200 && !statusDescription.EqualsIgnoreCase("OK")
&& !statusDescription.EqualsIgnoreCase("Connection Established")) && !statusDescription.EqualsIgnoreCase("Connection Established"))
{ {
throw new Exception("Upstream proxy failed to create a secure tunnel"); throw new Exception("Upstream proxy failed to create a secure tunnel");
} }
await stream.ReadAndIgnoreAllLinesAsync(cancellationToken); await stream.ReadAndIgnoreAllLinesAsync(cancellationToken);
} }
if (isHttps) if (isHttps)
{ {
var sslStream = new SslStream(stream, false, proxyServer.ValidateServerCertificate, var sslStream = new SslStream(stream, false, proxyServer.ValidateServerCertificate,
proxyServer.SelectClientCertificate); proxyServer.SelectClientCertificate);
stream = new CustomBufferedStream(sslStream, proxyServer.BufferPool, proxyServer.BufferSize); stream = new CustomBufferedStream(sslStream, proxyServer.BufferPool, proxyServer.BufferSize);
var options = new SslClientAuthenticationOptions var options = new SslClientAuthenticationOptions
{ {
ApplicationProtocols = applicationProtocols, ApplicationProtocols = applicationProtocols,
TargetHost = remoteHostName, TargetHost = remoteHostName,
ClientCertificates = null, ClientCertificates = null,
EnabledSslProtocols = proxyServer.SupportedSslProtocols, EnabledSslProtocols = proxyServer.SupportedSslProtocols,
CertificateRevocationCheckMode = proxyServer.CheckCertificateRevocation CertificateRevocationCheckMode = proxyServer.CheckCertificateRevocation
}; };
await sslStream.AuthenticateAsClientAsync(options, cancellationToken); await sslStream.AuthenticateAsClientAsync(options, cancellationToken);
#if NETCOREAPP2_1 #if NETCOREAPP2_1
negotiatedApplicationProtocol = sslStream.NegotiatedApplicationProtocol; negotiatedApplicationProtocol = sslStream.NegotiatedApplicationProtocol;
#endif #endif
} }
} }
catch (Exception) catch (Exception)
{ {
stream?.Dispose(); stream?.Dispose();
tcpClient?.Close(); tcpClient?.Close();
throw; throw;
} }
return new TcpServerConnection(proxyServer, tcpClient) return new TcpServerConnection(proxyServer, tcpClient)
{ {
UpStreamProxy = externalProxy, UpStreamProxy = externalProxy,
UpStreamEndPoint = upStreamEndPoint, UpStreamEndPoint = upStreamEndPoint,
HostName = remoteHostName, HostName = remoteHostName,
Port = remotePort, Port = remotePort,
IsHttps = isHttps, IsHttps = isHttps,
NegotiatedApplicationProtocol = negotiatedApplicationProtocol, NegotiatedApplicationProtocol = negotiatedApplicationProtocol,
UseUpstreamProxy = useUpstreamProxy, UseUpstreamProxy = useUpstreamProxy,
StreamWriter = new HttpRequestWriter(stream, proxyServer.BufferPool, proxyServer.BufferSize), StreamWriter = new HttpRequestWriter(stream, proxyServer.BufferPool, proxyServer.BufferSize),
Stream = stream, Stream = stream,
Version = httpVersion Version = httpVersion
}; };
} }
/// <summary> /// <summary>
/// Release connection back to cache. /// Release connection back to cache.
/// </summary> /// </summary>
/// <param name="connection">The Tcp server connection to return.</param> /// <param name="connection">The Tcp server connection to return.</param>
/// <param name="close">Should we just close the connection instead of reusing?</param> /// <param name="close">Should we just close the connection instead of reusing?</param>
internal async Task Release(TcpServerConnection connection, bool close = false) internal async Task Release(TcpServerConnection connection, bool close = false)
{ {
if (connection == null) if (connection == null)
{ {
return; return;
} }
if (close || connection.IsWinAuthenticated || !server.EnableConnectionPool) if (close || connection.IsWinAuthenticated || !server.EnableConnectionPool)
{ {
disposalBag.Add(connection); disposalBag.Add(connection);
return; return;
} }
connection.LastAccess = DateTime.Now; connection.LastAccess = DateTime.Now;
try try
{ {
await @lock.WaitAsync(); await @lock.WaitAsync();
while (true) while (true)
{ {
if (cache.TryGetValue(connection.CacheKey, out var existingConnections)) if (cache.TryGetValue(connection.CacheKey, out var existingConnections))
{ {
while (existingConnections.Count >= server.MaxCachedConnections) while (existingConnections.Count >= server.MaxCachedConnections)
{ {
if (existingConnections.TryDequeue(out var staleConnection)) if (existingConnections.TryDequeue(out var staleConnection))
{ {
disposalBag.Add(staleConnection); disposalBag.Add(staleConnection);
} }
} }
existingConnections.Enqueue(connection); existingConnections.Enqueue(connection);
break; break;
} }
if (cache.TryAdd(connection.CacheKey, if (cache.TryAdd(connection.CacheKey,
new ConcurrentQueue<TcpServerConnection>(new[] { connection }))) new ConcurrentQueue<TcpServerConnection>(new[] { connection })))
{ {
break; break;
} }
} }
} }
finally finally
{ {
@lock.Release(); @lock.Release();
} }
} }
internal async Task Release(Task<TcpServerConnection> connectionCreateTask, bool closeServerConnection) internal async Task Release(Task<TcpServerConnection> connectionCreateTask, bool closeServerConnection)
{ {
if (connectionCreateTask != null) if (connectionCreateTask != null)
{ {
TcpServerConnection connection = null; TcpServerConnection connection = null;
try try
{ {
connection = await connectionCreateTask; connection = await connectionCreateTask;
} }
catch { } catch { }
finally finally
{ {
await Release(connection, closeServerConnection); await Release(connection, closeServerConnection);
} }
} }
} }
private async Task clearOutdatedConnections() private async Task clearOutdatedConnections()
{ {
while (runCleanUpTask) while (runCleanUpTask)
{ {
try try
{ {
foreach (var item in cache) foreach (var item in cache)
{ {
var queue = item.Value; var queue = item.Value;
while (queue.Count > 0) while (queue.Count > 0)
{ {
if (queue.TryDequeue(out var connection)) if (queue.TryDequeue(out var connection))
{ {
var cutOff = DateTime.Now.AddSeconds(-1 * server.ConnectionTimeOutSeconds); var cutOff = DateTime.Now.AddSeconds(-1 * server.ConnectionTimeOutSeconds);
if (!server.EnableConnectionPool if (!server.EnableConnectionPool
|| connection.LastAccess < cutOff) || connection.LastAccess < cutOff)
{ {
disposalBag.Add(connection); disposalBag.Add(connection);
continue; continue;
} }
queue.Enqueue(connection); queue.Enqueue(connection);
break; break;
} }
} }
} }
try try
{ {
await @lock.WaitAsync(); await @lock.WaitAsync();
//clear empty queues //clear empty queues
var emptyKeys = cache.Where(x => x.Value.Count == 0).Select(x => x.Key).ToList(); var emptyKeys = cache.Where(x => x.Value.Count == 0).Select(x => x.Key).ToList();
foreach (string key in emptyKeys) foreach (string key in emptyKeys)
{ {
cache.TryRemove(key, out var _); cache.TryRemove(key, out var _);
} }
} }
finally finally
{ {
@lock.Release(); @lock.Release();
} }
while (!disposalBag.IsEmpty) while (!disposalBag.IsEmpty)
{ {
if (disposalBag.TryTake(out var connection)) if (disposalBag.TryTake(out var connection))
{ {
connection?.Dispose(); connection?.Dispose();
} }
} }
} }
catch (Exception e) catch (Exception e)
{ {
server.ExceptionFunc(new Exception("An error occurred when disposing server connections.", e)); server.ExceptionFunc(new Exception("An error occurred when disposing server connections.", e));
} }
finally finally
{ {
//cleanup every 3 seconds by default //cleanup every 3 seconds by default
await Task.Delay(1000 * 3); await Task.Delay(1000 * 3);
} }
} }
} }
public void Dispose() public void Dispose()
{ {
runCleanUpTask = false; runCleanUpTask = false;
try try
{ {
@lock.Wait(); @lock.Wait();
foreach (var queue in cache.Select(x => x.Value).ToList()) foreach (var queue in cache.Select(x => x.Value).ToList())
{ {
while (!queue.IsEmpty) while (!queue.IsEmpty)
{ {
if (queue.TryDequeue(out var connection)) if (queue.TryDequeue(out var connection))
{ {
disposalBag.Add(connection); disposalBag.Add(connection);
} }
} }
} }
cache.Clear(); cache.Clear();
} }
finally finally
{ {
@lock.Release(); @lock.Release();
} }
while (!disposalBag.IsEmpty) while (!disposalBag.IsEmpty)
{ {
if (disposalBag.TryTake(out var connection)) if (disposalBag.TryTake(out var connection))
{ {
connection?.Dispose(); connection?.Dispose();
} }
} }
} }
} }
} }
using System; using System;
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.Extensions; using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Http; using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Models; using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Shared; using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy namespace Titanium.Web.Proxy
{ {
public partial class ProxyServer public partial class ProxyServer
{ {
/// <summary> /// <summary>
/// Callback to authorize clients of this proxy instance. /// Callback to authorize clients of this proxy instance.
/// </summary> /// </summary>
/// <param name="session">The session event arguments.</param> /// <param name="session">The session event arguments.</param>
/// <returns>True if authorized.</returns> /// <returns>True if authorized.</returns>
private async Task<bool> checkAuthorization(SessionEventArgsBase session) private async Task<bool> checkAuthorization(SessionEventArgsBase session)
{ {
// If we are not authorizing clients return true // If we are not authorizing clients return true
if (ProxyBasicAuthenticateFunc == null && ProxySchemeAuthenticateFunc == null) if (ProxyBasicAuthenticateFunc == null && ProxySchemeAuthenticateFunc == null)
{ {
return true; return true;
} }
var httpHeaders = session.WebSession.Request.Headers; var httpHeaders = session.WebSession.Request.Headers;
try try
{ {
var header = httpHeaders.GetFirstHeader(KnownHeaders.ProxyAuthorization); var header = httpHeaders.GetFirstHeader(KnownHeaders.ProxyAuthorization);
if (header == null) if (header == null)
{ {
session.WebSession.Response = createAuthentication407Response("Proxy Authentication Required"); session.WebSession.Response = createAuthentication407Response("Proxy Authentication Required");
return false; return false;
} }
var headerValueParts = header.Value.Split(ProxyConstants.SpaceSplit); var headerValueParts = header.Value.Split(ProxyConstants.SpaceSplit);
if (headerValueParts.Length != 2) if (headerValueParts.Length != 2)
{ {
// Return not authorized // Return not authorized
session.WebSession.Response = createAuthentication407Response("Proxy Authentication Invalid"); session.WebSession.Response = createAuthentication407Response("Proxy Authentication Invalid");
return false; return false;
} }
if (ProxyBasicAuthenticateFunc != null) if (ProxyBasicAuthenticateFunc != null)
{ {
return await authenticateUserBasic(session, headerValueParts); return await authenticateUserBasic(session, headerValueParts);
} }
if (ProxySchemeAuthenticateFunc != null) if (ProxySchemeAuthenticateFunc != null)
{ {
var result = await ProxySchemeAuthenticateFunc(session, headerValueParts[0], headerValueParts[1]); var result = await ProxySchemeAuthenticateFunc(session, headerValueParts[0], headerValueParts[1]);
if (result.Result == ProxyAuthenticationResult.ContinuationNeeded) if (result.Result == ProxyAuthenticationResult.ContinuationNeeded)
{ {
session.WebSession.Response = createAuthentication407Response("Proxy Authentication Invalid", result.Continuation); session.WebSession.Response = createAuthentication407Response("Proxy Authentication Invalid", result.Continuation);
return false; return false;
} }
return result.Result == ProxyAuthenticationResult.Success; return result.Result == ProxyAuthenticationResult.Success;
} }
return false; return false;
} }
catch (Exception e) catch (Exception e)
{ {
ExceptionFunc(new ProxyAuthorizationException("Error whilst authorizing request", session, e, ExceptionFunc(new ProxyAuthorizationException("Error whilst authorizing request", session, e,
httpHeaders)); httpHeaders));
// Return not authorized // Return not authorized
session.WebSession.Response = createAuthentication407Response("Proxy Authentication Invalid"); session.WebSession.Response = createAuthentication407Response("Proxy Authentication Invalid");
return false; return false;
} }
} }
private async Task<bool> authenticateUserBasic(SessionEventArgsBase session, string[] headerValueParts) private async Task<bool> authenticateUserBasic(SessionEventArgsBase session, string[] headerValueParts)
{ {
if (!headerValueParts[0].EqualsIgnoreCase(KnownHeaders.ProxyAuthorizationBasic)) if (!headerValueParts[0].EqualsIgnoreCase(KnownHeaders.ProxyAuthorizationBasic))
{ {
// Return not authorized // Return not authorized
session.WebSession.Response = createAuthentication407Response("Proxy Authentication Invalid"); session.WebSession.Response = createAuthentication407Response("Proxy Authentication Invalid");
return false; return false;
} }
string decoded = Encoding.UTF8.GetString(Convert.FromBase64String(headerValueParts[1])); string decoded = Encoding.UTF8.GetString(Convert.FromBase64String(headerValueParts[1]));
int colonIndex = decoded.IndexOf(':'); int colonIndex = decoded.IndexOf(':');
if (colonIndex == -1) if (colonIndex == -1)
{ {
// Return not authorized // Return not authorized
session.WebSession.Response = createAuthentication407Response("Proxy Authentication Invalid"); session.WebSession.Response = createAuthentication407Response("Proxy Authentication Invalid");
return false; return false;
} }
string username = decoded.Substring(0, colonIndex); string username = decoded.Substring(0, colonIndex);
string password = decoded.Substring(colonIndex + 1); string password = decoded.Substring(colonIndex + 1);
bool authenticated = await ProxyBasicAuthenticateFunc(session, username, password); bool authenticated = await ProxyBasicAuthenticateFunc(session, username, password);
if (!authenticated) if (!authenticated)
{ {
session.WebSession.Response = createAuthentication407Response("Proxy Authentication Invalid"); session.WebSession.Response = createAuthentication407Response("Proxy Authentication Invalid");
} }
return authenticated; return authenticated;
} }
/// <summary> /// <summary>
/// Create an authentication required response. /// Create an authentication required response.
/// </summary> /// </summary>
/// <param name="description">Response description.</param> /// <param name="description">Response description.</param>
/// <returns></returns> /// <returns></returns>
private Response createAuthentication407Response(string description, string continuation = null) private Response createAuthentication407Response(string description, string continuation = null)
{ {
var response = new Response var response = new Response
{ {
HttpVersion = HttpHeader.Version11, HttpVersion = HttpHeader.Version11,
StatusCode = (int)HttpStatusCode.ProxyAuthenticationRequired, StatusCode = (int)HttpStatusCode.ProxyAuthenticationRequired,
StatusDescription = description StatusDescription = description
}; };
if (!string.IsNullOrWhiteSpace(continuation)) if (!string.IsNullOrWhiteSpace(continuation))
{ {
return createContinuationResponse(response, continuation); return createContinuationResponse(response, continuation);
} }
if (ProxyBasicAuthenticateFunc != null) if (ProxyBasicAuthenticateFunc != null)
{ {
response.Headers.AddHeader(KnownHeaders.ProxyAuthenticate, $"Basic realm=\"{ProxyAuthenticationRealm}\""); response.Headers.AddHeader(KnownHeaders.ProxyAuthenticate, $"Basic realm=\"{ProxyAuthenticationRealm}\"");
} }
if (ProxySchemeAuthenticateFunc != null) if (ProxySchemeAuthenticateFunc != null)
{ {
foreach (var scheme in ProxyAuthenticationSchemes) foreach (var scheme in ProxyAuthenticationSchemes)
{ {
response.Headers.AddHeader(KnownHeaders.ProxyAuthenticate, scheme); response.Headers.AddHeader(KnownHeaders.ProxyAuthenticate, scheme);
} }
} }
response.Headers.AddHeader(KnownHeaders.ProxyConnection, KnownHeaders.ProxyConnectionClose); response.Headers.AddHeader(KnownHeaders.ProxyConnection, KnownHeaders.ProxyConnectionClose);
response.Headers.FixProxyHeaders(); response.Headers.FixProxyHeaders();
return response; return response;
} }
private Response createContinuationResponse(Response response, string continuation) private Response createContinuationResponse(Response response, string continuation)
{ {
response.Headers.AddHeader(KnownHeaders.ProxyAuthenticate, continuation); response.Headers.AddHeader(KnownHeaders.ProxyAuthenticate, continuation);
response.Headers.AddHeader(KnownHeaders.ProxyConnection, KnownHeaders.ConnectionKeepAlive); response.Headers.AddHeader(KnownHeaders.ProxyConnection, KnownHeaders.ConnectionKeepAlive);
response.Headers.FixProxyHeaders(); response.Headers.FixProxyHeaders();
return response; return response;
} }
} }
} }
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Net.Sockets; using System.Net.Sockets;
using System.Security.Authentication; using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using StreamExtended; using StreamExtended;
using StreamExtended.Network; using StreamExtended.Network;
using Titanium.Web.Proxy.EventArguments; using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Extensions; using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Helpers; using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Helpers.WinHttp; using Titanium.Web.Proxy.Helpers.WinHttp;
using Titanium.Web.Proxy.Models; using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Network; using Titanium.Web.Proxy.Network;
using Titanium.Web.Proxy.Network.Tcp; using Titanium.Web.Proxy.Network.Tcp;
namespace Titanium.Web.Proxy namespace Titanium.Web.Proxy
{ {
/// <inheritdoc /> /// <inheritdoc />
/// <summary> /// <summary>
/// This class is the backbone of proxy. One can create as many instances as needed. /// This class is the backbone of proxy. One can create as many instances as needed.
/// However care should be taken to avoid using the same listening ports across multiple instances. /// However care should be taken to avoid using the same listening ports across multiple instances.
/// </summary> /// </summary>
public partial class ProxyServer : IDisposable public partial class ProxyServer : IDisposable
{ {
/// <summary> /// <summary>
/// HTTP &amp; HTTPS scheme shorthands. /// HTTP &amp; HTTPS scheme shorthands.
/// </summary> /// </summary>
internal static readonly string UriSchemeHttp = Uri.UriSchemeHttp; internal static readonly string UriSchemeHttp = Uri.UriSchemeHttp;
internal static readonly string UriSchemeHttps = Uri.UriSchemeHttps; internal static readonly string UriSchemeHttps = Uri.UriSchemeHttps;
/// <summary> /// <summary>
/// A default exception log func. /// A default exception log func.
/// </summary> /// </summary>
private readonly ExceptionHandler defaultExceptionFunc = e => { }; private readonly ExceptionHandler defaultExceptionFunc = e => { };
/// <summary> /// <summary>
/// Backing field for exposed public property. /// Backing field for exposed public property.
/// </summary> /// </summary>
private int clientConnectionCount; private int clientConnectionCount;
/// <summary> /// <summary>
/// Backing field for exposed public property. /// Backing field for exposed public property.
/// </summary> /// </summary>
private ExceptionHandler exceptionFunc; private ExceptionHandler exceptionFunc;
/// <summary> /// <summary>
/// Backing field for exposed public property. /// Backing field for exposed public property.
/// </summary> /// </summary>
private int serverConnectionCount; private int serverConnectionCount;
/// <summary> /// <summary>
/// Upstream proxy manager. /// Upstream proxy manager.
/// </summary> /// </summary>
private WinHttpWebProxyFinder systemProxyResolver; private WinHttpWebProxyFinder systemProxyResolver;
/// <inheritdoc /> /// <inheritdoc />
/// <summary> /// <summary>
/// Initializes a new instance of ProxyServer class with provided parameters. /// Initializes a new instance of ProxyServer class with provided parameters.
/// </summary> /// </summary>
/// <param name="userTrustRootCertificate"> /// <param name="userTrustRootCertificate">
/// Should fake HTTPS certificate be trusted by this machine's user certificate /// Should fake HTTPS certificate be trusted by this machine's user certificate
/// store? /// store?
/// </param> /// </param>
/// <param name="machineTrustRootCertificate">Should fake HTTPS certificate be trusted by this machine's certificate store?</param> /// <param name="machineTrustRootCertificate">Should fake HTTPS certificate be trusted by this machine's certificate store?</param>
/// <param name="trustRootCertificateAsAdmin"> /// <param name="trustRootCertificateAsAdmin">
/// Should we attempt to trust certificates with elevated permissions by /// Should we attempt to trust certificates with elevated permissions by
/// prompting for UAC if required? /// prompting for UAC if required?
/// </param> /// </param>
public ProxyServer(bool userTrustRootCertificate = true, bool machineTrustRootCertificate = false, public ProxyServer(bool userTrustRootCertificate = true, bool machineTrustRootCertificate = false,
bool trustRootCertificateAsAdmin = false) : this(null, null, userTrustRootCertificate, bool trustRootCertificateAsAdmin = false) : this(null, null, userTrustRootCertificate,
machineTrustRootCertificate, trustRootCertificateAsAdmin) machineTrustRootCertificate, trustRootCertificateAsAdmin)
{ {
} }
/// <summary> /// <summary>
/// Initializes a new instance of ProxyServer class with provided parameters. /// Initializes a new instance of ProxyServer class with provided parameters.
/// </summary> /// </summary>
/// <param name="rootCertificateName">Name of the root certificate.</param> /// <param name="rootCertificateName">Name of the root certificate.</param>
/// <param name="rootCertificateIssuerName">Name of the root certificate issuer.</param> /// <param name="rootCertificateIssuerName">Name of the root certificate issuer.</param>
/// <param name="userTrustRootCertificate"> /// <param name="userTrustRootCertificate">
/// Should fake HTTPS certificate be trusted by this machine's user certificate /// Should fake HTTPS certificate be trusted by this machine's user certificate
/// store? /// store?
/// </param> /// </param>
/// <param name="machineTrustRootCertificate">Should fake HTTPS certificate be trusted by this machine's certificate store?</param> /// <param name="machineTrustRootCertificate">Should fake HTTPS certificate be trusted by this machine's certificate store?</param>
/// <param name="trustRootCertificateAsAdmin"> /// <param name="trustRootCertificateAsAdmin">
/// Should we attempt to trust certificates with elevated permissions by /// Should we attempt to trust certificates with elevated permissions by
/// prompting for UAC if required? /// prompting for UAC if required?
/// </param> /// </param>
public ProxyServer(string rootCertificateName, string rootCertificateIssuerName, public ProxyServer(string rootCertificateName, string rootCertificateIssuerName,
bool userTrustRootCertificate = true, bool machineTrustRootCertificate = false, bool userTrustRootCertificate = true, bool machineTrustRootCertificate = false,
bool trustRootCertificateAsAdmin = false) bool trustRootCertificateAsAdmin = false)
{ {
// default values // default values
ConnectionTimeOutSeconds = 60; ConnectionTimeOutSeconds = 60;
if (BufferPool == null) if (BufferPool == null)
{ {
BufferPool = new DefaultBufferPool(); BufferPool = new DefaultBufferPool();
} }
ProxyEndPoints = new List<ProxyEndPoint>(); ProxyEndPoints = new List<ProxyEndPoint>();
tcpConnectionFactory = new TcpConnectionFactory(this); tcpConnectionFactory = new TcpConnectionFactory(this);
if (!RunTime.IsRunningOnMono && RunTime.IsWindows) if (!RunTime.IsRunningOnMono && RunTime.IsWindows)
{ {
systemProxySettingsManager = new SystemProxyManager(); systemProxySettingsManager = new SystemProxyManager();
} }
CertificateManager = new CertificateManager(rootCertificateName, rootCertificateIssuerName, CertificateManager = new CertificateManager(rootCertificateName, rootCertificateIssuerName,
userTrustRootCertificate, machineTrustRootCertificate, trustRootCertificateAsAdmin, ExceptionFunc); userTrustRootCertificate, machineTrustRootCertificate, trustRootCertificateAsAdmin, ExceptionFunc);
} }
/// <summary> /// <summary>
/// An factory that creates tcp connection to server. /// An factory that creates tcp connection to server.
/// </summary> /// </summary>
private TcpConnectionFactory tcpConnectionFactory { get; } private TcpConnectionFactory tcpConnectionFactory { get; }
/// <summary> /// <summary>
/// Manage system proxy settings. /// Manage system proxy settings.
/// </summary> /// </summary>
private SystemProxyManager systemProxySettingsManager { get; } private SystemProxyManager systemProxySettingsManager { get; }
//Number of exception retries when connection pool is enabled. //Number of exception retries when connection pool is enabled.
private int retries => EnableConnectionPool ? MaxCachedConnections : 0; private int retries => EnableConnectionPool ? MaxCachedConnections : 0;
/// <summary> /// <summary>
/// Is the proxy currently running? /// Is the proxy currently running?
/// </summary> /// </summary>
public bool ProxyRunning { get; private set; } public bool ProxyRunning { get; private set; }
/// <summary> /// <summary>
/// Gets or sets a value indicating whether requests will be chained to upstream gateway. /// Gets or sets a value indicating whether requests will be chained to upstream gateway.
/// Defaults to false. /// Defaults to false.
/// </summary> /// </summary>
public bool ForwardToUpstreamGateway { get; set; } public bool ForwardToUpstreamGateway { get; set; }
/// <summary> /// <summary>
/// Enable disable Windows Authentication (NTLM/Kerberos). /// Enable disable Windows Authentication (NTLM/Kerberos).
/// Note: NTLM/Kerberos will always send local credentials of current user /// Note: NTLM/Kerberos will always send local credentials of current user
/// running the proxy process. This is because a man /// running the proxy process. This is because a man
/// in middle attack with Windows domain authentication is not currently supported. /// in middle attack with Windows domain authentication is not currently supported.
/// Defaults to false. /// Defaults to false.
/// </summary> /// </summary>
public bool EnableWinAuth { get; set; } public bool EnableWinAuth { get; set; }
/// <summary> /// <summary>
/// Should we check for certificare revocation during SSL authentication to servers /// Should we check for certificare revocation during SSL authentication to servers
/// Note: If enabled can reduce performance. Defaults to false. /// Note: If enabled can reduce performance. Defaults to false.
/// </summary> /// </summary>
public X509RevocationMode CheckCertificateRevocation { get; set; } public X509RevocationMode CheckCertificateRevocation { get; set; }
/// <summary> /// <summary>
/// Does this proxy uses the HTTP protocol 100 continue behaviour strictly? /// Does this proxy uses the HTTP protocol 100 continue behaviour strictly?
/// Broken 100 contunue implementations on server/client may cause problems if enabled. /// Broken 100 contunue implementations on server/client may cause problems if enabled.
/// Defaults to false. /// Defaults to false.
/// </summary> /// </summary>
public bool Enable100ContinueBehaviour { get; set; } public bool Enable100ContinueBehaviour { get; set; }
/// <summary> /// <summary>
/// Should we enable experimental server connection pool? /// Should we enable experimental server connection pool?
/// Defaults to disable. /// Defaults to disable.
/// </summary> /// </summary>
public bool EnableConnectionPool { get; set; } public bool EnableConnectionPool { get; set; }
/// <summary> /// <summary>
/// Buffer size in bytes used throughout this proxy. /// Buffer size in bytes used throughout this proxy.
/// Default value is 8192 bytes. /// Default value is 8192 bytes.
/// </summary> /// </summary>
public int BufferSize { get; set; } = 8192; public int BufferSize { get; set; } = 8192;
/// <summary> /// <summary>
/// Seconds client/server connection are to be kept alive when waiting for read/write to complete. /// Seconds client/server connection are to be kept alive when waiting for read/write to complete.
/// This will also determine the pool eviction time when connection pool is enabled. /// This will also determine the pool eviction time when connection pool is enabled.
/// Default value is 60 seconds. /// Default value is 60 seconds.
/// </summary> /// </summary>
public int ConnectionTimeOutSeconds { get; set; } public int ConnectionTimeOutSeconds { get; set; }
/// <summary> /// <summary>
/// Maximum number of concurrent connections per remote host in cache. /// Maximum number of concurrent connections per remote host in cache.
/// Only valid when connection pooling is enabled. /// Only valid when connection pooling is enabled.
/// Default value is 2. /// Default value is 2.
/// </summary> /// </summary>
public int MaxCachedConnections { get; set; } = 2; public int MaxCachedConnections { get; set; } = 2;
/// <summary> /// <summary>
/// Number of seconds to linger when Tcp connection is in TIME_WAIT state. /// Number of seconds to linger when Tcp connection is in TIME_WAIT state.
/// Default value is 30. /// Default value is 30.
/// </summary> /// </summary>
public int TcpTimeWaitSeconds { get; set; } = 30; public int TcpTimeWaitSeconds { get; set; } = 30;
/// <summary> /// <summary>
/// Should we reuse client/server tcp sockets. /// Should we reuse client/server tcp sockets.
/// Default is true (disabled for linux/macOS due to bug in .Net core). /// Default is true (disabled for linux/macOS due to bug in .Net core).
/// </summary> /// </summary>
public bool ReuseSocket { get; set; } = true; public bool ReuseSocket { get; set; } = true;
/// <summary> /// <summary>
/// Total number of active client connections. /// Total number of active client connections.
/// </summary> /// </summary>
public int ClientConnectionCount => clientConnectionCount; public int ClientConnectionCount => clientConnectionCount;
/// <summary> /// <summary>
/// Total number of active server connections. /// Total number of active server connections.
/// </summary> /// </summary>
public int ServerConnectionCount => serverConnectionCount; public int ServerConnectionCount => serverConnectionCount;
/// <summary> /// <summary>
/// Realm used during Proxy Basic Authentication. /// Realm used during Proxy Basic Authentication.
/// </summary> /// </summary>
public string ProxyAuthenticationRealm { get; set; } = "TitaniumProxy"; public string ProxyAuthenticationRealm { get; set; } = "TitaniumProxy";
/// <summary> /// <summary>
/// List of supported Ssl versions. /// List of supported Ssl versions.
/// </summary> /// </summary>
public SslProtocols SupportedSslProtocols { get; set; } = public SslProtocols SupportedSslProtocols { get; set; } =
#if NET45 #if NET45
SslProtocols.Ssl3 | SslProtocols.Ssl3 |
#endif #endif
SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12; SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12;
/// <summary> /// <summary>
/// The buffer pool used throughout this proxy instance. /// The buffer pool used throughout this proxy instance.
/// Set custom implementations by implementing this interface. /// Set custom implementations by implementing this interface.
/// By default this uses DefaultBufferPool implementation available in StreamExtended library package. /// By default this uses DefaultBufferPool implementation available in StreamExtended library package.
/// </summary> /// </summary>
public IBufferPool BufferPool { get; set; } public IBufferPool BufferPool { get; set; }
/// <summary> /// <summary>
/// Manages certificates used by this proxy. /// Manages certificates used by this proxy.
/// </summary> /// </summary>
public CertificateManager CertificateManager { get; } public CertificateManager CertificateManager { get; }
/// <summary> /// <summary>
/// External proxy used for Http requests. /// External proxy used for Http requests.
/// </summary> /// </summary>
public ExternalProxy UpStreamHttpProxy { get; set; } public ExternalProxy UpStreamHttpProxy { get; set; }
/// <summary> /// <summary>
/// External proxy used for Https requests. /// External proxy used for Https requests.
/// </summary> /// </summary>
public ExternalProxy UpStreamHttpsProxy { get; set; } public ExternalProxy UpStreamHttpsProxy { get; set; }
/// <summary> /// <summary>
/// Local adapter/NIC endpoint where proxy makes request via. /// Local adapter/NIC endpoint where proxy makes request via.
/// Defaults via any IP addresses of this machine. /// Defaults via any IP addresses of this machine.
/// </summary> /// </summary>
public IPEndPoint UpStreamEndPoint { get; set; } = new IPEndPoint(IPAddress.Any, 0); public IPEndPoint UpStreamEndPoint { get; set; } = new IPEndPoint(IPAddress.Any, 0);
/// <summary> /// <summary>
/// A list of IpAddress and port this proxy is listening to. /// A list of IpAddress and port this proxy is listening to.
/// </summary> /// </summary>
public List<ProxyEndPoint> ProxyEndPoints { get; set; } public List<ProxyEndPoint> ProxyEndPoints { get; set; }
/// <summary> /// <summary>
/// A callback to provide authentication credentials for up stream proxy this proxy is using for HTTP(S) requests. /// A callback to provide authentication credentials for up stream proxy this proxy is using for HTTP(S) requests.
/// User should return the ExternalProxy object with valid credentials. /// User should return the ExternalProxy object with valid credentials.
/// </summary> /// </summary>
public Func<SessionEventArgsBase, Task<ExternalProxy>> GetCustomUpStreamProxyFunc { get; set; } public Func<SessionEventArgsBase, Task<ExternalProxy>> GetCustomUpStreamProxyFunc { get; set; }
/// <summary> /// <summary>
/// Callback for error events in this proxy instance. /// Callback for error events in this proxy instance.
/// </summary> /// </summary>
public ExceptionHandler ExceptionFunc public ExceptionHandler ExceptionFunc
{ {
get => exceptionFunc ?? defaultExceptionFunc; get => exceptionFunc ?? defaultExceptionFunc;
set set
{ {
exceptionFunc = value; exceptionFunc = value;
CertificateManager.ExceptionFunc = value; CertificateManager.ExceptionFunc = value;
} }
} }
/// <summary> /// <summary>
/// A callback to authenticate proxy clients via basic authentication. /// A callback to authenticate proxy clients via basic authentication.
/// Parameters are username and password as provided by client. /// Parameters are username and password as provided by client.
/// Should return true for successful authentication. /// Should return true for successful authentication.
/// </summary> /// </summary>
public Func<SessionEventArgsBase, string, string, Task<bool>> ProxyBasicAuthenticateFunc { get; set; } public Func<SessionEventArgsBase, string, string, Task<bool>> ProxyBasicAuthenticateFunc { get; set; }
/// <summary> /// <summary>
/// A pluggable callback to authenticate clients by scheme instead of requiring basic authentication through ProxyBasicAuthenticateFunc. /// A pluggable callback to authenticate clients by scheme instead of requiring basic authentication through ProxyBasicAuthenticateFunc.
/// Parameters are current working session, schemeType, and token as provided by a calling client. /// Parameters are current working session, schemeType, and token as provided by a calling client.
/// Should return success for successful authentication, continuation if the package requests, or failure. /// Should return success for successful authentication, continuation if the package requests, or failure.
/// </summary> /// </summary>
public Func<SessionEventArgsBase, string, string, Task<ProxyAuthenticationContext>> ProxySchemeAuthenticateFunc { get; set; } public Func<SessionEventArgsBase, string, string, Task<ProxyAuthenticationContext>> ProxySchemeAuthenticateFunc { get; set; }
/// <summary> /// <summary>
/// A collection of scheme types, e.g. basic, NTLM, Kerberos, Negotiate, to return if scheme authentication is required. /// A collection of scheme types, e.g. basic, NTLM, Kerberos, Negotiate, to return if scheme authentication is required.
/// Works in relation with ProxySchemeAuthenticateFunc. /// Works in relation with ProxySchemeAuthenticateFunc.
/// </summary> /// </summary>
public IEnumerable<string> ProxyAuthenticationSchemes { get; set; } = new string[0]; public IEnumerable<string> ProxyAuthenticationSchemes { get; set; } = new string[0];
/// <summary> /// <summary>
/// Event occurs when client connection count changed. /// Event occurs when client connection count changed.
/// </summary> /// </summary>
public event EventHandler ClientConnectionCountChanged; public event EventHandler ClientConnectionCountChanged;
/// <summary> /// <summary>
/// Event occurs when server connection count changed. /// Event occurs when server connection count changed.
/// </summary> /// </summary>
public event EventHandler ServerConnectionCountChanged; public event EventHandler ServerConnectionCountChanged;
/// <summary> /// <summary>
/// Event to override the default verification logic of remote SSL certificate received during authentication. /// Event to override the default verification logic of remote SSL certificate received during authentication.
/// </summary> /// </summary>
public event AsyncEventHandler<CertificateValidationEventArgs> ServerCertificateValidationCallback; public event AsyncEventHandler<CertificateValidationEventArgs> ServerCertificateValidationCallback;
/// <summary> /// <summary>
/// Event to override client certificate selection during mutual SSL authentication. /// Event to override client certificate selection during mutual SSL authentication.
/// </summary> /// </summary>
public event AsyncEventHandler<CertificateSelectionEventArgs> ClientCertificateSelectionCallback; public event AsyncEventHandler<CertificateSelectionEventArgs> ClientCertificateSelectionCallback;
/// <summary> /// <summary>
/// Intercept request event to server. /// Intercept request event to server.
/// </summary> /// </summary>
public event AsyncEventHandler<SessionEventArgs> BeforeRequest; public event AsyncEventHandler<SessionEventArgs> BeforeRequest;
/// <summary> /// <summary>
/// Intercept response event from server. /// Intercept response event from server.
/// </summary> /// </summary>
public event AsyncEventHandler<SessionEventArgs> BeforeResponse; public event AsyncEventHandler<SessionEventArgs> BeforeResponse;
/// <summary> /// <summary>
/// Intercept after response event from server. /// Intercept after response event from server.
/// </summary> /// </summary>
public event AsyncEventHandler<SessionEventArgs> AfterResponse; public event AsyncEventHandler<SessionEventArgs> AfterResponse;
/// <summary> /// <summary>
/// Customize TcpClient used for client connection upon create. /// Customize TcpClient used for client connection upon create.
/// </summary> /// </summary>
public event AsyncEventHandler<TcpClient> OnClientConnectionCreate; public event AsyncEventHandler<TcpClient> OnClientConnectionCreate;
/// <summary> /// <summary>
/// Customize TcpClient used for server connection upon create. /// Customize TcpClient used for server connection upon create.
/// </summary> /// </summary>
public event AsyncEventHandler<TcpClient> OnServerConnectionCreate; public event AsyncEventHandler<TcpClient> OnServerConnectionCreate;
/// <summary> /// <summary>
/// Add a proxy end point. /// Add a proxy end point.
/// </summary> /// </summary>
/// <param name="endPoint">The proxy endpoint.</param> /// <param name="endPoint">The proxy endpoint.</param>
public void AddEndPoint(ProxyEndPoint endPoint) public void AddEndPoint(ProxyEndPoint endPoint)
{ {
if (ProxyEndPoints.Any(x => if (ProxyEndPoints.Any(x =>
x.IpAddress.Equals(endPoint.IpAddress) && endPoint.Port != 0 && x.Port == endPoint.Port)) x.IpAddress.Equals(endPoint.IpAddress) && endPoint.Port != 0 && x.Port == endPoint.Port))
{ {
throw new Exception("Cannot add another endpoint to same port & ip address"); throw new Exception("Cannot add another endpoint to same port & ip address");
} }
ProxyEndPoints.Add(endPoint); ProxyEndPoints.Add(endPoint);
if (ProxyRunning) if (ProxyRunning)
{ {
listen(endPoint); listen(endPoint);
} }
} }
/// <summary> /// <summary>
/// Remove a proxy end point. /// Remove a proxy end point.
/// Will throw error if the end point does'nt exist. /// Will throw error if the end point does'nt exist.
/// </summary> /// </summary>
/// <param name="endPoint">The existing endpoint to remove.</param> /// <param name="endPoint">The existing endpoint to remove.</param>
public void RemoveEndPoint(ProxyEndPoint endPoint) public void RemoveEndPoint(ProxyEndPoint endPoint)
{ {
if (ProxyEndPoints.Contains(endPoint) == false) if (ProxyEndPoints.Contains(endPoint) == false)
{ {
throw new Exception("Cannot remove endPoints not added to proxy"); throw new Exception("Cannot remove endPoints not added to proxy");
} }
ProxyEndPoints.Remove(endPoint); ProxyEndPoints.Remove(endPoint);
if (ProxyRunning) if (ProxyRunning)
{ {
quitListen(endPoint); quitListen(endPoint);
} }
} }
/// <summary> /// <summary>
/// Set the given explicit end point as the default proxy server for current machine. /// Set the given explicit end point as the default proxy server for current machine.
/// </summary> /// </summary>
/// <param name="endPoint">The explicit endpoint.</param> /// <param name="endPoint">The explicit endpoint.</param>
public void SetAsSystemHttpProxy(ExplicitProxyEndPoint endPoint) public void SetAsSystemHttpProxy(ExplicitProxyEndPoint endPoint)
{ {
SetAsSystemProxy(endPoint, ProxyProtocolType.Http); SetAsSystemProxy(endPoint, ProxyProtocolType.Http);
} }
/// <summary> /// <summary>
/// Set the given explicit end point as the default proxy server for current machine. /// Set the given explicit end point as the default proxy server for current machine.
/// </summary> /// </summary>
/// <param name="endPoint">The explicit endpoint.</param> /// <param name="endPoint">The explicit endpoint.</param>
public void SetAsSystemHttpsProxy(ExplicitProxyEndPoint endPoint) public void SetAsSystemHttpsProxy(ExplicitProxyEndPoint endPoint)
{ {
SetAsSystemProxy(endPoint, ProxyProtocolType.Https); SetAsSystemProxy(endPoint, ProxyProtocolType.Https);
} }
/// <summary> /// <summary>
/// Set the given explicit end point as the default proxy server for current machine. /// Set the given explicit end point as the default proxy server for current machine.
/// </summary> /// </summary>
/// <param name="endPoint">The explicit endpoint.</param> /// <param name="endPoint">The explicit endpoint.</param>
/// <param name="protocolType">The proxy protocol type.</param> /// <param name="protocolType">The proxy protocol type.</param>
public void SetAsSystemProxy(ExplicitProxyEndPoint endPoint, ProxyProtocolType protocolType) public void SetAsSystemProxy(ExplicitProxyEndPoint endPoint, ProxyProtocolType protocolType)
{ {
if (RunTime.IsRunningOnMono) if (RunTime.IsRunningOnMono)
{ {
throw new Exception("Mono Runtime do not support system proxy settings."); throw new Exception("Mono Runtime do not support system proxy settings.");
} }
validateEndPointAsSystemProxy(endPoint); validateEndPointAsSystemProxy(endPoint);
bool isHttp = (protocolType & ProxyProtocolType.Http) > 0; bool isHttp = (protocolType & ProxyProtocolType.Http) > 0;
bool isHttps = (protocolType & ProxyProtocolType.Https) > 0; bool isHttps = (protocolType & ProxyProtocolType.Https) > 0;
if (isHttps) if (isHttps)
{ {
CertificateManager.EnsureRootCertificate(); CertificateManager.EnsureRootCertificate();
// If certificate was trusted by the machine // If certificate was trusted by the machine
if (!CertificateManager.CertValidated) if (!CertificateManager.CertValidated)
{ {
protocolType = protocolType & ~ProxyProtocolType.Https; protocolType = protocolType & ~ProxyProtocolType.Https;
isHttps = false; isHttps = false;
} }
} }
// clear any settings previously added // clear any settings previously added
if (isHttp) if (isHttp)
{ {
ProxyEndPoints.OfType<ExplicitProxyEndPoint>().ToList().ForEach(x => x.IsSystemHttpProxy = false); ProxyEndPoints.OfType<ExplicitProxyEndPoint>().ToList().ForEach(x => x.IsSystemHttpProxy = false);
} }
if (isHttps) if (isHttps)
{ {
ProxyEndPoints.OfType<ExplicitProxyEndPoint>().ToList().ForEach(x => x.IsSystemHttpsProxy = false); ProxyEndPoints.OfType<ExplicitProxyEndPoint>().ToList().ForEach(x => x.IsSystemHttpsProxy = false);
} }
systemProxySettingsManager.SetProxy( systemProxySettingsManager.SetProxy(
Equals(endPoint.IpAddress, IPAddress.Any) | Equals(endPoint.IpAddress, IPAddress.Any) |
Equals(endPoint.IpAddress, IPAddress.Loopback) Equals(endPoint.IpAddress, IPAddress.Loopback)
? "localhost" ? "localhost"
: endPoint.IpAddress.ToString(), : endPoint.IpAddress.ToString(),
endPoint.Port, endPoint.Port,
protocolType); protocolType);
if (isHttp) if (isHttp)
{ {
endPoint.IsSystemHttpProxy = true; endPoint.IsSystemHttpProxy = true;
} }
if (isHttps) if (isHttps)
{ {
endPoint.IsSystemHttpsProxy = true; endPoint.IsSystemHttpsProxy = true;
} }
string proxyType = null; string proxyType = null;
switch (protocolType) switch (protocolType)
{ {
case ProxyProtocolType.Http: case ProxyProtocolType.Http:
proxyType = "HTTP"; proxyType = "HTTP";
break; break;
case ProxyProtocolType.Https: case ProxyProtocolType.Https:
proxyType = "HTTPS"; proxyType = "HTTPS";
break; break;
case ProxyProtocolType.AllHttp: case ProxyProtocolType.AllHttp:
proxyType = "HTTP and HTTPS"; proxyType = "HTTP and HTTPS";
break; break;
} }
if (protocolType != ProxyProtocolType.None) if (protocolType != ProxyProtocolType.None)
{ {
Console.WriteLine("Set endpoint at Ip {0} and port: {1} as System {2} Proxy", endPoint.IpAddress, Console.WriteLine("Set endpoint at Ip {0} and port: {1} as System {2} Proxy", endPoint.IpAddress,
endPoint.Port, proxyType); endPoint.Port, proxyType);
} }
} }
/// <summary> /// <summary>
/// Clear HTTP proxy settings of current machine. /// Clear HTTP proxy settings of current machine.
/// </summary> /// </summary>
public void DisableSystemHttpProxy() public void DisableSystemHttpProxy()
{ {
DisableSystemProxy(ProxyProtocolType.Http); DisableSystemProxy(ProxyProtocolType.Http);
} }
/// <summary> /// <summary>
/// Clear HTTPS proxy settings of current machine. /// Clear HTTPS proxy settings of current machine.
/// </summary> /// </summary>
public void DisableSystemHttpsProxy() public void DisableSystemHttpsProxy()
{ {
DisableSystemProxy(ProxyProtocolType.Https); DisableSystemProxy(ProxyProtocolType.Https);
} }
/// <summary> /// <summary>
/// Clear the specified proxy setting for current machine. /// Clear the specified proxy setting for current machine.
/// </summary> /// </summary>
public void DisableSystemProxy(ProxyProtocolType protocolType) public void DisableSystemProxy(ProxyProtocolType protocolType)
{ {
if (RunTime.IsRunningOnMono) if (RunTime.IsRunningOnMono)
{ {
throw new Exception("Mono Runtime do not support system proxy settings."); throw new Exception("Mono Runtime do not support system proxy settings.");
} }
systemProxySettingsManager.RemoveProxy(protocolType); systemProxySettingsManager.RemoveProxy(protocolType);
} }
/// <summary> /// <summary>
/// Clear all proxy settings for current machine. /// Clear all proxy settings for current machine.
/// </summary> /// </summary>
public void DisableAllSystemProxies() public void DisableAllSystemProxies()
{ {
if (RunTime.IsRunningOnMono) if (RunTime.IsRunningOnMono)
{ {
throw new Exception("Mono Runtime do not support system proxy settings."); throw new Exception("Mono Runtime do not support system proxy settings.");
} }
systemProxySettingsManager.DisableAllProxy(); systemProxySettingsManager.DisableAllProxy();
} }
/// <summary> /// <summary>
/// Start this proxy server instance. /// Start this proxy server instance.
/// </summary> /// </summary>
public void Start() public void Start()
{ {
if (ProxyRunning) if (ProxyRunning)
{ {
throw new Exception("Proxy is already running."); throw new Exception("Proxy is already running.");
} }
if (ProxyEndPoints.OfType<ExplicitProxyEndPoint>().Any(x => x.GenericCertificate == null)) if (ProxyEndPoints.OfType<ExplicitProxyEndPoint>().Any(x => x.GenericCertificate == null))
{ {
CertificateManager.EnsureRootCertificate(); CertificateManager.EnsureRootCertificate();
} }
// clear any system proxy settings which is pointing to our own endpoint (causing a cycle) // clear any system proxy settings which is pointing to our own endpoint (causing a cycle)
// due to ungracious proxy shutdown before or something else // due to ungracious proxy shutdown before or something else
if (systemProxySettingsManager != null && RunTime.IsWindows) if (systemProxySettingsManager != null && RunTime.IsWindows)
{ {
var proxyInfo = systemProxySettingsManager.GetProxyInfoFromRegistry(); var proxyInfo = systemProxySettingsManager.GetProxyInfoFromRegistry();
if (proxyInfo.Proxies != null) if (proxyInfo.Proxies != null)
{ {
var protocolToRemove = ProxyProtocolType.None; var protocolToRemove = ProxyProtocolType.None;
foreach (var proxy in proxyInfo.Proxies.Values) foreach (var proxy in proxyInfo.Proxies.Values)
{ {
if (NetworkHelper.IsLocalIpAddress(proxy.HostName) if (NetworkHelper.IsLocalIpAddress(proxy.HostName)
&& ProxyEndPoints.Any(x => x.Port == proxy.Port)) && ProxyEndPoints.Any(x => x.Port == proxy.Port))
{ {
protocolToRemove |= proxy.ProtocolType; protocolToRemove |= proxy.ProtocolType;
} }
} }
if (protocolToRemove != ProxyProtocolType.None) if (protocolToRemove != ProxyProtocolType.None)
{ {
systemProxySettingsManager.RemoveProxy(protocolToRemove, false); systemProxySettingsManager.RemoveProxy(protocolToRemove, false);
} }
} }
} }
if (ForwardToUpstreamGateway && GetCustomUpStreamProxyFunc == null && systemProxySettingsManager != null) if (ForwardToUpstreamGateway && GetCustomUpStreamProxyFunc == null && systemProxySettingsManager != null)
{ {
// Use WinHttp to handle PAC/WAPD scripts. // Use WinHttp to handle PAC/WAPD scripts.
systemProxyResolver = new WinHttpWebProxyFinder(); systemProxyResolver = new WinHttpWebProxyFinder();
systemProxyResolver.LoadFromIE(); systemProxyResolver.LoadFromIE();
GetCustomUpStreamProxyFunc = getSystemUpStreamProxy; GetCustomUpStreamProxyFunc = getSystemUpStreamProxy;
} }
ProxyRunning = true; ProxyRunning = true;
CertificateManager.ClearIdleCertificates(); CertificateManager.ClearIdleCertificates();
foreach (var endPoint in ProxyEndPoints) foreach (var endPoint in ProxyEndPoints)
{ {
listen(endPoint); listen(endPoint);
} }
} }
/// <summary> /// <summary>
/// Stop this proxy server instance. /// Stop this proxy server instance.
/// </summary> /// </summary>
public void Stop() public void Stop()
{ {
if (!ProxyRunning) if (!ProxyRunning)
{ {
throw new Exception("Proxy is not running."); throw new Exception("Proxy is not running.");
} }
if (!RunTime.IsRunningOnMono && RunTime.IsWindows) if (!RunTime.IsRunningOnMono && RunTime.IsWindows)
{ {
bool setAsSystemProxy = ProxyEndPoints.OfType<ExplicitProxyEndPoint>() bool setAsSystemProxy = ProxyEndPoints.OfType<ExplicitProxyEndPoint>()
.Any(x => x.IsSystemHttpProxy || x.IsSystemHttpsProxy); .Any(x => x.IsSystemHttpProxy || x.IsSystemHttpsProxy);
if (setAsSystemProxy) if (setAsSystemProxy)
{ {
systemProxySettingsManager.RestoreOriginalSettings(); systemProxySettingsManager.RestoreOriginalSettings();
} }
} }
foreach (var endPoint in ProxyEndPoints) foreach (var endPoint in ProxyEndPoints)
{ {
quitListen(endPoint); quitListen(endPoint);
} }
ProxyEndPoints.Clear(); ProxyEndPoints.Clear();
CertificateManager?.StopClearIdleCertificates(); CertificateManager?.StopClearIdleCertificates();
tcpConnectionFactory.Dispose(); tcpConnectionFactory.Dispose();
ProxyRunning = false; ProxyRunning = false;
} }
/// <summary> /// <summary>
/// Listen on given end point of local machine. /// Listen on given end point of local machine.
/// </summary> /// </summary>
/// <param name="endPoint">The end point to listen.</param> /// <param name="endPoint">The end point to listen.</param>
private void listen(ProxyEndPoint endPoint) private void listen(ProxyEndPoint endPoint)
{ {
endPoint.Listener = new TcpListener(endPoint.IpAddress, endPoint.Port); endPoint.Listener = new TcpListener(endPoint.IpAddress, endPoint.Port);
//linux/macOS has a bug with socket reuse in .net core. //linux/macOS has a bug with socket reuse in .net core.
if (ReuseSocket && (RunTime.IsWindows || RunTime.IsRunningOnMono)) if (ReuseSocket && (RunTime.IsWindows || RunTime.IsRunningOnMono))
{ {
endPoint.Listener.Server.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); endPoint.Listener.Server.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
} }
try try
{ {
endPoint.Listener.Start(); endPoint.Listener.Start();
endPoint.Port = ((IPEndPoint)endPoint.Listener.LocalEndpoint).Port; endPoint.Port = ((IPEndPoint)endPoint.Listener.LocalEndpoint).Port;
// accept clients asynchronously // accept clients asynchronously
endPoint.Listener.BeginAcceptTcpClient(onAcceptConnection, endPoint); endPoint.Listener.BeginAcceptTcpClient(onAcceptConnection, endPoint);
} }
catch (SocketException ex) catch (SocketException ex)
{ {
var pex = new Exception( var pex = new Exception(
$"Endpoint {endPoint} failed to start. Check inner exception and exception data for details.", ex); $"Endpoint {endPoint} failed to start. Check inner exception and exception data for details.", ex);
pex.Data.Add("ipAddress", endPoint.IpAddress); pex.Data.Add("ipAddress", endPoint.IpAddress);
pex.Data.Add("port", endPoint.Port); pex.Data.Add("port", endPoint.Port);
throw pex; throw pex;
} }
} }
/// <summary> /// <summary>
/// Verify if its safe to set this end point as system proxy. /// Verify if its safe to set this end point as system proxy.
/// </summary> /// </summary>
/// <param name="endPoint">The end point to validate.</param> /// <param name="endPoint">The end point to validate.</param>
private void validateEndPointAsSystemProxy(ExplicitProxyEndPoint endPoint) private void validateEndPointAsSystemProxy(ExplicitProxyEndPoint endPoint)
{ {
if (endPoint == null) if (endPoint == null)
{ {
throw new ArgumentNullException(nameof(endPoint)); throw new ArgumentNullException(nameof(endPoint));
} }
if (ProxyEndPoints.Contains(endPoint) == false) if (ProxyEndPoints.Contains(endPoint) == false)
{ {
throw new Exception("Cannot set endPoints not added to proxy as system proxy"); throw new Exception("Cannot set endPoints not added to proxy as system proxy");
} }
if (!ProxyRunning) if (!ProxyRunning)
{ {
throw new Exception("Cannot set system proxy settings before proxy has been started."); throw new Exception("Cannot set system proxy settings before proxy has been started.");
} }
} }
/// <summary> /// <summary>
/// Gets the system up stream proxy. /// Gets the system up stream proxy.
/// </summary> /// </summary>
/// <param name="sessionEventArgs">The session.</param> /// <param name="sessionEventArgs">The session.</param>
/// <returns>The external proxy as task result.</returns> /// <returns>The external proxy as task result.</returns>
private Task<ExternalProxy> getSystemUpStreamProxy(SessionEventArgsBase sessionEventArgs) private Task<ExternalProxy> getSystemUpStreamProxy(SessionEventArgsBase sessionEventArgs)
{ {
var proxy = systemProxyResolver.GetProxy(sessionEventArgs.WebSession.Request.RequestUri); var proxy = systemProxyResolver.GetProxy(sessionEventArgs.WebSession.Request.RequestUri);
return Task.FromResult(proxy); return Task.FromResult(proxy);
} }
/// <summary> /// <summary>
/// Act when a connection is received from client. /// Act when a connection is received from client.
/// </summary> /// </summary>
private void onAcceptConnection(IAsyncResult asyn) private void onAcceptConnection(IAsyncResult asyn)
{ {
var endPoint = (ProxyEndPoint)asyn.AsyncState; var endPoint = (ProxyEndPoint)asyn.AsyncState;
TcpClient tcpClient = null; TcpClient tcpClient = null;
try try
{ {
// based on end point type call appropriate request handlers // based on end point type call appropriate request handlers
tcpClient = endPoint.Listener.EndAcceptTcpClient(asyn); tcpClient = endPoint.Listener.EndAcceptTcpClient(asyn);
} }
catch (ObjectDisposedException) catch (ObjectDisposedException)
{ {
// The listener was Stop()'d, disposing the underlying socket and // The listener was Stop()'d, disposing the underlying socket and
// triggering the completion of the callback. We're already exiting, // triggering the completion of the callback. We're already exiting,
// so just return. // so just return.
return; return;
} }
catch catch
{ {
// Other errors are discarded to keep proxy running // Other errors are discarded to keep proxy running
} }
if (tcpClient != null) if (tcpClient != null)
{ {
Task.Run(async () => { await handleClient(tcpClient, endPoint); }); Task.Run(async () => { await handleClient(tcpClient, endPoint); });
} }
// Get the listener that handles the client request. // Get the listener that handles the client request.
endPoint.Listener.BeginAcceptTcpClient(onAcceptConnection, endPoint); endPoint.Listener.BeginAcceptTcpClient(onAcceptConnection, endPoint);
} }
/// <summary> /// <summary>
/// Handle the client. /// Handle the client.
/// </summary> /// </summary>
/// <param name="tcpClient">The client.</param> /// <param name="tcpClient">The client.</param>
/// <param name="endPoint">The proxy endpoint.</param> /// <param name="endPoint">The proxy endpoint.</param>
/// <returns>The task.</returns> /// <returns>The task.</returns>
private async Task handleClient(TcpClient tcpClient, ProxyEndPoint endPoint) private async Task handleClient(TcpClient tcpClient, ProxyEndPoint endPoint)
{ {
tcpClient.ReceiveTimeout = ConnectionTimeOutSeconds * 1000; tcpClient.ReceiveTimeout = ConnectionTimeOutSeconds * 1000;
tcpClient.SendTimeout = ConnectionTimeOutSeconds * 1000; tcpClient.SendTimeout = ConnectionTimeOutSeconds * 1000;
tcpClient.SendBufferSize = BufferSize; tcpClient.SendBufferSize = BufferSize;
tcpClient.ReceiveBufferSize = BufferSize; tcpClient.ReceiveBufferSize = BufferSize;
tcpClient.LingerState = new LingerOption(true, TcpTimeWaitSeconds); tcpClient.LingerState = new LingerOption(true, TcpTimeWaitSeconds);
await InvokeConnectionCreateEvent(tcpClient, true); await InvokeConnectionCreateEvent(tcpClient, true);
using (var clientConnection = new TcpClientConnection(this, tcpClient)) using (var clientConnection = new TcpClientConnection(this, tcpClient))
{ {
if (endPoint is TransparentProxyEndPoint tep) if (endPoint is TransparentProxyEndPoint tep)
{ {
await handleClient(tep, clientConnection); await handleClient(tep, clientConnection);
} }
else else
{ {
await handleClient((ExplicitProxyEndPoint)endPoint, clientConnection); await handleClient((ExplicitProxyEndPoint)endPoint, clientConnection);
} }
} }
} }
/// <summary> /// <summary>
/// Handle exception. /// Handle exception.
/// </summary> /// </summary>
/// <param name="clientStream">The client stream.</param> /// <param name="clientStream">The client stream.</param>
/// <param name="exception">The exception.</param> /// <param name="exception">The exception.</param>
private void onException(CustomBufferedStream clientStream, Exception exception) private void onException(CustomBufferedStream clientStream, Exception exception)
{ {
#if DEBUG #if DEBUG
if (clientStream is DebugCustomBufferedStream debugStream) if (clientStream is DebugCustomBufferedStream debugStream)
{ {
debugStream.LogException(exception); debugStream.LogException(exception);
} }
#endif #endif
ExceptionFunc(exception); ExceptionFunc(exception);
} }
/// <summary> /// <summary>
/// Quit listening on the given end point. /// Quit listening on the given end point.
/// </summary> /// </summary>
private void quitListen(ProxyEndPoint endPoint) private void quitListen(ProxyEndPoint endPoint)
{ {
endPoint.Listener.Stop(); endPoint.Listener.Stop();
endPoint.Listener.Server.Dispose(); endPoint.Listener.Server.Dispose();
} }
/// <summary> /// <summary>
/// Update client connection count. /// Update client connection count.
/// </summary> /// </summary>
/// <param name="increment">Should we increment/decrement?</param> /// <param name="increment">Should we increment/decrement?</param>
internal void UpdateClientConnectionCount(bool increment) internal void UpdateClientConnectionCount(bool increment)
{ {
if (increment) if (increment)
{ {
Interlocked.Increment(ref clientConnectionCount); Interlocked.Increment(ref clientConnectionCount);
} }
else else
{ {
Interlocked.Decrement(ref clientConnectionCount); Interlocked.Decrement(ref clientConnectionCount);
} }
ClientConnectionCountChanged?.Invoke(this, EventArgs.Empty); ClientConnectionCountChanged?.Invoke(this, EventArgs.Empty);
} }
/// <summary> /// <summary>
/// Update server connection count. /// Update server connection count.
/// </summary> /// </summary>
/// <param name="increment">Should we increment/decrement?</param> /// <param name="increment">Should we increment/decrement?</param>
internal void UpdateServerConnectionCount(bool increment) internal void UpdateServerConnectionCount(bool increment)
{ {
if (increment) if (increment)
{ {
Interlocked.Increment(ref serverConnectionCount); Interlocked.Increment(ref serverConnectionCount);
} }
else else
{ {
Interlocked.Decrement(ref serverConnectionCount); Interlocked.Decrement(ref serverConnectionCount);
} }
ServerConnectionCountChanged?.Invoke(this, EventArgs.Empty); ServerConnectionCountChanged?.Invoke(this, EventArgs.Empty);
} }
/// <summary> /// <summary>
/// Invoke client/server tcp connection events if subscribed by API user. /// Invoke client/server tcp connection events if subscribed by API user.
/// </summary> /// </summary>
/// <param name="client">The TcpClient object.</param> /// <param name="client">The TcpClient object.</param>
/// <param name="isClientConnection">Is this a client connection created event? If not then we would assume that its a server connection create event.</param> /// <param name="isClientConnection">Is this a client connection created event? If not then we would assume that its a server connection create event.</param>
/// <returns></returns> /// <returns></returns>
internal async Task InvokeConnectionCreateEvent(TcpClient client, bool isClientConnection) internal async Task InvokeConnectionCreateEvent(TcpClient client, bool isClientConnection)
{ {
//client connection created //client connection created
if (isClientConnection && OnClientConnectionCreate != null) if (isClientConnection && OnClientConnectionCreate != null)
{ {
await OnClientConnectionCreate.InvokeAsync(this, client, ExceptionFunc); await OnClientConnectionCreate.InvokeAsync(this, client, ExceptionFunc);
} }
//server connection created //server connection created
if (!isClientConnection && OnServerConnectionCreate != null) if (!isClientConnection && OnServerConnectionCreate != null)
{ {
await OnServerConnectionCreate.InvokeAsync(this, client, ExceptionFunc); await OnServerConnectionCreate.InvokeAsync(this, client, ExceptionFunc);
} }
} }
/// <summary> /// <summary>
/// Connection retry policy when using connection pool. /// Connection retry policy when using connection pool.
/// </summary> /// </summary>
private RetryPolicy<T> retryPolicy<T>() where T : Exception private RetryPolicy<T> retryPolicy<T>() where T : Exception
{ {
return new RetryPolicy<T>(retries, tcpConnectionFactory); return new RetryPolicy<T>(retries, tcpConnectionFactory);
} }
/// <summary> /// <summary>
/// Dispose the Proxy instance. /// Dispose the Proxy instance.
/// </summary> /// </summary>
public void Dispose() public void Dispose()
{ {
if (ProxyRunning) if (ProxyRunning)
{ {
Stop(); Stop();
} }
CertificateManager?.Dispose(); CertificateManager?.Dispose();
BufferPool?.Dispose(); BufferPool?.Dispose();
} }
} }
} }
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
#if NETCOREAPP2_1 #if NETCOREAPP2_1
using System.Net.Security; using System.Net.Security;
#endif #endif
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using StreamExtended.Network; using StreamExtended.Network;
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.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.Shared;
namespace Titanium.Web.Proxy namespace Titanium.Web.Proxy
{ {
/// <summary> /// <summary>
/// Handle the request /// Handle the request
/// </summary> /// </summary>
public partial class ProxyServer public partial class ProxyServer
{ {
private bool isWindowsAuthenticationEnabledAndSupported => private bool isWindowsAuthenticationEnabledAndSupported =>
EnableWinAuth && RunTime.IsWindows && !RunTime.IsRunningOnMono; EnableWinAuth && RunTime.IsWindows && !RunTime.IsRunningOnMono;
/// <summary> /// <summary>
/// This is the core request handler method for a particular connection from client. /// This is the core request handler method for a particular connection from client.
/// Will create new session (request/response) sequence until /// Will create new session (request/response) sequence until
/// client/server abruptly terminates connection or by normal HTTP termination. /// client/server abruptly terminates connection or by normal HTTP termination.
/// </summary> /// </summary>
/// <param name="endPoint">The proxy endpoint.</param> /// <param name="endPoint">The proxy endpoint.</param>
/// <param name="clientConnection">The client connection.</param> /// <param name="clientConnection">The client connection.</param>
/// <param name="clientStream">The client stream.</param> /// <param name="clientStream">The client stream.</param>
/// <param name="clientStreamWriter">The client stream writer.</param> /// <param name="clientStreamWriter">The client stream writer.</param>
/// <param name="cancellationTokenSource">The cancellation token source for this async task.</param> /// <param name="cancellationTokenSource">The cancellation token source for this async task.</param>
/// <param name="httpsConnectHostname"> /// <param name="httpsConnectHostname">
/// The https hostname as appeared in CONNECT request if this is a HTTPS request from /// The https hostname as appeared in CONNECT request if this is a HTTPS request from
/// explicit endpoint. /// explicit endpoint.
/// </param> /// </param>
/// <param name="connectRequest">The Connect request if this is a HTTPS request from explicit endpoint.</param> /// <param name="connectRequest">The Connect request if this is a HTTPS request from explicit endpoint.</param>
/// <param name="prefetchConnectionTask">Prefetched server connection for current client using Connect/SNI headers.</param> /// <param name="prefetchConnectionTask">Prefetched server connection for current client using Connect/SNI headers.</param>
private async Task handleHttpSessionRequest(ProxyEndPoint endPoint, TcpClientConnection clientConnection, private async Task handleHttpSessionRequest(ProxyEndPoint endPoint, TcpClientConnection clientConnection,
CustomBufferedStream clientStream, HttpResponseWriter clientStreamWriter, CustomBufferedStream clientStream, HttpResponseWriter clientStreamWriter,
CancellationTokenSource cancellationTokenSource, string httpsConnectHostname, ConnectRequest connectRequest, CancellationTokenSource cancellationTokenSource, string httpsConnectHostname, ConnectRequest connectRequest,
Task<TcpServerConnection> prefetchConnectionTask = null) Task<TcpServerConnection> prefetchConnectionTask = null)
{ {
var prefetchTask = prefetchConnectionTask; var prefetchTask = prefetchConnectionTask;
TcpServerConnection connection = null; TcpServerConnection connection = null;
bool closeServerConnection = false; bool closeServerConnection = false;
try try
{ {
var cancellationToken = cancellationTokenSource.Token; var cancellationToken = cancellationTokenSource.Token;
// Loop through each subsequest request on this particular client connection // Loop through each subsequest request on this particular client connection
// (assuming HTTP connection is kept alive by client) // (assuming HTTP connection is kept alive by client)
while (true) while (true)
{ {
// read the request line // read the request line
string httpCmd = await clientStream.ReadLineAsync(cancellationToken); string httpCmd = await clientStream.ReadLineAsync(cancellationToken);
if (string.IsNullOrEmpty(httpCmd)) if (string.IsNullOrEmpty(httpCmd))
{ {
return; return;
} }
var args = new SessionEventArgs(this, endPoint, cancellationTokenSource) var args = new SessionEventArgs(this, endPoint, cancellationTokenSource)
{ {
ProxyClient = { ClientConnection = clientConnection }, ProxyClient = { ClientConnection = clientConnection },
WebSession = { ConnectRequest = connectRequest } WebSession = { ConnectRequest = connectRequest }
}; };
try try
{ {
try try
{ {
Request.ParseRequestLine(httpCmd, out string httpMethod, out string httpUrl, Request.ParseRequestLine(httpCmd, out string httpMethod, out string httpUrl,
out var version); out var version);
// Read the request headers in to unique and non-unique header collections // Read the request headers in to unique and non-unique header collections
await HeaderParser.ReadHeaders(clientStream, args.WebSession.Request.Headers, await HeaderParser.ReadHeaders(clientStream, args.WebSession.Request.Headers,
cancellationToken); cancellationToken);
Uri httpRemoteUri; Uri httpRemoteUri;
if (ProxyConstants.UriSchemeRegex.IsMatch(httpUrl)) if (ProxyConstants.UriSchemeRegex.IsMatch(httpUrl))
{ {
try try
{ {
httpRemoteUri = new Uri(httpUrl); httpRemoteUri = new Uri(httpUrl);
} }
catch (Exception ex) catch (Exception ex)
{ {
throw new Exception($"Invalid URI: '{httpUrl}'", ex); throw new Exception($"Invalid URI: '{httpUrl}'", ex);
} }
} }
else else
{ {
string host = args.WebSession.Request.Host ?? httpsConnectHostname; string host = args.WebSession.Request.Host ?? httpsConnectHostname;
string hostAndPath = host; string hostAndPath = host;
if (httpUrl.StartsWith("/")) if (httpUrl.StartsWith("/"))
{ {
hostAndPath += httpUrl; hostAndPath += httpUrl;
} }
string url = string.Concat(httpsConnectHostname == null ? "http://" : "https://", string url = string.Concat(httpsConnectHostname == null ? "http://" : "https://",
hostAndPath); hostAndPath);
try try
{ {
httpRemoteUri = new Uri(url); httpRemoteUri = new Uri(url);
} }
catch (Exception ex) catch (Exception ex)
{ {
throw new Exception($"Invalid URI: '{url}'", ex); throw new Exception($"Invalid URI: '{url}'", ex);
} }
} }
var request = args.WebSession.Request; var request = args.WebSession.Request;
request.RequestUri = httpRemoteUri; request.RequestUri = httpRemoteUri;
request.OriginalUrl = httpUrl; request.OriginalUrl = httpUrl;
request.Method = httpMethod; request.Method = httpMethod;
request.HttpVersion = version; request.HttpVersion = version;
args.ProxyClient.ClientStream = clientStream; args.ProxyClient.ClientStream = clientStream;
args.ProxyClient.ClientStreamWriter = clientStreamWriter; args.ProxyClient.ClientStreamWriter = clientStreamWriter;
if (!args.IsTransparent) if (!args.IsTransparent)
{ {
// proxy authorization check // proxy authorization check
if (httpsConnectHostname == null && await checkAuthorization(args) == false) if (httpsConnectHostname == null && await checkAuthorization(args) == false)
{ {
await invokeBeforeResponse(args); await invokeBeforeResponse(args);
// send the response // send the response
await clientStreamWriter.WriteResponseAsync(args.WebSession.Response, await clientStreamWriter.WriteResponseAsync(args.WebSession.Response,
cancellationToken: cancellationToken); cancellationToken: cancellationToken);
return; return;
} }
prepareRequestHeaders(request.Headers); prepareRequestHeaders(request.Headers);
request.Host = request.RequestUri.Authority; request.Host = request.RequestUri.Authority;
} }
// if win auth is enabled // if win auth is enabled
// we need a cache of request body // we need a cache of request body
// so that we can send it after authentication in WinAuthHandler.cs // so that we can send it after authentication in WinAuthHandler.cs
if (isWindowsAuthenticationEnabledAndSupported && request.HasBody) if (isWindowsAuthenticationEnabledAndSupported && request.HasBody)
{ {
await args.GetRequestBody(cancellationToken); await args.GetRequestBody(cancellationToken);
} }
//we need this to syphon out data from connection if API user changes them. //we need this to syphon out data from connection if API user changes them.
request.SetOriginalHeaders(); request.SetOriginalHeaders();
args.TimeLine["Request Received"] = DateTime.Now; args.TimeLine["Request Received"] = DateTime.Now;
// If user requested interception do it // If user requested interception do it
await invokeBeforeRequest(args); await invokeBeforeRequest(args);
var response = args.WebSession.Response; var response = args.WebSession.Response;
if (request.CancelRequest) if (request.CancelRequest)
{ {
// syphon out the request body from client before setting the new body // syphon out the request body from client before setting the new body
await args.SyphonOutBodyAsync(true, cancellationToken); await args.SyphonOutBodyAsync(true, cancellationToken);
await handleHttpSessionResponse(args); await handleHttpSessionResponse(args);
if (!response.KeepAlive) if (!response.KeepAlive)
{ {
return; return;
} }
continue; continue;
} }
//If prefetch task is available. //If prefetch task is available.
if (connection == null && prefetchTask != null) if (connection == null && prefetchTask != null)
{ {
connection = await prefetchTask; connection = await prefetchTask;
prefetchTask = null; prefetchTask = null;
} }
// create a new connection if cache key changes. // create a new connection if cache key changes.
// only gets hit when connection pool is disabled. // only gets hit when connection pool is disabled.
// or when prefetch task has a unexpectedly different connection. // or when prefetch task has a unexpectedly different connection.
if (connection != null if (connection != null
&& (await tcpConnectionFactory.GetConnectionCacheKey(this, args, && (await tcpConnectionFactory.GetConnectionCacheKey(this, args,
clientConnection.NegotiatedApplicationProtocol) clientConnection.NegotiatedApplicationProtocol)
!= connection.CacheKey)) != connection.CacheKey))
{ {
await tcpConnectionFactory.Release(connection); await tcpConnectionFactory.Release(connection);
connection = null; connection = null;
} }
//a connection generator task with captured parameters via closure. //a connection generator task with captured parameters via closure.
Func<Task<TcpServerConnection>> generator = () => Func<Task<TcpServerConnection>> generator = () =>
tcpConnectionFactory.GetServerConnection(this, args, isConnect: false, tcpConnectionFactory.GetServerConnection(this, args, isConnect: false,
applicationProtocol:clientConnection.NegotiatedApplicationProtocol, applicationProtocol:clientConnection.NegotiatedApplicationProtocol,
noCache: false, cancellationToken: cancellationToken); noCache: false, cancellationToken: cancellationToken);
//for connection pool, retry fails until cache is exhausted. //for connection pool, retry fails until cache is exhausted.
var result = await retryPolicy<ServerConnectionException>().ExecuteAsync(async (serverConnection) => var result = await retryPolicy<ServerConnectionException>().ExecuteAsync(async (serverConnection) =>
{ {
args.TimeLine["Server Connection Created"] = DateTime.Now; args.TimeLine["Server Connection Created"] = DateTime.Now;
// if upgrading to websocket then relay the request without reading the contents // if upgrading to websocket then relay the request without reading the contents
if (request.UpgradeToWebSocket) if (request.UpgradeToWebSocket)
{ {
await handleWebSocketUpgrade(httpCmd, args, request, await handleWebSocketUpgrade(httpCmd, args, request,
response, clientStream, clientStreamWriter, response, clientStream, clientStreamWriter,
serverConnection, cancellationTokenSource, cancellationToken); serverConnection, cancellationTokenSource, cancellationToken);
closeServerConnection = true; closeServerConnection = true;
return false; return false;
} }
// construct the web request that we are going to issue on behalf of the client. // construct the web request that we are going to issue on behalf of the client.
await handleHttpSessionRequest(serverConnection, args); await handleHttpSessionRequest(serverConnection, args);
return true; return true;
}, generator, connection); }, generator, connection);
//update connection to latest used //update connection to latest used
connection = result.LatestConnection; connection = result.LatestConnection;
//throw if exception happened //throw if exception happened
if(!result.IsSuccess) if(!result.IsSuccess)
{ {
throw result.Exception; throw result.Exception;
} }
if(!result.Continue) if(!result.Continue)
{ {
return; return;
} }
//user requested //user requested
if (args.WebSession.CloseServerConnection) if (args.WebSession.CloseServerConnection)
{ {
closeServerConnection = true; closeServerConnection = true;
return; return;
} }
// if connection is closing exit // if connection is closing exit
if (!response.KeepAlive) if (!response.KeepAlive)
{ {
closeServerConnection = true; closeServerConnection = true;
return; return;
} }
if (cancellationTokenSource.IsCancellationRequested) if (cancellationTokenSource.IsCancellationRequested)
{ {
throw new Exception("Session was terminated by user."); throw new Exception("Session was terminated by user.");
} }
//Get/release server connection for each HTTP session instead of per client connection. //Get/release server connection for each HTTP session instead of per client connection.
//This will be more efficient especially when client is idly holding server connection //This will be more efficient especially when client is idly holding server connection
//between sessions without using it. //between sessions without using it.
//Do not release authenticated connections for performance reasons. //Do not release authenticated connections for performance reasons.
//Otherwise it will keep authenticating per session. //Otherwise it will keep authenticating per session.
if (EnableConnectionPool && connection!=null if (EnableConnectionPool && connection!=null
&& !connection.IsWinAuthenticated) && !connection.IsWinAuthenticated)
{ {
await tcpConnectionFactory.Release(connection); await tcpConnectionFactory.Release(connection);
connection = null; connection = null;
} }
} }
catch (Exception e) when (!(e is ProxyHttpException)) catch (Exception e) when (!(e is ProxyHttpException))
{ {
throw new ProxyHttpException("Error occured whilst handling session request", e, args); throw new ProxyHttpException("Error occured whilst handling session request", e, args);
} }
} }
catch (Exception e) catch (Exception e)
{ {
args.Exception = e; args.Exception = e;
closeServerConnection = true; closeServerConnection = true;
throw; throw;
} }
finally finally
{ {
await invokeAfterResponse(args); await invokeAfterResponse(args);
args.Dispose(); args.Dispose();
} }
} }
} }
finally finally
{ {
await tcpConnectionFactory.Release(connection, await tcpConnectionFactory.Release(connection,
closeServerConnection); closeServerConnection);
await tcpConnectionFactory.Release(prefetchTask, closeServerConnection); await tcpConnectionFactory.Release(prefetchTask, closeServerConnection);
} }
} }
/// <summary> /// <summary>
/// Handle a specific session (request/response sequence) /// Handle a specific session (request/response sequence)
/// </summary> /// </summary>
/// <param name="serverConnection">The tcp connection.</param> /// <param name="serverConnection">The tcp connection.</param>
/// <param name="args">The session event arguments.</param> /// <param name="args">The session event arguments.</param>
/// <returns></returns> /// <returns></returns>
private async Task handleHttpSessionRequest(TcpServerConnection serverConnection, SessionEventArgs args) private async Task handleHttpSessionRequest(TcpServerConnection serverConnection, SessionEventArgs args)
{ {
var cancellationToken = args.CancellationTokenSource.Token; var cancellationToken = args.CancellationTokenSource.Token;
var request = args.WebSession.Request; var request = args.WebSession.Request;
request.Locked = true; request.Locked = true;
var body = request.CompressBodyAndUpdateContentLength(); var body = request.CompressBodyAndUpdateContentLength();
// if expect continue is enabled then send the headers first // if expect continue is enabled then send the headers first
// and see if server would return 100 conitinue // and see if server would return 100 conitinue
if (request.ExpectContinue) if (request.ExpectContinue)
{ {
args.WebSession.SetConnection(serverConnection); args.WebSession.SetConnection(serverConnection);
await args.WebSession.SendRequest(Enable100ContinueBehaviour, args.IsTransparent, await args.WebSession.SendRequest(Enable100ContinueBehaviour, args.IsTransparent,
cancellationToken); cancellationToken);
} }
// If 100 continue was the response inform that to the client // If 100 continue was the response inform that to the client
if (Enable100ContinueBehaviour) if (Enable100ContinueBehaviour)
{ {
var clientStreamWriter = args.ProxyClient.ClientStreamWriter; var clientStreamWriter = args.ProxyClient.ClientStreamWriter;
if (request.Is100Continue) if (request.Is100Continue)
{ {
await clientStreamWriter.WriteResponseStatusAsync(args.WebSession.Response.HttpVersion, await clientStreamWriter.WriteResponseStatusAsync(args.WebSession.Response.HttpVersion,
(int)HttpStatusCode.Continue, "Continue", cancellationToken); (int)HttpStatusCode.Continue, "Continue", cancellationToken);
await clientStreamWriter.WriteLineAsync(cancellationToken); await clientStreamWriter.WriteLineAsync(cancellationToken);
} }
else if (request.ExpectationFailed) else if (request.ExpectationFailed)
{ {
await clientStreamWriter.WriteResponseStatusAsync(args.WebSession.Response.HttpVersion, await clientStreamWriter.WriteResponseStatusAsync(args.WebSession.Response.HttpVersion,
(int)HttpStatusCode.ExpectationFailed, "Expectation Failed", cancellationToken); (int)HttpStatusCode.ExpectationFailed, "Expectation Failed", cancellationToken);
await clientStreamWriter.WriteLineAsync(cancellationToken); await clientStreamWriter.WriteLineAsync(cancellationToken);
} }
} }
// If expect continue is not enabled then set the connectio and send request headers // If expect continue is not enabled then set the connectio and send request headers
if (!request.ExpectContinue) if (!request.ExpectContinue)
{ {
args.WebSession.SetConnection(serverConnection); args.WebSession.SetConnection(serverConnection);
await args.WebSession.SendRequest(Enable100ContinueBehaviour, args.IsTransparent, await args.WebSession.SendRequest(Enable100ContinueBehaviour, args.IsTransparent,
cancellationToken); cancellationToken);
} }
// check if content-length is > 0 // check if content-length is > 0
if (request.ContentLength > 0) if (request.ContentLength > 0)
{ {
if (request.IsBodyRead) if (request.IsBodyRead)
{ {
var writer = args.WebSession.ServerConnection.StreamWriter; var writer = args.WebSession.ServerConnection.StreamWriter;
await writer.WriteBodyAsync(body, request.IsChunked, cancellationToken); await writer.WriteBodyAsync(body, request.IsChunked, cancellationToken);
} }
else else
{ {
if (!request.ExpectationFailed) if (!request.ExpectationFailed)
{ {
if (request.HasBody) if (request.HasBody)
{ {
HttpWriter writer = args.WebSession.ServerConnection.StreamWriter; HttpWriter writer = args.WebSession.ServerConnection.StreamWriter;
await args.CopyRequestBodyAsync(writer, TransformationMode.None, cancellationToken); await args.CopyRequestBodyAsync(writer, TransformationMode.None, cancellationToken);
} }
} }
} }
} }
// If not expectation failed response was returned by server then parse response // If not expectation failed response was returned by server then parse response
if (!request.ExpectationFailed) if (!request.ExpectationFailed)
{ {
await handleHttpSessionResponse(args); await handleHttpSessionResponse(args);
} }
args.TimeLine["Response Sent"] = DateTime.Now; args.TimeLine["Response Sent"] = DateTime.Now;
} }
/// <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>
private void prepareRequestHeaders(HeaderCollection requestHeaders) private void prepareRequestHeaders(HeaderCollection requestHeaders)
{ {
string acceptEncoding = requestHeaders.GetHeaderValueOrNull(KnownHeaders.AcceptEncoding); string acceptEncoding = requestHeaders.GetHeaderValueOrNull(KnownHeaders.AcceptEncoding);
if (acceptEncoding != null) if (acceptEncoding != null)
{ {
var supportedAcceptEncoding = new List<string>(); var supportedAcceptEncoding = new List<string>();
//only allow proxy supported compressions //only allow proxy supported compressions
supportedAcceptEncoding.AddRange(acceptEncoding.Split(',') supportedAcceptEncoding.AddRange(acceptEncoding.Split(',')
.Select(x => x.Trim()) .Select(x => x.Trim())
.Where(x => ProxyConstants.ProxySupportedCompressions.Contains(x))); .Where(x => ProxyConstants.ProxySupportedCompressions.Contains(x)));
//uncompressed is always supported by proxy //uncompressed is always supported by proxy
supportedAcceptEncoding.Add("identity"); supportedAcceptEncoding.Add("identity");
requestHeaders.SetOrAddHeaderValue(KnownHeaders.AcceptEncoding, requestHeaders.SetOrAddHeaderValue(KnownHeaders.AcceptEncoding,
string.Join(",", supportedAcceptEncoding)); string.Join(",", supportedAcceptEncoding));
} }
requestHeaders.FixProxyHeaders(); requestHeaders.FixProxyHeaders();
} }
/// <summary> /// <summary>
/// Invoke before request handler if it is set. /// Invoke before request handler if it is set.
/// </summary> /// </summary>
/// <param name="args">The session event arguments.</param> /// <param name="args">The session event arguments.</param>
/// <returns></returns> /// <returns></returns>
private async Task invokeBeforeRequest(SessionEventArgs args) private async Task invokeBeforeRequest(SessionEventArgs args)
{ {
if (BeforeRequest != null) if (BeforeRequest != null)
{ {
await BeforeRequest.InvokeAsync(this, args, ExceptionFunc); await BeforeRequest.InvokeAsync(this, args, ExceptionFunc);
} }
} }
} }
} }
using System; using System;
using System.Net; using System.Net;
using System.Threading.Tasks; using System.Threading.Tasks;
using Titanium.Web.Proxy.EventArguments; using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Extensions; using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Network.WinAuth.Security; using Titanium.Web.Proxy.Network.WinAuth.Security;
namespace Titanium.Web.Proxy namespace Titanium.Web.Proxy
{ {
/// <summary> /// <summary>
/// Handle the response from server. /// Handle the response from server.
/// </summary> /// </summary>
public partial class ProxyServer public partial class ProxyServer
{ {
/// <summary> /// <summary>
/// Called asynchronously when a request was successfull and we received the response. /// Called asynchronously when a request was successfull and we received the response.
/// </summary> /// </summary>
/// <param name="args">The session event arguments.</param> /// <param name="args">The session event arguments.</param>
/// <returns> The task.</returns> /// <returns> The task.</returns>
private async Task handleHttpSessionResponse(SessionEventArgs args) private async Task handleHttpSessionResponse(SessionEventArgs args)
{ {
var cancellationToken = args.CancellationTokenSource.Token; var cancellationToken = args.CancellationTokenSource.Token;
// read response & headers from server // read response & headers from server
await args.WebSession.ReceiveResponse(cancellationToken); await args.WebSession.ReceiveResponse(cancellationToken);
args.TimeLine["Response Received"] = DateTime.Now; args.TimeLine["Response Received"] = DateTime.Now;
var response = args.WebSession.Response; var response = args.WebSession.Response;
args.ReRequest = false; args.ReRequest = false;
// check for windows authentication // check for windows authentication
if (isWindowsAuthenticationEnabledAndSupported) if (isWindowsAuthenticationEnabledAndSupported)
{ {
if (response.StatusCode == (int)HttpStatusCode.Unauthorized) if (response.StatusCode == (int)HttpStatusCode.Unauthorized)
{ {
await handle401UnAuthorized(args); await handle401UnAuthorized(args);
} }
else else
{ {
WinAuthEndPoint.AuthenticatedResponse(args.WebSession.Data); WinAuthEndPoint.AuthenticatedResponse(args.WebSession.Data);
} }
} }
//save original values so that if user changes them //save original values so that if user changes them
//we can still use original values when syphoning out data from attached tcp connection. //we can still use original values when syphoning out data from attached tcp connection.
response.SetOriginalHeaders(); response.SetOriginalHeaders();
// if user requested call back then do it // if user requested call back then do it
if (!response.Locked) if (!response.Locked)
{ {
await invokeBeforeResponse(args); await invokeBeforeResponse(args);
} }
// it may changed in the user event // it may changed in the user event
response = args.WebSession.Response; response = args.WebSession.Response;
var clientStreamWriter = args.ProxyClient.ClientStreamWriter; var clientStreamWriter = args.ProxyClient.ClientStreamWriter;
//user set custom response by ignoring original response from server. //user set custom response by ignoring original response from server.
if (response.Locked) if (response.Locked)
{ {
//write custom user response with body and return. //write custom user response with body and return.
await clientStreamWriter.WriteResponseAsync(response, cancellationToken: cancellationToken); await clientStreamWriter.WriteResponseAsync(response, cancellationToken: cancellationToken);
if(args.WebSession.ServerConnection != null if(args.WebSession.ServerConnection != null
&& !args.WebSession.CloseServerConnection) && !args.WebSession.CloseServerConnection)
{ {
// syphon out the original response body from server connection // syphon out the original response body from server connection
// so that connection will be good to be reused. // so that connection will be good to be reused.
await args.SyphonOutBodyAsync(false, cancellationToken); await args.SyphonOutBodyAsync(false, cancellationToken);
} }
return; return;
} }
// if user requested to send request again // if user requested to send request again
// likely after making modifications from User Response Handler // likely after making modifications from User Response Handler
if (args.ReRequest) if (args.ReRequest)
{ {
// clear current response // clear current response
await args.ClearResponse(cancellationToken); await args.ClearResponse(cancellationToken);
await handleHttpSessionRequest(args.WebSession.ServerConnection, args); await handleHttpSessionRequest(args.WebSession.ServerConnection, args);
return; return;
} }
response.Locked = true; response.Locked = true;
// 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 clientStreamWriter.WriteResponseStatusAsync(response.HttpVersion, await clientStreamWriter.WriteResponseStatusAsync(response.HttpVersion,
(int)HttpStatusCode.Continue, "Continue", cancellationToken); (int)HttpStatusCode.Continue, "Continue", cancellationToken);
await clientStreamWriter.WriteLineAsync(cancellationToken); await clientStreamWriter.WriteLineAsync(cancellationToken);
} }
else if (response.ExpectationFailed) else if (response.ExpectationFailed)
{ {
await clientStreamWriter.WriteResponseStatusAsync(response.HttpVersion, await clientStreamWriter.WriteResponseStatusAsync(response.HttpVersion,
(int)HttpStatusCode.ExpectationFailed, "Expectation Failed", cancellationToken); (int)HttpStatusCode.ExpectationFailed, "Expectation Failed", cancellationToken);
await clientStreamWriter.WriteLineAsync(cancellationToken); await clientStreamWriter.WriteLineAsync(cancellationToken);
} }
if (!args.IsTransparent) if (!args.IsTransparent)
{ {
response.Headers.FixProxyHeaders(); response.Headers.FixProxyHeaders();
} }
if (response.IsBodyRead) if (response.IsBodyRead)
{ {
await clientStreamWriter.WriteResponseAsync(response, cancellationToken: cancellationToken); await clientStreamWriter.WriteResponseAsync(response, cancellationToken: cancellationToken);
} }
else else
{ {
// Write back response status to client // Write back response status to client
await clientStreamWriter.WriteResponseStatusAsync(response.HttpVersion, response.StatusCode, await clientStreamWriter.WriteResponseStatusAsync(response.HttpVersion, response.StatusCode,
response.StatusDescription, cancellationToken); response.StatusDescription, cancellationToken);
await clientStreamWriter.WriteHeadersAsync(response.Headers, cancellationToken: cancellationToken); await clientStreamWriter.WriteHeadersAsync(response.Headers, cancellationToken: cancellationToken);
// Write body if exists // Write body if exists
if (response.HasBody) if (response.HasBody)
{ {
await args.CopyResponseBodyAsync(clientStreamWriter, TransformationMode.None, await args.CopyResponseBodyAsync(clientStreamWriter, TransformationMode.None,
cancellationToken); cancellationToken);
} }
} }
} }
/// <summary> /// <summary>
/// Invoke before response if it is set. /// Invoke before response if it is set.
/// </summary> /// </summary>
/// <param name="args"></param> /// <param name="args"></param>
/// <returns></returns> /// <returns></returns>
private async Task invokeBeforeResponse(SessionEventArgs args) private async Task invokeBeforeResponse(SessionEventArgs args)
{ {
if (BeforeResponse != null) if (BeforeResponse != null)
{ {
await BeforeResponse.InvokeAsync(this, args, ExceptionFunc); await BeforeResponse.InvokeAsync(this, args, ExceptionFunc);
} }
} }
/// <summary> /// <summary>
/// Invoke after response if it is set. /// Invoke after response if it is set.
/// </summary> /// </summary>
/// <param name="args"></param> /// <param name="args"></param>
/// <returns></returns> /// <returns></returns>
private async Task invokeAfterResponse(SessionEventArgs args) private async Task invokeAfterResponse(SessionEventArgs args)
{ {
if (AfterResponse != null) if (AfterResponse != null)
{ {
await AfterResponse.InvokeAsync(this, args, ExceptionFunc); await AfterResponse.InvokeAsync(this, args, ExceptionFunc);
} }
} }
} }
} }
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>net45;netstandard2.0</TargetFrameworks> <TargetFrameworks>net45;netstandard2.0</TargetFrameworks>
<RootNamespace>Titanium.Web.Proxy</RootNamespace> <RootNamespace>Titanium.Web.Proxy</RootNamespace>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo> <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<SignAssembly>True</SignAssembly> <SignAssembly>True</SignAssembly>
<AssemblyOriginatorKeyFile>StrongNameKey.snk</AssemblyOriginatorKeyFile> <AssemblyOriginatorKeyFile>StrongNameKey.snk</AssemblyOriginatorKeyFile>
<DelaySign>False</DelaySign> <DelaySign>False</DelaySign>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks> <AllowUnsafeBlocks>True</AllowUnsafeBlocks>
<LangVersion>7.1</LangVersion> <LangVersion>7.1</LangVersion>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Portable.BouncyCastle" Version="1.8.2" /> <PackageReference Include="Portable.BouncyCastle" Version="1.8.2" />
<PackageReference Include="StreamExtended" Version="1.0.179" /> <PackageReference Include="StreamExtended" Version="1.0.179" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'"> <ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
<PackageReference Include="Microsoft.Win32.Registry"> <PackageReference Include="Microsoft.Win32.Registry">
<Version>4.4.0</Version> <Version>4.4.0</Version>
</PackageReference> </PackageReference>
<PackageReference Include="System.Security.Principal.Windows"> <PackageReference Include="System.Security.Principal.Windows">
<Version>4.4.1</Version> <Version>4.4.1</Version>
</PackageReference> </PackageReference>
</ItemGroup> </ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp2.1'"> <ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp2.1'">
<PackageReference Include="Microsoft.Win32.Registry"> <PackageReference Include="Microsoft.Win32.Registry">
<Version>4.4.0</Version> <Version>4.4.0</Version>
</PackageReference> </PackageReference>
<PackageReference Include="System.Security.Principal.Windows"> <PackageReference Include="System.Security.Principal.Windows">
<Version>4.4.1</Version> <Version>4.4.1</Version>
</PackageReference> </PackageReference>
</ItemGroup> </ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net45'"> <ItemGroup Condition="'$(TargetFramework)' == 'net45'">
<Reference Include="System.Web" /> <Reference Include="System.Web" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Update="Network\WinAuth\Security\Common.cs"> <Compile Update="Network\WinAuth\Security\Common.cs">
<ExcludeFromSourceAnalysis>True</ExcludeFromSourceAnalysis> <ExcludeFromSourceAnalysis>True</ExcludeFromSourceAnalysis>
<ExcludeFromStyleCop>True</ExcludeFromStyleCop> <ExcludeFromStyleCop>True</ExcludeFromStyleCop>
</Compile> </Compile>
<Compile Update="Network\WinAuth\Security\LittleEndian.cs"> <Compile Update="Network\WinAuth\Security\LittleEndian.cs">
<ExcludeFromSourceAnalysis>True</ExcludeFromSourceAnalysis> <ExcludeFromSourceAnalysis>True</ExcludeFromSourceAnalysis>
<ExcludeFromStyleCop>True</ExcludeFromStyleCop> <ExcludeFromStyleCop>True</ExcludeFromStyleCop>
</Compile> </Compile>
<Compile Update="Network\WinAuth\Security\Message.cs"> <Compile Update="Network\WinAuth\Security\Message.cs">
<ExcludeFromSourceAnalysis>True</ExcludeFromSourceAnalysis> <ExcludeFromSourceAnalysis>True</ExcludeFromSourceAnalysis>
<ExcludeFromStyleCop>True</ExcludeFromStyleCop> <ExcludeFromStyleCop>True</ExcludeFromStyleCop>
</Compile> </Compile>
<Compile Update="Network\WinAuth\Security\State.cs"> <Compile Update="Network\WinAuth\Security\State.cs">
<ExcludeFromSourceAnalysis>True</ExcludeFromSourceAnalysis> <ExcludeFromSourceAnalysis>True</ExcludeFromSourceAnalysis>
<ExcludeFromStyleCop>True</ExcludeFromStyleCop> <ExcludeFromStyleCop>True</ExcludeFromStyleCop>
</Compile> </Compile>
<Compile Update="Properties\AssemblyInfo.cs"> <Compile Update="Properties\AssemblyInfo.cs">
<ExcludeFromSourceAnalysis>True</ExcludeFromSourceAnalysis> <ExcludeFromSourceAnalysis>True</ExcludeFromSourceAnalysis>
<ExcludeFromStyleCop>True</ExcludeFromStyleCop> <ExcludeFromStyleCop>True</ExcludeFromStyleCop>
</Compile> </Compile>
</ItemGroup> </ItemGroup>
</Project> </Project>
\ No newline at end of file
...@@ -56,8 +56,8 @@ ...@@ -56,8 +56,8 @@
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\Titanium.Web.Proxy\Titanium.Web.Proxy.csproj"> <ProjectReference Include="..\..\src\Titanium.Web.Proxy\Titanium.Web.Proxy.csproj">
<Project>{8d73a1be-868c-42d2-9ece-f32cc1a02906}</Project> <Project>{91018b6d-a7a9-45be-9cb3-79cbb8b169a6}</Project>
<Name>Titanium.Web.Proxy</Name> <Name>Titanium.Web.Proxy</Name>
</ProjectReference> </ProjectReference>
</ItemGroup> </ItemGroup>
......
...@@ -65,13 +65,13 @@ ...@@ -65,13 +65,13 @@
<Compile Include="WinAuthTests.cs" /> <Compile Include="WinAuthTests.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\Titanium.Web.Proxy\Titanium.Web.Proxy.csproj"> <None Include="StrongNameKey.snk" />
<Project>{8d73a1be-868c-42d2-9ece-f32cc1a02906}</Project>
<Name>Titanium.Web.Proxy</Name>
</ProjectReference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="StrongNameKey.snk" /> <ProjectReference Include="..\..\src\Titanium.Web.Proxy\Titanium.Web.Proxy.csproj">
<Project>{91018b6d-a7a9-45be-9cb3-79cbb8b169a6}</Project>
<Name>Titanium.Web.Proxy</Name>
</ProjectReference>
</ItemGroup> </ItemGroup>
<Choose> <Choose>
<When Condition="'$(VisualStudioVersion)' == '10.0' And '$(IsCodedUITest)' == 'True'"> <When Condition="'$(VisualStudioVersion)' == '10.0' And '$(IsCodedUITest)' == '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