Unverified Commit c3fc1d35 authored by Jehonathan Thomas's avatar Jehonathan Thomas Committed by GitHub

Merge pull request #492 from justcoding121/master

3.0.655+
parents a4ea3803 4d00181e
...@@ -2,7 +2,8 @@ $PSake.use_exit_on_error = $true ...@@ -2,7 +2,8 @@ $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) $RepoRoot = $(Split-Path -parent $Here)
$SolutionRoot = "$RepoRoot\src"
$ProjectName = "Titanium.Web.Proxy" $ProjectName = "Titanium.Web.Proxy"
$GitHubProjectName = "Titanium-Web-Proxy" $GitHubProjectName = "Titanium-Web-Proxy"
...@@ -26,7 +27,7 @@ if(!$Branch) { $Branch = "local" } ...@@ -26,7 +27,7 @@ if(!$Branch) { $Branch = "local" }
if($Branch -eq "beta" ) { $Version = "$Version-beta" } if($Branch -eq "beta" ) { $Version = "$Version-beta" }
$NuGet = Join-Path $SolutionRoot ".nuget\nuget.exe" $NuGet = Join-Path $RepoRoot ".nuget\nuget.exe"
$MSBuild = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\2017\Community\MSBuild\15.0\Bin\msbuild.exe" $MSBuild = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\2017\Community\MSBuild\15.0\Bin\msbuild.exe"
$MSBuild -replace ' ', '` ' $MSBuild -replace ' ', '` '
...@@ -57,7 +58,7 @@ Task Restore-Packages -depends Install-BuildTools { ...@@ -57,7 +58,7 @@ Task Restore-Packages -depends Install-BuildTools {
#build #build
Task Build -depends Restore-Packages{ Task Build -depends Restore-Packages{
exec { . $MSBuild $SolutionFile /t:Build /v:normal /p:Configuration=$Configuration /t:restore } exec { . $MSBuild $SolutionFile /t:Build /v:normal /p:Configuration=$Configuration /p:Platform="Any CPU" /t:restore }
} }
#publish API documentation changes for GitHub pages under master\docs directory #publish API documentation changes for GitHub pages under master\docs directory
...@@ -65,12 +66,13 @@ Task Document -depends Build { ...@@ -65,12 +66,13 @@ Task Document -depends Build {
if($Branch -eq "master") if($Branch -eq "master")
{ {
#use docfx to generate API documentation from source metadata #use docfx to generate API documentation from source metadata
docfx docfx.json docfx docfx.json
#patch index.json so that it is always sorted #patch index.json so that it is always sorted
#otherwise git will think file was changed #otherwise git will think file was changed
$IndexJsonFile = "$SolutionRoot\docs\index.json" $IndexJsonFile = "$RepoRoot\docs\index.json"
$unsorted = Get-Content $IndexJsonFile | Out-String $unsorted = Get-Content $IndexJsonFile | Out-String
[Reflection.Assembly]::LoadFile("$Here\lib\Newtonsoft.Json.dll") [Reflection.Assembly]::LoadFile("$Here\lib\Newtonsoft.Json.dll")
[System.Reflection.Assembly]::LoadWithPartialName("System") [System.Reflection.Assembly]::LoadWithPartialName("System")
...@@ -79,7 +81,7 @@ Task Document -depends Build { ...@@ -79,7 +81,7 @@ Task Document -depends Build {
Set-Content -Path $IndexJsonFile -Value $obj Set-Content -Path $IndexJsonFile -Value $obj
#setup clone directory #setup clone directory
$TEMP_REPO_DIR =(Split-Path -parent $SolutionRoot) + "\temp-repo-clone" $TEMP_REPO_DIR =(Split-Path -parent $RepoRoot) + "\temp-repo-clone"
If(test-path $TEMP_REPO_DIR) If(test-path $TEMP_REPO_DIR)
{ {
...@@ -101,7 +103,7 @@ Task Document -depends Build { ...@@ -101,7 +103,7 @@ Task Document -depends Build {
cd "$TEMP_REPO_DIR\docs" cd "$TEMP_REPO_DIR\docs"
#copy docs to clone directory\docs #copy docs to clone directory\docs
Copy-Item -Path "$SolutionRoot\docs\*" -Destination "$TEMP_REPO_DIR\docs" -Recurse -Force Copy-Item -Path "$RepoRoot\docs\*" -Destination "$TEMP_REPO_DIR\docs" -Recurse -Force
#push changes to master #push changes to master
git config --global credential.helper store git config --global credential.helper store
...@@ -119,5 +121,5 @@ Task Document -depends Build { ...@@ -119,5 +121,5 @@ Task Document -depends Build {
#package nuget files #package nuget files
Task Package -depends Document { Task Package -depends Document {
exec { . $NuGet pack "$SolutionRoot\$ProjectName\$ProjectName.nuspec" -Properties Configuration=$Configuration -OutputDirectory "$SolutionRoot" -Version "$Version" } exec { . $NuGet pack "$SolutionRoot\$ProjectName\$ProjectName.nuspec" -Properties Configuration=$Configuration -OutputDirectory "$RepoRoot" -Version "$Version" }
} }
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
"src": [ "src": [
{ {
"files": [ "Titanium.Web.Proxy.Docs.sln"], "files": [ "Titanium.Web.Proxy.Docs.sln"],
"src": "../" "src": "../src/"
} }
], ],
"dest": "obj/api" "dest": "obj/api"
......
...@@ -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'))
......
{
"version": "0.2.0",
"configurations": [
{
"name": "NetCore|Debug|Basic Example",
"type": "coreclr",
"request": "launch",
"program": "${workspaceRoot}/examples/Titanium.Web.Proxy.Examples.Basic/bin/Debug/netcoreapp2.0/Titanium.Web.Proxy.Examples.Basic.NetCore.dll",
"args": [],
"cwd": "${workspaceRoot}",
"stopAtEntry": false,
"console": "integratedTerminal",
"preLaunchTask": "build-basic-example-netcore-debug"
},
{
"name": "NetCore|Release|Basic Example",
"type": "coreclr",
"request": "launch",
"program": "${workspaceRoot}/examples/Titanium.Web.Proxy.Examples.Basic/bin/Release/netcoreapp2.0/Titanium.Web.Proxy.Examples.Basic.NetCore.dll",
"args": [],
"cwd": "${workspaceRoot}",
"stopAtEntry": false,
"console": "integratedTerminal",
"preLaunchTask": "build-basic-example-netcore-release"
}
]
}
\ No newline at end of file
{
// The following will hide the js and map files in the editor
"files.exclude": {
"**/.build": true,
"**/.nuget": true,
"**/.vs": true,
"**/docs": true,
"**/packages": true,
"**/bin": true,
"**/obj": true,
"**/*.DotSettings": true,
"**/*.sln": true,
"**/tests/" : true,
"**/Titanium.Web.Proxy.Examples.Wpf/" : true,
"**/*.Basic.csproj/": true,
"**/*.Docs.csproj/": true,
"**/*.Proxy.csproj/": true,
"**/*.Proxy.csproj" : true
},
"search.exclude": {
"**/.build": true,
"**/.nuget": true,
"**/.vs": true,
"**/docs": true,
"**/packages": true,
"**/bin": true,
"**/obj": true,
"**/*.DotSettings": true,
"**/*.sln": true,
"**/tests/" : true,
"**/Titanium.Web.Proxy.Examples.Wpf/" : true,
"**/*.Basic.csproj/": true,
"**/*.Docs.csproj/": true,
"**/*.Proxy.csproj/": true,
"**/*.Proxy.csproj" : true
}
}
\ No newline at end of file
{
"version": "2.0.0",
"tasks": [
{
"label": "build-basic-example-netcore-debug",
"type": "process",
"command": "dotnet",
"args": ["build","${workspaceFolder}/examples/Titanium.Web.Proxy.Examples.Basic/Titanium.Web.Proxy.Examples.Basic.NetCore.csproj"],
"problemMatcher": "$msCompile",
"group": {
"kind": "build",
"isDefault": true
}
},
{
"label": "build-basic-example-netcore-release",
"type": "process",
"command": "dotnet",
"args": ["build","${workspaceFolder}/examples/Titanium.Web.Proxy.Examples.Basic/Titanium.Web.Proxy.Examples.Basic.NetCore.csproj", "-c", "Release"],
"problemMatcher": "$msCompile"
}
]
}
\ No newline at end of file
...@@ -9,14 +9,6 @@ Kindly report only issues/bugs here . For programming help or questions use [Sta ...@@ -9,14 +9,6 @@ Kindly report only issues/bugs here . For programming help or questions use [Sta
* [API Documentation](https://justcoding121.github.io/Titanium-Web-Proxy/docs/api/Titanium.Web.Proxy.ProxyServer.html) * [API Documentation](https://justcoding121.github.io/Titanium-Web-Proxy/docs/api/Titanium.Web.Proxy.ProxyServer.html)
* [Wiki & Contribution guidelines](https://github.com/justcoding121/Titanium-Web-Proxy/wiki) * [Wiki & Contribution guidelines](https://github.com/justcoding121/Titanium-Web-Proxy/wiki)
**Console example application screenshot**
![alt tag](https://raw.githubusercontent.com/justcoding121/Titanium-Web-Proxy/master/Examples/Titanium.Web.Proxy.Examples.Basic/Capture.PNG)
**GUI example application screenshot**
![alt tag](https://raw.githubusercontent.com/justcoding121/Titanium-Web-Proxy/master/Examples/Titanium.Web.Proxy.Examples.Wpf/Capture.PNG)
### Features ### Features
* Multithreaded & fully asynchronous proxy employing server connection pooling, certificate cache & buffer pooling * Multithreaded & fully asynchronous proxy employing server connection pooling, certificate cache & buffer pooling
...@@ -24,16 +16,12 @@ Kindly report only issues/bugs here . For programming help or questions use [Sta ...@@ -24,16 +16,12 @@ Kindly report only issues/bugs here . For programming help or questions use [Sta
* Supports mutual SSL authentication, proxy authentication & automatic upstream proxy detection * Supports mutual SSL authentication, proxy authentication & automatic upstream proxy detection
* Kerberos/NTLM authentication over HTTP protocols for windows domain * Kerberos/NTLM authentication over HTTP protocols for windows domain
### Usage ### Installation
Refer the HTTP Proxy Server library in your project, look up Test project to learn usage.
Install by [nuget](https://www.nuget.org/packages/Titanium.Web.Proxy) Install by [nuget](https://www.nuget.org/packages/Titanium.Web.Proxy)
For beta releases on [beta branch](https://github.com/justcoding121/Titanium-Web-Proxy/tree/beta) For beta releases on [beta branch](https://github.com/justcoding121/Titanium-Web-Proxy/tree/beta)
Install-Package Titanium.Web.Proxy Install-Package Titanium.Web.Proxy -Pre
For stable releases on [stable branch](https://github.com/justcoding121/Titanium-Web-Proxy/tree/stable) For stable releases on [stable branch](https://github.com/justcoding121/Titanium-Web-Proxy/tree/stable)
...@@ -44,6 +32,24 @@ Supports ...@@ -44,6 +32,24 @@ Supports
* .Net Standard 2.0 or above * .Net Standard 2.0 or above
* .Net Framework 4.5 or above * .Net Framework 4.5 or above
### Development environment
#### Windows
* Visual Studio Code as IDE for .NET core
* Visual Studio 2017 as IDE for .NET framework/.NET core
#### Mac OS
* Visual Studio Code as IDE for .NET core
* Visual Studio 2017 as IDE for Mono
#### Linux
* Visual Studio Code as IDE for .NET core
* Mono develop as IDE for Mono
### Usage
Refer the HTTP Proxy Server library in your project, look up Test project to learn usage.
Setup HTTP proxy: Setup HTTP proxy:
```csharp ```csharp
...@@ -229,3 +235,12 @@ public Task OnCertificateSelection(object sender, CertificateSelectionEventArgs ...@@ -229,3 +235,12 @@ public Task OnCertificateSelection(object sender, CertificateSelectionEventArgs
#### Collaborators #### Collaborators
* [honfika](https://github.com/honfika) * [honfika](https://github.com/honfika)
**Console example application screenshot**
![alt tag](https://raw.githubusercontent.com/justcoding121/Titanium-Web-Proxy/master/examples/Titanium.Web.Proxy.Examples.Basic/Capture.PNG)
**GUI example application screenshot**
![alt tag](https://raw.githubusercontent.com/justcoding121/Titanium-Web-Proxy/master/examples/Titanium.Web.Proxy.Examples.Wpf/Capture.PNG)
...@@ -41,7 +41,12 @@ skip_tags: true ...@@ -41,7 +41,12 @@ skip_tags: true
skip_commits: skip_commits:
author: buildbot121 author: buildbot121
files:
- docs/*
- .vscode/*
- README.md
- LICENSE
#---------------------------------# #---------------------------------#
# artifacts configuration # # artifacts configuration #
#---------------------------------# #---------------------------------#
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
<meta name="viewport" content="width=device-width"> <meta name="viewport" content="width=device-width">
<meta name="title" content="Delegate AsyncEventHandler&lt;TEventArgs&gt; <meta name="title" content="Delegate AsyncEventHandler&lt;TEventArgs&gt;
| Titanium Web Proxy "> | Titanium Web Proxy ">
<meta name="generator" content="docfx 2.38.1.0"> <meta name="generator" content="docfx 2.39.2.0">
<link rel="shortcut icon" href="../favicon.ico"> <link rel="shortcut icon" href="../favicon.ico">
<link rel="stylesheet" href="../styles/docfx.vendor.css"> <link rel="stylesheet" href="../styles/docfx.vendor.css">
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
<meta name="viewport" content="width=device-width"> <meta name="viewport" content="width=device-width">
<meta name="title" content="Class BeforeSslAuthenticateEventArgs <meta name="title" content="Class BeforeSslAuthenticateEventArgs
| Titanium Web Proxy "> | Titanium Web Proxy ">
<meta name="generator" content="docfx 2.38.1.0"> <meta name="generator" content="docfx 2.39.2.0">
<link rel="shortcut icon" href="../favicon.ico"> <link rel="shortcut icon" href="../favicon.ico">
<link rel="stylesheet" href="../styles/docfx.vendor.css"> <link rel="stylesheet" href="../styles/docfx.vendor.css">
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
<meta name="viewport" content="width=device-width"> <meta name="viewport" content="width=device-width">
<meta name="title" content="Class CertificateSelectionEventArgs <meta name="title" content="Class CertificateSelectionEventArgs
| Titanium Web Proxy "> | Titanium Web Proxy ">
<meta name="generator" content="docfx 2.38.1.0"> <meta name="generator" content="docfx 2.39.2.0">
<link rel="shortcut icon" href="../favicon.ico"> <link rel="shortcut icon" href="../favicon.ico">
<link rel="stylesheet" href="../styles/docfx.vendor.css"> <link rel="stylesheet" href="../styles/docfx.vendor.css">
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
<meta name="viewport" content="width=device-width"> <meta name="viewport" content="width=device-width">
<meta name="title" content="Class CertificateValidationEventArgs <meta name="title" content="Class CertificateValidationEventArgs
| Titanium Web Proxy "> | Titanium Web Proxy ">
<meta name="generator" content="docfx 2.38.1.0"> <meta name="generator" content="docfx 2.39.2.0">
<link rel="shortcut icon" href="../favicon.ico"> <link rel="shortcut icon" href="../favicon.ico">
<link rel="stylesheet" href="../styles/docfx.vendor.css"> <link rel="stylesheet" href="../styles/docfx.vendor.css">
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
<meta name="viewport" content="width=device-width"> <meta name="viewport" content="width=device-width">
<meta name="title" content="Class MultipartRequestPartSentEventArgs <meta name="title" content="Class MultipartRequestPartSentEventArgs
| Titanium Web Proxy "> | Titanium Web Proxy ">
<meta name="generator" content="docfx 2.38.1.0"> <meta name="generator" content="docfx 2.39.2.0">
<link rel="shortcut icon" href="../favicon.ico"> <link rel="shortcut icon" href="../favicon.ico">
<link rel="stylesheet" href="../styles/docfx.vendor.css"> <link rel="stylesheet" href="../styles/docfx.vendor.css">
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
<meta name="viewport" content="width=device-width"> <meta name="viewport" content="width=device-width">
<meta name="title" content="Class SessionEventArgs <meta name="title" content="Class SessionEventArgs
| Titanium Web Proxy "> | Titanium Web Proxy ">
<meta name="generator" content="docfx 2.38.1.0"> <meta name="generator" content="docfx 2.39.2.0">
<link rel="shortcut icon" href="../favicon.ico"> <link rel="shortcut icon" href="../favicon.ico">
<link rel="stylesheet" href="../styles/docfx.vendor.css"> <link rel="stylesheet" href="../styles/docfx.vendor.css">
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
<meta name="viewport" content="width=device-width"> <meta name="viewport" content="width=device-width">
<meta name="title" content="Class SessionEventArgsBase <meta name="title" content="Class SessionEventArgsBase
| Titanium Web Proxy "> | Titanium Web Proxy ">
<meta name="generator" content="docfx 2.38.1.0"> <meta name="generator" content="docfx 2.39.2.0">
<link rel="shortcut icon" href="../favicon.ico"> <link rel="shortcut icon" href="../favicon.ico">
<link rel="stylesheet" href="../styles/docfx.vendor.css"> <link rel="stylesheet" href="../styles/docfx.vendor.css">
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
<meta name="viewport" content="width=device-width"> <meta name="viewport" content="width=device-width">
<meta name="title" content="Class TunnelConnectSessionEventArgs <meta name="title" content="Class TunnelConnectSessionEventArgs
| Titanium Web Proxy "> | Titanium Web Proxy ">
<meta name="generator" content="docfx 2.38.1.0"> <meta name="generator" content="docfx 2.39.2.0">
<link rel="shortcut icon" href="../favicon.ico"> <link rel="shortcut icon" href="../favicon.ico">
<link rel="stylesheet" href="../styles/docfx.vendor.css"> <link rel="stylesheet" href="../styles/docfx.vendor.css">
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
<meta name="viewport" content="width=device-width"> <meta name="viewport" content="width=device-width">
<meta name="title" content="Namespace Titanium.Web.Proxy.EventArguments <meta name="title" content="Namespace Titanium.Web.Proxy.EventArguments
| Titanium Web Proxy "> | Titanium Web Proxy ">
<meta name="generator" content="docfx 2.38.1.0"> <meta name="generator" content="docfx 2.39.2.0">
<link rel="shortcut icon" href="../favicon.ico"> <link rel="shortcut icon" href="../favicon.ico">
<link rel="stylesheet" href="../styles/docfx.vendor.css"> <link rel="stylesheet" href="../styles/docfx.vendor.css">
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
<meta name="viewport" content="width=device-width"> <meta name="viewport" content="width=device-width">
<meta name="title" content="Delegate ExceptionHandler <meta name="title" content="Delegate ExceptionHandler
| Titanium Web Proxy "> | Titanium Web Proxy ">
<meta name="generator" content="docfx 2.38.1.0"> <meta name="generator" content="docfx 2.39.2.0">
<link rel="shortcut icon" href="../favicon.ico"> <link rel="shortcut icon" href="../favicon.ico">
<link rel="stylesheet" href="../styles/docfx.vendor.css"> <link rel="stylesheet" href="../styles/docfx.vendor.css">
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
<meta name="viewport" content="width=device-width"> <meta name="viewport" content="width=device-width">
<meta name="title" content="Class BodyNotFoundException <meta name="title" content="Class BodyNotFoundException
| Titanium Web Proxy "> | Titanium Web Proxy ">
<meta name="generator" content="docfx 2.38.1.0"> <meta name="generator" content="docfx 2.39.2.0">
<link rel="shortcut icon" href="../favicon.ico"> <link rel="shortcut icon" href="../favicon.ico">
<link rel="stylesheet" href="../styles/docfx.vendor.css"> <link rel="stylesheet" href="../styles/docfx.vendor.css">
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
<meta name="viewport" content="width=device-width"> <meta name="viewport" content="width=device-width">
<meta name="title" content="Class ProxyAuthorizationException <meta name="title" content="Class ProxyAuthorizationException
| Titanium Web Proxy "> | Titanium Web Proxy ">
<meta name="generator" content="docfx 2.38.1.0"> <meta name="generator" content="docfx 2.39.2.0">
<link rel="shortcut icon" href="../favicon.ico"> <link rel="shortcut icon" href="../favicon.ico">
<link rel="stylesheet" href="../styles/docfx.vendor.css"> <link rel="stylesheet" href="../styles/docfx.vendor.css">
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
<meta name="viewport" content="width=device-width"> <meta name="viewport" content="width=device-width">
<meta name="title" content="Class ProxyException <meta name="title" content="Class ProxyException
| Titanium Web Proxy "> | Titanium Web Proxy ">
<meta name="generator" content="docfx 2.38.1.0"> <meta name="generator" content="docfx 2.39.2.0">
<link rel="shortcut icon" href="../favicon.ico"> <link rel="shortcut icon" href="../favicon.ico">
<link rel="stylesheet" href="../styles/docfx.vendor.css"> <link rel="stylesheet" href="../styles/docfx.vendor.css">
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
<meta name="viewport" content="width=device-width"> <meta name="viewport" content="width=device-width">
<meta name="title" content="Class ProxyHttpException <meta name="title" content="Class ProxyHttpException
| Titanium Web Proxy "> | Titanium Web Proxy ">
<meta name="generator" content="docfx 2.38.1.0"> <meta name="generator" content="docfx 2.39.2.0">
<link rel="shortcut icon" href="../favicon.ico"> <link rel="shortcut icon" href="../favicon.ico">
<link rel="stylesheet" href="../styles/docfx.vendor.css"> <link rel="stylesheet" href="../styles/docfx.vendor.css">
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
<meta name="viewport" content="width=device-width"> <meta name="viewport" content="width=device-width">
<meta name="title" content="Namespace Titanium.Web.Proxy.Exceptions <meta name="title" content="Namespace Titanium.Web.Proxy.Exceptions
| Titanium Web Proxy "> | Titanium Web Proxy ">
<meta name="generator" content="docfx 2.38.1.0"> <meta name="generator" content="docfx 2.39.2.0">
<link rel="shortcut icon" href="../favicon.ico"> <link rel="shortcut icon" href="../favicon.ico">
<link rel="stylesheet" href="../styles/docfx.vendor.css"> <link rel="stylesheet" href="../styles/docfx.vendor.css">
......
<!DOCTYPE html>
<!--[if IE]><![endif]-->
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>Class RunTime
| Titanium Web Proxy </title>
<meta name="viewport" content="width=device-width">
<meta name="title" content="Class RunTime
| Titanium Web Proxy ">
<meta name="generator" content="docfx 2.39.2.0">
<link rel="shortcut icon" href="../favicon.ico">
<link rel="stylesheet" href="../styles/docfx.vendor.css">
<link rel="stylesheet" href="../styles/docfx.css">
<link rel="stylesheet" href="../styles/main.css">
<meta property="docfx:navrel" content="">
<meta property="docfx:tocrel" content="toc.html">
<meta property="docfx:rel" content="../">
</head>
<body data-spy="scroll" data-target="#affix" data-offset="120">
<div id="wrapper">
<header>
<nav id="autocollapse" class="navbar navbar-inverse ng-scope" role="navigation">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#navbar">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="../index.html">
<img id="logo" class="svg" src="../logo.svg" alt="">
</a>
</div>
<div class="collapse navbar-collapse" id="navbar">
<form class="navbar-form navbar-right" role="search" id="search">
<div class="form-group">
<input type="text" class="form-control" id="search-query" placeholder="Search" autocomplete="off">
</div>
</form>
</div>
</div>
</nav>
<div class="subnav navbar navbar-default">
<div class="container hide-when-search" id="breadcrumb">
<ul class="breadcrumb">
<li></li>
</ul>
</div>
</div>
</header>
<div class="container body-content">
<div id="search-results">
<div class="search-list"></div>
<div class="sr-items">
<p><i class="glyphicon glyphicon-refresh index-loading"></i></p>
</div>
<ul id="pagination"></ul>
</div>
</div>
<div role="main" class="container body-content hide-when-search">
<div class="sidenav hide-when-search">
<a class="btn toc-toggle collapse" data-toggle="collapse" href="#sidetoggle" aria-expanded="false" aria-controls="sidetoggle">Show / Hide Table of Contents</a>
<div class="sidetoggle collapse" id="sidetoggle">
<div id="sidetoc"></div>
</div>
</div>
<div class="article row grid-right">
<div class="col-md-10">
<article class="content wrap" id="_content" data-uid="Titanium.Web.Proxy.Helpers.RunTime">
<h1 id="Titanium_Web_Proxy_Helpers_RunTime" data-uid="Titanium.Web.Proxy.Helpers.RunTime" class="text-break">Class RunTime
</h1>
<div class="markdown level0 summary"><p>Run time helpers</p>
</div>
<div class="markdown level0 conceptual"></div>
<div class="inheritance">
<h5>Inheritance</h5>
<div class="level0"><a class="xref" href="https://docs.microsoft.com/dotnet/api/system.object">Object</a></div>
<div class="level1"><span class="xref">RunTime</span></div>
</div>
<div class="inheritedMembers">
<h5>Inherited Members</h5>
<div>
<a class="xref" href="https://docs.microsoft.com/dotnet/api/system.object.tostring#System_Object_ToString">Object.ToString()</a>
</div>
<div>
<a class="xref" href="https://docs.microsoft.com/dotnet/api/system.object.equals#System_Object_Equals_System_Object_">Object.Equals(Object)</a>
</div>
<div>
<a class="xref" href="https://docs.microsoft.com/dotnet/api/system.object.equals#System_Object_Equals_System_Object_System_Object_">Object.Equals(Object, Object)</a>
</div>
<div>
<a class="xref" href="https://docs.microsoft.com/dotnet/api/system.object.referenceequals#System_Object_ReferenceEquals_System_Object_System_Object_">Object.ReferenceEquals(Object, Object)</a>
</div>
<div>
<a class="xref" href="https://docs.microsoft.com/dotnet/api/system.object.gethashcode#System_Object_GetHashCode">Object.GetHashCode()</a>
</div>
<div>
<a class="xref" href="https://docs.microsoft.com/dotnet/api/system.object.gettype#System_Object_GetType">Object.GetType()</a>
</div>
<div>
<a class="xref" href="https://docs.microsoft.com/dotnet/api/system.object.memberwiseclone#System_Object_MemberwiseClone">Object.MemberwiseClone()</a>
</div>
</div>
<h6><strong>Namespace</strong>: <a class="xref" href="Titanium.Web.Proxy.Helpers.html">Titanium.Web.Proxy.Helpers</a></h6>
<h6><strong>Assembly</strong>: Titanium.Web.Proxy.dll</h6>
<h5 id="Titanium_Web_Proxy_Helpers_RunTime_syntax">Syntax</h5>
<div class="codewrapper">
<pre><code class="lang-csharp hljs">public static class RunTime</code></pre>
</div>
<h3 id="properties">Properties
</h3>
<a id="Titanium_Web_Proxy_Helpers_RunTime_IsLinux_" data-uid="Titanium.Web.Proxy.Helpers.RunTime.IsLinux*"></a>
<h4 id="Titanium_Web_Proxy_Helpers_RunTime_IsLinux" data-uid="Titanium.Web.Proxy.Helpers.RunTime.IsLinux">IsLinux</h4>
<div class="markdown level1 summary"></div>
<div class="markdown level1 conceptual"></div>
<h5 class="decalaration">Declaration</h5>
<div class="codewrapper">
<pre><code class="lang-csharp hljs">public static bool IsLinux { get; }</code></pre>
</div>
<h5 class="propertyValue">Property Value</h5>
<table class="table table-bordered table-striped table-condensed">
<thead>
<tr>
<th>Type</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><a class="xref" href="https://docs.microsoft.com/dotnet/api/system.boolean">Boolean</a></td>
<td></td>
</tr>
</tbody>
</table>
<a id="Titanium_Web_Proxy_Helpers_RunTime_IsMac_" data-uid="Titanium.Web.Proxy.Helpers.RunTime.IsMac*"></a>
<h4 id="Titanium_Web_Proxy_Helpers_RunTime_IsMac" data-uid="Titanium.Web.Proxy.Helpers.RunTime.IsMac">IsMac</h4>
<div class="markdown level1 summary"></div>
<div class="markdown level1 conceptual"></div>
<h5 class="decalaration">Declaration</h5>
<div class="codewrapper">
<pre><code class="lang-csharp hljs">public static bool IsMac { get; }</code></pre>
</div>
<h5 class="propertyValue">Property Value</h5>
<table class="table table-bordered table-striped table-condensed">
<thead>
<tr>
<th>Type</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><a class="xref" href="https://docs.microsoft.com/dotnet/api/system.boolean">Boolean</a></td>
<td></td>
</tr>
</tbody>
</table>
<a id="Titanium_Web_Proxy_Helpers_RunTime_IsWindows_" data-uid="Titanium.Web.Proxy.Helpers.RunTime.IsWindows*"></a>
<h4 id="Titanium_Web_Proxy_Helpers_RunTime_IsWindows" data-uid="Titanium.Web.Proxy.Helpers.RunTime.IsWindows">IsWindows</h4>
<div class="markdown level1 summary"></div>
<div class="markdown level1 conceptual"></div>
<h5 class="decalaration">Declaration</h5>
<div class="codewrapper">
<pre><code class="lang-csharp hljs">public static bool IsWindows { get; }</code></pre>
</div>
<h5 class="propertyValue">Property Value</h5>
<table class="table table-bordered table-striped table-condensed">
<thead>
<tr>
<th>Type</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><a class="xref" href="https://docs.microsoft.com/dotnet/api/system.boolean">Boolean</a></td>
<td></td>
</tr>
</tbody>
</table>
</article>
</div>
<div class="hidden-sm col-md-2" role="complementary">
<div class="sideaffix">
<div class="contribution">
<ul class="nav">
</ul>
</div>
<nav class="bs-docs-sidebar hidden-print hidden-xs hidden-sm affix" id="affix">
<!-- <p><a class="back-to-top" href="#top">Back to top</a><p> -->
</nav>
</div>
</div>
</div>
</div>
<footer>
<div class="grad-bottom"></div>
<div class="footer">
<div class="container">
<span class="pull-right">
<a href="#top">Back to top</a>
</span>
<span>Generated by <strong>DocFX</strong></span>
</div>
</div>
</footer>
</div>
<script type="text/javascript" src="../styles/docfx.vendor.js"></script>
<script type="text/javascript" src="../styles/docfx.js"></script>
<script type="text/javascript" src="../styles/main.js"></script>
</body>
</html>
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
<meta name="viewport" content="width=device-width"> <meta name="viewport" content="width=device-width">
<meta name="title" content="Namespace Titanium.Web.Proxy.Helpers <meta name="title" content="Namespace Titanium.Web.Proxy.Helpers
| Titanium Web Proxy "> | Titanium Web Proxy ">
<meta name="generator" content="docfx 2.35.2.0"> <meta name="generator" content="docfx 2.39.2.0">
<link rel="shortcut icon" href="../favicon.ico"> <link rel="shortcut icon" href="../favicon.ico">
<link rel="stylesheet" href="../styles/docfx.vendor.css"> <link rel="stylesheet" href="../styles/docfx.vendor.css">
...@@ -85,10 +85,11 @@ ...@@ -85,10 +85,11 @@
<div class="markdown level0 summary"></div> <div class="markdown level0 summary"></div>
<div class="markdown level0 conceptual"></div> <div class="markdown level0 conceptual"></div>
<div class="markdown level0 remarks"></div> <div class="markdown level0 remarks"></div>
<h3 id="enums">Enums <h3 id="classes">Classes
</h3> </h3>
<h4><a class="xref" href="Titanium.Web.Proxy.Helpers.ProxyProtocolType.html">ProxyProtocolType</a></h4> <h4><a class="xref" href="Titanium.Web.Proxy.Helpers.RunTime.html">RunTime</a></h4>
<section></section> <section><p>Run time helpers</p>
</section>
</article> </article>
</div> </div>
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
<meta name="viewport" content="width=device-width"> <meta name="viewport" content="width=device-width">
<meta name="title" content="Class ConnectRequest <meta name="title" content="Class ConnectRequest
| Titanium Web Proxy "> | Titanium Web Proxy ">
<meta name="generator" content="docfx 2.38.1.0"> <meta name="generator" content="docfx 2.39.2.0">
<link rel="shortcut icon" href="../favicon.ico"> <link rel="shortcut icon" href="../favicon.ico">
<link rel="stylesheet" href="../styles/docfx.vendor.css"> <link rel="stylesheet" href="../styles/docfx.vendor.css">
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
<meta name="viewport" content="width=device-width"> <meta name="viewport" content="width=device-width">
<meta name="title" content="Class ConnectResponse <meta name="title" content="Class ConnectResponse
| Titanium Web Proxy "> | Titanium Web Proxy ">
<meta name="generator" content="docfx 2.38.1.0"> <meta name="generator" content="docfx 2.39.2.0">
<link rel="shortcut icon" href="../favicon.ico"> <link rel="shortcut icon" href="../favicon.ico">
<link rel="stylesheet" href="../styles/docfx.vendor.css"> <link rel="stylesheet" href="../styles/docfx.vendor.css">
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
<meta name="viewport" content="width=device-width"> <meta name="viewport" content="width=device-width">
<meta name="title" content="Class HeaderCollection <meta name="title" content="Class HeaderCollection
| Titanium Web Proxy "> | Titanium Web Proxy ">
<meta name="generator" content="docfx 2.38.1.0"> <meta name="generator" content="docfx 2.39.2.0">
<link rel="shortcut icon" href="../favicon.ico"> <link rel="shortcut icon" href="../favicon.ico">
<link rel="stylesheet" href="../styles/docfx.vendor.css"> <link rel="stylesheet" href="../styles/docfx.vendor.css">
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
<meta name="viewport" content="width=device-width"> <meta name="viewport" content="width=device-width">
<meta name="title" content="Class HttpWebClient <meta name="title" content="Class HttpWebClient
| Titanium Web Proxy "> | Titanium Web Proxy ">
<meta name="generator" content="docfx 2.38.1.0"> <meta name="generator" content="docfx 2.39.2.0">
<link rel="shortcut icon" href="../favicon.ico"> <link rel="shortcut icon" href="../favicon.ico">
<link rel="stylesheet" href="../styles/docfx.vendor.css"> <link rel="stylesheet" href="../styles/docfx.vendor.css">
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
<meta name="viewport" content="width=device-width"> <meta name="viewport" content="width=device-width">
<meta name="title" content="Class KnownHeaders <meta name="title" content="Class KnownHeaders
| Titanium Web Proxy "> | Titanium Web Proxy ">
<meta name="generator" content="docfx 2.38.1.0"> <meta name="generator" content="docfx 2.39.2.0">
<link rel="shortcut icon" href="../favicon.ico"> <link rel="shortcut icon" href="../favicon.ico">
<link rel="stylesheet" href="../styles/docfx.vendor.css"> <link rel="stylesheet" href="../styles/docfx.vendor.css">
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
<meta name="viewport" content="width=device-width"> <meta name="viewport" content="width=device-width">
<meta name="title" content="Class Request <meta name="title" content="Class Request
| Titanium Web Proxy "> | Titanium Web Proxy ">
<meta name="generator" content="docfx 2.38.1.0"> <meta name="generator" content="docfx 2.39.2.0">
<link rel="shortcut icon" href="../favicon.ico"> <link rel="shortcut icon" href="../favicon.ico">
<link rel="stylesheet" href="../styles/docfx.vendor.css"> <link rel="stylesheet" href="../styles/docfx.vendor.css">
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
<meta name="viewport" content="width=device-width"> <meta name="viewport" content="width=device-width">
<meta name="title" content="Class RequestResponseBase <meta name="title" content="Class RequestResponseBase
| Titanium Web Proxy "> | Titanium Web Proxy ">
<meta name="generator" content="docfx 2.38.1.0"> <meta name="generator" content="docfx 2.39.2.0">
<link rel="shortcut icon" href="../favicon.ico"> <link rel="shortcut icon" href="../favicon.ico">
<link rel="stylesheet" href="../styles/docfx.vendor.css"> <link rel="stylesheet" href="../styles/docfx.vendor.css">
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
<meta name="viewport" content="width=device-width"> <meta name="viewport" content="width=device-width">
<meta name="title" content="Class Response <meta name="title" content="Class Response
| Titanium Web Proxy "> | Titanium Web Proxy ">
<meta name="generator" content="docfx 2.38.1.0"> <meta name="generator" content="docfx 2.39.2.0">
<link rel="shortcut icon" href="../favicon.ico"> <link rel="shortcut icon" href="../favicon.ico">
<link rel="stylesheet" href="../styles/docfx.vendor.css"> <link rel="stylesheet" href="../styles/docfx.vendor.css">
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
<meta name="viewport" content="width=device-width"> <meta name="viewport" content="width=device-width">
<meta name="title" content="Class GenericResponse <meta name="title" content="Class GenericResponse
| Titanium Web Proxy "> | Titanium Web Proxy ">
<meta name="generator" content="docfx 2.38.1.0"> <meta name="generator" content="docfx 2.39.2.0">
<link rel="shortcut icon" href="../favicon.ico"> <link rel="shortcut icon" href="../favicon.ico">
<link rel="stylesheet" href="../styles/docfx.vendor.css"> <link rel="stylesheet" href="../styles/docfx.vendor.css">
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
<meta name="viewport" content="width=device-width"> <meta name="viewport" content="width=device-width">
<meta name="title" content="Class OkResponse <meta name="title" content="Class OkResponse
| Titanium Web Proxy "> | Titanium Web Proxy ">
<meta name="generator" content="docfx 2.38.1.0"> <meta name="generator" content="docfx 2.39.2.0">
<link rel="shortcut icon" href="../favicon.ico"> <link rel="shortcut icon" href="../favicon.ico">
<link rel="stylesheet" href="../styles/docfx.vendor.css"> <link rel="stylesheet" href="../styles/docfx.vendor.css">
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
<meta name="viewport" content="width=device-width"> <meta name="viewport" content="width=device-width">
<meta name="title" content="Class RedirectResponse <meta name="title" content="Class RedirectResponse
| Titanium Web Proxy "> | Titanium Web Proxy ">
<meta name="generator" content="docfx 2.38.1.0"> <meta name="generator" content="docfx 2.39.2.0">
<link rel="shortcut icon" href="../favicon.ico"> <link rel="shortcut icon" href="../favicon.ico">
<link rel="stylesheet" href="../styles/docfx.vendor.css"> <link rel="stylesheet" href="../styles/docfx.vendor.css">
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
<meta name="viewport" content="width=device-width"> <meta name="viewport" content="width=device-width">
<meta name="title" content="Namespace Titanium.Web.Proxy.Http.Responses <meta name="title" content="Namespace Titanium.Web.Proxy.Http.Responses
| Titanium Web Proxy "> | Titanium Web Proxy ">
<meta name="generator" content="docfx 2.38.1.0"> <meta name="generator" content="docfx 2.39.2.0">
<link rel="shortcut icon" href="../favicon.ico"> <link rel="shortcut icon" href="../favicon.ico">
<link rel="stylesheet" href="../styles/docfx.vendor.css"> <link rel="stylesheet" href="../styles/docfx.vendor.css">
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
<meta name="viewport" content="width=device-width"> <meta name="viewport" content="width=device-width">
<meta name="title" content="Namespace Titanium.Web.Proxy.Http <meta name="title" content="Namespace Titanium.Web.Proxy.Http
| Titanium Web Proxy "> | Titanium Web Proxy ">
<meta name="generator" content="docfx 2.38.1.0"> <meta name="generator" content="docfx 2.39.2.0">
<link rel="shortcut icon" href="../favicon.ico"> <link rel="shortcut icon" href="../favicon.ico">
<link rel="stylesheet" href="../styles/docfx.vendor.css"> <link rel="stylesheet" href="../styles/docfx.vendor.css">
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
<meta name="viewport" content="width=device-width"> <meta name="viewport" content="width=device-width">
<meta name="title" content="Class ExplicitProxyEndPoint <meta name="title" content="Class ExplicitProxyEndPoint
| Titanium Web Proxy "> | Titanium Web Proxy ">
<meta name="generator" content="docfx 2.38.1.0"> <meta name="generator" content="docfx 2.39.2.0">
<link rel="shortcut icon" href="../favicon.ico"> <link rel="shortcut icon" href="../favicon.ico">
<link rel="stylesheet" href="../styles/docfx.vendor.css"> <link rel="stylesheet" href="../styles/docfx.vendor.css">
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
<meta name="viewport" content="width=device-width"> <meta name="viewport" content="width=device-width">
<meta name="title" content="Class ExternalProxy <meta name="title" content="Class ExternalProxy
| Titanium Web Proxy "> | Titanium Web Proxy ">
<meta name="generator" content="docfx 2.38.1.0"> <meta name="generator" content="docfx 2.39.2.0">
<link rel="shortcut icon" href="../favicon.ico"> <link rel="shortcut icon" href="../favicon.ico">
<link rel="stylesheet" href="../styles/docfx.vendor.css"> <link rel="stylesheet" href="../styles/docfx.vendor.css">
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
<meta name="viewport" content="width=device-width"> <meta name="viewport" content="width=device-width">
<meta name="title" content="Class HttpHeader <meta name="title" content="Class HttpHeader
| Titanium Web Proxy "> | Titanium Web Proxy ">
<meta name="generator" content="docfx 2.38.1.0"> <meta name="generator" content="docfx 2.39.2.0">
<link rel="shortcut icon" href="../favicon.ico"> <link rel="shortcut icon" href="../favicon.ico">
<link rel="stylesheet" href="../styles/docfx.vendor.css"> <link rel="stylesheet" href="../styles/docfx.vendor.css">
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
<meta name="viewport" content="width=device-width"> <meta name="viewport" content="width=device-width">
<meta name="title" content="Class ProxyEndPoint <meta name="title" content="Class ProxyEndPoint
| Titanium Web Proxy "> | Titanium Web Proxy ">
<meta name="generator" content="docfx 2.38.1.0"> <meta name="generator" content="docfx 2.39.2.0">
<link rel="shortcut icon" href="../favicon.ico"> <link rel="shortcut icon" href="../favicon.ico">
<link rel="stylesheet" href="../styles/docfx.vendor.css"> <link rel="stylesheet" href="../styles/docfx.vendor.css">
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
<meta name="viewport" content="width=device-width"> <meta name="viewport" content="width=device-width">
<meta name="title" content="Class TransparentProxyEndPoint <meta name="title" content="Class TransparentProxyEndPoint
| Titanium Web Proxy "> | Titanium Web Proxy ">
<meta name="generator" content="docfx 2.38.1.0"> <meta name="generator" content="docfx 2.39.2.0">
<link rel="shortcut icon" href="../favicon.ico"> <link rel="shortcut icon" href="../favicon.ico">
<link rel="stylesheet" href="../styles/docfx.vendor.css"> <link rel="stylesheet" href="../styles/docfx.vendor.css">
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
<meta name="viewport" content="width=device-width"> <meta name="viewport" content="width=device-width">
<meta name="title" content="Namespace Titanium.Web.Proxy.Models <meta name="title" content="Namespace Titanium.Web.Proxy.Models
| Titanium Web Proxy "> | Titanium Web Proxy ">
<meta name="generator" content="docfx 2.38.1.0"> <meta name="generator" content="docfx 2.39.2.0">
<link rel="shortcut icon" href="../favicon.ico"> <link rel="shortcut icon" href="../favicon.ico">
<link rel="stylesheet" href="../styles/docfx.vendor.css"> <link rel="stylesheet" href="../styles/docfx.vendor.css">
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
<meta name="viewport" content="width=device-width"> <meta name="viewport" content="width=device-width">
<meta name="title" content="Enum CertificateEngine <meta name="title" content="Enum CertificateEngine
| Titanium Web Proxy "> | Titanium Web Proxy ">
<meta name="generator" content="docfx 2.38.1.0"> <meta name="generator" content="docfx 2.39.2.0">
<link rel="shortcut icon" href="../favicon.ico"> <link rel="shortcut icon" href="../favicon.ico">
<link rel="stylesheet" href="../styles/docfx.vendor.css"> <link rel="stylesheet" href="../styles/docfx.vendor.css">
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
<meta name="viewport" content="width=device-width"> <meta name="viewport" content="width=device-width">
<meta name="title" content="Class CertificateManager <meta name="title" content="Class CertificateManager
| Titanium Web Proxy "> | Titanium Web Proxy ">
<meta name="generator" content="docfx 2.38.1.0"> <meta name="generator" content="docfx 2.39.2.0">
<link rel="shortcut icon" href="../favicon.ico"> <link rel="shortcut icon" href="../favicon.ico">
<link rel="stylesheet" href="../styles/docfx.vendor.css"> <link rel="stylesheet" href="../styles/docfx.vendor.css">
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
<meta name="viewport" content="width=device-width"> <meta name="viewport" content="width=device-width">
<meta name="title" content="Namespace Titanium.Web.Proxy.Network <meta name="title" content="Namespace Titanium.Web.Proxy.Network
| Titanium Web Proxy "> | Titanium Web Proxy ">
<meta name="generator" content="docfx 2.38.1.0"> <meta name="generator" content="docfx 2.39.2.0">
<link rel="shortcut icon" href="../favicon.ico"> <link rel="shortcut icon" href="../favicon.ico">
<link rel="stylesheet" href="../styles/docfx.vendor.css"> <link rel="stylesheet" href="../styles/docfx.vendor.css">
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
<meta name="viewport" content="width=device-width"> <meta name="viewport" content="width=device-width">
<meta name="title" content="Class ProxyServer <meta name="title" content="Class ProxyServer
| Titanium Web Proxy "> | Titanium Web Proxy ">
<meta name="generator" content="docfx 2.38.1.0"> <meta name="generator" content="docfx 2.39.2.0">
<link rel="shortcut icon" href="../favicon.ico"> <link rel="shortcut icon" href="../favicon.ico">
<link rel="stylesheet" href="../styles/docfx.vendor.css"> <link rel="stylesheet" href="../styles/docfx.vendor.css">
...@@ -423,7 +423,7 @@ Defaults to false.</p> ...@@ -423,7 +423,7 @@ Defaults to false.</p>
<a id="Titanium_Web_Proxy_ProxyServer_EnableConnectionPool_" data-uid="Titanium.Web.Proxy.ProxyServer.EnableConnectionPool*"></a> <a id="Titanium_Web_Proxy_ProxyServer_EnableConnectionPool_" data-uid="Titanium.Web.Proxy.ProxyServer.EnableConnectionPool*"></a>
<h4 id="Titanium_Web_Proxy_ProxyServer_EnableConnectionPool" data-uid="Titanium.Web.Proxy.ProxyServer.EnableConnectionPool">EnableConnectionPool</h4> <h4 id="Titanium_Web_Proxy_ProxyServer_EnableConnectionPool" data-uid="Titanium.Web.Proxy.ProxyServer.EnableConnectionPool">EnableConnectionPool</h4>
<div class="markdown level1 summary"><p>Should we enable experimental server connection pool? <div class="markdown level1 summary"><p>Should we enable experimental server connection pool?
Defaults to disable.</p> Defaults to true.</p>
</div> </div>
<div class="markdown level1 conceptual"></div> <div class="markdown level1 conceptual"></div>
<h5 class="decalaration">Declaration</h5> <h5 class="decalaration">Declaration</h5>
...@@ -447,6 +447,37 @@ Defaults to disable.</p> ...@@ -447,6 +447,37 @@ Defaults to disable.</p>
</table> </table>
<a id="Titanium_Web_Proxy_ProxyServer_EnableTcpServerConnectionPrefetch_" data-uid="Titanium.Web.Proxy.ProxyServer.EnableTcpServerConnectionPrefetch*"></a>
<h4 id="Titanium_Web_Proxy_ProxyServer_EnableTcpServerConnectionPrefetch" data-uid="Titanium.Web.Proxy.ProxyServer.EnableTcpServerConnectionPrefetch">EnableTcpServerConnectionPrefetch</h4>
<div class="markdown level1 summary"><p>Should we enable tcp server connection prefetching?
When enabled, as soon as we receive a client connection we concurrently initiate
corresponding server connection process using CONNECT hostname or SNI hostname on a separate task so that after parsing client request
we will have the server connection immediately ready or in the process of getting ready.
If a server connection is available in cache then this prefetch task will immediatly return with the available connection from cache.
Defaults to true.</p>
</div>
<div class="markdown level1 conceptual"></div>
<h5 class="decalaration">Declaration</h5>
<div class="codewrapper">
<pre><code class="lang-csharp hljs">public bool EnableTcpServerConnectionPrefetch { get; set; }</code></pre>
</div>
<h5 class="propertyValue">Property Value</h5>
<table class="table table-bordered table-striped table-condensed">
<thead>
<tr>
<th>Type</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><a class="xref" href="https://docs.microsoft.com/dotnet/api/system.boolean">Boolean</a></td>
<td></td>
</tr>
</tbody>
</table>
<a id="Titanium_Web_Proxy_ProxyServer_EnableWinAuth_" data-uid="Titanium.Web.Proxy.ProxyServer.EnableWinAuth*"></a> <a id="Titanium_Web_Proxy_ProxyServer_EnableWinAuth_" data-uid="Titanium.Web.Proxy.ProxyServer.EnableWinAuth*"></a>
<h4 id="Titanium_Web_Proxy_ProxyServer_EnableWinAuth" data-uid="Titanium.Web.Proxy.ProxyServer.EnableWinAuth">EnableWinAuth</h4> <h4 id="Titanium_Web_Proxy_ProxyServer_EnableWinAuth" data-uid="Titanium.Web.Proxy.ProxyServer.EnableWinAuth">EnableWinAuth</h4>
<div class="markdown level1 summary"><p>Enable disable Windows Authentication (NTLM/Kerberos). <div class="markdown level1 summary"><p>Enable disable Windows Authentication (NTLM/Kerberos).
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
<meta name="viewport" content="width=device-width"> <meta name="viewport" content="width=device-width">
<meta name="title" content="Namespace Titanium.Web.Proxy <meta name="title" content="Namespace Titanium.Web.Proxy
| Titanium Web Proxy "> | Titanium Web Proxy ">
<meta name="generator" content="docfx 2.38.1.0"> <meta name="generator" content="docfx 2.39.2.0">
<link rel="shortcut icon" href="../favicon.ico"> <link rel="shortcut icon" href="../favicon.ico">
<link rel="stylesheet" href="../styles/docfx.vendor.css"> <link rel="stylesheet" href="../styles/docfx.vendor.css">
......
...@@ -74,6 +74,16 @@ ...@@ -74,6 +74,16 @@
</li> </li>
</ul> </ul>
</li> </li>
<li>
<span class="expand-stub"></span>
<a href="Titanium.Web.Proxy.Helpers.html" name="" title="Titanium.Web.Proxy.Helpers">Titanium.Web.Proxy.Helpers</a>
<ul class="nav level2">
<li>
<a href="Titanium.Web.Proxy.Helpers.RunTime.html" name="" title="RunTime">RunTime</a>
</li>
</ul>
</li>
<li> <li>
<span class="expand-stub"></span> <span class="expand-stub"></span>
<a href="Titanium.Web.Proxy.Http.html" name="" title="Titanium.Web.Proxy.Http">Titanium.Web.Proxy.Http</a> <a href="Titanium.Web.Proxy.Http.html" name="" title="Titanium.Web.Proxy.Http">Titanium.Web.Proxy.Http</a>
......
...@@ -74,6 +74,16 @@ ...@@ -74,6 +74,16 @@
"title": "Class ProxyHttpException | Titanium Web Proxy", "title": "Class ProxyHttpException | Titanium Web Proxy",
"keywords": "Class ProxyHttpException Proxy HTTP exception. Inheritance Object Exception ProxyException ProxyHttpException Implements ISerializable _Exception Inherited Members Exception.GetBaseException() Exception.ToString() Exception.GetObjectData(SerializationInfo, StreamingContext) Exception.GetType() Exception.Message Exception.Data Exception.InnerException Exception.TargetSite Exception.StackTrace Exception.HelpLink Exception.Source Exception.HResult Exception.SerializeObjectState Object.Equals(Object) Object.Equals(Object, Object) Object.ReferenceEquals(Object, Object) Object.GetHashCode() Object.MemberwiseClone() Namespace : Titanium.Web.Proxy.Exceptions Assembly : Titanium.Web.Proxy.dll Syntax public class ProxyHttpException : ProxyException, ISerializable, _Exception Properties SessionEventArgs Gets session info associated to the exception. Declaration public SessionEventArgs SessionEventArgs { get; } Property Value Type Description SessionEventArgs Remarks This object properties should not be edited. Implements System.Runtime.Serialization.ISerializable System.Runtime.InteropServices._Exception" "keywords": "Class ProxyHttpException Proxy HTTP exception. Inheritance Object Exception ProxyException ProxyHttpException Implements ISerializable _Exception Inherited Members Exception.GetBaseException() Exception.ToString() Exception.GetObjectData(SerializationInfo, StreamingContext) Exception.GetType() Exception.Message Exception.Data Exception.InnerException Exception.TargetSite Exception.StackTrace Exception.HelpLink Exception.Source Exception.HResult Exception.SerializeObjectState Object.Equals(Object) Object.Equals(Object, Object) Object.ReferenceEquals(Object, Object) Object.GetHashCode() Object.MemberwiseClone() Namespace : Titanium.Web.Proxy.Exceptions Assembly : Titanium.Web.Proxy.dll Syntax public class ProxyHttpException : ProxyException, ISerializable, _Exception Properties SessionEventArgs Gets session info associated to the exception. Declaration public SessionEventArgs SessionEventArgs { get; } Property Value Type Description SessionEventArgs Remarks This object properties should not be edited. Implements System.Runtime.Serialization.ISerializable System.Runtime.InteropServices._Exception"
}, },
"api/Titanium.Web.Proxy.Helpers.html": {
"href": "api/Titanium.Web.Proxy.Helpers.html",
"title": "Namespace Titanium.Web.Proxy.Helpers | Titanium Web Proxy",
"keywords": "Namespace Titanium.Web.Proxy.Helpers Classes RunTime Run time helpers"
},
"api/Titanium.Web.Proxy.Helpers.RunTime.html": {
"href": "api/Titanium.Web.Proxy.Helpers.RunTime.html",
"title": "Class RunTime | Titanium Web Proxy",
"keywords": "Class RunTime Run time helpers Inheritance Object RunTime Inherited Members Object.ToString() Object.Equals(Object) Object.Equals(Object, Object) Object.ReferenceEquals(Object, Object) Object.GetHashCode() Object.GetType() Object.MemberwiseClone() Namespace : Titanium.Web.Proxy.Helpers Assembly : Titanium.Web.Proxy.dll Syntax public static class RunTime Properties IsLinux Declaration public static bool IsLinux { get; } Property Value Type Description Boolean IsMac Declaration public static bool IsMac { get; } Property Value Type Description Boolean IsWindows Declaration public static bool IsWindows { get; } Property Value Type Description Boolean"
},
"api/Titanium.Web.Proxy.html": { "api/Titanium.Web.Proxy.html": {
"href": "api/Titanium.Web.Proxy.html", "href": "api/Titanium.Web.Proxy.html",
"title": "Namespace Titanium.Web.Proxy | Titanium Web Proxy", "title": "Namespace Titanium.Web.Proxy | Titanium Web Proxy",
...@@ -192,6 +202,6 @@ ...@@ -192,6 +202,6 @@
"api/Titanium.Web.Proxy.ProxyServer.html": { "api/Titanium.Web.Proxy.ProxyServer.html": {
"href": "api/Titanium.Web.Proxy.ProxyServer.html", "href": "api/Titanium.Web.Proxy.ProxyServer.html",
"title": "Class ProxyServer | Titanium Web Proxy", "title": "Class ProxyServer | Titanium Web Proxy",
"keywords": "Class ProxyServer 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. Inheritance Object ProxyServer Implements IDisposable Inherited Members Object.ToString() Object.Equals(Object) Object.Equals(Object, Object) Object.ReferenceEquals(Object, Object) Object.GetHashCode() Object.GetType() Object.MemberwiseClone() Namespace : Titanium.Web.Proxy Assembly : Titanium.Web.Proxy.dll Syntax public class ProxyServer : IDisposable Constructors ProxyServer(Boolean, Boolean, Boolean) Initializes a new instance of ProxyServer class with provided parameters. Declaration public ProxyServer(bool userTrustRootCertificate = true, bool machineTrustRootCertificate = false, bool trustRootCertificateAsAdmin = false) Parameters Type Name Description Boolean userTrustRootCertificate Should fake HTTPS certificate be trusted by this machine's user certificate store? Boolean machineTrustRootCertificate Should fake HTTPS certificate be trusted by this machine's certificate store? Boolean trustRootCertificateAsAdmin Should we attempt to trust certificates with elevated permissions by prompting for UAC if required? ProxyServer(String, String, Boolean, Boolean, Boolean) Initializes a new instance of ProxyServer class with provided parameters. Declaration public ProxyServer(string rootCertificateName, string rootCertificateIssuerName, bool userTrustRootCertificate = true, bool machineTrustRootCertificate = false, bool trustRootCertificateAsAdmin = false) Parameters Type Name Description String rootCertificateName Name of the root certificate. String rootCertificateIssuerName Name of the root certificate issuer. Boolean userTrustRootCertificate Should fake HTTPS certificate be trusted by this machine's user certificate store? Boolean machineTrustRootCertificate Should fake HTTPS certificate be trusted by this machine's certificate store? Boolean trustRootCertificateAsAdmin Should we attempt to trust certificates with elevated permissions by prompting for UAC if required? Properties BufferPool The buffer pool used throughout this proxy instance. Set custom implementations by implementing this interface. By default this uses DefaultBufferPool implementation available in StreamExtended library package. Declaration public IBufferPool BufferPool { get; set; } Property Value Type Description StreamExtended.IBufferPool BufferSize Buffer size in bytes used throughout this proxy. Default value is 8192 bytes. Declaration public int BufferSize { get; set; } Property Value Type Description Int32 CertificateManager Manages certificates used by this proxy. Declaration public CertificateManager CertificateManager { get; } Property Value Type Description CertificateManager CheckCertificateRevocation Should we check for certificare revocation during SSL authentication to servers Note: If enabled can reduce performance. Defaults to false. Declaration public X509RevocationMode CheckCertificateRevocation { get; set; } Property Value Type Description X509RevocationMode ClientConnectionCount Total number of active client connections. Declaration public int ClientConnectionCount { get; } Property Value Type Description Int32 ConnectionTimeOutSeconds 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. Default value is 60 seconds. Declaration public int ConnectionTimeOutSeconds { get; set; } Property Value Type Description Int32 Enable100ContinueBehaviour Does this proxy uses the HTTP protocol 100 continue behaviour strictly? Broken 100 contunue implementations on server/client may cause problems if enabled. Defaults to false. Declaration public bool Enable100ContinueBehaviour { get; set; } Property Value Type Description Boolean EnableConnectionPool Should we enable experimental server connection pool? Defaults to disable. Declaration public bool EnableConnectionPool { get; set; } Property Value Type Description Boolean EnableWinAuth Enable disable Windows Authentication (NTLM/Kerberos). Note: NTLM/Kerberos will always send local credentials of current user running the proxy process. This is because a man in middle attack with Windows domain authentication is not currently supported. Defaults to false. Declaration public bool EnableWinAuth { get; set; } Property Value Type Description Boolean ExceptionFunc Callback for error events in this proxy instance. Declaration public ExceptionHandler ExceptionFunc { get; set; } Property Value Type Description ExceptionHandler ForwardToUpstreamGateway Gets or sets a value indicating whether requests will be chained to upstream gateway. Defaults to false. Declaration public bool ForwardToUpstreamGateway { get; set; } Property Value Type Description Boolean GetCustomUpStreamProxyFunc 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. Declaration public Func<SessionEventArgsBase, Task<ExternalProxy>> GetCustomUpStreamProxyFunc { get; set; } Property Value Type Description Func < SessionEventArgsBase , Task < ExternalProxy >> MaxCachedConnections Maximum number of concurrent connections per remote host in cache. Only valid when connection pooling is enabled. Default value is 2. Declaration public int MaxCachedConnections { get; set; } Property Value Type Description Int32 ProxyAuthenticationRealm Realm used during Proxy Basic Authentication. Declaration public string ProxyAuthenticationRealm { get; set; } Property Value Type Description String ProxyAuthenticationSchemes A collection of scheme types, e.g. basic, NTLM, Kerberos, Negotiate, to return if scheme authentication is required. Works in relation with ProxySchemeAuthenticateFunc. Declaration public IEnumerable<string> ProxyAuthenticationSchemes { get; set; } Property Value Type Description IEnumerable < String > ProxyBasicAuthenticateFunc A callback to authenticate proxy clients via basic authentication. Parameters are username and password as provided by client. Should return true for successful authentication. Declaration public Func<SessionEventArgsBase, string, string, Task<bool>> ProxyBasicAuthenticateFunc { get; set; } Property Value Type Description Func < SessionEventArgsBase , String , String , Task < Boolean >> ProxyEndPoints A list of IpAddress and port this proxy is listening to. Declaration public List<ProxyEndPoint> ProxyEndPoints { get; set; } Property Value Type Description List < ProxyEndPoint > ProxyRunning Is the proxy currently running? Declaration public bool ProxyRunning { get; } Property Value Type Description Boolean ProxySchemeAuthenticateFunc 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. Should return success for successful authentication, continuation if the package requests, or failure. Declaration public Func<SessionEventArgsBase, string, string, Task<ProxyAuthenticationContext>> ProxySchemeAuthenticateFunc { get; set; } Property Value Type Description Func < SessionEventArgsBase , String , String , Task < ProxyAuthenticationContext >> ReuseSocket Should we reuse client/server tcp sockets. Default is true (disabled for linux/macOS due to bug in .Net core). Declaration public bool ReuseSocket { get; set; } Property Value Type Description Boolean ServerConnectionCount Total number of active server connections. Declaration public int ServerConnectionCount { get; } Property Value Type Description Int32 SupportedSslProtocols List of supported Ssl versions. Declaration public SslProtocols SupportedSslProtocols { get; set; } Property Value Type Description SslProtocols TcpTimeWaitSeconds Number of seconds to linger when Tcp connection is in TIME_WAIT state. Default value is 30. Declaration public int TcpTimeWaitSeconds { get; set; } Property Value Type Description Int32 UpStreamEndPoint Local adapter/NIC endpoint where proxy makes request via. Defaults via any IP addresses of this machine. Declaration public IPEndPoint UpStreamEndPoint { get; set; } Property Value Type Description IPEndPoint UpStreamHttpProxy External proxy used for Http requests. Declaration public ExternalProxy UpStreamHttpProxy { get; set; } Property Value Type Description ExternalProxy UpStreamHttpsProxy External proxy used for Https requests. Declaration public ExternalProxy UpStreamHttpsProxy { get; set; } Property Value Type Description ExternalProxy Methods AddEndPoint(ProxyEndPoint) Add a proxy end point. Declaration public void AddEndPoint(ProxyEndPoint endPoint) Parameters Type Name Description ProxyEndPoint endPoint The proxy endpoint. DisableAllSystemProxies() Clear all proxy settings for current machine. Declaration public void DisableAllSystemProxies() DisableSystemHttpProxy() Clear HTTP proxy settings of current machine. Declaration public void DisableSystemHttpProxy() DisableSystemHttpsProxy() Clear HTTPS proxy settings of current machine. Declaration public void DisableSystemHttpsProxy() DisableSystemProxy(ProxyProtocolType) Clear the specified proxy setting for current machine. Declaration public void DisableSystemProxy(ProxyProtocolType protocolType) Parameters Type Name Description ProxyProtocolType protocolType Dispose() Dispose the Proxy instance. Declaration public void Dispose() RemoveEndPoint(ProxyEndPoint) Remove a proxy end point. Will throw error if the end point does'nt exist. Declaration public void RemoveEndPoint(ProxyEndPoint endPoint) Parameters Type Name Description ProxyEndPoint endPoint The existing endpoint to remove. SetAsSystemHttpProxy(ExplicitProxyEndPoint) Set the given explicit end point as the default proxy server for current machine. Declaration public void SetAsSystemHttpProxy(ExplicitProxyEndPoint endPoint) Parameters Type Name Description ExplicitProxyEndPoint endPoint The explicit endpoint. SetAsSystemHttpsProxy(ExplicitProxyEndPoint) Set the given explicit end point as the default proxy server for current machine. Declaration public void SetAsSystemHttpsProxy(ExplicitProxyEndPoint endPoint) Parameters Type Name Description ExplicitProxyEndPoint endPoint The explicit endpoint. SetAsSystemProxy(ExplicitProxyEndPoint, ProxyProtocolType) Set the given explicit end point as the default proxy server for current machine. Declaration public void SetAsSystemProxy(ExplicitProxyEndPoint endPoint, ProxyProtocolType protocolType) Parameters Type Name Description ExplicitProxyEndPoint endPoint The explicit endpoint. ProxyProtocolType protocolType The proxy protocol type. Start() Start this proxy server instance. Declaration public void Start() Stop() Stop this proxy server instance. Declaration public void Stop() Events AfterResponse Intercept after response event from server. Declaration public event AsyncEventHandler<SessionEventArgs> AfterResponse Event Type Type Description AsyncEventHandler < SessionEventArgs > BeforeRequest Intercept request event to server. Declaration public event AsyncEventHandler<SessionEventArgs> BeforeRequest Event Type Type Description AsyncEventHandler < SessionEventArgs > BeforeResponse Intercept response event from server. Declaration public event AsyncEventHandler<SessionEventArgs> BeforeResponse Event Type Type Description AsyncEventHandler < SessionEventArgs > ClientCertificateSelectionCallback Event to override client certificate selection during mutual SSL authentication. Declaration public event AsyncEventHandler<CertificateSelectionEventArgs> ClientCertificateSelectionCallback Event Type Type Description AsyncEventHandler < CertificateSelectionEventArgs > ClientConnectionCountChanged Event occurs when client connection count changed. Declaration public event EventHandler ClientConnectionCountChanged Event Type Type Description EventHandler OnClientConnectionCreate Customize TcpClient used for client connection upon create. Declaration public event AsyncEventHandler<TcpClient> OnClientConnectionCreate Event Type Type Description AsyncEventHandler < TcpClient > OnServerConnectionCreate Customize TcpClient used for server connection upon create. Declaration public event AsyncEventHandler<TcpClient> OnServerConnectionCreate Event Type Type Description AsyncEventHandler < TcpClient > ServerCertificateValidationCallback Event to override the default verification logic of remote SSL certificate received during authentication. Declaration public event AsyncEventHandler<CertificateValidationEventArgs> ServerCertificateValidationCallback Event Type Type Description AsyncEventHandler < CertificateValidationEventArgs > ServerConnectionCountChanged Event occurs when server connection count changed. Declaration public event EventHandler ServerConnectionCountChanged Event Type Type Description EventHandler Implements System.IDisposable" "keywords": "Class ProxyServer 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. Inheritance Object ProxyServer Implements IDisposable Inherited Members Object.ToString() Object.Equals(Object) Object.Equals(Object, Object) Object.ReferenceEquals(Object, Object) Object.GetHashCode() Object.GetType() Object.MemberwiseClone() Namespace : Titanium.Web.Proxy Assembly : Titanium.Web.Proxy.dll Syntax public class ProxyServer : IDisposable Constructors ProxyServer(Boolean, Boolean, Boolean) Initializes a new instance of ProxyServer class with provided parameters. Declaration public ProxyServer(bool userTrustRootCertificate = true, bool machineTrustRootCertificate = false, bool trustRootCertificateAsAdmin = false) Parameters Type Name Description Boolean userTrustRootCertificate Should fake HTTPS certificate be trusted by this machine's user certificate store? Boolean machineTrustRootCertificate Should fake HTTPS certificate be trusted by this machine's certificate store? Boolean trustRootCertificateAsAdmin Should we attempt to trust certificates with elevated permissions by prompting for UAC if required? ProxyServer(String, String, Boolean, Boolean, Boolean) Initializes a new instance of ProxyServer class with provided parameters. Declaration public ProxyServer(string rootCertificateName, string rootCertificateIssuerName, bool userTrustRootCertificate = true, bool machineTrustRootCertificate = false, bool trustRootCertificateAsAdmin = false) Parameters Type Name Description String rootCertificateName Name of the root certificate. String rootCertificateIssuerName Name of the root certificate issuer. Boolean userTrustRootCertificate Should fake HTTPS certificate be trusted by this machine's user certificate store? Boolean machineTrustRootCertificate Should fake HTTPS certificate be trusted by this machine's certificate store? Boolean trustRootCertificateAsAdmin Should we attempt to trust certificates with elevated permissions by prompting for UAC if required? Properties BufferPool The buffer pool used throughout this proxy instance. Set custom implementations by implementing this interface. By default this uses DefaultBufferPool implementation available in StreamExtended library package. Declaration public IBufferPool BufferPool { get; set; } Property Value Type Description StreamExtended.IBufferPool BufferSize Buffer size in bytes used throughout this proxy. Default value is 8192 bytes. Declaration public int BufferSize { get; set; } Property Value Type Description Int32 CertificateManager Manages certificates used by this proxy. Declaration public CertificateManager CertificateManager { get; } Property Value Type Description CertificateManager CheckCertificateRevocation Should we check for certificare revocation during SSL authentication to servers Note: If enabled can reduce performance. Defaults to false. Declaration public X509RevocationMode CheckCertificateRevocation { get; set; } Property Value Type Description X509RevocationMode ClientConnectionCount Total number of active client connections. Declaration public int ClientConnectionCount { get; } Property Value Type Description Int32 ConnectionTimeOutSeconds 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. Default value is 60 seconds. Declaration public int ConnectionTimeOutSeconds { get; set; } Property Value Type Description Int32 Enable100ContinueBehaviour Does this proxy uses the HTTP protocol 100 continue behaviour strictly? Broken 100 contunue implementations on server/client may cause problems if enabled. Defaults to false. Declaration public bool Enable100ContinueBehaviour { get; set; } Property Value Type Description Boolean EnableConnectionPool Should we enable experimental server connection pool? Defaults to true. Declaration public bool EnableConnectionPool { get; set; } Property Value Type Description Boolean EnableTcpServerConnectionPrefetch Should we enable tcp server connection prefetching? When enabled, as soon as we receive a client connection we concurrently initiate corresponding server connection process using CONNECT hostname or SNI hostname on a separate task so that after parsing client request we will have the server connection immediately ready or in the process of getting ready. If a server connection is available in cache then this prefetch task will immediatly return with the available connection from cache. Defaults to true. Declaration public bool EnableTcpServerConnectionPrefetch { get; set; } Property Value Type Description Boolean EnableWinAuth Enable disable Windows Authentication (NTLM/Kerberos). Note: NTLM/Kerberos will always send local credentials of current user running the proxy process. This is because a man in middle attack with Windows domain authentication is not currently supported. Defaults to false. Declaration public bool EnableWinAuth { get; set; } Property Value Type Description Boolean ExceptionFunc Callback for error events in this proxy instance. Declaration public ExceptionHandler ExceptionFunc { get; set; } Property Value Type Description ExceptionHandler ForwardToUpstreamGateway Gets or sets a value indicating whether requests will be chained to upstream gateway. Defaults to false. Declaration public bool ForwardToUpstreamGateway { get; set; } Property Value Type Description Boolean GetCustomUpStreamProxyFunc 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. Declaration public Func<SessionEventArgsBase, Task<ExternalProxy>> GetCustomUpStreamProxyFunc { get; set; } Property Value Type Description Func < SessionEventArgsBase , Task < ExternalProxy >> MaxCachedConnections Maximum number of concurrent connections per remote host in cache. Only valid when connection pooling is enabled. Default value is 2. Declaration public int MaxCachedConnections { get; set; } Property Value Type Description Int32 ProxyAuthenticationRealm Realm used during Proxy Basic Authentication. Declaration public string ProxyAuthenticationRealm { get; set; } Property Value Type Description String ProxyAuthenticationSchemes A collection of scheme types, e.g. basic, NTLM, Kerberos, Negotiate, to return if scheme authentication is required. Works in relation with ProxySchemeAuthenticateFunc. Declaration public IEnumerable<string> ProxyAuthenticationSchemes { get; set; } Property Value Type Description IEnumerable < String > ProxyBasicAuthenticateFunc A callback to authenticate proxy clients via basic authentication. Parameters are username and password as provided by client. Should return true for successful authentication. Declaration public Func<SessionEventArgsBase, string, string, Task<bool>> ProxyBasicAuthenticateFunc { get; set; } Property Value Type Description Func < SessionEventArgsBase , String , String , Task < Boolean >> ProxyEndPoints A list of IpAddress and port this proxy is listening to. Declaration public List<ProxyEndPoint> ProxyEndPoints { get; set; } Property Value Type Description List < ProxyEndPoint > ProxyRunning Is the proxy currently running? Declaration public bool ProxyRunning { get; } Property Value Type Description Boolean ProxySchemeAuthenticateFunc 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. Should return success for successful authentication, continuation if the package requests, or failure. Declaration public Func<SessionEventArgsBase, string, string, Task<ProxyAuthenticationContext>> ProxySchemeAuthenticateFunc { get; set; } Property Value Type Description Func < SessionEventArgsBase , String , String , Task < ProxyAuthenticationContext >> ReuseSocket Should we reuse client/server tcp sockets. Default is true (disabled for linux/macOS due to bug in .Net core). Declaration public bool ReuseSocket { get; set; } Property Value Type Description Boolean ServerConnectionCount Total number of active server connections. Declaration public int ServerConnectionCount { get; } Property Value Type Description Int32 SupportedSslProtocols List of supported Ssl versions. Declaration public SslProtocols SupportedSslProtocols { get; set; } Property Value Type Description SslProtocols TcpTimeWaitSeconds Number of seconds to linger when Tcp connection is in TIME_WAIT state. Default value is 30. Declaration public int TcpTimeWaitSeconds { get; set; } Property Value Type Description Int32 UpStreamEndPoint Local adapter/NIC endpoint where proxy makes request via. Defaults via any IP addresses of this machine. Declaration public IPEndPoint UpStreamEndPoint { get; set; } Property Value Type Description IPEndPoint UpStreamHttpProxy External proxy used for Http requests. Declaration public ExternalProxy UpStreamHttpProxy { get; set; } Property Value Type Description ExternalProxy UpStreamHttpsProxy External proxy used for Https requests. Declaration public ExternalProxy UpStreamHttpsProxy { get; set; } Property Value Type Description ExternalProxy Methods AddEndPoint(ProxyEndPoint) Add a proxy end point. Declaration public void AddEndPoint(ProxyEndPoint endPoint) Parameters Type Name Description ProxyEndPoint endPoint The proxy endpoint. DisableAllSystemProxies() Clear all proxy settings for current machine. Declaration public void DisableAllSystemProxies() DisableSystemHttpProxy() Clear HTTP proxy settings of current machine. Declaration public void DisableSystemHttpProxy() DisableSystemHttpsProxy() Clear HTTPS proxy settings of current machine. Declaration public void DisableSystemHttpsProxy() DisableSystemProxy(ProxyProtocolType) Clear the specified proxy setting for current machine. Declaration public void DisableSystemProxy(ProxyProtocolType protocolType) Parameters Type Name Description ProxyProtocolType protocolType Dispose() Dispose the Proxy instance. Declaration public void Dispose() RemoveEndPoint(ProxyEndPoint) Remove a proxy end point. Will throw error if the end point does'nt exist. Declaration public void RemoveEndPoint(ProxyEndPoint endPoint) Parameters Type Name Description ProxyEndPoint endPoint The existing endpoint to remove. SetAsSystemHttpProxy(ExplicitProxyEndPoint) Set the given explicit end point as the default proxy server for current machine. Declaration public void SetAsSystemHttpProxy(ExplicitProxyEndPoint endPoint) Parameters Type Name Description ExplicitProxyEndPoint endPoint The explicit endpoint. SetAsSystemHttpsProxy(ExplicitProxyEndPoint) Set the given explicit end point as the default proxy server for current machine. Declaration public void SetAsSystemHttpsProxy(ExplicitProxyEndPoint endPoint) Parameters Type Name Description ExplicitProxyEndPoint endPoint The explicit endpoint. SetAsSystemProxy(ExplicitProxyEndPoint, ProxyProtocolType) Set the given explicit end point as the default proxy server for current machine. Declaration public void SetAsSystemProxy(ExplicitProxyEndPoint endPoint, ProxyProtocolType protocolType) Parameters Type Name Description ExplicitProxyEndPoint endPoint The explicit endpoint. ProxyProtocolType protocolType The proxy protocol type. Start() Start this proxy server instance. Declaration public void Start() Stop() Stop this proxy server instance. Declaration public void Stop() Events AfterResponse Intercept after response event from server. Declaration public event AsyncEventHandler<SessionEventArgs> AfterResponse Event Type Type Description AsyncEventHandler < SessionEventArgs > BeforeRequest Intercept request event to server. Declaration public event AsyncEventHandler<SessionEventArgs> BeforeRequest Event Type Type Description AsyncEventHandler < SessionEventArgs > BeforeResponse Intercept response event from server. Declaration public event AsyncEventHandler<SessionEventArgs> BeforeResponse Event Type Type Description AsyncEventHandler < SessionEventArgs > ClientCertificateSelectionCallback Event to override client certificate selection during mutual SSL authentication. Declaration public event AsyncEventHandler<CertificateSelectionEventArgs> ClientCertificateSelectionCallback Event Type Type Description AsyncEventHandler < CertificateSelectionEventArgs > ClientConnectionCountChanged Event occurs when client connection count changed. Declaration public event EventHandler ClientConnectionCountChanged Event Type Type Description EventHandler OnClientConnectionCreate Customize TcpClient used for client connection upon create. Declaration public event AsyncEventHandler<TcpClient> OnClientConnectionCreate Event Type Type Description AsyncEventHandler < TcpClient > OnServerConnectionCreate Customize TcpClient used for server connection upon create. Declaration public event AsyncEventHandler<TcpClient> OnServerConnectionCreate Event Type Type Description AsyncEventHandler < TcpClient > ServerCertificateValidationCallback Event to override the default verification logic of remote SSL certificate received during authentication. Declaration public event AsyncEventHandler<CertificateValidationEventArgs> ServerCertificateValidationCallback Event Type Type Description AsyncEventHandler < CertificateValidationEventArgs > ServerConnectionCountChanged Event occurs when server connection count changed. Declaration public event EventHandler ServerConnectionCountChanged Event Type Type Description EventHandler Implements System.IDisposable"
} }
} }
...@@ -822,6 +822,57 @@ references: ...@@ -822,6 +822,57 @@ references:
isSpec: "True" isSpec: "True"
fullName: Titanium.Web.Proxy.Exceptions.ProxyHttpException.SessionEventArgs fullName: Titanium.Web.Proxy.Exceptions.ProxyHttpException.SessionEventArgs
nameWithType: ProxyHttpException.SessionEventArgs nameWithType: ProxyHttpException.SessionEventArgs
- uid: Titanium.Web.Proxy.Helpers
name: Titanium.Web.Proxy.Helpers
href: api/Titanium.Web.Proxy.Helpers.html
commentId: N:Titanium.Web.Proxy.Helpers
fullName: Titanium.Web.Proxy.Helpers
nameWithType: Titanium.Web.Proxy.Helpers
- uid: Titanium.Web.Proxy.Helpers.RunTime
name: RunTime
href: api/Titanium.Web.Proxy.Helpers.RunTime.html
commentId: T:Titanium.Web.Proxy.Helpers.RunTime
fullName: Titanium.Web.Proxy.Helpers.RunTime
nameWithType: RunTime
- uid: Titanium.Web.Proxy.Helpers.RunTime.IsLinux
name: IsLinux
href: api/Titanium.Web.Proxy.Helpers.RunTime.html#Titanium_Web_Proxy_Helpers_RunTime_IsLinux
commentId: P:Titanium.Web.Proxy.Helpers.RunTime.IsLinux
fullName: Titanium.Web.Proxy.Helpers.RunTime.IsLinux
nameWithType: RunTime.IsLinux
- uid: Titanium.Web.Proxy.Helpers.RunTime.IsLinux*
name: IsLinux
href: api/Titanium.Web.Proxy.Helpers.RunTime.html#Titanium_Web_Proxy_Helpers_RunTime_IsLinux_
commentId: Overload:Titanium.Web.Proxy.Helpers.RunTime.IsLinux
isSpec: "True"
fullName: Titanium.Web.Proxy.Helpers.RunTime.IsLinux
nameWithType: RunTime.IsLinux
- uid: Titanium.Web.Proxy.Helpers.RunTime.IsMac
name: IsMac
href: api/Titanium.Web.Proxy.Helpers.RunTime.html#Titanium_Web_Proxy_Helpers_RunTime_IsMac
commentId: P:Titanium.Web.Proxy.Helpers.RunTime.IsMac
fullName: Titanium.Web.Proxy.Helpers.RunTime.IsMac
nameWithType: RunTime.IsMac
- uid: Titanium.Web.Proxy.Helpers.RunTime.IsMac*
name: IsMac
href: api/Titanium.Web.Proxy.Helpers.RunTime.html#Titanium_Web_Proxy_Helpers_RunTime_IsMac_
commentId: Overload:Titanium.Web.Proxy.Helpers.RunTime.IsMac
isSpec: "True"
fullName: Titanium.Web.Proxy.Helpers.RunTime.IsMac
nameWithType: RunTime.IsMac
- uid: Titanium.Web.Proxy.Helpers.RunTime.IsWindows
name: IsWindows
href: api/Titanium.Web.Proxy.Helpers.RunTime.html#Titanium_Web_Proxy_Helpers_RunTime_IsWindows
commentId: P:Titanium.Web.Proxy.Helpers.RunTime.IsWindows
fullName: Titanium.Web.Proxy.Helpers.RunTime.IsWindows
nameWithType: RunTime.IsWindows
- uid: Titanium.Web.Proxy.Helpers.RunTime.IsWindows*
name: IsWindows
href: api/Titanium.Web.Proxy.Helpers.RunTime.html#Titanium_Web_Proxy_Helpers_RunTime_IsWindows_
commentId: Overload:Titanium.Web.Proxy.Helpers.RunTime.IsWindows
isSpec: "True"
fullName: Titanium.Web.Proxy.Helpers.RunTime.IsWindows
nameWithType: RunTime.IsWindows
- uid: Titanium.Web.Proxy.Http - uid: Titanium.Web.Proxy.Http
name: Titanium.Web.Proxy.Http name: Titanium.Web.Proxy.Http
href: api/Titanium.Web.Proxy.Http.html href: api/Titanium.Web.Proxy.Http.html
...@@ -2828,6 +2879,19 @@ references: ...@@ -2828,6 +2879,19 @@ references:
isSpec: "True" isSpec: "True"
fullName: Titanium.Web.Proxy.ProxyServer.EnableConnectionPool fullName: Titanium.Web.Proxy.ProxyServer.EnableConnectionPool
nameWithType: ProxyServer.EnableConnectionPool nameWithType: ProxyServer.EnableConnectionPool
- uid: Titanium.Web.Proxy.ProxyServer.EnableTcpServerConnectionPrefetch
name: EnableTcpServerConnectionPrefetch
href: api/Titanium.Web.Proxy.ProxyServer.html#Titanium_Web_Proxy_ProxyServer_EnableTcpServerConnectionPrefetch
commentId: P:Titanium.Web.Proxy.ProxyServer.EnableTcpServerConnectionPrefetch
fullName: Titanium.Web.Proxy.ProxyServer.EnableTcpServerConnectionPrefetch
nameWithType: ProxyServer.EnableTcpServerConnectionPrefetch
- uid: Titanium.Web.Proxy.ProxyServer.EnableTcpServerConnectionPrefetch*
name: EnableTcpServerConnectionPrefetch
href: api/Titanium.Web.Proxy.ProxyServer.html#Titanium_Web_Proxy_ProxyServer_EnableTcpServerConnectionPrefetch_
commentId: Overload:Titanium.Web.Proxy.ProxyServer.EnableTcpServerConnectionPrefetch
isSpec: "True"
fullName: Titanium.Web.Proxy.ProxyServer.EnableTcpServerConnectionPrefetch
nameWithType: ProxyServer.EnableTcpServerConnectionPrefetch
- uid: Titanium.Web.Proxy.ProxyServer.EnableWinAuth - uid: Titanium.Web.Proxy.ProxyServer.EnableWinAuth
name: EnableWinAuth name: EnableWinAuth
href: api/Titanium.Web.Proxy.ProxyServer.html#Titanium_Web_Proxy_ProxyServer_EnableWinAuth href: api/Titanium.Web.Proxy.ProxyServer.html#Titanium_Web_Proxy_ProxyServer_EnableWinAuth
......
using System; using System;
using Titanium.Web.Proxy.Examples.Basic.Helpers; using Titanium.Web.Proxy.Examples.Basic.Helpers;
using Titanium.Web.Proxy.Helpers;
namespace Titanium.Web.Proxy.Examples.Basic namespace Titanium.Web.Proxy.Examples.Basic
{ {
...@@ -9,8 +10,12 @@ namespace Titanium.Web.Proxy.Examples.Basic ...@@ -9,8 +10,12 @@ namespace Titanium.Web.Proxy.Examples.Basic
public static void Main(string[] args) public static void Main(string[] args)
{ {
// fix console hang due to QuickEdit mode
ConsoleHelper.DisableQuickEditMode(); if (RunTime.IsWindows)
{
// fix console hang due to QuickEdit mode
ConsoleHelper.DisableQuickEditMode();
}
// Start proxy controller // Start proxy controller
controller.StartProxy(); controller.StartProxy();
......
...@@ -24,7 +24,6 @@ namespace Titanium.Web.Proxy.Examples.Basic ...@@ -24,7 +24,6 @@ namespace Titanium.Web.Proxy.Examples.Basic
public ProxyTestController() public ProxyTestController()
{ {
proxyServer = new ProxyServer(); proxyServer = new ProxyServer();
proxyServer.EnableConnectionPool = true;
// generate root certificate without storing it in file system // generate root certificate without storing it in file system
//proxyServer.CertificateManager.CreateRootCertificate(false); //proxyServer.CertificateManager.CreateRootCertificate(false);
...@@ -108,13 +107,11 @@ namespace Titanium.Web.Proxy.Examples.Basic ...@@ -108,13 +107,11 @@ namespace Titanium.Web.Proxy.Examples.Basic
endPoint.IpAddress, endPoint.Port); endPoint.IpAddress, endPoint.Port);
} }
#if NETSTANDARD2_0 // Only explicit proxies can be set as system proxy!
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) //proxyServer.SetAsSystemHttpProxy(explicitEndPoint);
#endif //proxyServer.SetAsSystemHttpsProxy(explicitEndPoint);
if(RunTime.IsWindows)
{ {
// Only explicit proxies can be set as system proxy!
//proxyServer.SetAsSystemHttpProxy(explicitEndPoint);
//proxyServer.SetAsSystemHttpsProxy(explicitEndPoint);
proxyServer.SetAsSystemProxy(explicitEndPoint, ProxyProtocolType.AllHttp); proxyServer.SetAsSystemProxy(explicitEndPoint, ProxyProtocolType.AllHttp);
} }
} }
......
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>net45</TargetFrameworks>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<LangVersion>7.1</LangVersion>
<Platforms>AnyCPU;x64</Platforms>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Titanium.Web.Proxy\Titanium.Web.Proxy.Mono.csproj" />
</ItemGroup>
</Project>
\ No newline at end of file
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>netcoreapp2.0</TargetFrameworks>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<LangVersion>7.1</LangVersion>
</PropertyGroup>
<PropertyGroup>
<StartupObject />
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Titanium.Web.Proxy\Titanium.Web.Proxy.NetCore.csproj" />
</ItemGroup>
</Project>
\ No newline at end of file
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
<TargetFrameworks>net45;netcoreapp2.0</TargetFrameworks> <TargetFrameworks>net45;netcoreapp2.0</TargetFrameworks>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo> <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<LangVersion>7.1</LangVersion> <LangVersion>7.1</LangVersion>
<Platforms>AnyCPU;x64</Platforms>
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>
...@@ -12,7 +13,7 @@ ...@@ -12,7 +13,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
...@@ -50,6 +50,27 @@ ...@@ -50,6 +50,27 @@
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\x64\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<DebugType>full</DebugType>
<PlatformTarget>x64</PlatformTarget>
<LangVersion>7.1</LangVersion>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<Prefer32Bit>true</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
<OutputPath>bin\x64\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<Optimize>true</Optimize>
<DebugType>pdbonly</DebugType>
<PlatformTarget>x64</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<Prefer32Bit>true</Prefer32Bit>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="StreamExtended, Version=1.0.179.0, Culture=neutral, PublicKeyToken=bbfa0f1d54f50043, processorArchitecture=MSIL"> <Reference Include="StreamExtended, Version=1.0.179.0, Culture=neutral, PublicKeyToken=bbfa0f1d54f50043, processorArchitecture=MSIL">
<HintPath>..\..\packages\StreamExtended.1.0.179\lib\net45\StreamExtended.dll</HintPath> <HintPath>..\..\packages\StreamExtended.1.0.179\lib\net45\StreamExtended.dll</HintPath>
...@@ -116,12 +137,6 @@ ...@@ -116,12 +137,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 +144,11 @@ ...@@ -129,5 +144,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
{
"fileOptions": {
"excludeSearchPatterns": [
"**/*.Docs.csproj",
"**/tests/",
"**/Titanium.Web.Proxy.Examples.Wpf/",
"**/*.Basic.csproj",
"**/*.Proxy.csproj",
"**/*.Mono.csproj"
]
}
}
\ No newline at end of file
...@@ -3,13 +3,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 ...@@ -3,13 +3,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15 # Visual Studio 15
VisualStudioVersion = 15.0.27428.1 VisualStudioVersion = 15.0.27428.1
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{6FD3B84B-9283-4E9C-8C43-A234E9AA3EAA}"
ProjectSection(SolutionItems) = preProject
.nuget\NuGet.Config = .nuget\NuGet.Config
.nuget\NuGet.exe = .nuget\NuGet.exe
.nuget\NuGet.targets = .nuget\NuGet.targets
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Titanium.Web.Proxy.Docs", "Titanium.Web.Proxy\Titanium.Web.Proxy.Docs.csproj", "{EBF2EA46-EA00-4350-BE1D-D86AFD699DB3}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Titanium.Web.Proxy.Docs", "Titanium.Web.Proxy\Titanium.Web.Proxy.Docs.csproj", "{EBF2EA46-EA00-4350-BE1D-D86AFD699DB3}"
EndProject EndProject
Global Global
......

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26906.1
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Examples", "Examples", "{B6DBABDC-C985-4872-9C38-B4E5079CBC4B}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Documentation", "Documentation", "{38EA62D0-D2CB-465D-AF4F-407C5B4D4A1E}"
ProjectSection(SolutionItems) = preProject
..\LICENSE = ..\LICENSE
..\PULL_REQUEST_TEMPLATE.md = ..\PULL_REQUEST_TEMPLATE.md
..\README.md = ..\README.md
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{AC9AE37A-3059-4FDB-9A5C-363AD86F2EEF}"
ProjectSection(SolutionItems) = preProject
..\.build\build.ps1 = ..\.build\build.ps1
..\.build\docfx.json = ..\.build\docfx.json
..\.build\setup.ps1 = ..\.build\setup.ps1
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Titanium.Web.Proxy.Mono", "Titanium.Web.Proxy\Titanium.Web.Proxy.Mono.csproj", "{5985EBC2-75E8-4555-B715-B2302D879F9B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Titanium.Web.Proxy.Examples.Basic.Mono", "..\examples\Titanium.Web.Proxy.Examples.Basic\Titanium.Web.Proxy.Examples.Basic.Mono.csproj", "{9B5FA6A0-8D7C-46AD-B4F5-3AF6E2720C09}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{5985EBC2-75E8-4555-B715-B2302D879F9B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5985EBC2-75E8-4555-B715-B2302D879F9B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5985EBC2-75E8-4555-B715-B2302D879F9B}.Debug|x64.ActiveCfg = Debug|Any CPU
{5985EBC2-75E8-4555-B715-B2302D879F9B}.Debug|x64.Build.0 = Debug|Any CPU
{5985EBC2-75E8-4555-B715-B2302D879F9B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5985EBC2-75E8-4555-B715-B2302D879F9B}.Release|Any CPU.Build.0 = Release|Any CPU
{5985EBC2-75E8-4555-B715-B2302D879F9B}.Release|x64.ActiveCfg = Release|Any CPU
{5985EBC2-75E8-4555-B715-B2302D879F9B}.Release|x64.Build.0 = Release|Any CPU
{9B5FA6A0-8D7C-46AD-B4F5-3AF6E2720C09}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9B5FA6A0-8D7C-46AD-B4F5-3AF6E2720C09}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9B5FA6A0-8D7C-46AD-B4F5-3AF6E2720C09}.Debug|x64.ActiveCfg = Debug|Any CPU
{9B5FA6A0-8D7C-46AD-B4F5-3AF6E2720C09}.Debug|x64.Build.0 = Debug|Any CPU
{9B5FA6A0-8D7C-46AD-B4F5-3AF6E2720C09}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9B5FA6A0-8D7C-46AD-B4F5-3AF6E2720C09}.Release|Any CPU.Build.0 = Release|Any CPU
{9B5FA6A0-8D7C-46AD-B4F5-3AF6E2720C09}.Release|x64.ActiveCfg = Release|Any CPU
{9B5FA6A0-8D7C-46AD-B4F5-3AF6E2720C09}.Release|x64.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{9B5FA6A0-8D7C-46AD-B4F5-3AF6E2720C09} = {B6DBABDC-C985-4872-9C38-B4E5079CBC4B}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
EnterpriseLibraryConfigurationToolBinariesPath = .1.505.2\lib\NET35
SolutionGuid = {625C1EB5-44CF-47DE-A85A-B4C8C40ED90A}
EndGlobalSection
EndGlobal
...@@ -5,64 +5,80 @@ VisualStudioVersion = 15.0.26906.1 ...@@ -5,64 +5,80 @@ 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}"
ProjectSection(SolutionItems) = preProject
.nuget\NuGet.Config = .nuget\NuGet.Config
.nuget\NuGet.exe = .nuget\NuGet.exe
.nuget\NuGet.targets = .nuget\NuGet.targets
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Documentation", "Documentation", "{38EA62D0-D2CB-465D-AF4F-407C5B4D4A1E}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Documentation", "Documentation", "{38EA62D0-D2CB-465D-AF4F-407C5B4D4A1E}"
ProjectSection(SolutionItems) = preProject ProjectSection(SolutionItems) = preProject
LICENSE = LICENSE ..\LICENSE = ..\LICENSE
README.md = README.md ..\PULL_REQUEST_TEMPLATE.md = ..\PULL_REQUEST_TEMPLATE.md
..\README.md = ..\README.md
EndProjectSection EndProjectSection
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{AC9AE37A-3059-4FDB-9A5C-363AD86F2EEF}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{AC9AE37A-3059-4FDB-9A5C-363AD86F2EEF}"
ProjectSection(SolutionItems) = preProject ProjectSection(SolutionItems) = preProject
.build\Bootstrap.ps1 = .build\Bootstrap.ps1 ..\.build\build.ps1 = ..\.build\build.ps1
.build\Common.psm1 = .build\Common.psm1 ..\.build\docfx.json = ..\.build\docfx.json
.build\default.ps1 = .build\default.ps1 ..\.build\setup.ps1 = ..\.build\setup.ps1
EndProjectSection EndProjectSection
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
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
Release|Any CPU = Release|Any CPU Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64
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}.Debug|x64.ActiveCfg = Debug|x64
{8D73A1BE-868C-42D2-9ECE-F32CC1A02906}.Release|Any CPU.Build.0 = Release|Any CPU {91018B6D-A7A9-45BE-9CB3-79CBB8B169A6}.Debug|x64.Build.0 = Debug|x64
{91018B6D-A7A9-45BE-9CB3-79CBB8B169A6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{91018B6D-A7A9-45BE-9CB3-79CBB8B169A6}.Release|Any CPU.Build.0 = Release|Any CPU
{91018B6D-A7A9-45BE-9CB3-79CBB8B169A6}.Release|x64.ActiveCfg = Release|x64
{91018B6D-A7A9-45BE-9CB3-79CBB8B169A6}.Release|x64.Build.0 = Release|x64
{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}.Debug|x64.ActiveCfg = Debug|x64
{B517E3D0-D03B-436F-AB03-34BA0D5321AF}.Debug|x64.Build.0 = Debug|x64
{B517E3D0-D03B-436F-AB03-34BA0D5321AF}.Release|Any CPU.ActiveCfg = Release|Any CPU {B517E3D0-D03B-436F-AB03-34BA0D5321AF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B517E3D0-D03B-436F-AB03-34BA0D5321AF}.Release|Any CPU.Build.0 = Release|Any CPU {B517E3D0-D03B-436F-AB03-34BA0D5321AF}.Release|Any CPU.Build.0 = Release|Any CPU
{B517E3D0-D03B-436F-AB03-34BA0D5321AF}.Release|x64.ActiveCfg = Release|x64
{B517E3D0-D03B-436F-AB03-34BA0D5321AF}.Release|x64.Build.0 = Release|x64
{32231301-B0FB-4F9E-98DF-B3E8A88F4C16}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {32231301-B0FB-4F9E-98DF-B3E8A88F4C16}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{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}.Debug|x64.ActiveCfg = Debug|x64
{32231301-B0FB-4F9E-98DF-B3E8A88F4C16}.Debug|x64.Build.0 = Debug|x64
{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
{32231301-B0FB-4F9E-98DF-B3E8A88F4C16}.Release|x64.ActiveCfg = Release|x64
{32231301-B0FB-4F9E-98DF-B3E8A88F4C16}.Release|x64.Build.0 = Release|x64
{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}.Debug|x64.ActiveCfg = Debug|x64
{1FAC4205-4445-4F2B-BB8F-618E8A0C15FD}.Debug|x64.Build.0 = Debug|x64
{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
{1FAC4205-4445-4F2B-BB8F-618E8A0C15FD}.Release|x64.ActiveCfg = Release|x64
{1FAC4205-4445-4F2B-BB8F-618E8A0C15FD}.Release|x64.Build.0 = Release|x64
{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}.Debug|x64.ActiveCfg = Debug|x64
{4406CE17-9A39-4F28-8363-6169A4F799C1}.Debug|x64.Build.0 = Debug|x64
{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 {4406CE17-9A39-4F28-8363-6169A4F799C1}.Release|x64.ActiveCfg = Release|x64
{9A2C6980-90D1-4082-AD60-B2428F3D6197}.Debug|Any CPU.Build.0 = Debug|Any CPU {4406CE17-9A39-4F28-8363-6169A4F799C1}.Release|x64.Build.0 = Release|x64
{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 +86,8 @@ Global ...@@ -70,8 +86,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; }
} }
} }
...@@ -152,11 +152,14 @@ namespace Titanium.Web.Proxy ...@@ -152,11 +152,14 @@ namespace Titanium.Web.Proxy
SslStream sslStream = null; SslStream sslStream = null;
//don't pass cancellation token here if (EnableTcpServerConnectionPrefetch)
//it could cause floating server connections when client exits {
prefetchConnectionTask = tcpConnectionFactory.GetServerConnection(this, connectArgs, //don't pass cancellation token here
isConnect: true, applicationProtocols: null, noCache: false, //it could cause floating server connections when client exits
cancellationToken: CancellationToken.None); prefetchConnectionTask = tcpConnectionFactory.GetServerConnection(this, connectArgs,
isConnect: true, applicationProtocols: null, noCache: false,
cancellationToken: CancellationToken.None);
}
try try
{ {
...@@ -204,10 +207,10 @@ namespace Titanium.Web.Proxy ...@@ -204,10 +207,10 @@ namespace Titanium.Web.Proxy
decryptSsl = false; decryptSsl = false;
} }
if(!decryptSsl) if (!decryptSsl)
{ {
await tcpConnectionFactory.Release(prefetchConnectionTask, true); await tcpConnectionFactory.Release(prefetchConnectionTask, true);
prefetchConnectionTask = null; prefetchConnectionTask = null;
} }
} }
......
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;
#if NETSTANDARD2_0 #if NETSTANDARD2_0
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
#endif #endif
namespace Titanium.Web.Proxy.Helpers namespace Titanium.Web.Proxy.Helpers
{ {
/// <summary> /// <summary>
/// Run time helpers /// Run time helpers
/// </summary> /// </summary>
internal class RunTime public static class RunTime
{ {
/// <summary> /// <summary>
/// cache for mono runtime check /// cache for mono runtime check
...@@ -16,33 +16,49 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -16,33 +16,49 @@ namespace Titanium.Web.Proxy.Helpers
/// <returns></returns> /// <returns></returns>
private static readonly Lazy<bool> isRunningOnMono = new Lazy<bool>(() => Type.GetType("Mono.Runtime") != null); private static readonly Lazy<bool> isRunningOnMono = new Lazy<bool>(() => Type.GetType("Mono.Runtime") != null);
/// <summary>
/// cache for mono runtime check
/// </summary>
/// <returns></returns>
private static readonly Lazy<bool> isRunningOnMonoLinux = new Lazy<bool>(() => IsRunningOnMono && (int)Environment.OSVersion.Platform == 4);
/// <summary>
/// cache for mono runtime check
/// </summary>
/// <returns></returns>
private static readonly Lazy<bool> isRunningOnMonoMac = new Lazy<bool>(() => IsRunningOnMono && (int)Environment.OSVersion.Platform == 6);
#if NETSTANDARD2_0 #if NETSTANDARD2_0
/// <summary> /// <summary>
/// cache for Windows platform check /// cache for Windows platform check
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
private static readonly Lazy<bool> isRunningOnWindows private static bool isRunningOnWindows => RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
= new Lazy<bool>(() => RuntimeInformation.IsOSPlatform(OSPlatform.Windows)); private static bool isRunningOnLinux => RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
private static bool isRunningOnMac => RuntimeInformation.IsOSPlatform(OSPlatform.OSX);
private static readonly Lazy<bool> isRunningOnLinux
= new Lazy<bool>(() => RuntimeInformation.IsOSPlatform(OSPlatform.Linux));
#endif #endif
/// <summary> /// <summary>
/// Is running on Mono? /// Is running on Mono?
/// </summary> /// </summary>
internal static bool IsRunningOnMono => isRunningOnMono.Value; internal static bool IsRunningOnMono => isRunningOnMono.Value;
#if NETSTANDARD2_0 #if NETSTANDARD2_0
internal static bool IsLinux => isRunningOnLinux.Value; public static bool IsLinux => isRunningOnLinux;
#else #else
internal static bool IsLinux => !IsWindows; public static bool IsLinux => isRunningOnMonoLinux.Value;
#endif #endif
#if NETSTANDARD2_0 #if NETSTANDARD2_0
internal static bool IsWindows => isRunningOnWindows.Value; public static bool IsWindows => isRunningOnWindows;
#else #else
internal static bool IsWindows => true; public static bool IsWindows => !IsLinux && !IsMac;
#endif #endif
#if NETSTANDARD2_0
public static bool IsMac => isRunningOnMac;
#else
public static bool IsMac => isRunningOnMonoMac.Value;
#endif
} }
} }
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 true.
/// </summary> /// </summary>
public bool EnableConnectionPool { get; set; } public bool EnableConnectionPool { get; set; } = true;
/// <summary> /// <summary>
/// Buffer size in bytes used throughout this proxy. /// Should we enable tcp server connection prefetching?
/// Default value is 8192 bytes. /// When enabled, as soon as we receive a client connection we concurrently initiate
/// </summary> /// corresponding server connection process using CONNECT hostname or SNI hostname on a separate task so that after parsing client request
public int BufferSize { get; set; } = 8192; /// we will have the server connection immediately ready or in the process of getting ready.
/// If a server connection is available in cache then this prefetch task will immediatly return with the available connection from cache.
/// <summary> /// Defaults to true.
/// Seconds client/server connection are to be kept alive when waiting for read/write to complete. /// </summary>
/// This will also determine the pool eviction time when connection pool is enabled. public bool EnableTcpServerConnectionPrefetch { get; set; } = true;
/// Default value is 60 seconds.
/// </summary> /// <summary>
public int ConnectionTimeOutSeconds { get; set; } /// Buffer size in bytes used throughout this proxy.
/// Default value is 8192 bytes.
/// <summary> /// </summary>
/// Maximum number of concurrent connections per remote host in cache. public int BufferSize { get; set; } = 8192;
/// Only valid when connection pooling is enabled.
/// Default value is 2. /// <summary>
/// </summary> /// Seconds client/server connection are to be kept alive when waiting for read/write to complete.
public int MaxCachedConnections { get; set; } = 2; /// This will also determine the pool eviction time when connection pool is enabled.
/// Default value is 60 seconds.
/// <summary> /// </summary>
/// Number of seconds to linger when Tcp connection is in TIME_WAIT state. public int ConnectionTimeOutSeconds { get; set; }
/// Default value is 30.
/// </summary> /// <summary>
public int TcpTimeWaitSeconds { get; set; } = 30; /// Maximum number of concurrent connections per remote host in cache.
/// Only valid when connection pooling is enabled.
/// <summary> /// Default value is 2.
/// Should we reuse client/server tcp sockets. /// </summary>
/// Default is true (disabled for linux/macOS due to bug in .Net core). public int MaxCachedConnections { get; set; } = 2;
/// </summary>
public bool ReuseSocket { get; set; } = true; /// <summary>
/// Number of seconds to linger when Tcp connection is in TIME_WAIT state.
/// <summary> /// Default value is 30.
/// Total number of active client connections. /// </summary>
/// </summary> public int TcpTimeWaitSeconds { get; set; } = 30;
public int ClientConnectionCount => clientConnectionCount;
/// <summary>
/// <summary> /// Should we reuse client/server tcp sockets.
/// Total number of active server connections. /// Default is true (disabled for linux/macOS due to bug in .Net core).
/// </summary> /// </summary>
public int ServerConnectionCount => serverConnectionCount; public bool ReuseSocket { get; set; } = true;
/// <summary> /// <summary>
/// Realm used during Proxy Basic Authentication. /// Total number of active client connections.
/// </summary> /// </summary>
public string ProxyAuthenticationRealm { get; set; } = "TitaniumProxy"; public int ClientConnectionCount => clientConnectionCount;
/// <summary> /// <summary>
/// List of supported Ssl versions. /// Total number of active server connections.
/// </summary> /// </summary>
public SslProtocols SupportedSslProtocols { get; set; } = public int ServerConnectionCount => serverConnectionCount;
#if NET45
SslProtocols.Ssl3 | /// <summary>
#endif /// Realm used during Proxy Basic Authentication.
SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12; /// </summary>
public string ProxyAuthenticationRealm { get; set; } = "TitaniumProxy";
/// <summary>
/// The buffer pool used throughout this proxy instance. /// <summary>
/// Set custom implementations by implementing this interface. /// List of supported Ssl versions.
/// By default this uses DefaultBufferPool implementation available in StreamExtended library package. /// </summary>
/// </summary> public SslProtocols SupportedSslProtocols { get; set; } =
public IBufferPool BufferPool { get; set; } #if NET45
SslProtocols.Ssl3 |
/// <summary> #endif
/// Manages certificates used by this proxy. SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12;
/// </summary>
public CertificateManager CertificateManager { get; } /// <summary>
/// The buffer pool used throughout this proxy instance.
/// <summary> /// Set custom implementations by implementing this interface.
/// External proxy used for Http requests. /// By default this uses DefaultBufferPool implementation available in StreamExtended library package.
/// </summary> /// </summary>
public ExternalProxy UpStreamHttpProxy { get; set; } public IBufferPool BufferPool { get; set; }
/// <summary> /// <summary>
/// External proxy used for Https requests. /// Manages certificates used by this proxy.
/// </summary> /// </summary>
public ExternalProxy UpStreamHttpsProxy { get; set; } public CertificateManager CertificateManager { get; }
/// <summary> /// <summary>
/// Local adapter/NIC endpoint where proxy makes request via. /// External proxy used for Http requests.
/// Defaults via any IP addresses of this machine. /// </summary>
/// </summary> public ExternalProxy UpStreamHttpProxy { get; set; }
public IPEndPoint UpStreamEndPoint { get; set; } = new IPEndPoint(IPAddress.Any, 0);
/// <summary>
/// <summary> /// External proxy used for Https requests.
/// A list of IpAddress and port this proxy is listening to. /// </summary>
/// </summary> public ExternalProxy UpStreamHttpsProxy { get; set; }
public List<ProxyEndPoint> ProxyEndPoints { get; set; }
/// <summary>
/// <summary> /// Local adapter/NIC endpoint where proxy makes request via.
/// A callback to provide authentication credentials for up stream proxy this proxy is using for HTTP(S) requests. /// Defaults via any IP addresses of this machine.
/// User should return the ExternalProxy object with valid credentials. /// </summary>
/// </summary> public IPEndPoint UpStreamEndPoint { get; set; } = new IPEndPoint(IPAddress.Any, 0);
public Func<SessionEventArgsBase, Task<ExternalProxy>> GetCustomUpStreamProxyFunc { get; set; }
/// <summary>
/// <summary> /// A list of IpAddress and port this proxy is listening to.
/// Callback for error events in this proxy instance. /// </summary>
/// </summary> public List<ProxyEndPoint> ProxyEndPoints { get; set; }
public ExceptionHandler ExceptionFunc
{ /// <summary>
get => exceptionFunc ?? defaultExceptionFunc; /// A callback to provide authentication credentials for up stream proxy this proxy is using for HTTP(S) requests.
set /// User should return the ExternalProxy object with valid credentials.
{ /// </summary>
exceptionFunc = value; public Func<SessionEventArgsBase, Task<ExternalProxy>> GetCustomUpStreamProxyFunc { get; set; }
CertificateManager.ExceptionFunc = value;
} /// <summary>
} /// Callback for error events in this proxy instance.
/// </summary>
/// <summary> public ExceptionHandler ExceptionFunc
/// A callback to authenticate proxy clients via basic authentication. {
/// Parameters are username and password as provided by client. get => exceptionFunc ?? defaultExceptionFunc;
/// Should return true for successful authentication. set
/// </summary> {
public Func<SessionEventArgsBase, string, string, Task<bool>> ProxyBasicAuthenticateFunc { get; set; } exceptionFunc = value;
CertificateManager.ExceptionFunc = value;
/// <summary> }
/// 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.
/// Should return success for successful authentication, continuation if the package requests, or failure. /// <summary>
/// </summary> /// A callback to authenticate proxy clients via basic authentication.
public Func<SessionEventArgsBase, string, string, Task<ProxyAuthenticationContext>> ProxySchemeAuthenticateFunc { get; set; } /// Parameters are username and password as provided by client.
/// Should return true for successful authentication.
/// <summary> /// </summary>
/// A collection of scheme types, e.g. basic, NTLM, Kerberos, Negotiate, to return if scheme authentication is required. public Func<SessionEventArgsBase, string, string, Task<bool>> ProxyBasicAuthenticateFunc { get; set; }
/// Works in relation with ProxySchemeAuthenticateFunc.
/// </summary> /// <summary>
public IEnumerable<string> ProxyAuthenticationSchemes { get; set; } = new string[0]; /// 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.
/// <summary> /// Should return success for successful authentication, continuation if the package requests, or failure.
/// Event occurs when client connection count changed. /// </summary>
/// </summary> public Func<SessionEventArgsBase, string, string, Task<ProxyAuthenticationContext>> ProxySchemeAuthenticateFunc { get; set; }
public event EventHandler ClientConnectionCountChanged;
/// <summary>
/// <summary> /// A collection of scheme types, e.g. basic, NTLM, Kerberos, Negotiate, to return if scheme authentication is required.
/// Event occurs when server connection count changed. /// Works in relation with ProxySchemeAuthenticateFunc.
/// </summary> /// </summary>
public event EventHandler ServerConnectionCountChanged; public IEnumerable<string> ProxyAuthenticationSchemes { get; set; } = new string[0];
/// <summary> /// <summary>
/// Event to override the default verification logic of remote SSL certificate received during authentication. /// Event occurs when client connection count changed.
/// </summary> /// </summary>
public event AsyncEventHandler<CertificateValidationEventArgs> ServerCertificateValidationCallback; public event EventHandler ClientConnectionCountChanged;
/// <summary> /// <summary>
/// Event to override client certificate selection during mutual SSL authentication. /// Event occurs when server connection count changed.
/// </summary> /// </summary>
public event AsyncEventHandler<CertificateSelectionEventArgs> ClientCertificateSelectionCallback; public event EventHandler ServerConnectionCountChanged;
/// <summary> /// <summary>
/// Intercept request event to server. /// Event to override the default verification logic of remote SSL certificate received during authentication.
/// </summary> /// </summary>
public event AsyncEventHandler<SessionEventArgs> BeforeRequest; public event AsyncEventHandler<CertificateValidationEventArgs> ServerCertificateValidationCallback;
/// <summary> /// <summary>
/// Intercept response event from server. /// Event to override client certificate selection during mutual SSL authentication.
/// </summary> /// </summary>
public event AsyncEventHandler<SessionEventArgs> BeforeResponse; public event AsyncEventHandler<CertificateSelectionEventArgs> ClientCertificateSelectionCallback;
/// <summary> /// <summary>
/// Intercept after response event from server. /// Intercept request event to server.
/// </summary> /// </summary>
public event AsyncEventHandler<SessionEventArgs> AfterResponse; public event AsyncEventHandler<SessionEventArgs> BeforeRequest;
/// <summary> /// <summary>
/// Customize TcpClient used for client connection upon create. /// Intercept response event from server.
/// </summary> /// </summary>
public event AsyncEventHandler<TcpClient> OnClientConnectionCreate; public event AsyncEventHandler<SessionEventArgs> BeforeResponse;
/// <summary> /// <summary>
/// Customize TcpClient used for server connection upon create. /// Intercept after response event from server.
/// </summary> /// </summary>
public event AsyncEventHandler<TcpClient> OnServerConnectionCreate; public event AsyncEventHandler<SessionEventArgs> AfterResponse;
/// <summary> /// <summary>
/// Add a proxy end point. /// Customize TcpClient used for client connection upon create.
/// </summary> /// </summary>
/// <param name="endPoint">The proxy endpoint.</param> public event AsyncEventHandler<TcpClient> OnClientConnectionCreate;
public void AddEndPoint(ProxyEndPoint endPoint)
{ /// <summary>
if (ProxyEndPoints.Any(x => /// Customize TcpClient used for server connection upon create.
x.IpAddress.Equals(endPoint.IpAddress) && endPoint.Port != 0 && x.Port == endPoint.Port)) /// </summary>
{ public event AsyncEventHandler<TcpClient> OnServerConnectionCreate;
throw new Exception("Cannot add another endpoint to same port & ip address");
} /// <summary>
/// Add a proxy end point.
ProxyEndPoints.Add(endPoint); /// </summary>
/// <param name="endPoint">The proxy endpoint.</param>
if (ProxyRunning) public void AddEndPoint(ProxyEndPoint endPoint)
{ {
listen(endPoint); if (ProxyEndPoints.Any(x =>
} x.IpAddress.Equals(endPoint.IpAddress) && endPoint.Port != 0 && x.Port == endPoint.Port))
} {
throw new Exception("Cannot add another endpoint to same port & ip address");
/// <summary> }
/// Remove a proxy end point.
/// Will throw error if the end point does'nt exist. ProxyEndPoints.Add(endPoint);
/// </summary>
/// <param name="endPoint">The existing endpoint to remove.</param> if (ProxyRunning)
public void RemoveEndPoint(ProxyEndPoint endPoint) {
{ listen(endPoint);
if (ProxyEndPoints.Contains(endPoint) == false) }
{ }
throw new Exception("Cannot remove endPoints not added to proxy");
} /// <summary>
/// Remove a proxy end point.
ProxyEndPoints.Remove(endPoint); /// Will throw error if the end point does'nt exist.
/// </summary>
if (ProxyRunning) /// <param name="endPoint">The existing endpoint to remove.</param>
{ public void RemoveEndPoint(ProxyEndPoint endPoint)
quitListen(endPoint); {
} if (ProxyEndPoints.Contains(endPoint) == false)
} {
throw new Exception("Cannot remove endPoints not added to proxy");
/// <summary> }
/// Set the given explicit end point as the default proxy server for current machine.
/// </summary> ProxyEndPoints.Remove(endPoint);
/// <param name="endPoint">The explicit endpoint.</param>
public void SetAsSystemHttpProxy(ExplicitProxyEndPoint endPoint) if (ProxyRunning)
{ {
SetAsSystemProxy(endPoint, ProxyProtocolType.Http); quitListen(endPoint);
} }
}
/// <summary>
/// Set the given explicit end point as the default proxy server for current machine. /// <summary>
/// </summary> /// Set the given explicit end point as the default proxy server for current machine.
/// <param name="endPoint">The explicit endpoint.</param> /// </summary>
public void SetAsSystemHttpsProxy(ExplicitProxyEndPoint endPoint) /// <param name="endPoint">The explicit endpoint.</param>
{ public void SetAsSystemHttpProxy(ExplicitProxyEndPoint endPoint)
SetAsSystemProxy(endPoint, ProxyProtocolType.Https); {
} SetAsSystemProxy(endPoint, ProxyProtocolType.Http);
}
/// <summary>
/// Set the given explicit end point as the default proxy server for current machine. /// <summary>
/// </summary> /// Set the given explicit end point as the default proxy server for current machine.
/// <param name="endPoint">The explicit endpoint.</param> /// </summary>
/// <param name="protocolType">The proxy protocol type.</param> /// <param name="endPoint">The explicit endpoint.</param>
public void SetAsSystemProxy(ExplicitProxyEndPoint endPoint, ProxyProtocolType protocolType) public void SetAsSystemHttpsProxy(ExplicitProxyEndPoint endPoint)
{ {
if (RunTime.IsRunningOnMono) SetAsSystemProxy(endPoint, ProxyProtocolType.Https);
{ }
throw new Exception("Mono Runtime do not support system proxy settings.");
} /// <summary>
/// Set the given explicit end point as the default proxy server for current machine.
validateEndPointAsSystemProxy(endPoint); /// </summary>
/// <param name="endPoint">The explicit endpoint.</param>
bool isHttp = (protocolType & ProxyProtocolType.Http) > 0; /// <param name="protocolType">The proxy protocol type.</param>
bool isHttps = (protocolType & ProxyProtocolType.Https) > 0; public void SetAsSystemProxy(ExplicitProxyEndPoint endPoint, ProxyProtocolType protocolType)
{
if (isHttps) if (RunTime.IsRunningOnMono)
{ {
CertificateManager.EnsureRootCertificate(); throw new Exception("Mono Runtime do not support system proxy settings.");
}
// If certificate was trusted by the machine
if (!CertificateManager.CertValidated) validateEndPointAsSystemProxy(endPoint);
{
protocolType = protocolType & ~ProxyProtocolType.Https; bool isHttp = (protocolType & ProxyProtocolType.Http) > 0;
isHttps = false; bool isHttps = (protocolType & ProxyProtocolType.Https) > 0;
}
} if (isHttps)
{
// clear any settings previously added CertificateManager.EnsureRootCertificate();
if (isHttp)
{ // If certificate was trusted by the machine
ProxyEndPoints.OfType<ExplicitProxyEndPoint>().ToList().ForEach(x => x.IsSystemHttpProxy = false); if (!CertificateManager.CertValidated)
} {
protocolType = protocolType & ~ProxyProtocolType.Https;
if (isHttps) isHttps = false;
{ }
ProxyEndPoints.OfType<ExplicitProxyEndPoint>().ToList().ForEach(x => x.IsSystemHttpsProxy = false); }
}
// clear any settings previously added
systemProxySettingsManager.SetProxy( if (isHttp)
Equals(endPoint.IpAddress, IPAddress.Any) | {
Equals(endPoint.IpAddress, IPAddress.Loopback) ProxyEndPoints.OfType<ExplicitProxyEndPoint>().ToList().ForEach(x => x.IsSystemHttpProxy = false);
? "localhost" }
: endPoint.IpAddress.ToString(),
endPoint.Port, if (isHttps)
protocolType); {
ProxyEndPoints.OfType<ExplicitProxyEndPoint>().ToList().ForEach(x => x.IsSystemHttpsProxy = false);
if (isHttp) }
{
endPoint.IsSystemHttpProxy = true; systemProxySettingsManager.SetProxy(
} Equals(endPoint.IpAddress, IPAddress.Any) |
Equals(endPoint.IpAddress, IPAddress.Loopback)
if (isHttps) ? "localhost"
{ : endPoint.IpAddress.ToString(),
endPoint.IsSystemHttpsProxy = true; endPoint.Port,
} protocolType);
string proxyType = null; if (isHttp)
switch (protocolType) {
{ endPoint.IsSystemHttpProxy = true;
case ProxyProtocolType.Http: }
proxyType = "HTTP";
break; if (isHttps)
case ProxyProtocolType.Https: {
proxyType = "HTTPS"; endPoint.IsSystemHttpsProxy = true;
break; }
case ProxyProtocolType.AllHttp:
proxyType = "HTTP and HTTPS"; string proxyType = null;
break; switch (protocolType)
} {
case ProxyProtocolType.Http:
if (protocolType != ProxyProtocolType.None) proxyType = "HTTP";
{ break;
Console.WriteLine("Set endpoint at Ip {0} and port: {1} as System {2} Proxy", endPoint.IpAddress, case ProxyProtocolType.Https:
endPoint.Port, proxyType); proxyType = "HTTPS";
} break;
} case ProxyProtocolType.AllHttp:
proxyType = "HTTP and HTTPS";
/// <summary> break;
/// Clear HTTP proxy settings of current machine. }
/// </summary>
public void DisableSystemHttpProxy() if (protocolType != ProxyProtocolType.None)
{ {
DisableSystemProxy(ProxyProtocolType.Http); Console.WriteLine("Set endpoint at Ip {0} and port: {1} as System {2} Proxy", endPoint.IpAddress,
} endPoint.Port, proxyType);
}
/// <summary> }
/// Clear HTTPS proxy settings of current machine.
/// </summary> /// <summary>
public void DisableSystemHttpsProxy() /// Clear HTTP proxy settings of current machine.
{ /// </summary>
DisableSystemProxy(ProxyProtocolType.Https); public void DisableSystemHttpProxy()
} {
DisableSystemProxy(ProxyProtocolType.Http);
/// <summary> }
/// Clear the specified proxy setting for current machine.
/// </summary> /// <summary>
public void DisableSystemProxy(ProxyProtocolType protocolType) /// Clear HTTPS proxy settings of current machine.
{ /// </summary>
if (RunTime.IsRunningOnMono) public void DisableSystemHttpsProxy()
{ {
throw new Exception("Mono Runtime do not support system proxy settings."); DisableSystemProxy(ProxyProtocolType.Https);
} }
systemProxySettingsManager.RemoveProxy(protocolType); /// <summary>
} /// Clear the specified proxy setting for current machine.
/// </summary>
/// <summary> public void DisableSystemProxy(ProxyProtocolType protocolType)
/// Clear all proxy settings for current machine. {
/// </summary> if (RunTime.IsRunningOnMono)
public void DisableAllSystemProxies() {
{ throw new Exception("Mono Runtime do not support system proxy settings.");
if (RunTime.IsRunningOnMono) }
{
throw new Exception("Mono Runtime do not support system proxy settings."); systemProxySettingsManager.RemoveProxy(protocolType);
} }
systemProxySettingsManager.DisableAllProxy(); /// <summary>
} /// Clear all proxy settings for current machine.
/// </summary>
/// <summary> public void DisableAllSystemProxies()
/// Start this proxy server instance. {
/// </summary> if (RunTime.IsRunningOnMono)
public void Start() {
{ throw new Exception("Mono Runtime do not support system proxy settings.");
if (ProxyRunning) }
{
throw new Exception("Proxy is already running."); systemProxySettingsManager.DisableAllProxy();
} }
if (ProxyEndPoints.OfType<ExplicitProxyEndPoint>().Any(x => x.GenericCertificate == null)) /// <summary>
{ /// Start this proxy server instance.
CertificateManager.EnsureRootCertificate(); /// </summary>
} public void Start()
{
// clear any system proxy settings which is pointing to our own endpoint (causing a cycle) if (ProxyRunning)
// due to ungracious proxy shutdown before or something else {
if (systemProxySettingsManager != null && RunTime.IsWindows) throw new Exception("Proxy is already running.");
{ }
var proxyInfo = systemProxySettingsManager.GetProxyInfoFromRegistry();
if (proxyInfo.Proxies != null) if (ProxyEndPoints.OfType<ExplicitProxyEndPoint>().Any(x => x.GenericCertificate == null))
{ {
var protocolToRemove = ProxyProtocolType.None; CertificateManager.EnsureRootCertificate();
foreach (var proxy in proxyInfo.Proxies.Values) }
{
if (NetworkHelper.IsLocalIpAddress(proxy.HostName) // clear any system proxy settings which is pointing to our own endpoint (causing a cycle)
&& ProxyEndPoints.Any(x => x.Port == proxy.Port)) // due to ungracious proxy shutdown before or something else
{ if (systemProxySettingsManager != null && RunTime.IsWindows)
protocolToRemove |= proxy.ProtocolType; {
} var proxyInfo = systemProxySettingsManager.GetProxyInfoFromRegistry();
} if (proxyInfo.Proxies != null)
{
if (protocolToRemove != ProxyProtocolType.None) var protocolToRemove = ProxyProtocolType.None;
{ foreach (var proxy in proxyInfo.Proxies.Values)
systemProxySettingsManager.RemoveProxy(protocolToRemove, false); {
} if (NetworkHelper.IsLocalIpAddress(proxy.HostName)
} && ProxyEndPoints.Any(x => x.Port == proxy.Port))
} {
protocolToRemove |= proxy.ProtocolType;
if (ForwardToUpstreamGateway && GetCustomUpStreamProxyFunc == null && systemProxySettingsManager != null) }
{ }
// Use WinHttp to handle PAC/WAPD scripts.
systemProxyResolver = new WinHttpWebProxyFinder(); if (protocolToRemove != ProxyProtocolType.None)
systemProxyResolver.LoadFromIE(); {
systemProxySettingsManager.RemoveProxy(protocolToRemove, false);
GetCustomUpStreamProxyFunc = getSystemUpStreamProxy; }
} }
}
ProxyRunning = true;
if (ForwardToUpstreamGateway && GetCustomUpStreamProxyFunc == null && systemProxySettingsManager != null)
CertificateManager.ClearIdleCertificates(); {
// Use WinHttp to handle PAC/WAPD scripts.
foreach (var endPoint in ProxyEndPoints) systemProxyResolver = new WinHttpWebProxyFinder();
{ systemProxyResolver.LoadFromIE();
listen(endPoint);
} GetCustomUpStreamProxyFunc = getSystemUpStreamProxy;
} }
/// <summary> ProxyRunning = true;
/// Stop this proxy server instance.
/// </summary> CertificateManager.ClearIdleCertificates();
public void Stop()
{ foreach (var endPoint in ProxyEndPoints)
if (!ProxyRunning) {
{ listen(endPoint);
throw new Exception("Proxy is not running."); }
} }
if (!RunTime.IsRunningOnMono && RunTime.IsWindows) /// <summary>
{ /// Stop this proxy server instance.
bool setAsSystemProxy = ProxyEndPoints.OfType<ExplicitProxyEndPoint>() /// </summary>
.Any(x => x.IsSystemHttpProxy || x.IsSystemHttpsProxy); public void Stop()
{
if (setAsSystemProxy) if (!ProxyRunning)
{ {
systemProxySettingsManager.RestoreOriginalSettings(); throw new Exception("Proxy is not running.");
} }
}
if (!RunTime.IsRunningOnMono && RunTime.IsWindows)
foreach (var endPoint in ProxyEndPoints) {
{ bool setAsSystemProxy = ProxyEndPoints.OfType<ExplicitProxyEndPoint>()
quitListen(endPoint); .Any(x => x.IsSystemHttpProxy || x.IsSystemHttpsProxy);
}
if (setAsSystemProxy)
ProxyEndPoints.Clear(); {
systemProxySettingsManager.RestoreOriginalSettings();
CertificateManager?.StopClearIdleCertificates(); }
tcpConnectionFactory.Dispose(); }
ProxyRunning = false; foreach (var endPoint in ProxyEndPoints)
} {
quitListen(endPoint);
/// <summary> }
/// Listen on given end point of local machine.
/// </summary> ProxyEndPoints.Clear();
/// <param name="endPoint">The end point to listen.</param>
private void listen(ProxyEndPoint endPoint) CertificateManager?.StopClearIdleCertificates();
{ tcpConnectionFactory.Dispose();
endPoint.Listener = new TcpListener(endPoint.IpAddress, endPoint.Port);
ProxyRunning = false;
//linux/macOS has a bug with socket reuse in .net core. }
if (ReuseSocket && (RunTime.IsWindows || RunTime.IsRunningOnMono))
{ /// <summary>
endPoint.Listener.Server.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); /// Listen on given end point of local machine.
} /// </summary>
/// <param name="endPoint">The end point to listen.</param>
try private void listen(ProxyEndPoint endPoint)
{ {
endPoint.Listener.Start(); endPoint.Listener = new TcpListener(endPoint.IpAddress, endPoint.Port);
endPoint.Port = ((IPEndPoint)endPoint.Listener.LocalEndpoint).Port; //linux/macOS has a bug with socket reuse in .net core.
if (ReuseSocket && (RunTime.IsWindows || RunTime.IsRunningOnMono))
// accept clients asynchronously {
endPoint.Listener.BeginAcceptTcpClient(onAcceptConnection, endPoint); endPoint.Listener.Server.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
} }
catch (SocketException ex)
{ try
var pex = new Exception( {
$"Endpoint {endPoint} failed to start. Check inner exception and exception data for details.", ex); endPoint.Listener.Start();
pex.Data.Add("ipAddress", endPoint.IpAddress);
pex.Data.Add("port", endPoint.Port); endPoint.Port = ((IPEndPoint)endPoint.Listener.LocalEndpoint).Port;
throw pex;
} // accept clients asynchronously
} endPoint.Listener.BeginAcceptTcpClient(onAcceptConnection, endPoint);
}
/// <summary> catch (SocketException ex)
/// Verify if its safe to set this end point as system proxy. {
/// </summary> var pex = new Exception(
/// <param name="endPoint">The end point to validate.</param> $"Endpoint {endPoint} failed to start. Check inner exception and exception data for details.", ex);
private void validateEndPointAsSystemProxy(ExplicitProxyEndPoint endPoint) pex.Data.Add("ipAddress", endPoint.IpAddress);
{ pex.Data.Add("port", endPoint.Port);
if (endPoint == null) throw pex;
{ }
throw new ArgumentNullException(nameof(endPoint)); }
}
/// <summary>
if (ProxyEndPoints.Contains(endPoint) == false) /// Verify if its safe to set this end point as system proxy.
{ /// </summary>
throw new Exception("Cannot set endPoints not added to proxy as system proxy"); /// <param name="endPoint">The end point to validate.</param>
} private void validateEndPointAsSystemProxy(ExplicitProxyEndPoint endPoint)
{
if (!ProxyRunning) if (endPoint == null)
{ {
throw new Exception("Cannot set system proxy settings before proxy has been started."); throw new ArgumentNullException(nameof(endPoint));
} }
}
if (ProxyEndPoints.Contains(endPoint) == false)
/// <summary> {
/// Gets the system up stream proxy. throw new Exception("Cannot set endPoints not added to proxy as system proxy");
/// </summary> }
/// <param name="sessionEventArgs">The session.</param>
/// <returns>The external proxy as task result.</returns> if (!ProxyRunning)
private Task<ExternalProxy> getSystemUpStreamProxy(SessionEventArgsBase sessionEventArgs) {
{ throw new Exception("Cannot set system proxy settings before proxy has been started.");
var proxy = systemProxyResolver.GetProxy(sessionEventArgs.WebSession.Request.RequestUri); }
return Task.FromResult(proxy); }
}
/// <summary>
/// <summary> /// Gets the system up stream proxy.
/// Act when a connection is received from client. /// </summary>
/// </summary> /// <param name="sessionEventArgs">The session.</param>
private void onAcceptConnection(IAsyncResult asyn) /// <returns>The external proxy as task result.</returns>
{ private Task<ExternalProxy> getSystemUpStreamProxy(SessionEventArgsBase sessionEventArgs)
var endPoint = (ProxyEndPoint)asyn.AsyncState; {
var proxy = systemProxyResolver.GetProxy(sessionEventArgs.WebSession.Request.RequestUri);
TcpClient tcpClient = null; return Task.FromResult(proxy);
}
try
{ /// <summary>
// based on end point type call appropriate request handlers /// Act when a connection is received from client.
tcpClient = endPoint.Listener.EndAcceptTcpClient(asyn); /// </summary>
} private void onAcceptConnection(IAsyncResult asyn)
catch (ObjectDisposedException) {
{ var endPoint = (ProxyEndPoint)asyn.AsyncState;
// The listener was Stop()'d, disposing the underlying socket and
// triggering the completion of the callback. We're already exiting, TcpClient tcpClient = null;
// so just return.
return; try
} {
catch // based on end point type call appropriate request handlers
{ tcpClient = endPoint.Listener.EndAcceptTcpClient(asyn);
// Other errors are discarded to keep proxy running }
} catch (ObjectDisposedException)
{
if (tcpClient != null) // The listener was Stop()'d, disposing the underlying socket and
{ // triggering the completion of the callback. We're already exiting,
Task.Run(async () => { await handleClient(tcpClient, endPoint); }); // so just return.
} return;
}
// Get the listener that handles the client request. catch
endPoint.Listener.BeginAcceptTcpClient(onAcceptConnection, endPoint); {
} // Other errors are discarded to keep proxy running
}
/// <summary>
/// Handle the client. if (tcpClient != null)
/// </summary> {
/// <param name="tcpClient">The client.</param> Task.Run(async () => { await handleClient(tcpClient, endPoint); });
/// <param name="endPoint">The proxy endpoint.</param> }
/// <returns>The task.</returns>
private async Task handleClient(TcpClient tcpClient, ProxyEndPoint endPoint) // Get the listener that handles the client request.
{ endPoint.Listener.BeginAcceptTcpClient(onAcceptConnection, endPoint);
tcpClient.ReceiveTimeout = ConnectionTimeOutSeconds * 1000; }
tcpClient.SendTimeout = ConnectionTimeOutSeconds * 1000;
tcpClient.SendBufferSize = BufferSize; /// <summary>
tcpClient.ReceiveBufferSize = BufferSize; /// Handle the client.
tcpClient.LingerState = new LingerOption(true, TcpTimeWaitSeconds); /// </summary>
/// <param name="tcpClient">The client.</param>
await InvokeConnectionCreateEvent(tcpClient, true); /// <param name="endPoint">The proxy endpoint.</param>
/// <returns>The task.</returns>
using (var clientConnection = new TcpClientConnection(this, tcpClient)) private async Task handleClient(TcpClient tcpClient, ProxyEndPoint endPoint)
{ {
if (endPoint is TransparentProxyEndPoint tep) tcpClient.ReceiveTimeout = ConnectionTimeOutSeconds * 1000;
{ tcpClient.SendTimeout = ConnectionTimeOutSeconds * 1000;
await handleClient(tep, clientConnection); tcpClient.SendBufferSize = BufferSize;
} tcpClient.ReceiveBufferSize = BufferSize;
else tcpClient.LingerState = new LingerOption(true, TcpTimeWaitSeconds);
{
await handleClient((ExplicitProxyEndPoint)endPoint, clientConnection); await InvokeConnectionCreateEvent(tcpClient, true);
}
} using (var clientConnection = new TcpClientConnection(this, tcpClient))
} {
if (endPoint is TransparentProxyEndPoint tep)
/// <summary> {
/// Handle exception. await handleClient(tep, clientConnection);
/// </summary> }
/// <param name="clientStream">The client stream.</param> else
/// <param name="exception">The exception.</param> {
private void onException(CustomBufferedStream clientStream, Exception exception) await handleClient((ExplicitProxyEndPoint)endPoint, clientConnection);
{ }
#if DEBUG }
if (clientStream is DebugCustomBufferedStream debugStream) }
{
debugStream.LogException(exception); /// <summary>
} /// Handle exception.
#endif /// </summary>
/// <param name="clientStream">The client stream.</param>
ExceptionFunc(exception); /// <param name="exception">The exception.</param>
} private void onException(CustomBufferedStream clientStream, Exception exception)
{
/// <summary> #if DEBUG
/// Quit listening on the given end point. if (clientStream is DebugCustomBufferedStream debugStream)
/// </summary> {
private void quitListen(ProxyEndPoint endPoint) debugStream.LogException(exception);
{ }
endPoint.Listener.Stop(); #endif
endPoint.Listener.Server.Dispose();
} ExceptionFunc(exception);
}
/// <summary>
/// Update client connection count. /// <summary>
/// </summary> /// Quit listening on the given end point.
/// <param name="increment">Should we increment/decrement?</param> /// </summary>
internal void UpdateClientConnectionCount(bool increment) private void quitListen(ProxyEndPoint endPoint)
{ {
if (increment) endPoint.Listener.Stop();
{ endPoint.Listener.Server.Dispose();
Interlocked.Increment(ref clientConnectionCount); }
}
else /// <summary>
{ /// Update client connection count.
Interlocked.Decrement(ref clientConnectionCount); /// </summary>
} /// <param name="increment">Should we increment/decrement?</param>
internal void UpdateClientConnectionCount(bool increment)
ClientConnectionCountChanged?.Invoke(this, EventArgs.Empty); {
} if (increment)
{
/// <summary> Interlocked.Increment(ref clientConnectionCount);
/// Update server connection count. }
/// </summary> else
/// <param name="increment">Should we increment/decrement?</param> {
internal void UpdateServerConnectionCount(bool increment) Interlocked.Decrement(ref clientConnectionCount);
{ }
if (increment)
{ ClientConnectionCountChanged?.Invoke(this, EventArgs.Empty);
Interlocked.Increment(ref serverConnectionCount); }
}
else /// <summary>
{ /// Update server connection count.
Interlocked.Decrement(ref serverConnectionCount); /// </summary>
} /// <param name="increment">Should we increment/decrement?</param>
internal void UpdateServerConnectionCount(bool increment)
ServerConnectionCountChanged?.Invoke(this, EventArgs.Empty); {
} if (increment)
{
/// <summary> Interlocked.Increment(ref serverConnectionCount);
/// Invoke client/server tcp connection events if subscribed by API user. }
/// </summary> else
/// <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> Interlocked.Decrement(ref serverConnectionCount);
/// <returns></returns> }
internal async Task InvokeConnectionCreateEvent(TcpClient client, bool isClientConnection)
{ ServerConnectionCountChanged?.Invoke(this, EventArgs.Empty);
//client connection created }
if (isClientConnection && OnClientConnectionCreate != null)
{ /// <summary>
await OnClientConnectionCreate.InvokeAsync(this, client, ExceptionFunc); /// Invoke client/server tcp connection events if subscribed by API user.
} /// </summary>
/// <param name="client">The TcpClient object.</param>
//server connection created /// <param name="isClientConnection">Is this a client connection created event? If not then we would assume that its a server connection create event.</param>
if (!isClientConnection && OnServerConnectionCreate != null) /// <returns></returns>
{ internal async Task InvokeConnectionCreateEvent(TcpClient client, bool isClientConnection)
await OnServerConnectionCreate.InvokeAsync(this, client, ExceptionFunc); {
} //client connection created
} if (isClientConnection && OnClientConnectionCreate != null)
{
/// <summary> await OnClientConnectionCreate.InvokeAsync(this, client, ExceptionFunc);
/// Connection retry policy when using connection pool. }
/// </summary>
private RetryPolicy<T> retryPolicy<T>() where T : Exception //server connection created
{ if (!isClientConnection && OnServerConnectionCreate != null)
return new RetryPolicy<T>(retries, tcpConnectionFactory); {
} await OnServerConnectionCreate.InvokeAsync(this, client, ExceptionFunc);
}
/// <summary> }
/// Dispose the Proxy instance.
/// </summary> /// <summary>
public void Dispose() /// Connection retry policy when using connection pool.
{ /// </summary>
if (ProxyRunning) private RetryPolicy<T> retryPolicy<T>() where T : Exception
{ {
Stop(); return new RetryPolicy<T>(retries, tcpConnectionFactory);
} }
CertificateManager?.Dispose(); /// <summary>
BufferPool?.Dispose(); /// Dispose the Proxy instance.
} /// </summary>
} public void Dispose()
} {
if (ProxyRunning)
{
Stop();
}
CertificateManager?.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">
<PropertyGroup>
<TargetFrameworks>net45</TargetFrameworks>
<RootNamespace>Titanium.Web.Proxy</RootNamespace>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<SignAssembly>True</SignAssembly>
<AssemblyOriginatorKeyFile>StrongNameKey.snk</AssemblyOriginatorKeyFile>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
<LangVersion>7.1</LangVersion>
<Platforms>AnyCPU;x64</Platforms>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Portable.BouncyCastle" Version="1.8.2" />
<PackageReference Include="StreamExtended" Version="1.0.179" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net45'">
<Reference Include="System.Web" />
</ItemGroup>
<ItemGroup>
<Compile Update="Network\WinAuth\Security\Common.cs">
<ExcludeFromSourceAnalysis>True</ExcludeFromSourceAnalysis>
<ExcludeFromStyleCop>True</ExcludeFromStyleCop>
</Compile>
<Compile Update="Network\WinAuth\Security\LittleEndian.cs">
<ExcludeFromSourceAnalysis>True</ExcludeFromSourceAnalysis>
<ExcludeFromStyleCop>True</ExcludeFromStyleCop>
</Compile>
<Compile Update="Network\WinAuth\Security\Message.cs">
<ExcludeFromSourceAnalysis>True</ExcludeFromSourceAnalysis>
<ExcludeFromStyleCop>True</ExcludeFromStyleCop>
</Compile>
<Compile Update="Network\WinAuth\Security\State.cs">
<ExcludeFromSourceAnalysis>True</ExcludeFromSourceAnalysis>
<ExcludeFromStyleCop>True</ExcludeFromStyleCop>
</Compile>
<Compile Update="Properties\AssemblyInfo.cs">
<ExcludeFromSourceAnalysis>True</ExcludeFromSourceAnalysis>
<ExcludeFromStyleCop>True</ExcludeFromStyleCop>
</Compile>
</ItemGroup>
</Project>
\ No newline at end of file
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0</TargetFrameworks>
<RootNamespace>Titanium.Web.Proxy</RootNamespace>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<SignAssembly>True</SignAssembly>
<AssemblyOriginatorKeyFile>StrongNameKey.snk</AssemblyOriginatorKeyFile>
<DelaySign>False</DelaySign>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
<LangVersion>7.1</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Portable.BouncyCastle" Version="1.8.2" />
<PackageReference Include="StreamExtended" Version="1.0.179" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
<PackageReference Include="Microsoft.Win32.Registry">
<Version>4.4.0</Version>
</PackageReference>
<PackageReference Include="System.Security.Principal.Windows">
<Version>4.4.1</Version>
</PackageReference>
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp2.1'">
<PackageReference Include="Microsoft.Win32.Registry">
<Version>4.4.0</Version>
</PackageReference>
<PackageReference Include="System.Security.Principal.Windows">
<Version>4.4.1</Version>
</PackageReference>
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net45'">
<Reference Include="System.Web" />
</ItemGroup>
<ItemGroup>
<Compile Update="Network\WinAuth\Security\Common.cs">
<ExcludeFromSourceAnalysis>True</ExcludeFromSourceAnalysis>
<ExcludeFromStyleCop>True</ExcludeFromStyleCop>
</Compile>
<Compile Update="Network\WinAuth\Security\LittleEndian.cs">
<ExcludeFromSourceAnalysis>True</ExcludeFromSourceAnalysis>
<ExcludeFromStyleCop>True</ExcludeFromStyleCop>
</Compile>
<Compile Update="Network\WinAuth\Security\Message.cs">
<ExcludeFromSourceAnalysis>True</ExcludeFromSourceAnalysis>
<ExcludeFromStyleCop>True</ExcludeFromStyleCop>
</Compile>
<Compile Update="Network\WinAuth\Security\State.cs">
<ExcludeFromSourceAnalysis>True</ExcludeFromSourceAnalysis>
<ExcludeFromStyleCop>True</ExcludeFromStyleCop>
</Compile>
<Compile Update="Properties\AssemblyInfo.cs">
<ExcludeFromSourceAnalysis>True</ExcludeFromSourceAnalysis>
<ExcludeFromStyleCop>True</ExcludeFromStyleCop>
</Compile>
</ItemGroup>
</Project>
\ No newline at end of file
<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> <Platforms>AnyCPU;x64</Platforms>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Portable.BouncyCastle" Version="1.8.2" /> <ItemGroup>
<PackageReference Include="StreamExtended" Version="1.0.179" /> <PackageReference Include="Portable.BouncyCastle" Version="1.8.2" />
</ItemGroup> <PackageReference Include="StreamExtended" Version="1.0.179" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
<PackageReference Include="Microsoft.Win32.Registry"> <ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
<Version>4.4.0</Version> <PackageReference Include="Microsoft.Win32.Registry">
</PackageReference> <Version>4.4.0</Version>
<PackageReference Include="System.Security.Principal.Windows"> </PackageReference>
<Version>4.4.1</Version> <PackageReference Include="System.Security.Principal.Windows">
</PackageReference> <Version>4.4.1</Version>
</ItemGroup> </PackageReference>
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp2.1'">
<PackageReference Include="Microsoft.Win32.Registry"> <ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp2.1'">
<Version>4.4.0</Version> <PackageReference Include="Microsoft.Win32.Registry">
</PackageReference> <Version>4.4.0</Version>
<PackageReference Include="System.Security.Principal.Windows"> </PackageReference>
<Version>4.4.1</Version> <PackageReference Include="System.Security.Principal.Windows">
</PackageReference> <Version>4.4.1</Version>
</ItemGroup> </PackageReference>
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net45'">
<Reference Include="System.Web" /> <ItemGroup Condition="'$(TargetFramework)' == 'net45'">
</ItemGroup> <Reference Include="System.Web" />
</ItemGroup>
<ItemGroup>
<Compile Update="Network\WinAuth\Security\Common.cs"> <ItemGroup>
<ExcludeFromSourceAnalysis>True</ExcludeFromSourceAnalysis> <Compile Update="Network\WinAuth\Security\Common.cs">
<ExcludeFromStyleCop>True</ExcludeFromStyleCop> <ExcludeFromSourceAnalysis>True</ExcludeFromSourceAnalysis>
</Compile> <ExcludeFromStyleCop>True</ExcludeFromStyleCop>
<Compile Update="Network\WinAuth\Security\LittleEndian.cs"> </Compile>
<ExcludeFromSourceAnalysis>True</ExcludeFromSourceAnalysis> <Compile Update="Network\WinAuth\Security\LittleEndian.cs">
<ExcludeFromStyleCop>True</ExcludeFromStyleCop> <ExcludeFromSourceAnalysis>True</ExcludeFromSourceAnalysis>
</Compile> <ExcludeFromStyleCop>True</ExcludeFromStyleCop>
<Compile Update="Network\WinAuth\Security\Message.cs"> </Compile>
<ExcludeFromSourceAnalysis>True</ExcludeFromSourceAnalysis> <Compile Update="Network\WinAuth\Security\Message.cs">
<ExcludeFromStyleCop>True</ExcludeFromStyleCop> <ExcludeFromSourceAnalysis>True</ExcludeFromSourceAnalysis>
</Compile> <ExcludeFromStyleCop>True</ExcludeFromStyleCop>
<Compile Update="Network\WinAuth\Security\State.cs"> </Compile>
<ExcludeFromSourceAnalysis>True</ExcludeFromSourceAnalysis> <Compile Update="Network\WinAuth\Security\State.cs">
<ExcludeFromStyleCop>True</ExcludeFromStyleCop> <ExcludeFromSourceAnalysis>True</ExcludeFromSourceAnalysis>
</Compile> <ExcludeFromStyleCop>True</ExcludeFromStyleCop>
<Compile Update="Properties\AssemblyInfo.cs"> </Compile>
<ExcludeFromSourceAnalysis>True</ExcludeFromSourceAnalysis> <Compile Update="Properties\AssemblyInfo.cs">
<ExcludeFromStyleCop>True</ExcludeFromStyleCop> <ExcludeFromSourceAnalysis>True</ExcludeFromSourceAnalysis>
</Compile> <ExcludeFromStyleCop>True</ExcludeFromStyleCop>
</ItemGroup> </Compile>
</ItemGroup>
</Project> </Project>
\ No newline at end of file
...@@ -63,13 +63,16 @@ namespace Titanium.Web.Proxy ...@@ -63,13 +63,16 @@ namespace Titanium.Web.Proxy
if (endPoint.DecryptSsl && args.DecryptSsl) if (endPoint.DecryptSsl && args.DecryptSsl)
{ {
//don't pass cancellation token here if(EnableTcpServerConnectionPrefetch)
//it could cause floating server connections when client exits {
prefetchConnectionTask = tcpConnectionFactory.GetServerConnection(httpsHostName, endPoint.Port, //don't pass cancellation token here
httpVersion: null, isHttps: true, applicationProtocols: null, isConnect: false, //it could cause floating server connections when client exits
proxyServer: this, upStreamEndPoint: UpStreamEndPoint, externalProxy: UpStreamHttpsProxy, prefetchConnectionTask = tcpConnectionFactory.GetServerConnection(httpsHostName, endPoint.Port,
noCache: false, cancellationToken: CancellationToken.None); httpVersion: null, isHttps: true, applicationProtocols: null, isConnect: false,
proxyServer: this, upStreamEndPoint: UpStreamEndPoint, externalProxy: UpStreamHttpsProxy,
noCache: false, cancellationToken: CancellationToken.None);
}
SslStream sslStream = null; SslStream sslStream = null;
//do client authentication using fake certificate //do client authentication using fake certificate
......
...@@ -35,6 +35,24 @@ ...@@ -35,6 +35,24 @@
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\x64\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<DebugType>full</DebugType>
<PlatformTarget>x64</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
<OutputPath>bin\x64\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<Optimize>true</Optimize>
<DebugType>pdbonly</DebugType>
<PlatformTarget>x64</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="System.Net.Http" /> <Reference Include="System.Net.Http" />
...@@ -56,8 +74,8 @@ ...@@ -56,8 +74,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>
......
...@@ -42,6 +42,25 @@ ...@@ -42,6 +42,25 @@
<PropertyGroup> <PropertyGroup>
<AssemblyOriginatorKeyFile>StrongNameKey.snk</AssemblyOriginatorKeyFile> <AssemblyOriginatorKeyFile>StrongNameKey.snk</AssemblyOriginatorKeyFile>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\x64\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<DebugType>full</DebugType>
<PlatformTarget>x64</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
<OutputPath>bin\x64\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Optimize>true</Optimize>
<DebugType>pdbonly</DebugType>
<PlatformTarget>x64</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="System" /> <Reference Include="System" />
</ItemGroup> </ItemGroup>
...@@ -65,13 +84,13 @@ ...@@ -65,13 +84,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