Unverified Commit 09ae3f10 authored by Jehonathan Thomas's avatar Jehonathan Thomas Committed by GitHub

Merge pull request #493 from justcoding121/beta

Stable
parents 042a3a06 c3fc1d35
......@@ -2,7 +2,8 @@ $PSake.use_exit_on_error = $true
$Here = "$(Split-Path -parent $MyInvocation.MyCommand.Definition)"
$SolutionRoot = (Split-Path -parent $Here)
$RepoRoot = $(Split-Path -parent $Here)
$SolutionRoot = "$RepoRoot\src"
$ProjectName = "Titanium.Web.Proxy"
$GitHubProjectName = "Titanium-Web-Proxy"
......@@ -26,7 +27,7 @@ if(!$Branch) { $Branch = "local" }
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 -replace ' ', '` '
......@@ -57,7 +58,7 @@ Task Restore-Packages -depends Install-BuildTools {
#build
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
......@@ -65,12 +66,13 @@ Task Document -depends Build {
if($Branch -eq "master")
{
#use docfx to generate API documentation from source metadata
docfx docfx.json
#patch index.json so that it is always sorted
#otherwise git will think file was changed
$IndexJsonFile = "$SolutionRoot\docs\index.json"
$IndexJsonFile = "$RepoRoot\docs\index.json"
$unsorted = Get-Content $IndexJsonFile | Out-String
[Reflection.Assembly]::LoadFile("$Here\lib\Newtonsoft.Json.dll")
[System.Reflection.Assembly]::LoadWithPartialName("System")
......@@ -79,7 +81,7 @@ Task Document -depends Build {
Set-Content -Path $IndexJsonFile -Value $obj
#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)
{
......@@ -101,7 +103,7 @@ Task Document -depends Build {
cd "$TEMP_REPO_DIR\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
git config --global credential.helper store
......@@ -119,5 +121,5 @@ Task Document -depends Build {
#package nuget files
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 @@
"src": [
{
"files": [ "Titanium.Web.Proxy.Docs.sln"],
"src": "../"
"src": "../src/"
}
],
"dest": "obj/api"
......
......@@ -6,7 +6,7 @@ param (
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..."
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
* [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)
**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
* 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
* Supports mutual SSL authentication, proxy authentication & automatic upstream proxy detection
* Kerberos/NTLM authentication over HTTP protocols for windows domain
### Usage
Refer the HTTP Proxy Server library in your project, look up Test project to learn usage.
### Installation
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)
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)
......@@ -44,6 +32,24 @@ Supports
* .Net Standard 2.0 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:
```csharp
......@@ -229,3 +235,12 @@ public Task OnCertificateSelection(object sender, CertificateSelectionEventArgs
#### Collaborators
* [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
skip_commits:
author: buildbot121
files:
- docs/*
- .vscode/*
- README.md
- LICENSE
#---------------------------------#
# artifacts configuration #
#---------------------------------#
......
......@@ -10,7 +10,7 @@
<meta name="viewport" content="width=device-width">
<meta name="title" content="Delegate AsyncEventHandler&lt;TEventArgs&gt;
| Titanium Web Proxy ">
<meta name="generator" content="docfx 2.37.2.0">
<meta name="generator" content="docfx 2.39.2.0">
<link rel="shortcut icon" href="../favicon.ico">
<link rel="stylesheet" href="../styles/docfx.vendor.css">
......
......@@ -10,7 +10,7 @@
<meta name="viewport" content="width=device-width">
<meta name="title" content="Class BeforeSslAuthenticateEventArgs
| Titanium Web Proxy ">
<meta name="generator" content="docfx 2.37.2.0">
<meta name="generator" content="docfx 2.39.2.0">
<link rel="shortcut icon" href="../favicon.ico">
<link rel="stylesheet" href="../styles/docfx.vendor.css">
......
......@@ -10,7 +10,7 @@
<meta name="viewport" content="width=device-width">
<meta name="title" content="Class CertificateSelectionEventArgs
| Titanium Web Proxy ">
<meta name="generator" content="docfx 2.37.2.0">
<meta name="generator" content="docfx 2.39.2.0">
<link rel="shortcut icon" href="../favicon.ico">
<link rel="stylesheet" href="../styles/docfx.vendor.css">
......
......@@ -10,7 +10,7 @@
<meta name="viewport" content="width=device-width">
<meta name="title" content="Class CertificateValidationEventArgs
| Titanium Web Proxy ">
<meta name="generator" content="docfx 2.37.2.0">
<meta name="generator" content="docfx 2.39.2.0">
<link rel="shortcut icon" href="../favicon.ico">
<link rel="stylesheet" href="../styles/docfx.vendor.css">
......
......@@ -10,7 +10,7 @@
<meta name="viewport" content="width=device-width">
<meta name="title" content="Class MultipartRequestPartSentEventArgs
| Titanium Web Proxy ">
<meta name="generator" content="docfx 2.37.2.0">
<meta name="generator" content="docfx 2.39.2.0">
<link rel="shortcut icon" href="../favicon.ico">
<link rel="stylesheet" href="../styles/docfx.vendor.css">
......
......@@ -10,7 +10,7 @@
<meta name="viewport" content="width=device-width">
<meta name="title" content="Class SessionEventArgs
| Titanium Web Proxy ">
<meta name="generator" content="docfx 2.37.2.0">
<meta name="generator" content="docfx 2.39.2.0">
<link rel="shortcut icon" href="../favicon.ico">
<link rel="stylesheet" href="../styles/docfx.vendor.css">
......
......@@ -10,7 +10,7 @@
<meta name="viewport" content="width=device-width">
<meta name="title" content="Class SessionEventArgsBase
| Titanium Web Proxy ">
<meta name="generator" content="docfx 2.37.2.0">
<meta name="generator" content="docfx 2.39.2.0">
<link rel="shortcut icon" href="../favicon.ico">
<link rel="stylesheet" href="../styles/docfx.vendor.css">
......
......@@ -10,7 +10,7 @@
<meta name="viewport" content="width=device-width">
<meta name="title" content="Class TunnelConnectSessionEventArgs
| Titanium Web Proxy ">
<meta name="generator" content="docfx 2.37.2.0">
<meta name="generator" content="docfx 2.39.2.0">
<link rel="shortcut icon" href="../favicon.ico">
<link rel="stylesheet" href="../styles/docfx.vendor.css">
......
......@@ -10,7 +10,7 @@
<meta name="viewport" content="width=device-width">
<meta name="title" content="Namespace Titanium.Web.Proxy.EventArguments
| Titanium Web Proxy ">
<meta name="generator" content="docfx 2.37.2.0">
<meta name="generator" content="docfx 2.39.2.0">
<link rel="shortcut icon" href="../favicon.ico">
<link rel="stylesheet" href="../styles/docfx.vendor.css">
......
......@@ -10,7 +10,7 @@
<meta name="viewport" content="width=device-width">
<meta name="title" content="Delegate ExceptionHandler
| Titanium Web Proxy ">
<meta name="generator" content="docfx 2.37.2.0">
<meta name="generator" content="docfx 2.39.2.0">
<link rel="shortcut icon" href="../favicon.ico">
<link rel="stylesheet" href="../styles/docfx.vendor.css">
......
......@@ -10,7 +10,7 @@
<meta name="viewport" content="width=device-width">
<meta name="title" content="Class BodyNotFoundException
| Titanium Web Proxy ">
<meta name="generator" content="docfx 2.37.2.0">
<meta name="generator" content="docfx 2.39.2.0">
<link rel="shortcut icon" href="../favicon.ico">
<link rel="stylesheet" href="../styles/docfx.vendor.css">
......
......@@ -10,7 +10,7 @@
<meta name="viewport" content="width=device-width">
<meta name="title" content="Class ProxyAuthorizationException
| Titanium Web Proxy ">
<meta name="generator" content="docfx 2.37.2.0">
<meta name="generator" content="docfx 2.39.2.0">
<link rel="shortcut icon" href="../favicon.ico">
<link rel="stylesheet" href="../styles/docfx.vendor.css">
......
......@@ -10,7 +10,7 @@
<meta name="viewport" content="width=device-width">
<meta name="title" content="Class ProxyException
| Titanium Web Proxy ">
<meta name="generator" content="docfx 2.37.2.0">
<meta name="generator" content="docfx 2.39.2.0">
<link rel="shortcut icon" href="../favicon.ico">
<link rel="stylesheet" href="../styles/docfx.vendor.css">
......
......@@ -10,7 +10,7 @@
<meta name="viewport" content="width=device-width">
<meta name="title" content="Class ProxyHttpException
| Titanium Web Proxy ">
<meta name="generator" content="docfx 2.37.2.0">
<meta name="generator" content="docfx 2.39.2.0">
<link rel="shortcut icon" href="../favicon.ico">
<link rel="stylesheet" href="../styles/docfx.vendor.css">
......
......@@ -10,7 +10,7 @@
<meta name="viewport" content="width=device-width">
<meta name="title" content="Namespace Titanium.Web.Proxy.Exceptions
| Titanium Web Proxy ">
<meta name="generator" content="docfx 2.37.2.0">
<meta name="generator" content="docfx 2.39.2.0">
<link rel="shortcut icon" href="../favicon.ico">
<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 @@
<meta name="viewport" content="width=device-width">
<meta name="title" content="Namespace Titanium.Web.Proxy.Helpers
| 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="stylesheet" href="../styles/docfx.vendor.css">
......@@ -85,10 +85,11 @@
<div class="markdown level0 summary"></div>
<div class="markdown level0 conceptual"></div>
<div class="markdown level0 remarks"></div>
<h3 id="enums">Enums
<h3 id="classes">Classes
</h3>
<h4><a class="xref" href="Titanium.Web.Proxy.Helpers.ProxyProtocolType.html">ProxyProtocolType</a></h4>
<section></section>
<h4><a class="xref" href="Titanium.Web.Proxy.Helpers.RunTime.html">RunTime</a></h4>
<section><p>Run time helpers</p>
</section>
</article>
</div>
......
......@@ -10,7 +10,7 @@
<meta name="viewport" content="width=device-width">
<meta name="title" content="Class ConnectRequest
| Titanium Web Proxy ">
<meta name="generator" content="docfx 2.37.2.0">
<meta name="generator" content="docfx 2.39.2.0">
<link rel="shortcut icon" href="../favicon.ico">
<link rel="stylesheet" href="../styles/docfx.vendor.css">
......
......@@ -10,7 +10,7 @@
<meta name="viewport" content="width=device-width">
<meta name="title" content="Class ConnectResponse
| Titanium Web Proxy ">
<meta name="generator" content="docfx 2.37.2.0">
<meta name="generator" content="docfx 2.39.2.0">
<link rel="shortcut icon" href="../favicon.ico">
<link rel="stylesheet" href="../styles/docfx.vendor.css">
......
......@@ -10,7 +10,7 @@
<meta name="viewport" content="width=device-width">
<meta name="title" content="Class HeaderCollection
| Titanium Web Proxy ">
<meta name="generator" content="docfx 2.37.2.0">
<meta name="generator" content="docfx 2.39.2.0">
<link rel="shortcut icon" href="../favicon.ico">
<link rel="stylesheet" href="../styles/docfx.vendor.css">
......
......@@ -10,7 +10,7 @@
<meta name="viewport" content="width=device-width">
<meta name="title" content="Class HttpWebClient
| Titanium Web Proxy ">
<meta name="generator" content="docfx 2.37.2.0">
<meta name="generator" content="docfx 2.39.2.0">
<link rel="shortcut icon" href="../favicon.ico">
<link rel="stylesheet" href="../styles/docfx.vendor.css">
......
......@@ -10,7 +10,7 @@
<meta name="viewport" content="width=device-width">
<meta name="title" content="Class KnownHeaders
| Titanium Web Proxy ">
<meta name="generator" content="docfx 2.37.2.0">
<meta name="generator" content="docfx 2.39.2.0">
<link rel="shortcut icon" href="../favicon.ico">
<link rel="stylesheet" href="../styles/docfx.vendor.css">
......
......@@ -10,7 +10,7 @@
<meta name="viewport" content="width=device-width">
<meta name="title" content="Class Request
| Titanium Web Proxy ">
<meta name="generator" content="docfx 2.37.2.0">
<meta name="generator" content="docfx 2.39.2.0">
<link rel="shortcut icon" href="../favicon.ico">
<link rel="stylesheet" href="../styles/docfx.vendor.css">
......
......@@ -10,7 +10,7 @@
<meta name="viewport" content="width=device-width">
<meta name="title" content="Class RequestResponseBase
| Titanium Web Proxy ">
<meta name="generator" content="docfx 2.37.2.0">
<meta name="generator" content="docfx 2.39.2.0">
<link rel="shortcut icon" href="../favicon.ico">
<link rel="stylesheet" href="../styles/docfx.vendor.css">
......
......@@ -10,7 +10,7 @@
<meta name="viewport" content="width=device-width">
<meta name="title" content="Class Response
| Titanium Web Proxy ">
<meta name="generator" content="docfx 2.37.2.0">
<meta name="generator" content="docfx 2.39.2.0">
<link rel="shortcut icon" href="../favicon.ico">
<link rel="stylesheet" href="../styles/docfx.vendor.css">
......
......@@ -10,7 +10,7 @@
<meta name="viewport" content="width=device-width">
<meta name="title" content="Class GenericResponse
| Titanium Web Proxy ">
<meta name="generator" content="docfx 2.37.2.0">
<meta name="generator" content="docfx 2.39.2.0">
<link rel="shortcut icon" href="../favicon.ico">
<link rel="stylesheet" href="../styles/docfx.vendor.css">
......
......@@ -10,7 +10,7 @@
<meta name="viewport" content="width=device-width">
<meta name="title" content="Class OkResponse
| Titanium Web Proxy ">
<meta name="generator" content="docfx 2.37.2.0">
<meta name="generator" content="docfx 2.39.2.0">
<link rel="shortcut icon" href="../favicon.ico">
<link rel="stylesheet" href="../styles/docfx.vendor.css">
......
......@@ -10,7 +10,7 @@
<meta name="viewport" content="width=device-width">
<meta name="title" content="Class RedirectResponse
| Titanium Web Proxy ">
<meta name="generator" content="docfx 2.37.2.0">
<meta name="generator" content="docfx 2.39.2.0">
<link rel="shortcut icon" href="../favicon.ico">
<link rel="stylesheet" href="../styles/docfx.vendor.css">
......
......@@ -10,7 +10,7 @@
<meta name="viewport" content="width=device-width">
<meta name="title" content="Namespace Titanium.Web.Proxy.Http.Responses
| Titanium Web Proxy ">
<meta name="generator" content="docfx 2.37.2.0">
<meta name="generator" content="docfx 2.39.2.0">
<link rel="shortcut icon" href="../favicon.ico">
<link rel="stylesheet" href="../styles/docfx.vendor.css">
......
......@@ -10,7 +10,7 @@
<meta name="viewport" content="width=device-width">
<meta name="title" content="Namespace Titanium.Web.Proxy.Http
| Titanium Web Proxy ">
<meta name="generator" content="docfx 2.37.2.0">
<meta name="generator" content="docfx 2.39.2.0">
<link rel="shortcut icon" href="../favicon.ico">
<link rel="stylesheet" href="../styles/docfx.vendor.css">
......
......@@ -10,7 +10,7 @@
<meta name="viewport" content="width=device-width">
<meta name="title" content="Class ExplicitProxyEndPoint
| Titanium Web Proxy ">
<meta name="generator" content="docfx 2.37.2.0">
<meta name="generator" content="docfx 2.39.2.0">
<link rel="shortcut icon" href="../favicon.ico">
<link rel="stylesheet" href="../styles/docfx.vendor.css">
......
......@@ -10,7 +10,7 @@
<meta name="viewport" content="width=device-width">
<meta name="title" content="Class ExternalProxy
| Titanium Web Proxy ">
<meta name="generator" content="docfx 2.37.2.0">
<meta name="generator" content="docfx 2.39.2.0">
<link rel="shortcut icon" href="../favicon.ico">
<link rel="stylesheet" href="../styles/docfx.vendor.css">
......
......@@ -10,7 +10,7 @@
<meta name="viewport" content="width=device-width">
<meta name="title" content="Class HttpHeader
| Titanium Web Proxy ">
<meta name="generator" content="docfx 2.37.2.0">
<meta name="generator" content="docfx 2.39.2.0">
<link rel="shortcut icon" href="../favicon.ico">
<link rel="stylesheet" href="../styles/docfx.vendor.css">
......
......@@ -10,7 +10,7 @@
<meta name="viewport" content="width=device-width">
<meta name="title" content="Class ProxyEndPoint
| Titanium Web Proxy ">
<meta name="generator" content="docfx 2.37.2.0">
<meta name="generator" content="docfx 2.39.2.0">
<link rel="shortcut icon" href="../favicon.ico">
<link rel="stylesheet" href="../styles/docfx.vendor.css">
......
......@@ -10,7 +10,7 @@
<meta name="viewport" content="width=device-width">
<meta name="title" content="Class TransparentProxyEndPoint
| Titanium Web Proxy ">
<meta name="generator" content="docfx 2.37.2.0">
<meta name="generator" content="docfx 2.39.2.0">
<link rel="shortcut icon" href="../favicon.ico">
<link rel="stylesheet" href="../styles/docfx.vendor.css">
......
......@@ -10,7 +10,7 @@
<meta name="viewport" content="width=device-width">
<meta name="title" content="Namespace Titanium.Web.Proxy.Models
| Titanium Web Proxy ">
<meta name="generator" content="docfx 2.37.2.0">
<meta name="generator" content="docfx 2.39.2.0">
<link rel="shortcut icon" href="../favicon.ico">
<link rel="stylesheet" href="../styles/docfx.vendor.css">
......
......@@ -10,7 +10,7 @@
<meta name="viewport" content="width=device-width">
<meta name="title" content="Enum CertificateEngine
| Titanium Web Proxy ">
<meta name="generator" content="docfx 2.37.2.0">
<meta name="generator" content="docfx 2.39.2.0">
<link rel="shortcut icon" href="../favicon.ico">
<link rel="stylesheet" href="../styles/docfx.vendor.css">
......@@ -110,7 +110,8 @@ Default.</p>
</tr>
<tr>
<td id="Titanium_Web_Proxy_Network_CertificateEngine_DefaultWindows">DefaultWindows</td>
<td><p>Uses Windows Certification Generation API.
<td><p>Uses Windows Certification Generation API and only valid in Windows OS.
Observed to be faster than BouncyCastle.
Bug #468 Reported.</p>
</td>
</tr>
......
......@@ -10,7 +10,7 @@
<meta name="viewport" content="width=device-width">
<meta name="title" content="Class CertificateManager
| Titanium Web Proxy ">
<meta name="generator" content="docfx 2.37.2.0">
<meta name="generator" content="docfx 2.39.2.0">
<link rel="shortcut icon" href="../favicon.ico">
<link rel="stylesheet" href="../styles/docfx.vendor.css">
......
......@@ -10,7 +10,7 @@
<meta name="viewport" content="width=device-width">
<meta name="title" content="Namespace Titanium.Web.Proxy.Network
| Titanium Web Proxy ">
<meta name="generator" content="docfx 2.37.2.0">
<meta name="generator" content="docfx 2.39.2.0">
<link rel="shortcut icon" href="../favicon.ico">
<link rel="stylesheet" href="../styles/docfx.vendor.css">
......
......@@ -10,7 +10,7 @@
<meta name="viewport" content="width=device-width">
<meta name="title" content="Class ProxyServer
| Titanium Web Proxy ">
<meta name="generator" content="docfx 2.37.2.0">
<meta name="generator" content="docfx 2.39.2.0">
<link rel="shortcut icon" href="../favicon.ico">
<link rel="stylesheet" href="../styles/docfx.vendor.css">
......@@ -423,7 +423,7 @@ Defaults to false.</p>
<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>
<div class="markdown level1 summary"><p>Should we enable experimental server connection pool?
Defaults to disable.</p>
Defaults to true.</p>
</div>
<div class="markdown level1 conceptual"></div>
<h5 class="decalaration">Declaration</h5>
......@@ -447,6 +447,37 @@ Defaults to disable.</p>
</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>
<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).
......
......@@ -10,7 +10,7 @@
<meta name="viewport" content="width=device-width">
<meta name="title" content="Namespace Titanium.Web.Proxy
| Titanium Web Proxy ">
<meta name="generator" content="docfx 2.37.2.0">
<meta name="generator" content="docfx 2.39.2.0">
<link rel="shortcut icon" href="../favicon.ico">
<link rel="stylesheet" href="../styles/docfx.vendor.css">
......
......@@ -11,145 +11,163 @@
<div class="toc" id="toc">
<ul class="nav level1">
<li>
<span class="expand-stub"></span>
<a href="Titanium.Web.Proxy.html" name="" title="Titanium.Web.Proxy">Titanium.Web.Proxy</a>
<ul class="nav level2">
<li>
<a href="Titanium.Web.Proxy.ExceptionHandler.html" name="" title="ExceptionHandler">ExceptionHandler</a>
</li>
<li>
<a href="Titanium.Web.Proxy.ProxyServer.html" name="" title="ProxyServer">ProxyServer</a>
</li>
</ul> </li>
<li>
<span class="expand-stub"></span>
<a href="Titanium.Web.Proxy.EventArguments.html" name="" title="Titanium.Web.Proxy.EventArguments">Titanium.Web.Proxy.EventArguments</a>
<ul class="nav level2">
<li>
<a href="Titanium.Web.Proxy.EventArguments.AsyncEventHandler-1.html" name="" title="AsyncEventHandler&lt;TEventArgs&gt;">AsyncEventHandler&lt;TEventArgs&gt;</a>
</li>
<li>
<a href="Titanium.Web.Proxy.EventArguments.BeforeSslAuthenticateEventArgs.html" name="" title="BeforeSslAuthenticateEventArgs">BeforeSslAuthenticateEventArgs</a>
</li>
<li>
<a href="Titanium.Web.Proxy.EventArguments.CertificateSelectionEventArgs.html" name="" title="CertificateSelectionEventArgs">CertificateSelectionEventArgs</a>
</li>
<li>
<a href="Titanium.Web.Proxy.EventArguments.CertificateValidationEventArgs.html" name="" title="CertificateValidationEventArgs">CertificateValidationEventArgs</a>
</li>
<li>
<a href="Titanium.Web.Proxy.EventArguments.MultipartRequestPartSentEventArgs.html" name="" title="MultipartRequestPartSentEventArgs">MultipartRequestPartSentEventArgs</a>
</li>
<li>
<a href="Titanium.Web.Proxy.EventArguments.SessionEventArgs.html" name="" title="SessionEventArgs">SessionEventArgs</a>
</li>
<li>
<a href="Titanium.Web.Proxy.EventArguments.SessionEventArgsBase.html" name="" title="SessionEventArgsBase">SessionEventArgsBase</a>
</li>
<li>
<a href="Titanium.Web.Proxy.EventArguments.TunnelConnectSessionEventArgs.html" name="" title="TunnelConnectSessionEventArgs">TunnelConnectSessionEventArgs</a>
</li>
</ul> </li>
<li>
<span class="expand-stub"></span>
<a href="Titanium.Web.Proxy.Exceptions.html" name="" title="Titanium.Web.Proxy.Exceptions">Titanium.Web.Proxy.Exceptions</a>
<ul class="nav level2">
<li>
<a href="Titanium.Web.Proxy.Exceptions.BodyNotFoundException.html" name="" title="BodyNotFoundException">BodyNotFoundException</a>
</li>
<li>
<a href="Titanium.Web.Proxy.Exceptions.ProxyAuthorizationException.html" name="" title="ProxyAuthorizationException">ProxyAuthorizationException</a>
</li>
<li>
<a href="Titanium.Web.Proxy.Exceptions.ProxyException.html" name="" title="ProxyException">ProxyException</a>
</li>
<li>
<a href="Titanium.Web.Proxy.Exceptions.ProxyHttpException.html" name="" title="ProxyHttpException">ProxyHttpException</a>
</li>
</ul> </li>
<li>
<span class="expand-stub"></span>
<a href="Titanium.Web.Proxy.Http.html" name="" title="Titanium.Web.Proxy.Http">Titanium.Web.Proxy.Http</a>
<ul class="nav level2">
<li>
<a href="Titanium.Web.Proxy.Http.ConnectRequest.html" name="" title="ConnectRequest">ConnectRequest</a>
</li>
<li>
<a href="Titanium.Web.Proxy.Http.ConnectResponse.html" name="" title="ConnectResponse">ConnectResponse</a>
</li>
<li>
<a href="Titanium.Web.Proxy.Http.HeaderCollection.html" name="" title="HeaderCollection">HeaderCollection</a>
</li>
<li>
<a href="Titanium.Web.Proxy.Http.HttpWebClient.html" name="" title="HttpWebClient">HttpWebClient</a>
</li>
<li>
<a href="Titanium.Web.Proxy.Http.KnownHeaders.html" name="" title="KnownHeaders">KnownHeaders</a>
</li>
<li>
<a href="Titanium.Web.Proxy.Http.Request.html" name="" title="Request">Request</a>
</li>
<li>
<a href="Titanium.Web.Proxy.Http.RequestResponseBase.html" name="" title="RequestResponseBase">RequestResponseBase</a>
</li>
<li>
<a href="Titanium.Web.Proxy.Http.Response.html" name="" title="Response">Response</a>
</li>
</ul> </li>
<li>
<span class="expand-stub"></span>
<a href="Titanium.Web.Proxy.Http.Responses.html" name="" title="Titanium.Web.Proxy.Http.Responses">Titanium.Web.Proxy.Http.Responses</a>
<ul class="nav level2">
<li>
<a href="Titanium.Web.Proxy.Http.Responses.GenericResponse.html" name="" title="GenericResponse">GenericResponse</a>
</li>
<li>
<a href="Titanium.Web.Proxy.Http.Responses.OkResponse.html" name="" title="OkResponse">OkResponse</a>
</li>
<li>
<a href="Titanium.Web.Proxy.Http.Responses.RedirectResponse.html" name="" title="RedirectResponse">RedirectResponse</a>
</li>
</ul> </li>
<li>
<span class="expand-stub"></span>
<a href="Titanium.Web.Proxy.Models.html" name="" title="Titanium.Web.Proxy.Models">Titanium.Web.Proxy.Models</a>
<ul class="nav level2">
<li>
<a href="Titanium.Web.Proxy.Models.ExplicitProxyEndPoint.html" name="" title="ExplicitProxyEndPoint">ExplicitProxyEndPoint</a>
</li>
<li>
<a href="Titanium.Web.Proxy.Models.ExternalProxy.html" name="" title="ExternalProxy">ExternalProxy</a>
</li>
<li>
<a href="Titanium.Web.Proxy.Models.HttpHeader.html" name="" title="HttpHeader">HttpHeader</a>
</li>
<li>
<a href="Titanium.Web.Proxy.Models.ProxyEndPoint.html" name="" title="ProxyEndPoint">ProxyEndPoint</a>
</li>
<li>
<a href="Titanium.Web.Proxy.Models.TransparentProxyEndPoint.html" name="" title="TransparentProxyEndPoint">TransparentProxyEndPoint</a>
</li>
</ul> </li>
<li>
<span class="expand-stub"></span>
<a href="Titanium.Web.Proxy.Network.html" name="" title="Titanium.Web.Proxy.Network">Titanium.Web.Proxy.Network</a>
<ul class="nav level2">
<li>
<a href="Titanium.Web.Proxy.Network.CertificateEngine.html" name="" title="CertificateEngine">CertificateEngine</a>
</li>
<li>
<a href="Titanium.Web.Proxy.Network.CertificateManager.html" name="" title="CertificateManager">CertificateManager</a>
</li>
</ul> </li>
</ul> </div>
<li>
<span class="expand-stub"></span>
<a href="Titanium.Web.Proxy.html" name="" title="Titanium.Web.Proxy">Titanium.Web.Proxy</a>
<ul class="nav level2">
<li>
<a href="Titanium.Web.Proxy.ExceptionHandler.html" name="" title="ExceptionHandler">ExceptionHandler</a>
</li>
<li>
<a href="Titanium.Web.Proxy.ProxyServer.html" name="" title="ProxyServer">ProxyServer</a>
</li>
</ul>
</li>
<li>
<span class="expand-stub"></span>
<a href="Titanium.Web.Proxy.EventArguments.html" name="" title="Titanium.Web.Proxy.EventArguments">Titanium.Web.Proxy.EventArguments</a>
<ul class="nav level2">
<li>
<a href="Titanium.Web.Proxy.EventArguments.AsyncEventHandler-1.html" name="" title="AsyncEventHandler&lt;TEventArgs&gt;">AsyncEventHandler&lt;TEventArgs&gt;</a>
</li>
<li>
<a href="Titanium.Web.Proxy.EventArguments.BeforeSslAuthenticateEventArgs.html" name="" title="BeforeSslAuthenticateEventArgs">BeforeSslAuthenticateEventArgs</a>
</li>
<li>
<a href="Titanium.Web.Proxy.EventArguments.CertificateSelectionEventArgs.html" name="" title="CertificateSelectionEventArgs">CertificateSelectionEventArgs</a>
</li>
<li>
<a href="Titanium.Web.Proxy.EventArguments.CertificateValidationEventArgs.html" name="" title="CertificateValidationEventArgs">CertificateValidationEventArgs</a>
</li>
<li>
<a href="Titanium.Web.Proxy.EventArguments.MultipartRequestPartSentEventArgs.html" name="" title="MultipartRequestPartSentEventArgs">MultipartRequestPartSentEventArgs</a>
</li>
<li>
<a href="Titanium.Web.Proxy.EventArguments.SessionEventArgs.html" name="" title="SessionEventArgs">SessionEventArgs</a>
</li>
<li>
<a href="Titanium.Web.Proxy.EventArguments.SessionEventArgsBase.html" name="" title="SessionEventArgsBase">SessionEventArgsBase</a>
</li>
<li>
<a href="Titanium.Web.Proxy.EventArguments.TunnelConnectSessionEventArgs.html" name="" title="TunnelConnectSessionEventArgs">TunnelConnectSessionEventArgs</a>
</li>
</ul>
</li>
<li>
<span class="expand-stub"></span>
<a href="Titanium.Web.Proxy.Exceptions.html" name="" title="Titanium.Web.Proxy.Exceptions">Titanium.Web.Proxy.Exceptions</a>
<ul class="nav level2">
<li>
<a href="Titanium.Web.Proxy.Exceptions.BodyNotFoundException.html" name="" title="BodyNotFoundException">BodyNotFoundException</a>
</li>
<li>
<a href="Titanium.Web.Proxy.Exceptions.ProxyAuthorizationException.html" name="" title="ProxyAuthorizationException">ProxyAuthorizationException</a>
</li>
<li>
<a href="Titanium.Web.Proxy.Exceptions.ProxyException.html" name="" title="ProxyException">ProxyException</a>
</li>
<li>
<a href="Titanium.Web.Proxy.Exceptions.ProxyHttpException.html" name="" title="ProxyHttpException">ProxyHttpException</a>
</li>
</ul>
</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>
<span class="expand-stub"></span>
<a href="Titanium.Web.Proxy.Http.html" name="" title="Titanium.Web.Proxy.Http">Titanium.Web.Proxy.Http</a>
<ul class="nav level2">
<li>
<a href="Titanium.Web.Proxy.Http.ConnectRequest.html" name="" title="ConnectRequest">ConnectRequest</a>
</li>
<li>
<a href="Titanium.Web.Proxy.Http.ConnectResponse.html" name="" title="ConnectResponse">ConnectResponse</a>
</li>
<li>
<a href="Titanium.Web.Proxy.Http.HeaderCollection.html" name="" title="HeaderCollection">HeaderCollection</a>
</li>
<li>
<a href="Titanium.Web.Proxy.Http.HttpWebClient.html" name="" title="HttpWebClient">HttpWebClient</a>
</li>
<li>
<a href="Titanium.Web.Proxy.Http.KnownHeaders.html" name="" title="KnownHeaders">KnownHeaders</a>
</li>
<li>
<a href="Titanium.Web.Proxy.Http.Request.html" name="" title="Request">Request</a>
</li>
<li>
<a href="Titanium.Web.Proxy.Http.RequestResponseBase.html" name="" title="RequestResponseBase">RequestResponseBase</a>
</li>
<li>
<a href="Titanium.Web.Proxy.Http.Response.html" name="" title="Response">Response</a>
</li>
</ul>
</li>
<li>
<span class="expand-stub"></span>
<a href="Titanium.Web.Proxy.Http.Responses.html" name="" title="Titanium.Web.Proxy.Http.Responses">Titanium.Web.Proxy.Http.Responses</a>
<ul class="nav level2">
<li>
<a href="Titanium.Web.Proxy.Http.Responses.GenericResponse.html" name="" title="GenericResponse">GenericResponse</a>
</li>
<li>
<a href="Titanium.Web.Proxy.Http.Responses.OkResponse.html" name="" title="OkResponse">OkResponse</a>
</li>
<li>
<a href="Titanium.Web.Proxy.Http.Responses.RedirectResponse.html" name="" title="RedirectResponse">RedirectResponse</a>
</li>
</ul>
</li>
<li>
<span class="expand-stub"></span>
<a href="Titanium.Web.Proxy.Models.html" name="" title="Titanium.Web.Proxy.Models">Titanium.Web.Proxy.Models</a>
<ul class="nav level2">
<li>
<a href="Titanium.Web.Proxy.Models.ExplicitProxyEndPoint.html" name="" title="ExplicitProxyEndPoint">ExplicitProxyEndPoint</a>
</li>
<li>
<a href="Titanium.Web.Proxy.Models.ExternalProxy.html" name="" title="ExternalProxy">ExternalProxy</a>
</li>
<li>
<a href="Titanium.Web.Proxy.Models.HttpHeader.html" name="" title="HttpHeader">HttpHeader</a>
</li>
<li>
<a href="Titanium.Web.Proxy.Models.ProxyEndPoint.html" name="" title="ProxyEndPoint">ProxyEndPoint</a>
</li>
<li>
<a href="Titanium.Web.Proxy.Models.TransparentProxyEndPoint.html" name="" title="TransparentProxyEndPoint">TransparentProxyEndPoint</a>
</li>
</ul>
</li>
<li>
<span class="expand-stub"></span>
<a href="Titanium.Web.Proxy.Network.html" name="" title="Titanium.Web.Proxy.Network">Titanium.Web.Proxy.Network</a>
<ul class="nav level2">
<li>
<a href="Titanium.Web.Proxy.Network.CertificateEngine.html" name="" title="CertificateEngine">CertificateEngine</a>
</li>
<li>
<a href="Titanium.Web.Proxy.Network.CertificateManager.html" name="" title="CertificateManager">CertificateManager</a>
</li>
</ul>
</li>
</ul>
</div>
</div>
</div>
</div>
\ No newline at end of file
......@@ -74,6 +74,16 @@
"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"
},
"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": {
"href": "api/Titanium.Web.Proxy.html",
"title": "Namespace Titanium.Web.Proxy | Titanium Web Proxy",
......@@ -177,7 +187,7 @@
"api/Titanium.Web.Proxy.Network.CertificateEngine.html": {
"href": "api/Titanium.Web.Proxy.Network.CertificateEngine.html",
"title": "Enum CertificateEngine | Titanium Web Proxy",
"keywords": "Enum CertificateEngine Certificate Engine option. Namespace : Titanium.Web.Proxy.Network Assembly : Titanium.Web.Proxy.dll Syntax public enum CertificateEngine Fields Name Description BouncyCastle Uses BouncyCastle 3rd party library. Default. DefaultWindows Uses Windows Certification Generation API. Bug #468 Reported."
"keywords": "Enum CertificateEngine Certificate Engine option. Namespace : Titanium.Web.Proxy.Network Assembly : Titanium.Web.Proxy.dll Syntax public enum CertificateEngine Fields Name Description BouncyCastle Uses BouncyCastle 3rd party library. Default. DefaultWindows Uses Windows Certification Generation API and only valid in Windows OS. Observed to be faster than BouncyCastle. Bug #468 Reported."
},
"api/Titanium.Web.Proxy.Network.CertificateManager.html": {
"href": "api/Titanium.Web.Proxy.Network.CertificateManager.html",
......@@ -192,6 +202,6 @@
"api/Titanium.Web.Proxy.ProxyServer.html": {
"href": "api/Titanium.Web.Proxy.ProxyServer.html",
"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"
}
}
......@@ -387,7 +387,10 @@ $(function () {
}
} else {
if (util.getAbsolutePath(href) === currentAbsPath) {
isActive = true;
var dropdown = $(e).attr('data-toggle') == "dropdown"
if (!dropdown) {
isActive = true;
}
}
}
if (isActive) {
......
......@@ -27,14 +27,15 @@ return{aliases:["styl"],cI:!1,k:"if else for in",i:"("+l.join("|")+")",c:[e.QSM,
built_in:"ip eip rip al ah bl bh cl ch dl dh sil dil bpl spl r8b r9b r10b r11b r12b r13b r14b r15b ax bx cx dx si di bp sp r8w r9w r10w r11w r12w r13w r14w r15w eax ebx ecx edx esi edi ebp esp eip r8d r9d r10d r11d r12d r13d r14d r15d rax rbx rcx rdx rsi rdi rbp rsp r8 r9 r10 r11 r12 r13 r14 r15 cs ds es fs gs ss st st0 st1 st2 st3 st4 st5 st6 st7 mm0 mm1 mm2 mm3 mm4 mm5 mm6 mm7 xmm0 xmm1 xmm2 xmm3 xmm4 xmm5 xmm6 xmm7 xmm8 xmm9 xmm10 xmm11 xmm12 xmm13 xmm14 xmm15 xmm16 xmm17 xmm18 xmm19 xmm20 xmm21 xmm22 xmm23 xmm24 xmm25 xmm26 xmm27 xmm28 xmm29 xmm30 xmm31 ymm0 ymm1 ymm2 ymm3 ymm4 ymm5 ymm6 ymm7 ymm8 ymm9 ymm10 ymm11 ymm12 ymm13 ymm14 ymm15 ymm16 ymm17 ymm18 ymm19 ymm20 ymm21 ymm22 ymm23 ymm24 ymm25 ymm26 ymm27 ymm28 ymm29 ymm30 ymm31 zmm0 zmm1 zmm2 zmm3 zmm4 zmm5 zmm6 zmm7 zmm8 zmm9 zmm10 zmm11 zmm12 zmm13 zmm14 zmm15 zmm16 zmm17 zmm18 zmm19 zmm20 zmm21 zmm22 zmm23 zmm24 zmm25 zmm26 zmm27 zmm28 zmm29 zmm30 zmm31 k0 k1 k2 k3 k4 k5 k6 k7 bnd0 bnd1 bnd2 bnd3 cr0 cr1 cr2 cr3 cr4 cr8 dr0 dr1 dr2 dr3 dr8 tr3 tr4 tr5 tr6 tr7 r0 r1 r2 r3 r4 r5 r6 r7 r0b r1b r2b r3b r4b r5b r6b r7b r0w r1w r2w r3w r4w r5w r6w r7w r0d r1d r2d r3d r4d r5d r6d r7d r0h r1h r2h r3h r0l r1l r2l r3l r4l r5l r6l r7l r8l r9l r10l r11l r12l r13l r14l r15l db dw dd dq dt ddq do dy dz resb resw resd resq rest resdq reso resy resz incbin equ times byte word dword qword nosplit rel abs seg wrt strict near far a32 ptr",meta:"%define %xdefine %+ %undef %defstr %deftok %assign %strcat %strlen %substr %rotate %elif %else %endif %if %ifmacro %ifctx %ifidn %ifidni %ifid %ifnum %ifstr %iftoken %ifempty %ifenv %error %warning %fatal %rep %endrep %include %push %pop %repl %pathsearch %depend %use %arg %stacksize %local %line %comment %endcomment .nolist __FILE__ __LINE__ __SECT__ __BITS__ __OUTPUT_FORMAT__ __DATE__ __TIME__ __DATE_NUM__ __TIME_NUM__ __UTC_DATE__ __UTC_TIME__ __UTC_DATE_NUM__ __UTC_TIME_NUM__ __PASS__ struc endstruc istruc at iend align alignb sectalign daz nodaz up down zero default option assume public bits use16 use32 use64 default section segment absolute extern global common cpu float __utf16__ __utf16le__ __utf16be__ __utf32__ __utf32le__ __utf32be__ __float8__ __float16__ __float32__ __float64__ __float80m__ __float80e__ __float128l__ __float128h__ __Infinity__ __QNaN__ __SNaN__ Inf NaN QNaN SNaN float8 float16 float32 float64 float80m float80e float128l float128h __FLOAT_DAZ__ __FLOAT_ROUND__ __FLOAT__"},c:[e.C(";","$",{r:0}),{cN:"number",v:[{b:"\\b(?:([0-9][0-9_]*)?\\.[0-9_]*(?:[eE][+-]?[0-9_]+)?|(0[Xx])?[0-9][0-9_]*\\.?[0-9_]*(?:[pP](?:[+-]?[0-9_]+)?)?)\\b",r:0},{b:"\\$[0-9][0-9A-Fa-f]*",r:0},{b:"\\b(?:[0-9A-Fa-f][0-9A-Fa-f_]*[Hh]|[0-9][0-9_]*[DdTt]?|[0-7][0-7_]*[QqOo]|[0-1][0-1_]*[BbYy])\\b"},{b:"\\b(?:0[Xx][0-9A-Fa-f_]+|0[DdTt][0-9_]+|0[QqOo][0-7_]+|0[BbYy][0-1_]+)\\b"}]},e.QSM,{cN:"string",v:[{b:"'",e:"[^\\\\]'"},{b:"`",e:"[^\\\\]`"}],r:0},{cN:"symbol",v:[{b:"^\\s*[A-Za-z._?][A-Za-z0-9_$#@~.?]*(:|\\s+label)"},{b:"^\\s*%%[A-Za-z0-9_$#@~.?]*:"}],r:0},{cN:"subst",b:"%[0-9]+",r:0},{cN:"subst",b:"%!S+",r:0},{cN:"meta",b:/^\s*\.[\w_-]+/}]}}),e.registerLanguage("xl",function(e){var t="ObjectLoader Animate MovieCredits Slides Filters Shading Materials LensFlare Mapping VLCAudioVideo StereoDecoder PointCloud NetworkAccess RemoteControl RegExp ChromaKey Snowfall NodeJS Speech Charts",r={keyword:"if then else do while until for loop import with is as where when by data constant integer real text name boolean symbol infix prefix postfix block tree",literal:"true false nil",built_in:"in mod rem and or xor not abs sign floor ceil sqrt sin cos tan asin acos atan exp expm1 log log2 log10 log1p pi at text_length text_range text_find text_replace contains page slide basic_slide title_slide title subtitle fade_in fade_out fade_at clear_color color line_color line_width texture_wrap texture_transform texture scale_?x scale_?y scale_?z? translate_?x translate_?y translate_?z? rotate_?x rotate_?y rotate_?z? rectangle circle ellipse sphere path line_to move_to quad_to curve_to theme background contents locally time mouse_?x mouse_?y mouse_buttons "+t},a={cN:"string",b:'"',e:'"',i:"\\n"},i={cN:"string",b:"'",e:"'",i:"\\n"},n={cN:"string",b:"<<",e:">>"},o={cN:"number",b:"[0-9]+#[0-9A-Z_]+(\\.[0-9-A-Z_]+)?#?([Ee][+-]?[0-9]+)?"},s={bK:"import",e:"$",k:r,c:[a]},l={cN:"function",b:/[a-z][^\n]*->/,rB:!0,e:/->/,c:[e.inherit(e.TM,{starts:{eW:!0,k:r}})]};return{aliases:["tao"],l:/[a-zA-Z][a-zA-Z0-9_?]*/,k:r,c:[e.CLCM,e.CBCM,a,i,n,l,s,o,e.NM]}}),e.registerLanguage("xquery",function(e){var t="for let if while then else return where group by xquery encoding versionmodule namespace boundary-space preserve strip default collation base-uri orderingcopy-namespaces order declare import schema namespace function option in allowing emptyat tumbling window sliding window start when only end when previous next stable ascendingdescending empty greatest least some every satisfies switch case typeswitch try catch andor to union intersect instance of treat as castable cast map array delete insert intoreplace value rename copy modify update",r="false true xs:string xs:integer element item xs:date xs:datetime xs:float xs:double xs:decimal QName xs:anyURI xs:long xs:int xs:short xs:byte attribute",a={b:/\$[a-zA-Z0-9\-]+/},i={cN:"number",b:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",r:0},n={cN:"string",v:[{b:/"/,e:/"/,c:[{b:/""/,r:0}]},{b:/'/,e:/'/,c:[{b:/''/,r:0}]}]},o={cN:"meta",b:"%\\w+"},s={cN:"comment",b:"\\(:",e:":\\)",r:10,c:[{cN:"doctag",b:"@\\w+"}]},l={b:"{",e:"}"},c=[a,n,i,s,o,l];return l.c=c,{aliases:["xpath","xq"],cI:!1,l:/[a-zA-Z\$][a-zA-Z0-9_:\-]*/,i:/(proc)|(abstract)|(extends)|(until)|(#)/,k:{keyword:t,literal:r},c:c}}),e.registerLanguage("zephir",function(e){var t={cN:"string",c:[e.BE],v:[{b:'b"',e:'"'},{b:"b'",e:"'"},e.inherit(e.ASM,{i:null}),e.inherit(e.QSM,{i:null})]},r={v:[e.BNM,e.CNM]};return{aliases:["zep"],cI:!0,k:"and include_once list abstract global private echo interface as static endswitch array null if endwhile or const for endforeach self var let while isset public protected exit foreach throw elseif include __FILE__ empty require_once do xor return parent clone use __CLASS__ __LINE__ else break print eval new catch __METHOD__ case exception default die require __FUNCTION__ enddeclare final try switch continue endfor endif declare unset true false trait goto instanceof insteadof __DIR__ __NAMESPACE__ yield finally int uint long ulong char uchar double float bool boolean stringlikely unlikely",c:[e.CLCM,e.HCM,e.C("/\\*","\\*/",{c:[{cN:"doctag",b:"@[A-Za-z]+"}]}),e.C("__halt_compiler.+?;",!1,{eW:!0,k:"__halt_compiler",l:e.UIR}),{cN:"string",b:"<<<['\"]?\\w+['\"]?$",e:"^\\w+;",c:[e.BE]},{b:/(::|->)+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/},{cN:"function",bK:"function",e:/[;{]/,eE:!0,i:"\\$|\\[|%",c:[e.UTM,{cN:"params",b:"\\(",e:"\\)",c:["self",e.CBCM,t,r]}]},{cN:"class",bK:"class interface",e:"{",eE:!0,i:/[:\(\$"]/,c:[{bK:"extends implements"},e.UTM]},{bK:"namespace",e:";",i:/[\.']/,c:[e.UTM]},{bK:"use",e:";",c:[e.UTM]},{b:"=>"},t,r]}}),e});
/*! url - v1.8.6 - 2013-11-22 */window.url=function(){function a(a){return!isNaN(parseFloat(a))&&isFinite(a)}return function(b,c){var d=c||window.location.toString();if(!b)return d;b=b.toString(),"//"===d.substring(0,2)?d="http:"+d:1===d.split("://").length&&(d="http://"+d),c=d.split("/");var e={auth:""},f=c[2].split("@");1===f.length?f=f[0].split(":"):(e.auth=f[0],f=f[1].split(":")),e.protocol=c[0],e.hostname=f[0],e.port=f[1]||("https"===e.protocol.split(":")[0].toLowerCase()?"443":"80"),e.pathname=(c.length>3?"/":"")+c.slice(3,c.length).join("/").split("?")[0].split("#")[0];var g=e.pathname;"/"===g.charAt(g.length-1)&&(g=g.substring(0,g.length-1));var h=e.hostname,i=h.split("."),j=g.split("/");if("hostname"===b)return h;if("domain"===b)return/^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$/.test(h)?h:i.slice(-2).join(".");if("sub"===b)return i.slice(0,i.length-2).join(".");if("port"===b)return e.port;if("protocol"===b)return e.protocol.split(":")[0];if("auth"===b)return e.auth;if("user"===b)return e.auth.split(":")[0];if("pass"===b)return e.auth.split(":")[1]||"";if("path"===b)return e.pathname;if("."===b.charAt(0)){if(b=b.substring(1),a(b))return b=parseInt(b,10),i[0>b?i.length+b:b-1]||""}else{if(a(b))return b=parseInt(b,10),j[0>b?j.length+b:b]||"";if("file"===b)return j.slice(-1)[0];if("filename"===b)return j.slice(-1)[0].split(".")[0];if("fileext"===b)return j.slice(-1)[0].split(".")[1]||"";if("?"===b.charAt(0)||"#"===b.charAt(0)){var k=d,l=null;if("?"===b.charAt(0)?k=(k.split("?")[1]||"").split("#")[0]:"#"===b.charAt(0)&&(k=k.split("#")[1]||""),!b.charAt(1))return k;b=b.substring(1),k=k.split("&");for(var m=0,n=k.length;n>m;m++)if(l=k[m].split("="),l[0]===b)return l[1]||"";return null}}return""}}(),"undefined"!=typeof jQuery&&jQuery.extend({url:function(a,b){return window.url(a,b)}});
/*
* jQuery pagination plugin v1.4.1
* http://esimakin.github.io/twbs-pagination/
* jQuery Bootstrap Pagination v1.4.2
* https://github.com/josecebe/twbs-pagination
*
* Copyright 2014-2016, Eugene Simakin
* Released under Apache 2.0 license
* Copyright 2014-2018, Eugene Simakin <john-24@list.ru>
* Released under Apache-2.0 license
* http://apache.org/licenses/LICENSE-2.0.html
*/
(function(e,d,a,f){var b=e.fn.twbsPagination;var c=function(i,g){this.$element=e(i);this.options=e.extend({},e.fn.twbsPagination.defaults,g);if(this.options.startPage<1||this.options.startPage>this.options.totalPages){throw new Error("Start page option is incorrect")}this.options.totalPages=parseInt(this.options.totalPages);if(isNaN(this.options.totalPages)){throw new Error("Total pages option is not correct!")}this.options.visiblePages=parseInt(this.options.visiblePages);if(isNaN(this.options.visiblePages)){throw new Error("Visible pages option is not correct!")}if(this.options.onPageClick instanceof Function){this.$element.first().on("page",this.options.onPageClick)}if(this.options.hideOnlyOnePage&&this.options.totalPages==1){this.$element.trigger("page",1);return this}if(this.options.totalPages<this.options.visiblePages){this.options.visiblePages=this.options.totalPages}if(this.options.href){this.options.startPage=this.getPageFromQueryString();if(!this.options.startPage){this.options.startPage=1}}var h=(typeof this.$element.prop==="function")?this.$element.prop("tagName"):this.$element.attr("tagName");if(h==="UL"){this.$listContainer=this.$element}else{this.$listContainer=e("<ul></ul>")}this.$listContainer.addClass(this.options.paginationClass);if(h!=="UL"){this.$element.append(this.$listContainer)}if(this.options.initiateStartPageClick){this.show(this.options.startPage)}else{this.render(this.getPages(this.options.startPage));this.setupEvents()}return this};c.prototype={constructor:c,destroy:function(){this.$element.empty();this.$element.removeData("twbs-pagination");this.$element.off("page");return this},show:function(g){if(g<1||g>this.options.totalPages){throw new Error("Page is incorrect.")}this.currentPage=g;this.render(this.getPages(g));this.setupEvents();this.$element.trigger("page",g);return this},buildListItems:function(g){var l=[];if(this.options.first){l.push(this.buildItem("first",1))}if(this.options.prev){var k=g.currentPage>1?g.currentPage-1:this.options.loop?this.options.totalPages:1;l.push(this.buildItem("prev",k))}for(var h=0;h<g.numeric.length;h++){l.push(this.buildItem("page",g.numeric[h]))}if(this.options.next){var j=g.currentPage<this.options.totalPages?g.currentPage+1:this.options.loop?1:this.options.totalPages;l.push(this.buildItem("next",j))}if(this.options.last){l.push(this.buildItem("last",this.options.totalPages))}return l},buildItem:function(i,j){var k=e("<li></li>"),h=e("<a></a>"),g=this.options[i]?this.makeText(this.options[i],j):j;k.addClass(this.options[i+"Class"]);k.data("page",j);k.data("page-type",i);k.append(h.attr("href",this.makeHref(j)).addClass(this.options.anchorClass).html(g));return k},getPages:function(j){var g=[];var k=Math.floor(this.options.visiblePages/2);var l=j-k+1-this.options.visiblePages%2;var h=j+k;if(l<=0){l=1;h=this.options.visiblePages}if(h>this.options.totalPages){l=this.options.totalPages-this.options.visiblePages+1;h=this.options.totalPages}var i=l;while(i<=h){g.push(i);i++}return{currentPage:j,numeric:g}},render:function(g){var i=this;this.$listContainer.children().remove();var h=this.buildListItems(g);jQuery.each(h,function(j,k){i.$listContainer.append(k)});this.$listContainer.children().each(function(){var k=e(this),j=k.data("page-type");switch(j){case"page":if(k.data("page")===g.currentPage){k.addClass(i.options.activeClass)}break;case"first":k.toggleClass(i.options.disabledClass,g.currentPage===1);break;case"last":k.toggleClass(i.options.disabledClass,g.currentPage===i.options.totalPages);break;case"prev":k.toggleClass(i.options.disabledClass,!i.options.loop&&g.currentPage===1);break;case"next":k.toggleClass(i.options.disabledClass,!i.options.loop&&g.currentPage===i.options.totalPages);break;default:break}})},setupEvents:function(){var g=this;this.$listContainer.off("click").on("click","li",function(h){var i=e(this);if(i.hasClass(g.options.disabledClass)||i.hasClass(g.options.activeClass)){return false}!g.options.href&&h.preventDefault();g.show(parseInt(i.data("page")))})},makeHref:function(g){return this.options.href?this.generateQueryString(g):"#"},makeText:function(h,g){return h.replace(this.options.pageVariable,g).replace(this.options.totalPagesVariable,this.options.totalPages)},getPageFromQueryString:function(g){var h=this.getSearchString(g),i=new RegExp(this.options.pageVariable+"(=([^&#]*)|&|#|$)"),j=i.exec(h);if(!j||!j[2]){return null}j=decodeURIComponent(j[2]);j=parseInt(j);if(isNaN(j)){return null}return j},generateQueryString:function(g,h){var i=this.getSearchString(h),j=new RegExp(this.options.pageVariable+"=*[^&#]*");if(!i){return""}return"?"+i.replace(j,this.options.pageVariable+"="+g)},getSearchString:function(g){var h=g||d.location.search;if(h===""){return null}if(h.indexOf("?")===0){h=h.substr(1)}return h}};e.fn.twbsPagination=function(i){var h=Array.prototype.slice.call(arguments,1);var k;var l=e(this);var j=l.data("twbs-pagination");var g=typeof i==="object"?i:{};if(!j){l.data("twbs-pagination",(j=new c(this,g)))}if(typeof i==="string"){k=j[i].apply(j,h)}return(k===f)?l:k};e.fn.twbsPagination.defaults={totalPages:1,startPage:1,visiblePages:5,initiateStartPageClick:true,hideOnlyOnePage:false,href:false,pageVariable:"{{page}}",totalPagesVariable:"{{total_pages}}",page:null,first:"First",prev:"Previous",next:"Next",last:"Last",loop:false,onPageClick:null,paginationClass:"pagination",nextClass:"page-item next",prevClass:"page-item prev",lastClass:"page-item last",firstClass:"page-item first",pageClass:"page-item",activeClass:"active",disabledClass:"disabled",anchorClass:"page-link"};e.fn.twbsPagination.Constructor=c;e.fn.twbsPagination.noConflict=function(){e.fn.twbsPagination=b;return this};e.fn.twbsPagination.version="1.4.1"})(window.jQuery,window,document);
!function(o,e,t,s){"use strict";var i=o.fn.twbsPagination,r=function(t,s){if(this.$element=o(t),this.options=o.extend({},o.fn.twbsPagination.defaults,s),this.options.startPage<1||this.options.startPage>this.options.totalPages)throw new Error("Start page option is incorrect");if(this.options.totalPages=parseInt(this.options.totalPages),isNaN(this.options.totalPages))throw new Error("Total pages option is not correct!");if(this.options.visiblePages=parseInt(this.options.visiblePages),isNaN(this.options.visiblePages))throw new Error("Visible pages option is not correct!");if(this.options.beforePageClick instanceof Function&&this.$element.first().on("beforePage",this.options.beforePageClick),this.options.onPageClick instanceof Function&&this.$element.first().on("page",this.options.onPageClick),this.options.hideOnlyOnePage&&1==this.options.totalPages)return this.options.initiateStartPageClick&&this.$element.trigger("page",1),this;if(this.options.href&&(this.options.startPage=this.getPageFromQueryString(),this.options.startPage||(this.options.startPage=1)),"UL"===("function"==typeof this.$element.prop?this.$element.prop("tagName"):this.$element.attr("tagName")))this.$listContainer=this.$element;else{var e=this.$element,i=o([]);e.each(function(t){var s=o("<ul></ul>");o(this).append(s),i.push(s[0])}),this.$listContainer=i,this.$element=i}return this.$listContainer.addClass(this.options.paginationClass),this.options.initiateStartPageClick?this.show(this.options.startPage):(this.currentPage=this.options.startPage,this.render(this.getPages(this.options.startPage)),this.setupEvents()),this};r.prototype={constructor:r,destroy:function(){return this.$element.empty(),this.$element.removeData("twbs-pagination"),this.$element.off("page"),this},show:function(t){if(t<1||t>this.options.totalPages)throw new Error("Page is incorrect.");this.currentPage=t,this.$element.trigger("beforePage",t);var s=this.getPages(t);return this.render(s),this.setupEvents(),this.$element.trigger("page",t),s},enable:function(){this.show(this.currentPage)},disable:function(){var t=this;this.$listContainer.off("click").on("click","li",function(t){t.preventDefault()}),this.$listContainer.children().each(function(){o(this).hasClass(t.options.activeClass)||o(this).addClass(t.options.disabledClass)})},buildListItems:function(t){var s=[];if(this.options.first&&s.push(this.buildItem("first",1)),this.options.prev){var e=1<t.currentPage?t.currentPage-1:this.options.loop?this.options.totalPages:1;s.push(this.buildItem("prev",e))}for(var i=0;i<t.numeric.length;i++)s.push(this.buildItem("page",t.numeric[i]));if(this.options.next){var a=t.currentPage<this.options.totalPages?t.currentPage+1:this.options.loop?1:this.options.totalPages;s.push(this.buildItem("next",a))}return this.options.last&&s.push(this.buildItem("last",this.options.totalPages)),s},buildItem:function(t,s){var e=o("<li></li>"),i=o("<a></a>"),a=this.options[t]?this.makeText(this.options[t],s):s;return e.addClass(this.options[t+"Class"]),e.data("page",s),e.data("page-type",t),e.append(i.attr("href",this.makeHref(s)).addClass(this.options.anchorClass).html(a)),e},getPages:function(t){var s=[],e=Math.floor(this.options.visiblePages/2),i=t-e+1-this.options.visiblePages%2,a=t+e,n=this.options.visiblePages;n>this.options.totalPages&&(n=this.options.totalPages),i<=0&&(i=1,a=n),a>this.options.totalPages&&(i=this.options.totalPages-n+1,a=this.options.totalPages);for(var o=i;o<=a;)s.push(o),o++;return{currentPage:t,numeric:s}},render:function(s){var e=this;this.$listContainer.children().remove();var t=this.buildListItems(s);o.each(t,function(t,s){e.$listContainer.append(s)}),this.$listContainer.children().each(function(){var t=o(this);switch(t.data("page-type")){case"page":t.data("page")===s.currentPage&&t.addClass(e.options.activeClass);break;case"first":t.toggleClass(e.options.disabledClass,1===s.currentPage);break;case"last":t.toggleClass(e.options.disabledClass,s.currentPage===e.options.totalPages);break;case"prev":t.toggleClass(e.options.disabledClass,!e.options.loop&&1===s.currentPage);break;case"next":t.toggleClass(e.options.disabledClass,!e.options.loop&&s.currentPage===e.options.totalPages)}})},setupEvents:function(){var e=this;this.$listContainer.off("click").on("click","li",function(t){var s=o(this);if(s.hasClass(e.options.disabledClass)||s.hasClass(e.options.activeClass))return!1;!e.options.href&&t.preventDefault(),e.show(parseInt(s.data("page")))})},changeTotalPages:function(t,s){return this.options.totalPages=t,this.show(s)},makeHref:function(t){return this.options.href?this.generateQueryString(t):"#"},makeText:function(t,s){return t.replace(this.options.pageVariable,s).replace(this.options.totalPagesVariable,this.options.totalPages)},getPageFromQueryString:function(t){var s=this.getSearchString(t),e=new RegExp(this.options.pageVariable+"(=([^&#]*)|&|#|$)").exec(s);return e&&e[2]?(e=decodeURIComponent(e[2]),e=parseInt(e),isNaN(e)?null:e):null},generateQueryString:function(t,s){var e=this.getSearchString(s),i=new RegExp(this.options.pageVariable+"=*[^&#]*");return e?"?"+e.replace(i,this.options.pageVariable+"="+t):""},getSearchString:function(t){var s=t||e.location.search;return""===s?null:(0===s.indexOf("?")&&(s=s.substr(1)),s)},getCurrentPage:function(){return this.currentPage},getTotalPages:function(){return this.options.totalPages}},o.fn.twbsPagination=function(t){var s,e=Array.prototype.slice.call(arguments,1),i=o(this),a=i.data("twbs-pagination"),n="object"==typeof t?t:{};return a||i.data("twbs-pagination",a=new r(this,n)),"string"==typeof t&&(s=a[t].apply(a,e)),void 0===s?i:s},o.fn.twbsPagination.defaults={totalPages:1,startPage:1,visiblePages:5,initiateStartPageClick:!0,hideOnlyOnePage:!1,href:!1,pageVariable:"{{page}}",totalPagesVariable:"{{total_pages}}",page:null,first:"First",prev:"Previous",next:"Next",last:"Last",loop:!1,beforePageClick:null,onPageClick:null,paginationClass:"pagination",nextClass:"page-item next",prevClass:"page-item prev",lastClass:"page-item last",firstClass:"page-item first",pageClass:"page-item",activeClass:"active",disabledClass:"disabled",anchorClass:"page-link"},o.fn.twbsPagination.Constructor=r,o.fn.twbsPagination.noConflict=function(){return o.fn.twbsPagination=i,this},o.fn.twbsPagination.version="1.4.2"}(window.jQuery,window,document);
/*!***************************************************
* mark.js v8.11.1
* https://markjs.io/
......
......@@ -822,6 +822,57 @@ references:
isSpec: "True"
fullName: Titanium.Web.Proxy.Exceptions.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
name: Titanium.Web.Proxy.Http
href: api/Titanium.Web.Proxy.Http.html
......@@ -2828,6 +2879,19 @@ references:
isSpec: "True"
fullName: Titanium.Web.Proxy.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
name: EnableWinAuth
href: api/Titanium.Web.Proxy.ProxyServer.html#Titanium_Web_Proxy_ProxyServer_EnableWinAuth
......
using System;
using Titanium.Web.Proxy.Examples.Basic.Helpers;
using Titanium.Web.Proxy.Helpers;
namespace Titanium.Web.Proxy.Examples.Basic
{
......@@ -9,8 +10,12 @@ namespace Titanium.Web.Proxy.Examples.Basic
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
controller.StartProxy();
......
......@@ -24,7 +24,6 @@ namespace Titanium.Web.Proxy.Examples.Basic
public ProxyTestController()
{
proxyServer = new ProxyServer();
proxyServer.EnableConnectionPool = true;
// generate root certificate without storing it in file system
//proxyServer.CertificateManager.CreateRootCertificate(false);
......@@ -108,13 +107,11 @@ namespace Titanium.Web.Proxy.Examples.Basic
endPoint.IpAddress, endPoint.Port);
}
#if NETSTANDARD2_0
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
#endif
// Only explicit proxies can be set as system proxy!
//proxyServer.SetAsSystemHttpProxy(explicitEndPoint);
//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);
}
}
......
<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 @@
<TargetFrameworks>net45;netcoreapp2.0</TargetFrameworks>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<LangVersion>7.1</LangVersion>
<Platforms>AnyCPU;x64</Platforms>
</PropertyGroup>
<PropertyGroup>
......@@ -12,7 +13,7 @@
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\Titanium.Web.Proxy\Titanium.Web.Proxy.csproj" />
<ProjectReference Include="..\..\src\Titanium.Web.Proxy\Titanium.Web.Proxy.csproj" />
</ItemGroup>
</Project>
\ No newline at end of file
......@@ -50,6 +50,27 @@
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</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>
<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>
......@@ -116,12 +137,6 @@
<ItemGroup>
<None Include="App.config" />
</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>
<BootstrapperPackage Include="Microsoft.Net.Framework.3.5.SP1">
<Visible>False</Visible>
......@@ -129,5 +144,11 @@
<Install>false</Install>
</BootstrapperPackage>
</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" />
</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
# Visual Studio 15
VisualStudioVersion = 15.0.27428.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}"
EndProject
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
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Examples", "Examples", "{B6DBABDC-C985-4872-9C38-B4E5079CBC4B}"
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}"
ProjectSection(SolutionItems) = preProject
LICENSE = LICENSE
README.md = README.md
..\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\Bootstrap.ps1 = .build\Bootstrap.ps1
.build\Common.psm1 = .build\Common.psm1
.build\default.ps1 = .build\default.ps1
..\.build\build.ps1 = ..\.build\build.ps1
..\.build\docfx.json = ..\.build\docfx.json
..\.build\setup.ps1 = ..\.build\setup.ps1
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{BC1E0789-D348-49CF-8B67-5E99D50EDF64}"
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
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
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
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
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
{8D73A1BE-868C-42D2-9ECE-F32CC1A02906}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8D73A1BE-868C-42D2-9ECE-F32CC1A02906}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8D73A1BE-868C-42D2-9ECE-F32CC1A02906}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8D73A1BE-868C-42D2-9ECE-F32CC1A02906}.Release|Any CPU.Build.0 = Release|Any CPU
{91018B6D-A7A9-45BE-9CB3-79CBB8B169A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{91018B6D-A7A9-45BE-9CB3-79CBB8B169A6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{91018B6D-A7A9-45BE-9CB3-79CBB8B169A6}.Debug|x64.ActiveCfg = Debug|x64
{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.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.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.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.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.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.Build.0 = Release|Any CPU
{9A2C6980-90D1-4082-AD60-B2428F3D6197}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9A2C6980-90D1-4082-AD60-B2428F3D6197}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9A2C6980-90D1-4082-AD60-B2428F3D6197}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9A2C6980-90D1-4082-AD60-B2428F3D6197}.Release|Any CPU.Build.0 = Release|Any CPU
{4406CE17-9A39-4F28-8363-6169A4F799C1}.Release|x64.ActiveCfg = Release|x64
{4406CE17-9A39-4F28-8363-6169A4F799C1}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
......@@ -70,8 +86,8 @@ Global
GlobalSection(NestedProjects) = preSolution
{B517E3D0-D03B-436F-AB03-34BA0D5321AF} = {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}
{9A2C6980-90D1-4082-AD60-B2428F3D6197} = {B6DBABDC-C985-4872-9C38-B4E5079CBC4B}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
EnterpriseLibraryConfigurationToolBinariesPath = .1.505.2\lib\NET35
......
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using StreamExtended.Network;
using Titanium.Web.Proxy.Compression;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Http.Responses;
using Titanium.Web.Proxy.Models;
namespace Titanium.Web.Proxy.EventArguments
{
/// <summary>
/// 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 ends when client terminates connection to proxy
/// or when server terminates connection from proxy.
/// </summary>
public class SessionEventArgs : SessionEventArgsBase
{
private static readonly byte[] emptyData = new byte[0];
/// <summary>
/// Backing field for corresponding public property
/// </summary>
private bool reRequest;
/// <summary>
/// Constructor to initialize the proxy
/// </summary>
internal SessionEventArgs(ProxyServer server, ProxyEndPoint endPoint,
CancellationTokenSource cancellationTokenSource)
: this(server, endPoint, null, cancellationTokenSource)
{
}
protected SessionEventArgs(ProxyServer server, ProxyEndPoint endPoint,
Request request, CancellationTokenSource cancellationTokenSource)
: base(server, endPoint, cancellationTokenSource, request)
{
}
private bool hasMulipartEventSubscribers => MultipartRequestPartSent != null;
/// <summary>
/// Should we send the request again ?
/// </summary>
public bool ReRequest
{
get => reRequest;
set
{
if (WebSession.Response.StatusCode == 0)
{
throw new Exception("Response status code is empty. Cannot request again a request " + "which was never send to server.");
}
reRequest = value;
}
}
/// <summary>
/// Occurs when multipart request part sent.
/// </summary>
public event EventHandler<MultipartRequestPartSentEventArgs> MultipartRequestPartSent;
private ICustomStreamReader getStreamReader(bool isRequest)
{
return isRequest ? ProxyClient.ClientStream : WebSession.ServerConnection.Stream;
}
private HttpWriter getStreamWriter(bool isRequest)
{
return isRequest ? (HttpWriter)ProxyClient.ClientStreamWriter : WebSession.ServerConnection.StreamWriter;
}
/// <summary>
/// Read request body content as bytes[] for current session
/// </summary>
private async Task readRequestBodyAsync(CancellationToken cancellationToken)
{
WebSession.Request.EnsureBodyAvailable(false);
var request = WebSession.Request;
// If not already read (not cached yet)
if (!request.IsBodyRead)
{
var body = await readBodyAsync(true, cancellationToken);
request.Body = body;
// Now set the flag to true
// So that next time we can deliver body from cache
request.IsBodyRead = true;
OnDataSent(body, 0, body.Length);
}
}
/// <summary>
/// reinit response object
/// </summary>
internal async Task ClearResponse(CancellationToken cancellationToken)
{
// syphon out the response body from server
await SyphonOutBodyAsync(false, cancellationToken);
WebSession.Response = new Response();
}
internal void OnMultipartRequestPartSent(string boundary, HeaderCollection headers)
{
try
{
MultipartRequestPartSent?.Invoke(this, new MultipartRequestPartSentEventArgs(boundary, headers));
}
catch (Exception ex)
{
exceptionFunc(new Exception("Exception thrown in user event", ex));
}
}
/// <summary>
/// Read response body as byte[] for current response
/// </summary>
private async Task readResponseBodyAsync(CancellationToken cancellationToken)
{
if (!WebSession.Request.Locked)
{
throw new Exception("You cannot read the response body before request is made to server.");
}
var response = WebSession.Response;
if (!response.HasBody)
{
return;
}
// If not already read (not cached yet)
if (!response.IsBodyRead)
{
var body = await readBodyAsync(false, cancellationToken);
response.Body = body;
// Now set the flag to true
// So that next time we can deliver body from cache
response.IsBodyRead = true;
OnDataReceived(body, 0, body.Length);
}
}
private async Task<byte[]> readBodyAsync(bool isRequest, CancellationToken cancellationToken)
{
using (var bodyStream = new MemoryStream())
{
var writer = new HttpWriter(bodyStream, bufferPool, bufferSize);
if (isRequest)
{
await CopyRequestBodyAsync(writer, TransformationMode.Uncompress, cancellationToken);
}
else
{
await CopyResponseBodyAsync(writer, TransformationMode.Uncompress, cancellationToken);
}
return bodyStream.ToArray();
}
}
/// <summary>
/// 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.
/// </summary>
/// <param name="isRequest"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
internal async Task SyphonOutBodyAsync(bool isRequest, CancellationToken cancellationToken)
{
var requestResponse = isRequest ? (RequestResponseBase)WebSession.Request : WebSession.Response;
if (requestResponse.OriginalIsBodyRead || !requestResponse.OriginalHasBody)
{
return;
}
using (var bodyStream = new MemoryStream())
{
var writer = new HttpWriter(bodyStream, bufferPool, bufferSize);
await copyBodyAsync(isRequest, true, writer, TransformationMode.None, null, cancellationToken);
}
}
/// <summary>
/// This is called when the request is PUT/POST/PATCH to read the body
/// </summary>
/// <returns></returns>
internal async Task CopyRequestBodyAsync(HttpWriter writer, TransformationMode transformation, CancellationToken cancellationToken)
{
var request = WebSession.Request;
long contentLength = request.ContentLength;
// send the request body bytes to server
if (contentLength > 0 && hasMulipartEventSubscribers && request.IsMultipartFormData)
{
var reader = getStreamReader(true);
string boundary = HttpHelper.GetBoundaryFromContentType(request.ContentType);
using (var copyStream = new CopyStream(reader, writer, bufferPool, bufferSize))
{
while (contentLength > copyStream.ReadBytes)
{
long read = await readUntilBoundaryAsync(copyStream, contentLength, boundary, cancellationToken);
if (read == 0)
{
break;
}
if (contentLength > copyStream.ReadBytes)
{
var headers = new HeaderCollection();
await HeaderParser.ReadHeaders(copyStream, headers, cancellationToken);
OnMultipartRequestPartSent(boundary, headers);
}
}
await copyStream.FlushAsync(cancellationToken);
}
}
else
{
await copyBodyAsync(true, false, writer, transformation, OnDataSent, cancellationToken);
}
}
internal async Task CopyResponseBodyAsync(HttpWriter writer, TransformationMode transformation, CancellationToken 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)
{
var stream = getStreamReader(isRequest);
var requestResponse = isRequest ? (RequestResponseBase)WebSession.Request : WebSession.Response;
bool isChunked = useOriginalHeaderValues? requestResponse.OriginalIsChunked : requestResponse.IsChunked;
long contentLength = useOriginalHeaderValues ? requestResponse.OriginalContentLength : requestResponse.ContentLength;
if (transformation == TransformationMode.None)
{
await writer.CopyBodyAsync(stream, isChunked, contentLength, onCopy, cancellationToken);
return;
}
LimitedStream limitedStream;
Stream decompressStream = null;
string contentEncoding = useOriginalHeaderValues ? requestResponse.OriginalContentEncoding : requestResponse.ContentEncoding;
Stream s = limitedStream = new LimitedStream(stream, bufferPool, isChunked, contentLength);
if (transformation == TransformationMode.Uncompress && contentEncoding != null)
{
s = decompressStream = DecompressionFactory.Create(contentEncoding, s);
}
try
{
using (var bufStream = new CustomBufferedStream(s, bufferPool, bufferSize, true))
{
await writer.CopyBodyAsync(bufStream, false, -1, onCopy, cancellationToken);
}
}
finally
{
decompressStream?.Dispose();
await limitedStream.Finish();
limitedStream.Dispose();
}
}
/// <summary>
/// Read a line from the byte stream
/// </summary>
/// <returns></returns>
private async Task<long> readUntilBoundaryAsync(ICustomStreamReader reader, long totalBytesToRead, string boundary, CancellationToken cancellationToken)
{
int bufferDataLength = 0;
var buffer = bufferPool.GetBuffer(bufferSize);
try
{
int boundaryLength = boundary.Length + 4;
long bytesRead = 0;
while (bytesRead < totalBytesToRead && (reader.DataAvailable || await reader.FillBufferAsync(cancellationToken)))
{
byte newChar = reader.ReadByteFromBuffer();
buffer[bufferDataLength] = newChar;
bufferDataLength++;
bytesRead++;
if (bufferDataLength >= boundaryLength)
{
int startIdx = bufferDataLength - boundaryLength;
if (buffer[startIdx] == '-' && buffer[startIdx + 1] == '-')
{
startIdx += 2;
bool ok = true;
for (int i = 0; i < boundary.Length; i++)
{
if (buffer[startIdx + i] != boundary[i])
{
ok = false;
break;
}
}
if (ok)
{
break;
}
}
}
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
const int bytesToKeep = 100;
Buffer.BlockCopy(buffer, buffer.Length - bytesToKeep, buffer, 0, bytesToKeep);
bufferDataLength = bytesToKeep;
}
}
return bytesRead;
}
finally
{
bufferPool.ReturnBuffer(buffer);
}
}
/// <summary>
/// Gets the request body as bytes.
/// </summary>
/// <param name="cancellationToken">Optional cancellation token for this async task.</param>
/// <returns>The body as bytes.</returns>
public async Task<byte[]> GetRequestBody(CancellationToken cancellationToken = default)
{
if (!WebSession.Request.IsBodyRead)
{
await readRequestBodyAsync(cancellationToken);
}
return WebSession.Request.Body;
}
/// <summary>
/// Gets the request body as string.
/// </summary>
/// <param name="cancellationToken">Optional cancellation token for this async task.</param>
/// <returns>The body as string.</returns>
public async Task<string> GetRequestBodyAsString(CancellationToken cancellationToken = default)
{
if (!WebSession.Request.IsBodyRead)
{
await readRequestBodyAsync(cancellationToken);
}
return WebSession.Request.BodyString;
}
/// <summary>
/// Sets the request body.
/// </summary>
/// <param name="body">The request body bytes.</param>
public void SetRequestBody(byte[] body)
{
var request = WebSession.Request;
if (request.Locked)
{
throw new Exception("You cannot call this function after request is made to server.");
}
request.Body = body;
}
/// <summary>
/// Sets the body with the specified string.
/// </summary>
/// <param name="body">The request body string to set.</param>
public void SetRequestBodyString(string body)
{
if (WebSession.Request.Locked)
{
throw new Exception("You cannot call this function after request is made to server.");
}
SetRequestBody(WebSession.Request.Encoding.GetBytes(body));
}
/// <summary>
/// Gets the response body as bytes.
/// </summary>
/// <param name="cancellationToken">Optional cancellation token for this async task.</param>
/// <returns>The resulting bytes.</returns>
public async Task<byte[]> GetResponseBody(CancellationToken cancellationToken = default)
{
if (!WebSession.Response.IsBodyRead)
{
await readResponseBodyAsync(cancellationToken);
}
return WebSession.Response.Body;
}
/// <summary>
/// Gets the response body as string.
/// </summary>
/// <param name="cancellationToken">Optional cancellation token for this async task.</param>
/// <returns>The string body.</returns>
public async Task<string> GetResponseBodyAsString(CancellationToken cancellationToken = default)
{
if (!WebSession.Response.IsBodyRead)
{
await readResponseBodyAsync(cancellationToken);
}
return WebSession.Response.BodyString;
}
/// <summary>
/// Set the response body bytes.
/// </summary>
/// <param name="body">The body bytes to set.</param>
public void SetResponseBody(byte[] body)
{
if (!WebSession.Request.Locked)
{
throw new Exception("You cannot call this function before request is made to server.");
}
var response = WebSession.Response;
response.Body = body;
}
/// <summary>
/// Replace the response body with the specified string.
/// </summary>
/// <param name="body">The body string to set.</param>
public void SetResponseBodyString(string body)
{
if (!WebSession.Request.Locked)
{
throw new Exception("You cannot call this function before request is made to server.");
}
var bodyBytes = WebSession.Response.Encoding.GetBytes(body);
SetResponseBody(bodyBytes);
}
/// <summary>
/// Before request is made to server respond with the specified HTML string to client
/// and ignore the request.
/// </summary>
/// <param name="html">HTML content to sent.</param>
/// <param name="headers">HTTP response headers.</param>
/// <param name="closeServerConnection">Close the server connection used by request if any?</param>
public void Ok(string html, Dictionary<string, HttpHeader> headers = null,
bool closeServerConnection = false)
{
var response = new OkResponse();
if (headers != null)
{
response.Headers.AddHeaders(headers);
}
response.HttpVersion = WebSession.Request.HttpVersion;
response.Body = response.Encoding.GetBytes(html ?? string.Empty);
Respond(response, closeServerConnection);
}
/// <summary>
/// Before request is made to server respond with the specified byte[] to client
/// and ignore the request.
/// </summary>
/// <param name="result">The html content bytes.</param>
/// <param name="headers">The HTTP headers.</param>
/// <param name="closeServerConnection">Close the server connection used by request if any?</param>
public void Ok(byte[] result, Dictionary<string, HttpHeader> headers = null,
bool closeServerConnection = false)
{
var response = new OkResponse();
response.Headers.AddHeaders(headers);
response.HttpVersion = WebSession.Request.HttpVersion;
response.Body = result;
Respond(response, closeServerConnection);
}
/// <summary>
/// Before request is made to server 
/// respond with the specified HTML string and the specified status to client.
/// And then ignore the request. 
/// </summary>
/// <param name="html">The html content.</param>
/// <param name="status">The HTTP status code.</param>
/// <param name="headers">The HTTP headers.</param>
/// <param name="closeServerConnection">Close the server connection used by request if any?</param>
public void GenericResponse(string html, HttpStatusCode status,
Dictionary<string, HttpHeader> headers = null, bool closeServerConnection = false)
{
var response = new GenericResponse(status);
response.HttpVersion = WebSession.Request.HttpVersion;
response.Headers.AddHeaders(headers);
response.Body = response.Encoding.GetBytes(html ?? string.Empty);
Respond(response, closeServerConnection);
}
/// <summary>
/// Before request is made to server respond with the specified byte[],
/// the specified status to client. And then ignore the request.
/// </summary>
/// <param name="result">The bytes to sent.</param>
/// <param name="status">The HTTP status code.</param>
/// <param name="headers">The HTTP headers.</param>
/// <param name="closeServerConnection">Close the server connection used by request if any?</param>
public void GenericResponse(byte[] result, HttpStatusCode status,
Dictionary<string, HttpHeader> headers, bool closeServerConnection = false)
{
var response = new GenericResponse(status);
response.HttpVersion = WebSession.Request.HttpVersion;
response.Headers.AddHeaders(headers);
response.Body = result;
Respond(response, closeServerConnection);
}
/// <summary>
/// Redirect to provided URL.
/// </summary>
/// <param name="url">The URL to redirect.</param>
/// <param name="closeServerConnection">Close the server connection used by request if any?</param>
public void Redirect(string url, bool closeServerConnection = false)
{
var response = new RedirectResponse();
response.HttpVersion = WebSession.Request.HttpVersion;
response.Headers.AddHeader(KnownHeaders.Location, url);
response.Body = emptyData;
Respond(response, closeServerConnection);
}
/// <summary>
/// Respond with given response object to client.
/// </summary>
/// <param name="response">The response object.</param>
/// <param name="closeServerConnection">Close the server connection used by request if any?</param>
public void Respond(Response response, bool closeServerConnection = false)
{
//request already send/ready to be sent.
if (WebSession.Request.Locked)
{
//response already received from server and ready to be sent to client.
if (WebSession.Response.Locked)
{
throw new Exception("You cannot call this function after response is sent to the client.");
}
//cleanup original response.
if (closeServerConnection)
{
//no need to cleanup original connection.
//it will be closed any way.
TerminateServerConnection();
}
response.SetOriginalHeaders(WebSession.Response);
//response already received from server but not yet ready to sent to client.
WebSession.Response = response;
WebSession.Response.Locked = true;
}
//request not yet sent/not yet ready to be sent.
else
{
WebSession.Request.Locked = true;
WebSession.Request.CancelRequest = true;
//set new response.
WebSession.Response = response;
WebSession.Response.Locked = true;
}
}
/// <summary>
/// Terminate the connection to server at the end of this HTTP request/response session.
/// </summary>
public void TerminateServerConnection()
{
WebSession.CloseServerConnection = true;
}
/// <summary>
/// Implement any cleanup here
/// </summary>
public override void Dispose()
{
MultipartRequestPartSent = null;
base.Dispose();
}
}
}
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using StreamExtended.Network;
using Titanium.Web.Proxy.Compression;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Http.Responses;
using Titanium.Web.Proxy.Models;
namespace Titanium.Web.Proxy.EventArguments
{
/// <summary>
/// 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 ends when client terminates connection to proxy
/// or when server terminates connection from proxy.
/// </summary>
public class SessionEventArgs : SessionEventArgsBase
{
private static readonly byte[] emptyData = new byte[0];
/// <summary>
/// Backing field for corresponding public property
/// </summary>
private bool reRequest;
/// <summary>
/// Constructor to initialize the proxy
/// </summary>
internal SessionEventArgs(ProxyServer server, ProxyEndPoint endPoint,
CancellationTokenSource cancellationTokenSource)
: this(server, endPoint, null, cancellationTokenSource)
{
}
protected SessionEventArgs(ProxyServer server, ProxyEndPoint endPoint,
Request request, CancellationTokenSource cancellationTokenSource)
: base(server, endPoint, cancellationTokenSource, request)
{
}
private bool hasMulipartEventSubscribers => MultipartRequestPartSent != null;
/// <summary>
/// Should we send the request again ?
/// </summary>
public bool ReRequest
{
get => reRequest;
set
{
if (WebSession.Response.StatusCode == 0)
{
throw new Exception("Response status code is empty. Cannot request again a request " + "which was never send to server.");
}
reRequest = value;
}
}
/// <summary>
/// Occurs when multipart request part sent.
/// </summary>
public event EventHandler<MultipartRequestPartSentEventArgs> MultipartRequestPartSent;
private ICustomStreamReader getStreamReader(bool isRequest)
{
return isRequest ? ProxyClient.ClientStream : WebSession.ServerConnection.Stream;
}
private HttpWriter getStreamWriter(bool isRequest)
{
return isRequest ? (HttpWriter)ProxyClient.ClientStreamWriter : WebSession.ServerConnection.StreamWriter;
}
/// <summary>
/// Read request body content as bytes[] for current session
/// </summary>
private async Task readRequestBodyAsync(CancellationToken cancellationToken)
{
WebSession.Request.EnsureBodyAvailable(false);
var request = WebSession.Request;
// If not already read (not cached yet)
if (!request.IsBodyRead)
{
var body = await readBodyAsync(true, cancellationToken);
request.Body = body;
// Now set the flag to true
// So that next time we can deliver body from cache
request.IsBodyRead = true;
OnDataSent(body, 0, body.Length);
}
}
/// <summary>
/// reinit response object
/// </summary>
internal async Task ClearResponse(CancellationToken cancellationToken)
{
// syphon out the response body from server
await SyphonOutBodyAsync(false, cancellationToken);
WebSession.Response = new Response();
}
internal void OnMultipartRequestPartSent(string boundary, HeaderCollection headers)
{
try
{
MultipartRequestPartSent?.Invoke(this, new MultipartRequestPartSentEventArgs(boundary, headers));
}
catch (Exception ex)
{
exceptionFunc(new Exception("Exception thrown in user event", ex));
}
}
/// <summary>
/// Read response body as byte[] for current response
/// </summary>
private async Task readResponseBodyAsync(CancellationToken cancellationToken)
{
if (!WebSession.Request.Locked)
{
throw new Exception("You cannot read the response body before request is made to server.");
}
var response = WebSession.Response;
if (!response.HasBody)
{
return;
}
// If not already read (not cached yet)
if (!response.IsBodyRead)
{
var body = await readBodyAsync(false, cancellationToken);
response.Body = body;
// Now set the flag to true
// So that next time we can deliver body from cache
response.IsBodyRead = true;
OnDataReceived(body, 0, body.Length);
}
}
private async Task<byte[]> readBodyAsync(bool isRequest, CancellationToken cancellationToken)
{
using (var bodyStream = new MemoryStream())
{
var writer = new HttpWriter(bodyStream, bufferPool, bufferSize);
if (isRequest)
{
await CopyRequestBodyAsync(writer, TransformationMode.Uncompress, cancellationToken);
}
else
{
await CopyResponseBodyAsync(writer, TransformationMode.Uncompress, cancellationToken);
}
return bodyStream.ToArray();
}
}
/// <summary>
/// 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.
/// </summary>
/// <param name="isRequest"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
internal async Task SyphonOutBodyAsync(bool isRequest, CancellationToken cancellationToken)
{
var requestResponse = isRequest ? (RequestResponseBase)WebSession.Request : WebSession.Response;
if (requestResponse.OriginalIsBodyRead || !requestResponse.OriginalHasBody)
{
return;
}
using (var bodyStream = new MemoryStream())
{
var writer = new HttpWriter(bodyStream, bufferPool, bufferSize);
await copyBodyAsync(isRequest, true, writer, TransformationMode.None, null, cancellationToken);
}
}
/// <summary>
/// This is called when the request is PUT/POST/PATCH to read the body
/// </summary>
/// <returns></returns>
internal async Task CopyRequestBodyAsync(HttpWriter writer, TransformationMode transformation, CancellationToken cancellationToken)
{
var request = WebSession.Request;
long contentLength = request.ContentLength;
// send the request body bytes to server
if (contentLength > 0 && hasMulipartEventSubscribers && request.IsMultipartFormData)
{
var reader = getStreamReader(true);
string boundary = HttpHelper.GetBoundaryFromContentType(request.ContentType);
using (var copyStream = new CopyStream(reader, writer, bufferPool, bufferSize))
{
while (contentLength > copyStream.ReadBytes)
{
long read = await readUntilBoundaryAsync(copyStream, contentLength, boundary, cancellationToken);
if (read == 0)
{
break;
}
if (contentLength > copyStream.ReadBytes)
{
var headers = new HeaderCollection();
await HeaderParser.ReadHeaders(copyStream, headers, cancellationToken);
OnMultipartRequestPartSent(boundary, headers);
}
}
await copyStream.FlushAsync(cancellationToken);
}
}
else
{
await copyBodyAsync(true, false, writer, transformation, OnDataSent, cancellationToken);
}
}
internal async Task CopyResponseBodyAsync(HttpWriter writer, TransformationMode transformation, CancellationToken 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)
{
var stream = getStreamReader(isRequest);
var requestResponse = isRequest ? (RequestResponseBase)WebSession.Request : WebSession.Response;
bool isChunked = useOriginalHeaderValues? requestResponse.OriginalIsChunked : requestResponse.IsChunked;
long contentLength = useOriginalHeaderValues ? requestResponse.OriginalContentLength : requestResponse.ContentLength;
if (transformation == TransformationMode.None)
{
await writer.CopyBodyAsync(stream, isChunked, contentLength, onCopy, cancellationToken);
return;
}
LimitedStream limitedStream;
Stream decompressStream = null;
string contentEncoding = useOriginalHeaderValues ? requestResponse.OriginalContentEncoding : requestResponse.ContentEncoding;
Stream s = limitedStream = new LimitedStream(stream, bufferPool, isChunked, contentLength);
if (transformation == TransformationMode.Uncompress && contentEncoding != null)
{
s = decompressStream = DecompressionFactory.Create(contentEncoding, s);
}
try
{
using (var bufStream = new CustomBufferedStream(s, bufferPool, bufferSize, true))
{
await writer.CopyBodyAsync(bufStream, false, -1, onCopy, cancellationToken);
}
}
finally
{
decompressStream?.Dispose();
await limitedStream.Finish();
limitedStream.Dispose();
}
}
/// <summary>
/// Read a line from the byte stream
/// </summary>
/// <returns></returns>
private async Task<long> readUntilBoundaryAsync(ICustomStreamReader reader, long totalBytesToRead, string boundary, CancellationToken cancellationToken)
{
int bufferDataLength = 0;
var buffer = bufferPool.GetBuffer(bufferSize);
try
{
int boundaryLength = boundary.Length + 4;
long bytesRead = 0;
while (bytesRead < totalBytesToRead && (reader.DataAvailable || await reader.FillBufferAsync(cancellationToken)))
{
byte newChar = reader.ReadByteFromBuffer();
buffer[bufferDataLength] = newChar;
bufferDataLength++;
bytesRead++;
if (bufferDataLength >= boundaryLength)
{
int startIdx = bufferDataLength - boundaryLength;
if (buffer[startIdx] == '-' && buffer[startIdx + 1] == '-')
{
startIdx += 2;
bool ok = true;
for (int i = 0; i < boundary.Length; i++)
{
if (buffer[startIdx + i] != boundary[i])
{
ok = false;
break;
}
}
if (ok)
{
break;
}
}
}
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
const int bytesToKeep = 100;
Buffer.BlockCopy(buffer, buffer.Length - bytesToKeep, buffer, 0, bytesToKeep);
bufferDataLength = bytesToKeep;
}
}
return bytesRead;
}
finally
{
bufferPool.ReturnBuffer(buffer);
}
}
/// <summary>
/// Gets the request body as bytes.
/// </summary>
/// <param name="cancellationToken">Optional cancellation token for this async task.</param>
/// <returns>The body as bytes.</returns>
public async Task<byte[]> GetRequestBody(CancellationToken cancellationToken = default)
{
if (!WebSession.Request.IsBodyRead)
{
await readRequestBodyAsync(cancellationToken);
}
return WebSession.Request.Body;
}
/// <summary>
/// Gets the request body as string.
/// </summary>
/// <param name="cancellationToken">Optional cancellation token for this async task.</param>
/// <returns>The body as string.</returns>
public async Task<string> GetRequestBodyAsString(CancellationToken cancellationToken = default)
{
if (!WebSession.Request.IsBodyRead)
{
await readRequestBodyAsync(cancellationToken);
}
return WebSession.Request.BodyString;
}
/// <summary>
/// Sets the request body.
/// </summary>
/// <param name="body">The request body bytes.</param>
public void SetRequestBody(byte[] body)
{
var request = WebSession.Request;
if (request.Locked)
{
throw new Exception("You cannot call this function after request is made to server.");
}
request.Body = body;
}
/// <summary>
/// Sets the body with the specified string.
/// </summary>
/// <param name="body">The request body string to set.</param>
public void SetRequestBodyString(string body)
{
if (WebSession.Request.Locked)
{
throw new Exception("You cannot call this function after request is made to server.");
}
SetRequestBody(WebSession.Request.Encoding.GetBytes(body));
}
/// <summary>
/// Gets the response body as bytes.
/// </summary>
/// <param name="cancellationToken">Optional cancellation token for this async task.</param>
/// <returns>The resulting bytes.</returns>
public async Task<byte[]> GetResponseBody(CancellationToken cancellationToken = default)
{
if (!WebSession.Response.IsBodyRead)
{
await readResponseBodyAsync(cancellationToken);
}
return WebSession.Response.Body;
}
/// <summary>
/// Gets the response body as string.
/// </summary>
/// <param name="cancellationToken">Optional cancellation token for this async task.</param>
/// <returns>The string body.</returns>
public async Task<string> GetResponseBodyAsString(CancellationToken cancellationToken = default)
{
if (!WebSession.Response.IsBodyRead)
{
await readResponseBodyAsync(cancellationToken);
}
return WebSession.Response.BodyString;
}
/// <summary>
/// Set the response body bytes.
/// </summary>
/// <param name="body">The body bytes to set.</param>
public void SetResponseBody(byte[] body)
{
if (!WebSession.Request.Locked)
{
throw new Exception("You cannot call this function before request is made to server.");
}
var response = WebSession.Response;
response.Body = body;
}
/// <summary>
/// Replace the response body with the specified string.
/// </summary>
/// <param name="body">The body string to set.</param>
public void SetResponseBodyString(string body)
{
if (!WebSession.Request.Locked)
{
throw new Exception("You cannot call this function before request is made to server.");
}
var bodyBytes = WebSession.Response.Encoding.GetBytes(body);
SetResponseBody(bodyBytes);
}
/// <summary>
/// Before request is made to server respond with the specified HTML string to client
/// and ignore the request.
/// </summary>
/// <param name="html">HTML content to sent.</param>
/// <param name="headers">HTTP response headers.</param>
/// <param name="closeServerConnection">Close the server connection used by request if any?</param>
public void Ok(string html, Dictionary<string, HttpHeader> headers = null,
bool closeServerConnection = false)
{
var response = new OkResponse();
if (headers != null)
{
response.Headers.AddHeaders(headers);
}
response.HttpVersion = WebSession.Request.HttpVersion;
response.Body = response.Encoding.GetBytes(html ?? string.Empty);
Respond(response, closeServerConnection);
}
/// <summary>
/// Before request is made to server respond with the specified byte[] to client
/// and ignore the request.
/// </summary>
/// <param name="result">The html content bytes.</param>
/// <param name="headers">The HTTP headers.</param>
/// <param name="closeServerConnection">Close the server connection used by request if any?</param>
public void Ok(byte[] result, Dictionary<string, HttpHeader> headers = null,
bool closeServerConnection = false)
{
var response = new OkResponse();
response.Headers.AddHeaders(headers);
response.HttpVersion = WebSession.Request.HttpVersion;
response.Body = result;
Respond(response, closeServerConnection);
}
/// <summary>
/// Before request is made to server 
/// respond with the specified HTML string and the specified status to client.
/// And then ignore the request. 
/// </summary>
/// <param name="html">The html content.</param>
/// <param name="status">The HTTP status code.</param>
/// <param name="headers">The HTTP headers.</param>
/// <param name="closeServerConnection">Close the server connection used by request if any?</param>
public void GenericResponse(string html, HttpStatusCode status,
Dictionary<string, HttpHeader> headers = null, bool closeServerConnection = false)
{
var response = new GenericResponse(status);
response.HttpVersion = WebSession.Request.HttpVersion;
response.Headers.AddHeaders(headers);
response.Body = response.Encoding.GetBytes(html ?? string.Empty);
Respond(response, closeServerConnection);
}
/// <summary>
/// Before request is made to server respond with the specified byte[],
/// the specified status to client. And then ignore the request.
/// </summary>
/// <param name="result">The bytes to sent.</param>
/// <param name="status">The HTTP status code.</param>
/// <param name="headers">The HTTP headers.</param>
/// <param name="closeServerConnection">Close the server connection used by request if any?</param>
public void GenericResponse(byte[] result, HttpStatusCode status,
Dictionary<string, HttpHeader> headers, bool closeServerConnection = false)
{
var response = new GenericResponse(status);
response.HttpVersion = WebSession.Request.HttpVersion;
response.Headers.AddHeaders(headers);
response.Body = result;
Respond(response, closeServerConnection);
}
/// <summary>
/// Redirect to provided URL.
/// </summary>
/// <param name="url">The URL to redirect.</param>
/// <param name="closeServerConnection">Close the server connection used by request if any?</param>
public void Redirect(string url, bool closeServerConnection = false)
{
var response = new RedirectResponse();
response.HttpVersion = WebSession.Request.HttpVersion;
response.Headers.AddHeader(KnownHeaders.Location, url);
response.Body = emptyData;
Respond(response, closeServerConnection);
}
/// <summary>
/// Respond with given response object to client.
/// </summary>
/// <param name="response">The response object.</param>
/// <param name="closeServerConnection">Close the server connection used by request if any?</param>
public void Respond(Response response, bool closeServerConnection = false)
{
//request already send/ready to be sent.
if (WebSession.Request.Locked)
{
//response already received from server and ready to be sent to client.
if (WebSession.Response.Locked)
{
throw new Exception("You cannot call this function after response is sent to the client.");
}
//cleanup original response.
if (closeServerConnection)
{
//no need to cleanup original connection.
//it will be closed any way.
TerminateServerConnection();
}
response.SetOriginalHeaders(WebSession.Response);
//response already received from server but not yet ready to sent to client.
WebSession.Response = response;
WebSession.Response.Locked = true;
}
//request not yet sent/not yet ready to be sent.
else
{
WebSession.Request.Locked = true;
WebSession.Request.CancelRequest = true;
//set new response.
WebSession.Response = response;
WebSession.Response.Locked = true;
}
}
/// <summary>
/// Terminate the connection to server at the end of this HTTP request/response session.
/// </summary>
public void TerminateServerConnection()
{
WebSession.CloseServerConnection = true;
}
/// <summary>
/// Implement any cleanup here
/// </summary>
public override void Dispose()
{
MultipartRequestPartSent = null;
base.Dispose();
}
}
}
using System;
using System.Collections.Generic;
using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Models;
namespace Titanium.Web.Proxy.Exceptions
{
/// <summary>
/// Proxy authorization exception.
/// </summary>
public class ProxyAuthorizationException : ProxyException
{
/// <summary>
/// Initializes a new instance of the <see cref="ProxyAuthorizationException" /> class.
/// </summary>
/// <param name="message">Exception message.</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="headers">Http's headers associated</param>
internal ProxyAuthorizationException(string message, SessionEventArgsBase session, Exception innerException,
IEnumerable<HttpHeader> headers) : base(message, innerException)
{
Session = session;
Headers = headers;
}
/// <summary>
/// The current session within which this error happened.
/// </summary>
public SessionEventArgsBase Session { get; }
/// <summary>
/// Headers associated with the authorization exception.
/// </summary>
public IEnumerable<HttpHeader> Headers { get; }
}
}
using System;
using System.Collections.Generic;
using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Models;
namespace Titanium.Web.Proxy.Exceptions
{
/// <summary>
/// Proxy authorization exception.
/// </summary>
public class ProxyAuthorizationException : ProxyException
{
/// <summary>
/// Initializes a new instance of the <see cref="ProxyAuthorizationException" /> class.
/// </summary>
/// <param name="message">Exception message.</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="headers">Http's headers associated</param>
internal ProxyAuthorizationException(string message, SessionEventArgsBase session, Exception innerException,
IEnumerable<HttpHeader> headers) : base(message, innerException)
{
Session = session;
Headers = headers;
}
/// <summary>
/// The current session within which this error happened.
/// </summary>
public SessionEventArgsBase Session { get; }
/// <summary>
/// Headers associated with the authorization exception.
/// </summary>
public IEnumerable<HttpHeader> Headers { get; }
}
}
using System;
namespace Titanium.Web.Proxy.Exceptions
{
/// <summary>
/// Base class exception associated with this proxy server.
/// </summary>
public abstract class ProxyException : Exception
{
/// <summary>
/// Initializes a new instance of the <see cref="ProxyException" /> class.
/// - must be invoked by derived classes' constructors
/// </summary>
/// <param name="message">Exception message</param>
protected ProxyException(string message) : base(message)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ProxyException" /> class.
/// - must be invoked by derived classes' constructors
/// </summary>
/// <param name="message">Excception message</param>
/// <param name="innerException">Inner exception associated</param>
protected ProxyException(string message, Exception innerException) : base(message, innerException)
{
}
}
}
using System;
namespace Titanium.Web.Proxy.Exceptions
{
/// <summary>
/// Base class exception associated with this proxy server.
/// </summary>
public abstract class ProxyException : Exception
{
/// <summary>
/// Initializes a new instance of the <see cref="ProxyException" /> class.
/// - must be invoked by derived classes' constructors
/// </summary>
/// <param name="message">Exception message</param>
protected ProxyException(string message) : base(message)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ProxyException" /> class.
/// - must be invoked by derived classes' constructors
/// </summary>
/// <param name="message">Excception message</param>
/// <param name="innerException">Inner exception associated</param>
protected ProxyException(string message, Exception innerException) : base(message, innerException)
{
}
}
}
using System;
using Titanium.Web.Proxy.EventArguments;
namespace Titanium.Web.Proxy.Exceptions
{
/// <summary>
/// Proxy HTTP exception.
/// </summary>
public class ProxyHttpException : ProxyException
{
/// <summary>
/// Initializes a new instance of the <see cref="ProxyHttpException" /> class.
/// </summary>
/// <param name="message">Message for this exception</param>
/// <param name="innerException">Associated inner 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(
message, innerException)
{
SessionEventArgs = sessionEventArgs;
}
/// <summary>
/// Gets session info associated to the exception.
/// </summary>
/// <remarks>
/// This object properties should not be edited.
/// </remarks>
public SessionEventArgs SessionEventArgs { get; }
}
}
using System;
using Titanium.Web.Proxy.EventArguments;
namespace Titanium.Web.Proxy.Exceptions
{
/// <summary>
/// Proxy HTTP exception.
/// </summary>
public class ProxyHttpException : ProxyException
{
/// <summary>
/// Initializes a new instance of the <see cref="ProxyHttpException" /> class.
/// </summary>
/// <param name="message">Message for this exception</param>
/// <param name="innerException">Associated inner 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(
message, innerException)
{
SessionEventArgs = sessionEventArgs;
}
/// <summary>
/// Gets session info associated to the exception.
/// </summary>
/// <remarks>
/// This object properties should not be edited.
/// </remarks>
public SessionEventArgs SessionEventArgs { get; }
}
}
......@@ -152,11 +152,14 @@ namespace Titanium.Web.Proxy
SslStream sslStream = null;
//don't pass cancellation token here
//it could cause floating server connections when client exits
prefetchConnectionTask = tcpConnectionFactory.GetServerConnection(this, connectArgs,
isConnect: true, applicationProtocols: null, noCache: false,
cancellationToken: CancellationToken.None);
if (EnableTcpServerConnectionPrefetch)
{
//don't pass cancellation token here
//it could cause floating server connections when client exits
prefetchConnectionTask = tcpConnectionFactory.GetServerConnection(this, connectArgs,
isConnect: true, applicationProtocols: null, noCache: false,
cancellationToken: CancellationToken.None);
}
try
{
......@@ -204,10 +207,10 @@ namespace Titanium.Web.Proxy
decryptSsl = false;
}
if(!decryptSsl)
if (!decryptSsl)
{
await tcpConnectionFactory.Release(prefetchConnectionTask, true);
prefetchConnectionTask = null;
prefetchConnectionTask = null;
}
}
......
......@@ -16,7 +16,7 @@ namespace Titanium.Web.Proxy.Helpers
private readonly Stream stream;
private readonly IBufferPool bufferPool;
private static readonly byte[] newLine = ProxyConstants.NewLine;
private static readonly byte[] newLine = ProxyConstants.NewLineBytes;
private static readonly Encoder encoder = Encoding.ASCII.GetEncoder();
......@@ -109,9 +109,9 @@ namespace Titanium.Web.Proxy.Helpers
var headerBuilder = new StringBuilder();
foreach (var header in headers)
{
headerBuilder.AppendLine(header.ToString());
headerBuilder.Append($"{header.ToString()}{ProxyConstants.NewLine}");
}
headerBuilder.AppendLine();
headerBuilder.Append(ProxyConstants.NewLine);
await WriteAsync(headerBuilder.ToString(), cancellationToken);
......
using System.Linq;
using System.Net;
using System.Net.Sockets;
namespace Titanium.Web.Proxy.Helpers
{
internal class NetworkHelper
{
/// <summary>
/// Adapated from below link
/// http://stackoverflow.com/questions/11834091/how-to-check-if-localhost
/// </summary>
/// <param name="address"></param>
/// <returns></returns>
internal static bool IsLocalIpAddress(IPAddress address)
{
if (IPAddress.IsLoopback(address))
{
return true;
}
// get local IP addresses
var localIPs = Dns.GetHostAddresses(Dns.GetHostName());
// test if any host IP equals to any local IP or to localhost
return localIPs.Contains(address);
}
internal static bool IsLocalIpAddress(string hostName)
{
hostName = hostName.ToLower();
if (hostName == "127.0.0.1"
|| hostName == "localhost")
{
return true;
}
var localhostDnsName = Dns.GetHostName().ToLower();
//if hostname matches current machine DNS name
if (hostName == localhostDnsName)
{
return true;
}
var isLocalhost = false;
IPHostEntry hostEntry = null;
//check if parsable to an IP Address
if (IPAddress.TryParse(hostName, out var ipAddress))
{
hostEntry = Dns.GetHostEntry(localhostDnsName);
isLocalhost = hostEntry.AddressList.Any(x => x.Equals(ipAddress));
}
if (!isLocalhost)
{
try
{
hostEntry = Dns.GetHostEntry(hostName);
isLocalhost = hostEntry.AddressList.Any(x => hostEntry.AddressList.Any(x.Equals));
}
catch (SocketException)
{
}
}
return isLocalhost;
}
}
}
using System.Linq;
using System.Net;
using System.Net.Sockets;
namespace Titanium.Web.Proxy.Helpers
{
internal class NetworkHelper
{
/// <summary>
/// Adapated from below link
/// http://stackoverflow.com/questions/11834091/how-to-check-if-localhost
/// </summary>
/// <param name="address"></param>
/// <returns></returns>
internal static bool IsLocalIpAddress(IPAddress address)
{
if (IPAddress.IsLoopback(address))
{
return true;
}
// get local IP addresses
var localIPs = Dns.GetHostAddresses(Dns.GetHostName());
// test if any host IP equals to any local IP or to localhost
return localIPs.Contains(address);
}
internal static bool IsLocalIpAddress(string hostName)
{
hostName = hostName.ToLower();
if (hostName == "127.0.0.1"
|| hostName == "localhost")
{
return true;
}
var localhostDnsName = Dns.GetHostName().ToLower();
//if hostname matches current machine DNS name
if (hostName == localhostDnsName)
{
return true;
}
var isLocalhost = false;
IPHostEntry hostEntry = null;
//check if parsable to an IP Address
if (IPAddress.TryParse(hostName, out var ipAddress))
{
hostEntry = Dns.GetHostEntry(localhostDnsName);
isLocalhost = hostEntry.AddressList.Any(x => x.Equals(ipAddress));
}
if (!isLocalhost)
{
try
{
hostEntry = Dns.GetHostEntry(hostName);
isLocalhost = hostEntry.AddressList.Any(x => hostEntry.AddressList.Any(x.Equals));
}
catch (SocketException)
{
}
}
return isLocalhost;
}
}
}
using System;
#if NETSTANDARD2_0
using System.Runtime.InteropServices;
#endif
namespace Titanium.Web.Proxy.Helpers
{
/// <summary>
/// Run time helpers
/// </summary>
internal class RunTime
public static class RunTime
{
/// <summary>
/// cache for mono runtime check
......@@ -16,33 +16,49 @@ namespace Titanium.Web.Proxy.Helpers
/// <returns></returns>
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
/// <summary>
/// cache for Windows platform check
/// </summary>
/// <returns></returns>
private static readonly Lazy<bool> isRunningOnWindows
= new Lazy<bool>(() => RuntimeInformation.IsOSPlatform(OSPlatform.Windows));
private static readonly Lazy<bool> isRunningOnLinux
= new Lazy<bool>(() => RuntimeInformation.IsOSPlatform(OSPlatform.Linux));
/// <summary>
/// cache for Windows platform check
/// </summary>
/// <returns></returns>
private static bool isRunningOnWindows => RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
private static bool isRunningOnLinux => RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
private static bool isRunningOnMac => RuntimeInformation.IsOSPlatform(OSPlatform.OSX);
#endif
/// <summary>
/// Is running on Mono?
/// </summary>
internal static bool IsRunningOnMono => isRunningOnMono.Value;
#if NETSTANDARD2_0
internal static bool IsLinux => isRunningOnLinux.Value;
public static bool IsLinux => isRunningOnLinux;
#else
internal static bool IsLinux => !IsWindows;
public static bool IsLinux => isRunningOnMonoLinux.Value;
#endif
#if NETSTANDARD2_0
internal static bool IsWindows => isRunningOnWindows.Value;
public static bool IsWindows => isRunningOnWindows;
#else
internal static bool IsWindows => true;
public static bool IsWindows => !IsLinux && !IsMac;
#endif
#if NETSTANDARD2_0
public static bool IsMac => isRunningOnMac;
#else
public static bool IsMac => isRunningOnMonoMac.Value;
#endif
}
}
using System;
using System.IO;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Exceptions;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Network.Tcp;
namespace Titanium.Web.Proxy.Http
{
/// <summary>
/// Used to communicate with the server over HTTP(S)
/// </summary>
public class HttpWebClient
{
internal HttpWebClient(Request request = null, Response response = null)
{
Request = request ?? new Request();
Response = response ?? new Response();
}
/// <summary>
/// Connection to server
/// </summary>
internal TcpServerConnection ServerConnection { get; set; }
/// <summary>
/// Should we close the server connection at the end of this HTTP request/response session.
/// </summary>
internal bool CloseServerConnection { get; set; }
/// <summary>
/// Stores internal data for the session.
/// </summary>
internal InternalDataStore Data { get; } = new InternalDataStore();
/// <summary>
/// Gets or sets the user data.
/// </summary>
public object UserData { get; set; }
/// <summary>
/// Override UpStreamEndPoint for this request; Local NIC via request is made
/// </summary>
public IPEndPoint UpStreamEndPoint { get; set; }
/// <summary>
/// Headers passed with Connect.
/// </summary>
public ConnectRequest ConnectRequest { get; internal set; }
/// <summary>
/// Web Request.
/// </summary>
public Request Request { get; }
/// <summary>
/// Web Response.
/// </summary>
public Response Response { get; internal set; }
/// <summary>
/// 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
/// </summary>
public Lazy<int> ProcessId { get; internal set; }
/// <summary>
/// Is Https?
/// </summary>
public bool IsHttps => Request.IsHttps;
/// <summary>
/// Set the tcp connection to server used by this webclient
/// </summary>
/// <param name="serverConnection">Instance of <see cref="TcpServerConnection" /></param>
internal void SetConnection(TcpServerConnection serverConnection)
{
serverConnection.LastAccess = DateTime.Now;
ServerConnection = serverConnection;
}
/// <summary>
/// Prepare and send the http(s) request
/// </summary>
/// <returns></returns>
internal async Task SendRequest(bool enable100ContinueBehaviour, bool isTransparent,
CancellationToken cancellationToken)
{
var upstreamProxy = ServerConnection.UpStreamProxy;
bool useUpstreamProxy = upstreamProxy != null && ServerConnection.IsHttps == false;
var writer = ServerConnection.StreamWriter;
// prepare the request & headers
await writer.WriteLineAsync(Request.CreateRequestLine(Request.Method,
useUpstreamProxy || isTransparent ? Request.OriginalUrl : Request.RequestUri.PathAndQuery,
Request.HttpVersion), cancellationToken);
var headerBuilder = new StringBuilder();
// Send Authentication to Upstream proxy if needed
if (!isTransparent && upstreamProxy != null
&& ServerConnection.IsHttps == false
&& !string.IsNullOrEmpty(upstreamProxy.UserName)
&& upstreamProxy.Password != null)
{
headerBuilder.AppendLine(HttpHeader.ProxyConnectionKeepAlive.ToString());
headerBuilder.AppendLine(HttpHeader.GetProxyAuthorizationHeader(upstreamProxy.UserName, upstreamProxy.Password).ToString());
}
// write request headers
foreach (var header in Request.Headers)
{
if (isTransparent || header.Name != KnownHeaders.ProxyAuthorization)
{
headerBuilder.AppendLine(header.ToString());
}
}
headerBuilder.AppendLine();
await writer.WriteAsync(headerBuilder.ToString(), cancellationToken);
if (enable100ContinueBehaviour)
{
if (Request.ExpectContinue)
{
string httpStatus;
try
{
httpStatus = await ServerConnection.Stream.ReadLineAsync(cancellationToken);
if (httpStatus == null)
{
throw new ServerConnectionException("Server connection was closed.");
}
}
catch (Exception e) when (!(e is ServerConnectionException))
{
throw new ServerConnectionException("Server connection was closed.");
}
Response.ParseResponseLine(httpStatus, out _, out int responseStatusCode,
out string responseStatusDescription);
// find if server is willing for expect continue
if (responseStatusCode == (int)HttpStatusCode.Continue
&& responseStatusDescription.EqualsIgnoreCase("continue"))
{
Request.Is100Continue = true;
await ServerConnection.Stream.ReadLineAsync(cancellationToken);
}
else if (responseStatusCode == (int)HttpStatusCode.ExpectationFailed
&& responseStatusDescription.EqualsIgnoreCase("expectation failed"))
{
Request.ExpectationFailed = true;
await ServerConnection.Stream.ReadLineAsync(cancellationToken);
}
}
}
}
/// <summary>
/// Receive and parse the http response from server
/// </summary>
/// <returns></returns>
internal async Task ReceiveResponse(CancellationToken cancellationToken)
{
// return if this is already read
if (Response.StatusCode != 0)
{
return;
}
string httpStatus;
try
{
httpStatus = await ServerConnection.Stream.ReadLineAsync(cancellationToken);
if (httpStatus == null)
{
throw new ServerConnectionException("Server connection was closed.");
}
}
catch (Exception e) when (!(e is ServerConnectionException))
{
throw new ServerConnectionException("Server connection was closed.");
}
if (httpStatus == string.Empty)
{
httpStatus = await ServerConnection.Stream.ReadLineAsync(cancellationToken);
}
Response.ParseResponseLine(httpStatus, out var version, out int statusCode, out string statusDescription);
Response.HttpVersion = version;
Response.StatusCode = statusCode;
Response.StatusDescription = statusDescription;
// For HTTP 1.1 comptibility server may send expect-continue even if not asked for it in request
if (Response.StatusCode == (int)HttpStatusCode.Continue
&& Response.StatusDescription.EqualsIgnoreCase("continue"))
{
// Read the next line after 100-continue
Response.Is100Continue = true;
Response.StatusCode = 0;
await ServerConnection.Stream.ReadLineAsync(cancellationToken);
// now receive response
await ReceiveResponse(cancellationToken);
return;
}
if (Response.StatusCode == (int)HttpStatusCode.ExpectationFailed
&& Response.StatusDescription.EqualsIgnoreCase("expectation failed"))
{
// read next line after expectation failed response
Response.ExpectationFailed = true;
Response.StatusCode = 0;
await ServerConnection.Stream.ReadLineAsync(cancellationToken);
// now receive response
await ReceiveResponse(cancellationToken);
return;
}
// Read the response headers in to unique and non-unique header collections
await HeaderParser.ReadHeaders(ServerConnection.Stream, Response.Headers, cancellationToken);
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
internal void FinishSession()
{
ServerConnection = null;
ConnectRequest?.FinishSession();
Request?.FinishSession();
Response?.FinishSession();
Data.Clear();
UserData = null;
}
}
}
using System;
using System.IO;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Exceptions;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Network.Tcp;
using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Http
{
/// <summary>
/// Used to communicate with the server over HTTP(S)
/// </summary>
public class HttpWebClient
{
internal HttpWebClient(Request request = null, Response response = null)
{
Request = request ?? new Request();
Response = response ?? new Response();
}
/// <summary>
/// Connection to server
/// </summary>
internal TcpServerConnection ServerConnection { get; set; }
/// <summary>
/// Should we close the server connection at the end of this HTTP request/response session.
/// </summary>
internal bool CloseServerConnection { get; set; }
/// <summary>
/// Stores internal data for the session.
/// </summary>
internal InternalDataStore Data { get; } = new InternalDataStore();
/// <summary>
/// Gets or sets the user data.
/// </summary>
public object UserData { get; set; }
/// <summary>
/// Override UpStreamEndPoint for this request; Local NIC via request is made
/// </summary>
public IPEndPoint UpStreamEndPoint { get; set; }
/// <summary>
/// Headers passed with Connect.
/// </summary>
public ConnectRequest ConnectRequest { get; internal set; }
/// <summary>
/// Web Request.
/// </summary>
public Request Request { get; }
/// <summary>
/// Web Response.
/// </summary>
public Response Response { get; internal set; }
/// <summary>
/// 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
/// </summary>
public Lazy<int> ProcessId { get; internal set; }
/// <summary>
/// Is Https?
/// </summary>
public bool IsHttps => Request.IsHttps;
/// <summary>
/// Set the tcp connection to server used by this webclient
/// </summary>
/// <param name="serverConnection">Instance of <see cref="TcpServerConnection" /></param>
internal void SetConnection(TcpServerConnection serverConnection)
{
serverConnection.LastAccess = DateTime.Now;
ServerConnection = serverConnection;
}
/// <summary>
/// Prepare and send the http(s) request
/// </summary>
/// <returns></returns>
internal async Task SendRequest(bool enable100ContinueBehaviour, bool isTransparent,
CancellationToken cancellationToken)
{
var upstreamProxy = ServerConnection.UpStreamProxy;
bool useUpstreamProxy = upstreamProxy != null && ServerConnection.IsHttps == false;
var writer = ServerConnection.StreamWriter;
// prepare the request & headers
await writer.WriteLineAsync(Request.CreateRequestLine(Request.Method,
useUpstreamProxy || isTransparent ? Request.OriginalUrl : Request.RequestUri.PathAndQuery,
Request.HttpVersion), cancellationToken);
var headerBuilder = new StringBuilder();
// Send Authentication to Upstream proxy if needed
if (!isTransparent && upstreamProxy != null
&& ServerConnection.IsHttps == false
&& !string.IsNullOrEmpty(upstreamProxy.UserName)
&& upstreamProxy.Password != null)
{
headerBuilder.Append($"{HttpHeader.ProxyConnectionKeepAlive}{ProxyConstants.NewLine}");
headerBuilder.Append($"{HttpHeader.GetProxyAuthorizationHeader(upstreamProxy.UserName, upstreamProxy.Password)}{ProxyConstants.NewLine}");
}
// write request headers
foreach (var header in Request.Headers)
{
if (isTransparent || header.Name != KnownHeaders.ProxyAuthorization)
{
headerBuilder.Append($"{header}{ProxyConstants.NewLine}");
}
}
headerBuilder.Append(ProxyConstants.NewLine);
await writer.WriteAsync(headerBuilder.ToString(), cancellationToken);
if (enable100ContinueBehaviour)
{
if (Request.ExpectContinue)
{
string httpStatus;
try
{
httpStatus = await ServerConnection.Stream.ReadLineAsync(cancellationToken);
if (httpStatus == null)
{
throw new ServerConnectionException("Server connection was closed.");
}
}
catch (Exception e) when (!(e is ServerConnectionException))
{
throw new ServerConnectionException("Server connection was closed.");
}
Response.ParseResponseLine(httpStatus, out _, out int responseStatusCode,
out string responseStatusDescription);
// find if server is willing for expect continue
if (responseStatusCode == (int)HttpStatusCode.Continue
&& responseStatusDescription.EqualsIgnoreCase("continue"))
{
Request.Is100Continue = true;
await ServerConnection.Stream.ReadLineAsync(cancellationToken);
}
else if (responseStatusCode == (int)HttpStatusCode.ExpectationFailed
&& responseStatusDescription.EqualsIgnoreCase("expectation failed"))
{
Request.ExpectationFailed = true;
await ServerConnection.Stream.ReadLineAsync(cancellationToken);
}
}
}
}
/// <summary>
/// Receive and parse the http response from server
/// </summary>
/// <returns></returns>
internal async Task ReceiveResponse(CancellationToken cancellationToken)
{
// return if this is already read
if (Response.StatusCode != 0)
{
return;
}
string httpStatus;
try
{
httpStatus = await ServerConnection.Stream.ReadLineAsync(cancellationToken);
if (httpStatus == null)
{
throw new ServerConnectionException("Server connection was closed.");
}
}
catch (Exception e) when (!(e is ServerConnectionException))
{
throw new ServerConnectionException("Server connection was closed.");
}
if (httpStatus == string.Empty)
{
httpStatus = await ServerConnection.Stream.ReadLineAsync(cancellationToken);
}
Response.ParseResponseLine(httpStatus, out var version, out int statusCode, out string statusDescription);
Response.HttpVersion = version;
Response.StatusCode = statusCode;
Response.StatusDescription = statusDescription;
// For HTTP 1.1 comptibility server may send expect-continue even if not asked for it in request
if (Response.StatusCode == (int)HttpStatusCode.Continue
&& Response.StatusDescription.EqualsIgnoreCase("continue"))
{
// Read the next line after 100-continue
Response.Is100Continue = true;
Response.StatusCode = 0;
await ServerConnection.Stream.ReadLineAsync(cancellationToken);
// now receive response
await ReceiveResponse(cancellationToken);
return;
}
if (Response.StatusCode == (int)HttpStatusCode.ExpectationFailed
&& Response.StatusDescription.EqualsIgnoreCase("expectation failed"))
{
// read next line after expectation failed response
Response.ExpectationFailed = true;
Response.StatusCode = 0;
await ServerConnection.Stream.ReadLineAsync(cancellationToken);
// now receive response
await ReceiveResponse(cancellationToken);
return;
}
// Read the response headers in to unique and non-unique header collections
await HeaderParser.ReadHeaders(ServerConnection.Stream, Response.Headers, cancellationToken);
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
internal void FinishSession()
{
ServerConnection = null;
ConnectRequest?.FinishSession();
Request?.FinishSession();
Response?.FinishSession();
Data.Clear();
UserData = null;
}
}
}
......@@ -140,13 +140,13 @@ namespace Titanium.Web.Proxy.Http
get
{
var sb = new StringBuilder();
sb.AppendLine(CreateRequestLine(Method, OriginalUrl, HttpVersion));
sb.Append($"{CreateRequestLine(Method, OriginalUrl, HttpVersion)}{ProxyConstants.NewLine}");
foreach (var header in Headers)
{
sb.AppendLine(header.ToString());
sb.Append($"{header.ToString()}{ProxyConstants.NewLine}");
}
sb.AppendLine();
sb.Append(ProxyConstants.NewLine);
return sb.ToString();
}
}
......
......@@ -110,13 +110,13 @@ namespace Titanium.Web.Proxy.Http
get
{
var sb = new StringBuilder();
sb.AppendLine(CreateResponseLine(HttpVersion, StatusCode, StatusDescription));
sb.Append($"{CreateResponseLine(HttpVersion, StatusCode, StatusDescription)}{ProxyConstants.NewLine}");
foreach (var header in Headers)
{
sb.AppendLine(header.ToString());
sb.Append($"{header.ToString()}{ProxyConstants.NewLine}");
}
sb.AppendLine();
sb.Append(ProxyConstants.NewLine);
return sb.ToString();
}
}
......@@ -145,7 +145,7 @@ namespace Titanium.Web.Proxy.Http
out string statusDescription)
{
var httpResult = httpStatus.Split(ProxyConstants.SpaceSplit, 3);
if (httpResult.Length != 3)
if (httpResult.Length <= 1)
{
throw new Exception("Invalid HTTP status line: " + httpStatus);
}
......@@ -159,7 +159,7 @@ namespace Titanium.Web.Proxy.Http
}
statusCode = int.Parse(httpResult[1]);
statusDescription = httpResult[2];
statusDescription = httpResult.Length > 2 ? httpResult[2] : string.Empty;
}
}
}
using System;
using System.Net;
namespace Titanium.Web.Proxy.Models
{
/// <summary>
/// An upstream proxy this proxy uses if any.
/// </summary>
public class ExternalProxy
{
private static readonly Lazy<NetworkCredential> defaultCredentials =
new Lazy<NetworkCredential>(() => CredentialCache.DefaultNetworkCredentials);
private string password;
private string userName;
/// <summary>
/// Use default windows credentials?
/// </summary>
public bool UseDefaultCredentials { get; set; }
/// <summary>
/// Bypass this proxy for connections to localhost?
/// </summary>
public bool BypassLocalhost { get; set; }
/// <summary>
/// Username.
/// </summary>
public string UserName
{
get => UseDefaultCredentials ? defaultCredentials.Value.UserName : userName;
set
{
userName = value;
if (defaultCredentials.Value.UserName != userName)
{
UseDefaultCredentials = false;
}
}
}
/// <summary>
/// Password.
/// </summary>
public string Password
{
get => UseDefaultCredentials ? defaultCredentials.Value.Password : password;
set
{
password = value;
if (defaultCredentials.Value.Password != password)
{
UseDefaultCredentials = false;
}
}
}
/// <summary>
/// Host name.
/// </summary>
public string HostName { get; set; }
/// <summary>
/// Port.
/// </summary>
public int Port { get; set; }
/// <summary>
/// Get cache key for Tcp connection cahe.
/// </summary>
/// <returns></returns>
internal string GetCacheKey()
{
return $"{HostName}-{Port}" + (UseDefaultCredentials ? $"-{UserName}-{Password}" : string.Empty);
}
/// <summary>
/// returns data in Hostname:port format.
/// </summary>
/// <returns></returns>
public override string ToString()
{
return $"{HostName}:{Port}";
}
}
}
using System;
using System.Net;
namespace Titanium.Web.Proxy.Models
{
/// <summary>
/// An upstream proxy this proxy uses if any.
/// </summary>
public class ExternalProxy
{
private static readonly Lazy<NetworkCredential> defaultCredentials =
new Lazy<NetworkCredential>(() => CredentialCache.DefaultNetworkCredentials);
private string password;
private string userName;
/// <summary>
/// Use default windows credentials?
/// </summary>
public bool UseDefaultCredentials { get; set; }
/// <summary>
/// Bypass this proxy for connections to localhost?
/// </summary>
public bool BypassLocalhost { get; set; }
/// <summary>
/// Username.
/// </summary>
public string UserName
{
get => UseDefaultCredentials ? defaultCredentials.Value.UserName : userName;
set
{
userName = value;
if (defaultCredentials.Value.UserName != userName)
{
UseDefaultCredentials = false;
}
}
}
/// <summary>
/// Password.
/// </summary>
public string Password
{
get => UseDefaultCredentials ? defaultCredentials.Value.Password : password;
set
{
password = value;
if (defaultCredentials.Value.Password != password)
{
UseDefaultCredentials = false;
}
}
}
/// <summary>
/// Host name.
/// </summary>
public string HostName { get; set; }
/// <summary>
/// Port.
/// </summary>
public int Port { get; set; }
/// <summary>
/// Get cache key for Tcp connection cahe.
/// </summary>
/// <returns></returns>
internal string GetCacheKey()
{
return $"{HostName}-{Port}" + (UseDefaultCredentials ? $"-{UserName}-{Password}" : string.Empty);
}
/// <summary>
/// returns data in Hostname:port format.
/// </summary>
/// <returns></returns>
public override string ToString()
{
return $"{HostName}:{Port}";
}
}
}
......@@ -52,6 +52,7 @@ namespace Titanium.Web.Proxy.Network.Certificate
internal WinCertificateMaker(ExceptionHandler exceptionFunc)
{
this.exceptionFunc = exceptionFunc;
typeX500DN = Type.GetTypeFromProgID("X509Enrollment.CX500DistinguishedName", true);
typeX509PrivateKey = Type.GetTypeFromProgID("X509Enrollment.CX509PrivateKey", true);
typeOID = Type.GetTypeFromProgID("X509Enrollment.CObjectId", true);
......@@ -74,13 +75,41 @@ namespace Titanium.Web.Proxy.Network.Certificate
/// <summary>
/// Make certificate.
/// </summary>
/// <param name="sSubjectCN"></param>
/// <param name="isRoot"></param>
/// <param name="signingCert"></param>
/// <returns></returns>
public X509Certificate2 MakeCertificate(string sSubjectCN, bool isRoot, X509Certificate2 signingCert = null)
{
return makeCertificateInternal(sSubjectCN, isRoot, true, signingCert);
return makeCertificate(sSubjectCN, isRoot, true, signingCert);
}
private X509Certificate2 makeCertificate(string sSubjectCN, bool isRoot,
bool switchToMTAIfNeeded, X509Certificate2 signingCert = null,
CancellationToken cancellationToken = default)
{
if (switchToMTAIfNeeded && Thread.CurrentThread.GetApartmentState() != ApartmentState.MTA)
{
return Task.Run(() => makeCertificate(sSubjectCN, isRoot, false, signingCert),
cancellationToken).Result;
}
// Subject
string fullSubject = $"CN={sSubjectCN}";
// Sig Algo
const string hashAlgo = "SHA256";
// Grace Days
const int graceDays = -366;
// ValiDays
const int validDays = 1825;
// KeyLength
const int keyLength = 2048;
var graceTime = DateTime.Now.AddDays(graceDays);
var now = DateTime.Now;
var certificate = makeCertificate(isRoot, sSubjectCN, fullSubject, keyLength, hashAlgo, graceTime,
now.AddDays(validDays), isRoot ? null : signingCert);
return certificate;
}
private X509Certificate2 makeCertificate(bool isRoot, string subject, string fullSubject,
......@@ -271,39 +300,9 @@ namespace Titanium.Web.Proxy.Network.Certificate
string empty = (string)typeX509Enrollment.InvokeMember("CreatePFX", BindingFlags.InvokeMethod, null,
x509Enrollment, typeValue);
return new X509Certificate2(Convert.FromBase64String(empty), string.Empty, X509KeyStorageFlags.Exportable);
}
private X509Certificate2 makeCertificateInternal(string sSubjectCN, bool isRoot,
bool switchToMTAIfNeeded, X509Certificate2 signingCert = null,
CancellationToken cancellationToken = default)
{
if (switchToMTAIfNeeded && Thread.CurrentThread.GetApartmentState() != ApartmentState.MTA)
{
return Task.Run(() => makeCertificateInternal(sSubjectCN, isRoot, false, signingCert),
cancellationToken).Result;
}
// Subject
string fullSubject = $"CN={sSubjectCN}";
// Sig Algo
const string hashAlgo = "SHA256";
// Grace Days
const int graceDays = -366;
// ValiDays
const int validDays = 1825;
// KeyLength
const int keyLength = 2048;
var graceTime = DateTime.Now.AddDays(graceDays);
var now = DateTime.Now;
var certificate = makeCertificate(isRoot, sSubjectCN, fullSubject, keyLength, hashAlgo, graceTime,
now.AddDays(validDays), isRoot ? null : signingCert);
return certificate;
}
}
}
......@@ -25,7 +25,8 @@ namespace Titanium.Web.Proxy.Network
BouncyCastle = 0,
/// <summary>
/// Uses Windows Certification Generation API.
/// Uses Windows Certification Generation API and only valid in Windows OS.
/// Observed to be faster than BouncyCastle.
/// Bug #468 Reported.
/// </summary>
DefaultWindows = 1
......
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Security;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using StreamExtended.Network;
using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Models;
namespace Titanium.Web.Proxy.Network.Tcp
{
/// <summary>
/// A class that manages Tcp Connection to server used by this proxy server.
/// </summary>
internal class TcpConnectionFactory : IDisposable
{
//Tcp server connection pool cache
private readonly ConcurrentDictionary<string, ConcurrentQueue<TcpServerConnection>> cache
= new ConcurrentDictionary<string, ConcurrentQueue<TcpServerConnection>>();
//Tcp connections waiting to be disposed by cleanup task
private readonly ConcurrentBag<TcpServerConnection> disposalBag =
new ConcurrentBag<TcpServerConnection>();
//cache object race operations lock
private readonly SemaphoreSlim @lock = new SemaphoreSlim(1);
private volatile bool runCleanUpTask = true;
internal TcpConnectionFactory(ProxyServer server)
{
this.server = server;
Task.Run(async () => await clearOutdatedConnections());
}
internal ProxyServer server { get; set; }
internal string GetConnectionCacheKey(string remoteHostName, int remotePort,
bool isHttps, List<SslApplicationProtocol> applicationProtocols,
ProxyServer proxyServer, IPEndPoint upStreamEndPoint, ExternalProxy externalProxy)
{
//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.
//That can create cache miss for same server connection unneccessarily expecially when prefetcing with Connect.
//http version 2 is separated using applicationProtocols below.
var cacheKeyBuilder = new StringBuilder($"{remoteHostName}-{remotePort}-" +
//when creating Tcp client isConnect won't matter
$"{isHttps}-");
if (applicationProtocols != null)
{
foreach (var protocol in applicationProtocols.OrderBy(x => x))
{
cacheKeyBuilder.Append($"{protocol}-");
}
}
cacheKeyBuilder.Append(upStreamEndPoint != null
? $"{upStreamEndPoint.Address}-{upStreamEndPoint.Port}-"
: string.Empty);
cacheKeyBuilder.Append(externalProxy != null ? $"{externalProxy.GetCacheKey()}-" : string.Empty);
return cacheKeyBuilder.ToString();
}
/// <summary>
/// Gets the connection cache key.
/// </summary>
/// <param name="args">The session event arguments.</param>
/// <param name="applicationProtocol"></param>
/// <returns></returns>
internal async Task<string> GetConnectionCacheKey(ProxyServer server, SessionEventArgsBase args,
SslApplicationProtocol applicationProtocol)
{
List<SslApplicationProtocol> applicationProtocols = null;
if (applicationProtocol != default)
{
applicationProtocols = new List<SslApplicationProtocol> { applicationProtocol };
}
ExternalProxy customUpStreamProxy = null;
bool isHttps = args.IsHttps;
if (server.GetCustomUpStreamProxyFunc != null)
{
customUpStreamProxy = await server.GetCustomUpStreamProxyFunc(args);
}
args.CustomUpStreamProxyUsed = customUpStreamProxy;
return GetConnectionCacheKey(
args.WebSession.Request.RequestUri.Host,
args.WebSession.Request.RequestUri.Port,
isHttps, applicationProtocols,
server, args.WebSession.UpStreamEndPoint ?? server.UpStreamEndPoint,
customUpStreamProxy ?? (isHttps ? server.UpStreamHttpsProxy : server.UpStreamHttpProxy));
}
/// <summary>
/// Create a server connection.
/// </summary>
/// <param name="args">The session event arguments.</param>
/// <param name="isConnect">Is this a CONNECT request.</param>
/// <param name="applicationProtocol"></param>
/// <param name="cancellationToken">The cancellation token for this async task.</param>
/// <returns></returns>
internal Task<TcpServerConnection> GetServerConnection(ProxyServer server, SessionEventArgsBase args, bool isConnect,
SslApplicationProtocol applicationProtocol, bool noCache, CancellationToken cancellationToken)
{
List<SslApplicationProtocol> applicationProtocols = null;
if (applicationProtocol != default)
{
applicationProtocols = new List<SslApplicationProtocol> { applicationProtocol };
}
return GetServerConnection(server, args, isConnect, applicationProtocols, noCache, cancellationToken);
}
/// <summary>
/// Create a server connection.
/// </summary>
/// <param name="args">The session event arguments.</param>
/// <param name="isConnect">Is this a CONNECT request.</param>
/// <param name="applicationProtocols"></param>
/// <param name="cancellationToken">The cancellation token for this async task.</param>
/// <returns></returns>
internal async Task<TcpServerConnection> GetServerConnection(ProxyServer server, SessionEventArgsBase args, bool isConnect,
List<SslApplicationProtocol> applicationProtocols, bool noCache, CancellationToken cancellationToken)
{
ExternalProxy customUpStreamProxy = null;
bool isHttps = args.IsHttps;
if (server.GetCustomUpStreamProxyFunc != null)
{
customUpStreamProxy = await server.GetCustomUpStreamProxyFunc(args);
}
args.CustomUpStreamProxyUsed = customUpStreamProxy;
return await GetServerConnection(
args.WebSession.Request.RequestUri.Host,
args.WebSession.Request.RequestUri.Port,
args.WebSession.Request.HttpVersion,
isHttps, applicationProtocols, isConnect,
server, args.WebSession.UpStreamEndPoint ?? server.UpStreamEndPoint,
customUpStreamProxy ?? (isHttps ? server.UpStreamHttpsProxy : server.UpStreamHttpProxy),
noCache, cancellationToken);
}
/// <summary>
/// Gets a TCP connection to server from connection pool.
/// </summary>
/// <param name="remoteHostName">The remote hostname.</param>
/// <param name="remotePort">The remote port.</param>
/// <param name="httpVersion">The http version to use.</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="isConnect">Is this a CONNECT request.</param>
/// <param name="proxyServer">The current ProxyServer instance.</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="noCache">Not from cache/create new connection.</param>
/// <param name="cancellationToken">The cancellation token for this async task.</param>
/// <returns></returns>
internal async Task<TcpServerConnection> GetServerConnection(string remoteHostName, int remotePort,
Version httpVersion, bool isHttps, List<SslApplicationProtocol> applicationProtocols, bool isConnect,
ProxyServer proxyServer, IPEndPoint upStreamEndPoint, ExternalProxy externalProxy,
bool noCache, CancellationToken cancellationToken)
{
var cacheKey = GetConnectionCacheKey(remoteHostName, remotePort,
isHttps, applicationProtocols,
proxyServer, upStreamEndPoint, externalProxy);
if (proxyServer.EnableConnectionPool && !noCache)
{
if (cache.TryGetValue(cacheKey, out var existingConnections))
{
while (existingConnections.Count > 0)
{
if (existingConnections.TryDequeue(out var recentConnection))
{
//+3 seconds for potential delay after getting connection
var cutOff = DateTime.Now.AddSeconds(-1 * proxyServer.ConnectionTimeOutSeconds + 3);
if (recentConnection.LastAccess > cutOff
&& recentConnection.TcpClient.IsGoodConnection())
{
return recentConnection;
}
disposalBag.Add(recentConnection);
}
}
}
}
var connection = await createServerConnection(remoteHostName, remotePort, httpVersion, isHttps,
applicationProtocols, isConnect, proxyServer, upStreamEndPoint, externalProxy, cancellationToken);
connection.CacheKey = cacheKey;
return connection;
}
/// <summary>
/// Creates a TCP connection to server
/// </summary>
/// <param name="remoteHostName">The remote hostname.</param>
/// <param name="remotePort">The remote port.</param>
/// <param name="httpVersion">The http version to use.</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="isConnect">Is this a CONNECT request.</param>
/// <param name="proxyServer">The current ProxyServer instance.</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="cancellationToken">The cancellation token for this async task.</param>
/// <returns></returns>
private async Task<TcpServerConnection> createServerConnection(string remoteHostName, int remotePort,
Version httpVersion, bool isHttps, List<SslApplicationProtocol> applicationProtocols, bool isConnect,
ProxyServer proxyServer, IPEndPoint upStreamEndPoint, ExternalProxy externalProxy,
CancellationToken cancellationToken)
{
//deny connection to proxy end points to avoid infinite connection loop.
if (server.ProxyEndPoints.Any(x => x.Port == remotePort)
&& NetworkHelper.IsLocalIpAddress(remoteHostName))
{
throw new Exception($"A client is making HTTP request to one of the listening ports of this proxy {remoteHostName}:{remotePort}");
}
if (externalProxy != null)
{
if (server.ProxyEndPoints.Any(x => x.Port == externalProxy.Port)
&& 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}");
}
}
bool useUpstreamProxy = false;
// check if external proxy is set for HTTP/HTTPS
if (externalProxy != null &&
!(externalProxy.HostName == remoteHostName && externalProxy.Port == remotePort))
{
useUpstreamProxy = true;
// check if we need to ByPass
if (externalProxy.BypassLocalhost && NetworkHelper.IsLocalIpAddress(remoteHostName))
{
useUpstreamProxy = false;
}
}
TcpClient tcpClient = null;
CustomBufferedStream stream = null;
SslApplicationProtocol negotiatedApplicationProtocol = default;
try
{
tcpClient = new TcpClient(upStreamEndPoint)
{
ReceiveTimeout = proxyServer.ConnectionTimeOutSeconds * 1000,
SendTimeout = proxyServer.ConnectionTimeOutSeconds * 1000,
SendBufferSize = proxyServer.BufferSize,
ReceiveBufferSize = proxyServer.BufferSize,
LingerState = new LingerOption(true, proxyServer.TcpTimeWaitSeconds)
};
//linux has a bug with socket reuse in .net core.
if (proxyServer.ReuseSocket && RunTime.IsWindows || RunTime.IsRunningOnMono)
{
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 (useUpstreamProxy)
{
await tcpClient.ConnectAsync(externalProxy.HostName, externalProxy.Port);
}
else
{
await tcpClient.ConnectAsync(remoteHostName, remotePort);
}
await proxyServer.InvokeConnectionCreateEvent(tcpClient, false);
stream = new CustomBufferedStream(tcpClient.GetStream(), proxyServer.BufferPool, proxyServer.BufferSize);
if (useUpstreamProxy && (isConnect || isHttps))
{
var writer = new HttpRequestWriter(stream, proxyServer.BufferPool, proxyServer.BufferSize);
var connectRequest = new ConnectRequest
{
OriginalUrl = $"{remoteHostName}:{remotePort}",
HttpVersion = httpVersion
};
connectRequest.Headers.AddHeader(KnownHeaders.Connection, KnownHeaders.ConnectionKeepAlive);
if (!string.IsNullOrEmpty(externalProxy.UserName) && externalProxy.Password != null)
{
connectRequest.Headers.AddHeader(HttpHeader.ProxyConnectionKeepAlive);
connectRequest.Headers.AddHeader(
HttpHeader.GetProxyAuthorizationHeader(externalProxy.UserName, externalProxy.Password));
}
await writer.WriteRequestAsync(connectRequest, cancellationToken: cancellationToken);
string httpStatus = await stream.ReadLineAsync(cancellationToken);
Response.ParseResponseLine(httpStatus, out _, out int statusCode, out string statusDescription);
if (statusCode != 200 && !statusDescription.EqualsIgnoreCase("OK")
&& !statusDescription.EqualsIgnoreCase("Connection Established"))
{
throw new Exception("Upstream proxy failed to create a secure tunnel");
}
await stream.ReadAndIgnoreAllLinesAsync(cancellationToken);
}
if (isHttps)
{
var sslStream = new SslStream(stream, false, proxyServer.ValidateServerCertificate,
proxyServer.SelectClientCertificate);
stream = new CustomBufferedStream(sslStream, proxyServer.BufferPool, proxyServer.BufferSize);
var options = new SslClientAuthenticationOptions
{
ApplicationProtocols = applicationProtocols,
TargetHost = remoteHostName,
ClientCertificates = null,
EnabledSslProtocols = proxyServer.SupportedSslProtocols,
CertificateRevocationCheckMode = proxyServer.CheckCertificateRevocation
};
await sslStream.AuthenticateAsClientAsync(options, cancellationToken);
#if NETCOREAPP2_1
negotiatedApplicationProtocol = sslStream.NegotiatedApplicationProtocol;
#endif
}
}
catch (Exception)
{
stream?.Dispose();
tcpClient?.Close();
throw;
}
return new TcpServerConnection(proxyServer, tcpClient)
{
UpStreamProxy = externalProxy,
UpStreamEndPoint = upStreamEndPoint,
HostName = remoteHostName,
Port = remotePort,
IsHttps = isHttps,
NegotiatedApplicationProtocol = negotiatedApplicationProtocol,
UseUpstreamProxy = useUpstreamProxy,
StreamWriter = new HttpRequestWriter(stream, proxyServer.BufferPool, proxyServer.BufferSize),
Stream = stream,
Version = httpVersion
};
}
/// <summary>
/// Release connection back to cache.
/// </summary>
/// <param name="connection">The Tcp server connection to return.</param>
/// <param name="close">Should we just close the connection instead of reusing?</param>
internal async Task Release(TcpServerConnection connection, bool close = false)
{
if (connection == null)
{
return;
}
if (close || connection.IsWinAuthenticated || !server.EnableConnectionPool)
{
disposalBag.Add(connection);
return;
}
connection.LastAccess = DateTime.Now;
try
{
await @lock.WaitAsync();
while (true)
{
if (cache.TryGetValue(connection.CacheKey, out var existingConnections))
{
while (existingConnections.Count >= server.MaxCachedConnections)
{
if (existingConnections.TryDequeue(out var staleConnection))
{
disposalBag.Add(staleConnection);
}
}
existingConnections.Enqueue(connection);
break;
}
if (cache.TryAdd(connection.CacheKey,
new ConcurrentQueue<TcpServerConnection>(new[] { connection })))
{
break;
}
}
}
finally
{
@lock.Release();
}
}
internal async Task Release(Task<TcpServerConnection> connectionCreateTask, bool closeServerConnection)
{
if (connectionCreateTask != null)
{
TcpServerConnection connection = null;
try
{
connection = await connectionCreateTask;
}
catch { }
finally
{
await Release(connection, closeServerConnection);
}
}
}
private async Task clearOutdatedConnections()
{
while (runCleanUpTask)
{
try
{
foreach (var item in cache)
{
var queue = item.Value;
while (queue.Count > 0)
{
if (queue.TryDequeue(out var connection))
{
var cutOff = DateTime.Now.AddSeconds(-1 * server.ConnectionTimeOutSeconds);
if (!server.EnableConnectionPool
|| connection.LastAccess < cutOff)
{
disposalBag.Add(connection);
continue;
}
queue.Enqueue(connection);
break;
}
}
}
try
{
await @lock.WaitAsync();
//clear empty queues
var emptyKeys = cache.Where(x => x.Value.Count == 0).Select(x => x.Key).ToList();
foreach (string key in emptyKeys)
{
cache.TryRemove(key, out var _);
}
}
finally
{
@lock.Release();
}
while (!disposalBag.IsEmpty)
{
if (disposalBag.TryTake(out var connection))
{
connection?.Dispose();
}
}
}
catch (Exception e)
{
server.ExceptionFunc(new Exception("An error occurred when disposing server connections.", e));
}
finally
{
//cleanup every 3 seconds by default
await Task.Delay(1000 * 3);
}
}
}
public void Dispose()
{
runCleanUpTask = false;
try
{
@lock.Wait();
foreach (var queue in cache.Select(x => x.Value).ToList())
{
while (!queue.IsEmpty)
{
if (queue.TryDequeue(out var connection))
{
disposalBag.Add(connection);
}
}
}
cache.Clear();
}
finally
{
@lock.Release();
}
while (!disposalBag.IsEmpty)
{
if (disposalBag.TryTake(out var connection))
{
connection?.Dispose();
}
}
}
}
}
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Security;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using StreamExtended.Network;
using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Models;
namespace Titanium.Web.Proxy.Network.Tcp
{
/// <summary>
/// A class that manages Tcp Connection to server used by this proxy server.
/// </summary>
internal class TcpConnectionFactory : IDisposable
{
//Tcp server connection pool cache
private readonly ConcurrentDictionary<string, ConcurrentQueue<TcpServerConnection>> cache
= new ConcurrentDictionary<string, ConcurrentQueue<TcpServerConnection>>();
//Tcp connections waiting to be disposed by cleanup task
private readonly ConcurrentBag<TcpServerConnection> disposalBag =
new ConcurrentBag<TcpServerConnection>();
//cache object race operations lock
private readonly SemaphoreSlim @lock = new SemaphoreSlim(1);
private volatile bool runCleanUpTask = true;
internal TcpConnectionFactory(ProxyServer server)
{
this.server = server;
Task.Run(async () => await clearOutdatedConnections());
}
internal ProxyServer server { get; set; }
internal string GetConnectionCacheKey(string remoteHostName, int remotePort,
bool isHttps, List<SslApplicationProtocol> applicationProtocols,
ProxyServer proxyServer, IPEndPoint upStreamEndPoint, ExternalProxy externalProxy)
{
//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.
//That can create cache miss for same server connection unneccessarily expecially when prefetcing with Connect.
//http version 2 is separated using applicationProtocols below.
var cacheKeyBuilder = new StringBuilder($"{remoteHostName}-{remotePort}-" +
//when creating Tcp client isConnect won't matter
$"{isHttps}-");
if (applicationProtocols != null)
{
foreach (var protocol in applicationProtocols.OrderBy(x => x))
{
cacheKeyBuilder.Append($"{protocol}-");
}
}
cacheKeyBuilder.Append(upStreamEndPoint != null
? $"{upStreamEndPoint.Address}-{upStreamEndPoint.Port}-"
: string.Empty);
cacheKeyBuilder.Append(externalProxy != null ? $"{externalProxy.GetCacheKey()}-" : string.Empty);
return cacheKeyBuilder.ToString();
}
/// <summary>
/// Gets the connection cache key.
/// </summary>
/// <param name="args">The session event arguments.</param>
/// <param name="applicationProtocol"></param>
/// <returns></returns>
internal async Task<string> GetConnectionCacheKey(ProxyServer server, SessionEventArgsBase args,
SslApplicationProtocol applicationProtocol)
{
List<SslApplicationProtocol> applicationProtocols = null;
if (applicationProtocol != default)
{
applicationProtocols = new List<SslApplicationProtocol> { applicationProtocol };
}
ExternalProxy customUpStreamProxy = null;
bool isHttps = args.IsHttps;
if (server.GetCustomUpStreamProxyFunc != null)
{
customUpStreamProxy = await server.GetCustomUpStreamProxyFunc(args);
}
args.CustomUpStreamProxyUsed = customUpStreamProxy;
return GetConnectionCacheKey(
args.WebSession.Request.RequestUri.Host,
args.WebSession.Request.RequestUri.Port,
isHttps, applicationProtocols,
server, args.WebSession.UpStreamEndPoint ?? server.UpStreamEndPoint,
customUpStreamProxy ?? (isHttps ? server.UpStreamHttpsProxy : server.UpStreamHttpProxy));
}
/// <summary>
/// Create a server connection.
/// </summary>
/// <param name="args">The session event arguments.</param>
/// <param name="isConnect">Is this a CONNECT request.</param>
/// <param name="applicationProtocol"></param>
/// <param name="cancellationToken">The cancellation token for this async task.</param>
/// <returns></returns>
internal Task<TcpServerConnection> GetServerConnection(ProxyServer server, SessionEventArgsBase args, bool isConnect,
SslApplicationProtocol applicationProtocol, bool noCache, CancellationToken cancellationToken)
{
List<SslApplicationProtocol> applicationProtocols = null;
if (applicationProtocol != default)
{
applicationProtocols = new List<SslApplicationProtocol> { applicationProtocol };
}
return GetServerConnection(server, args, isConnect, applicationProtocols, noCache, cancellationToken);
}
/// <summary>
/// Create a server connection.
/// </summary>
/// <param name="args">The session event arguments.</param>
/// <param name="isConnect">Is this a CONNECT request.</param>
/// <param name="applicationProtocols"></param>
/// <param name="cancellationToken">The cancellation token for this async task.</param>
/// <returns></returns>
internal async Task<TcpServerConnection> GetServerConnection(ProxyServer server, SessionEventArgsBase args, bool isConnect,
List<SslApplicationProtocol> applicationProtocols, bool noCache, CancellationToken cancellationToken)
{
ExternalProxy customUpStreamProxy = null;
bool isHttps = args.IsHttps;
if (server.GetCustomUpStreamProxyFunc != null)
{
customUpStreamProxy = await server.GetCustomUpStreamProxyFunc(args);
}
args.CustomUpStreamProxyUsed = customUpStreamProxy;
return await GetServerConnection(
args.WebSession.Request.RequestUri.Host,
args.WebSession.Request.RequestUri.Port,
args.WebSession.Request.HttpVersion,
isHttps, applicationProtocols, isConnect,
server, args.WebSession.UpStreamEndPoint ?? server.UpStreamEndPoint,
customUpStreamProxy ?? (isHttps ? server.UpStreamHttpsProxy : server.UpStreamHttpProxy),
noCache, cancellationToken);
}
/// <summary>
/// Gets a TCP connection to server from connection pool.
/// </summary>
/// <param name="remoteHostName">The remote hostname.</param>
/// <param name="remotePort">The remote port.</param>
/// <param name="httpVersion">The http version to use.</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="isConnect">Is this a CONNECT request.</param>
/// <param name="proxyServer">The current ProxyServer instance.</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="noCache">Not from cache/create new connection.</param>
/// <param name="cancellationToken">The cancellation token for this async task.</param>
/// <returns></returns>
internal async Task<TcpServerConnection> GetServerConnection(string remoteHostName, int remotePort,
Version httpVersion, bool isHttps, List<SslApplicationProtocol> applicationProtocols, bool isConnect,
ProxyServer proxyServer, IPEndPoint upStreamEndPoint, ExternalProxy externalProxy,
bool noCache, CancellationToken cancellationToken)
{
var cacheKey = GetConnectionCacheKey(remoteHostName, remotePort,
isHttps, applicationProtocols,
proxyServer, upStreamEndPoint, externalProxy);
if (proxyServer.EnableConnectionPool && !noCache)
{
if (cache.TryGetValue(cacheKey, out var existingConnections))
{
while (existingConnections.Count > 0)
{
if (existingConnections.TryDequeue(out var recentConnection))
{
//+3 seconds for potential delay after getting connection
var cutOff = DateTime.Now.AddSeconds(-1 * proxyServer.ConnectionTimeOutSeconds + 3);
if (recentConnection.LastAccess > cutOff
&& recentConnection.TcpClient.IsGoodConnection())
{
return recentConnection;
}
disposalBag.Add(recentConnection);
}
}
}
}
var connection = await createServerConnection(remoteHostName, remotePort, httpVersion, isHttps,
applicationProtocols, isConnect, proxyServer, upStreamEndPoint, externalProxy, cancellationToken);
connection.CacheKey = cacheKey;
return connection;
}
/// <summary>
/// Creates a TCP connection to server
/// </summary>
/// <param name="remoteHostName">The remote hostname.</param>
/// <param name="remotePort">The remote port.</param>
/// <param name="httpVersion">The http version to use.</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="isConnect">Is this a CONNECT request.</param>
/// <param name="proxyServer">The current ProxyServer instance.</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="cancellationToken">The cancellation token for this async task.</param>
/// <returns></returns>
private async Task<TcpServerConnection> createServerConnection(string remoteHostName, int remotePort,
Version httpVersion, bool isHttps, List<SslApplicationProtocol> applicationProtocols, bool isConnect,
ProxyServer proxyServer, IPEndPoint upStreamEndPoint, ExternalProxy externalProxy,
CancellationToken cancellationToken)
{
//deny connection to proxy end points to avoid infinite connection loop.
if (server.ProxyEndPoints.Any(x => x.Port == remotePort)
&& NetworkHelper.IsLocalIpAddress(remoteHostName))
{
throw new Exception($"A client is making HTTP request to one of the listening ports of this proxy {remoteHostName}:{remotePort}");
}
if (externalProxy != null)
{
if (server.ProxyEndPoints.Any(x => x.Port == externalProxy.Port)
&& 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}");
}
}
bool useUpstreamProxy = false;
// check if external proxy is set for HTTP/HTTPS
if (externalProxy != null &&
!(externalProxy.HostName == remoteHostName && externalProxy.Port == remotePort))
{
useUpstreamProxy = true;
// check if we need to ByPass
if (externalProxy.BypassLocalhost && NetworkHelper.IsLocalIpAddress(remoteHostName))
{
useUpstreamProxy = false;
}
}
TcpClient tcpClient = null;
CustomBufferedStream stream = null;
SslApplicationProtocol negotiatedApplicationProtocol = default;
try
{
tcpClient = new TcpClient(upStreamEndPoint)
{
ReceiveTimeout = proxyServer.ConnectionTimeOutSeconds * 1000,
SendTimeout = proxyServer.ConnectionTimeOutSeconds * 1000,
SendBufferSize = proxyServer.BufferSize,
ReceiveBufferSize = proxyServer.BufferSize,
LingerState = new LingerOption(true, proxyServer.TcpTimeWaitSeconds)
};
//linux has a bug with socket reuse in .net core.
if (proxyServer.ReuseSocket && RunTime.IsWindows || RunTime.IsRunningOnMono)
{
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 (useUpstreamProxy)
{
await tcpClient.ConnectAsync(externalProxy.HostName, externalProxy.Port);
}
else
{
await tcpClient.ConnectAsync(remoteHostName, remotePort);
}
await proxyServer.InvokeConnectionCreateEvent(tcpClient, false);
stream = new CustomBufferedStream(tcpClient.GetStream(), proxyServer.BufferPool, proxyServer.BufferSize);
if (useUpstreamProxy && (isConnect || isHttps))
{
var writer = new HttpRequestWriter(stream, proxyServer.BufferPool, proxyServer.BufferSize);
var connectRequest = new ConnectRequest
{
OriginalUrl = $"{remoteHostName}:{remotePort}",
HttpVersion = httpVersion
};
connectRequest.Headers.AddHeader(KnownHeaders.Connection, KnownHeaders.ConnectionKeepAlive);
if (!string.IsNullOrEmpty(externalProxy.UserName) && externalProxy.Password != null)
{
connectRequest.Headers.AddHeader(HttpHeader.ProxyConnectionKeepAlive);
connectRequest.Headers.AddHeader(
HttpHeader.GetProxyAuthorizationHeader(externalProxy.UserName, externalProxy.Password));
}
await writer.WriteRequestAsync(connectRequest, cancellationToken: cancellationToken);
string httpStatus = await stream.ReadLineAsync(cancellationToken);
Response.ParseResponseLine(httpStatus, out _, out int statusCode, out string statusDescription);
if (statusCode != 200 && !statusDescription.EqualsIgnoreCase("OK")
&& !statusDescription.EqualsIgnoreCase("Connection Established"))
{
throw new Exception("Upstream proxy failed to create a secure tunnel");
}
await stream.ReadAndIgnoreAllLinesAsync(cancellationToken);
}
if (isHttps)
{
var sslStream = new SslStream(stream, false, proxyServer.ValidateServerCertificate,
proxyServer.SelectClientCertificate);
stream = new CustomBufferedStream(sslStream, proxyServer.BufferPool, proxyServer.BufferSize);
var options = new SslClientAuthenticationOptions
{
ApplicationProtocols = applicationProtocols,
TargetHost = remoteHostName,
ClientCertificates = null,
EnabledSslProtocols = proxyServer.SupportedSslProtocols,
CertificateRevocationCheckMode = proxyServer.CheckCertificateRevocation
};
await sslStream.AuthenticateAsClientAsync(options, cancellationToken);
#if NETCOREAPP2_1
negotiatedApplicationProtocol = sslStream.NegotiatedApplicationProtocol;
#endif
}
}
catch (Exception)
{
stream?.Dispose();
tcpClient?.Close();
throw;
}
return new TcpServerConnection(proxyServer, tcpClient)
{
UpStreamProxy = externalProxy,
UpStreamEndPoint = upStreamEndPoint,
HostName = remoteHostName,
Port = remotePort,
IsHttps = isHttps,
NegotiatedApplicationProtocol = negotiatedApplicationProtocol,
UseUpstreamProxy = useUpstreamProxy,
StreamWriter = new HttpRequestWriter(stream, proxyServer.BufferPool, proxyServer.BufferSize),
Stream = stream,
Version = httpVersion
};
}
/// <summary>
/// Release connection back to cache.
/// </summary>
/// <param name="connection">The Tcp server connection to return.</param>
/// <param name="close">Should we just close the connection instead of reusing?</param>
internal async Task Release(TcpServerConnection connection, bool close = false)
{
if (connection == null)
{
return;
}
if (close || connection.IsWinAuthenticated || !server.EnableConnectionPool)
{
disposalBag.Add(connection);
return;
}
connection.LastAccess = DateTime.Now;
try
{
await @lock.WaitAsync();
while (true)
{
if (cache.TryGetValue(connection.CacheKey, out var existingConnections))
{
while (existingConnections.Count >= server.MaxCachedConnections)
{
if (existingConnections.TryDequeue(out var staleConnection))
{
disposalBag.Add(staleConnection);
}
}
existingConnections.Enqueue(connection);
break;
}
if (cache.TryAdd(connection.CacheKey,
new ConcurrentQueue<TcpServerConnection>(new[] { connection })))
{
break;
}
}
}
finally
{
@lock.Release();
}
}
internal async Task Release(Task<TcpServerConnection> connectionCreateTask, bool closeServerConnection)
{
if (connectionCreateTask != null)
{
TcpServerConnection connection = null;
try
{
connection = await connectionCreateTask;
}
catch { }
finally
{
await Release(connection, closeServerConnection);
}
}
}
private async Task clearOutdatedConnections()
{
while (runCleanUpTask)
{
try
{
foreach (var item in cache)
{
var queue = item.Value;
while (queue.Count > 0)
{
if (queue.TryDequeue(out var connection))
{
var cutOff = DateTime.Now.AddSeconds(-1 * server.ConnectionTimeOutSeconds);
if (!server.EnableConnectionPool
|| connection.LastAccess < cutOff)
{
disposalBag.Add(connection);
continue;
}
queue.Enqueue(connection);
break;
}
}
}
try
{
await @lock.WaitAsync();
//clear empty queues
var emptyKeys = cache.Where(x => x.Value.Count == 0).Select(x => x.Key).ToList();
foreach (string key in emptyKeys)
{
cache.TryRemove(key, out var _);
}
}
finally
{
@lock.Release();
}
while (!disposalBag.IsEmpty)
{
if (disposalBag.TryTake(out var connection))
{
connection?.Dispose();
}
}
}
catch (Exception e)
{
server.ExceptionFunc(new Exception("An error occurred when disposing server connections.", e));
}
finally
{
//cleanup every 3 seconds by default
await Task.Delay(1000 * 3);
}
}
}
public void Dispose()
{
runCleanUpTask = false;
try
{
@lock.Wait();
foreach (var queue in cache.Select(x => x.Value).ToList())
{
while (!queue.IsEmpty)
{
if (queue.TryDequeue(out var connection))
{
disposalBag.Add(connection);
}
}
}
cache.Clear();
}
finally
{
@lock.Release();
}
while (!disposalBag.IsEmpty)
{
if (disposalBag.TryTake(out var connection))
{
connection?.Dispose();
}
}
}
}
}
using System;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Exceptions;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy
{
public partial class ProxyServer
{
/// <summary>
/// Callback to authorize clients of this proxy instance.
/// </summary>
/// <param name="session">The session event arguments.</param>
/// <returns>True if authorized.</returns>
private async Task<bool> checkAuthorization(SessionEventArgsBase session)
{
// If we are not authorizing clients return true
if (ProxyBasicAuthenticateFunc == null && ProxySchemeAuthenticateFunc == null)
{
return true;
}
var httpHeaders = session.WebSession.Request.Headers;
try
{
var header = httpHeaders.GetFirstHeader(KnownHeaders.ProxyAuthorization);
if (header == null)
{
session.WebSession.Response = createAuthentication407Response("Proxy Authentication Required");
return false;
}
var headerValueParts = header.Value.Split(ProxyConstants.SpaceSplit);
if (headerValueParts.Length != 2)
{
// Return not authorized
session.WebSession.Response = createAuthentication407Response("Proxy Authentication Invalid");
return false;
}
if (ProxyBasicAuthenticateFunc != null)
{
return await authenticateUserBasic(session, headerValueParts);
}
if (ProxySchemeAuthenticateFunc != null)
{
var result = await ProxySchemeAuthenticateFunc(session, headerValueParts[0], headerValueParts[1]);
if (result.Result == ProxyAuthenticationResult.ContinuationNeeded)
{
session.WebSession.Response = createAuthentication407Response("Proxy Authentication Invalid", result.Continuation);
return false;
}
return result.Result == ProxyAuthenticationResult.Success;
}
return false;
}
catch (Exception e)
{
ExceptionFunc(new ProxyAuthorizationException("Error whilst authorizing request", session, e,
httpHeaders));
// Return not authorized
session.WebSession.Response = createAuthentication407Response("Proxy Authentication Invalid");
return false;
}
}
private async Task<bool> authenticateUserBasic(SessionEventArgsBase session, string[] headerValueParts)
{
if (!headerValueParts[0].EqualsIgnoreCase(KnownHeaders.ProxyAuthorizationBasic))
{
// Return not authorized
session.WebSession.Response = createAuthentication407Response("Proxy Authentication Invalid");
return false;
}
string decoded = Encoding.UTF8.GetString(Convert.FromBase64String(headerValueParts[1]));
int colonIndex = decoded.IndexOf(':');
if (colonIndex == -1)
{
// Return not authorized
session.WebSession.Response = createAuthentication407Response("Proxy Authentication Invalid");
return false;
}
string username = decoded.Substring(0, colonIndex);
string password = decoded.Substring(colonIndex + 1);
bool authenticated = await ProxyBasicAuthenticateFunc(session, username, password);
if (!authenticated)
{
session.WebSession.Response = createAuthentication407Response("Proxy Authentication Invalid");
}
return authenticated;
}
/// <summary>
/// Create an authentication required response.
/// </summary>
/// <param name="description">Response description.</param>
/// <returns></returns>
private Response createAuthentication407Response(string description, string continuation = null)
{
var response = new Response
{
HttpVersion = HttpHeader.Version11,
StatusCode = (int)HttpStatusCode.ProxyAuthenticationRequired,
StatusDescription = description
};
if (!string.IsNullOrWhiteSpace(continuation))
{
return createContinuationResponse(response, continuation);
}
if (ProxyBasicAuthenticateFunc != null)
{
response.Headers.AddHeader(KnownHeaders.ProxyAuthenticate, $"Basic realm=\"{ProxyAuthenticationRealm}\"");
}
if (ProxySchemeAuthenticateFunc != null)
{
foreach (var scheme in ProxyAuthenticationSchemes)
{
response.Headers.AddHeader(KnownHeaders.ProxyAuthenticate, scheme);
}
}
response.Headers.AddHeader(KnownHeaders.ProxyConnection, KnownHeaders.ProxyConnectionClose);
response.Headers.FixProxyHeaders();
return response;
}
private Response createContinuationResponse(Response response, string continuation)
{
response.Headers.AddHeader(KnownHeaders.ProxyAuthenticate, continuation);
response.Headers.AddHeader(KnownHeaders.ProxyConnection, KnownHeaders.ConnectionKeepAlive);
response.Headers.FixProxyHeaders();
return response;
}
}
}
using System;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Exceptions;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy
{
public partial class ProxyServer
{
/// <summary>
/// Callback to authorize clients of this proxy instance.
/// </summary>
/// <param name="session">The session event arguments.</param>
/// <returns>True if authorized.</returns>
private async Task<bool> checkAuthorization(SessionEventArgsBase session)
{
// If we are not authorizing clients return true
if (ProxyBasicAuthenticateFunc == null && ProxySchemeAuthenticateFunc == null)
{
return true;
}
var httpHeaders = session.WebSession.Request.Headers;
try
{
var header = httpHeaders.GetFirstHeader(KnownHeaders.ProxyAuthorization);
if (header == null)
{
session.WebSession.Response = createAuthentication407Response("Proxy Authentication Required");
return false;
}
var headerValueParts = header.Value.Split(ProxyConstants.SpaceSplit);
if (headerValueParts.Length != 2)
{
// Return not authorized
session.WebSession.Response = createAuthentication407Response("Proxy Authentication Invalid");
return false;
}
if (ProxyBasicAuthenticateFunc != null)
{
return await authenticateUserBasic(session, headerValueParts);
}
if (ProxySchemeAuthenticateFunc != null)
{
var result = await ProxySchemeAuthenticateFunc(session, headerValueParts[0], headerValueParts[1]);
if (result.Result == ProxyAuthenticationResult.ContinuationNeeded)
{
session.WebSession.Response = createAuthentication407Response("Proxy Authentication Invalid", result.Continuation);
return false;
}
return result.Result == ProxyAuthenticationResult.Success;
}
return false;
}
catch (Exception e)
{
ExceptionFunc(new ProxyAuthorizationException("Error whilst authorizing request", session, e,
httpHeaders));
// Return not authorized
session.WebSession.Response = createAuthentication407Response("Proxy Authentication Invalid");
return false;
}
}
private async Task<bool> authenticateUserBasic(SessionEventArgsBase session, string[] headerValueParts)
{
if (!headerValueParts[0].EqualsIgnoreCase(KnownHeaders.ProxyAuthorizationBasic))
{
// Return not authorized
session.WebSession.Response = createAuthentication407Response("Proxy Authentication Invalid");
return false;
}
string decoded = Encoding.UTF8.GetString(Convert.FromBase64String(headerValueParts[1]));
int colonIndex = decoded.IndexOf(':');
if (colonIndex == -1)
{
// Return not authorized
session.WebSession.Response = createAuthentication407Response("Proxy Authentication Invalid");
return false;
}
string username = decoded.Substring(0, colonIndex);
string password = decoded.Substring(colonIndex + 1);
bool authenticated = await ProxyBasicAuthenticateFunc(session, username, password);
if (!authenticated)
{
session.WebSession.Response = createAuthentication407Response("Proxy Authentication Invalid");
}
return authenticated;
}
/// <summary>
/// Create an authentication required response.
/// </summary>
/// <param name="description">Response description.</param>
/// <returns></returns>
private Response createAuthentication407Response(string description, string continuation = null)
{
var response = new Response
{
HttpVersion = HttpHeader.Version11,
StatusCode = (int)HttpStatusCode.ProxyAuthenticationRequired,
StatusDescription = description
};
if (!string.IsNullOrWhiteSpace(continuation))
{
return createContinuationResponse(response, continuation);
}
if (ProxyBasicAuthenticateFunc != null)
{
response.Headers.AddHeader(KnownHeaders.ProxyAuthenticate, $"Basic realm=\"{ProxyAuthenticationRealm}\"");
}
if (ProxySchemeAuthenticateFunc != null)
{
foreach (var scheme in ProxyAuthenticationSchemes)
{
response.Headers.AddHeader(KnownHeaders.ProxyAuthenticate, scheme);
}
}
response.Headers.AddHeader(KnownHeaders.ProxyConnection, KnownHeaders.ProxyConnectionClose);
response.Headers.FixProxyHeaders();
return response;
}
private Response createContinuationResponse(Response response, string continuation)
{
response.Headers.AddHeader(KnownHeaders.ProxyAuthenticate, continuation);
response.Headers.AddHeader(KnownHeaders.ProxyConnection, KnownHeaders.ConnectionKeepAlive);
response.Headers.FixProxyHeaders();
return response;
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
using StreamExtended;
using StreamExtended.Network;
using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Helpers.WinHttp;
using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Network;
using Titanium.Web.Proxy.Network.Tcp;
namespace Titanium.Web.Proxy
{
/// <inheritdoc />
/// <summary>
/// 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.
/// </summary>
public partial class ProxyServer : IDisposable
{
/// <summary>
/// HTTP &amp; HTTPS scheme shorthands.
/// </summary>
internal static readonly string UriSchemeHttp = Uri.UriSchemeHttp;
internal static readonly string UriSchemeHttps = Uri.UriSchemeHttps;
/// <summary>
/// A default exception log func.
/// </summary>
private readonly ExceptionHandler defaultExceptionFunc = e => { };
/// <summary>
/// Backing field for exposed public property.
/// </summary>
private int clientConnectionCount;
/// <summary>
/// Backing field for exposed public property.
/// </summary>
private ExceptionHandler exceptionFunc;
/// <summary>
/// Backing field for exposed public property.
/// </summary>
private int serverConnectionCount;
/// <summary>
/// Upstream proxy manager.
/// </summary>
private WinHttpWebProxyFinder systemProxyResolver;
/// <inheritdoc />
/// <summary>
/// Initializes a new instance of ProxyServer class with provided parameters.
/// </summary>
/// <param name="userTrustRootCertificate">
/// Should fake HTTPS certificate be trusted by this machine's user certificate
/// store?
/// </param>
/// <param name="machineTrustRootCertificate">Should fake HTTPS certificate be trusted by this machine's certificate store?</param>
/// <param name="trustRootCertificateAsAdmin">
/// Should we attempt to trust certificates with elevated permissions by
/// prompting for UAC if required?
/// </param>
public ProxyServer(bool userTrustRootCertificate = true, bool machineTrustRootCertificate = false,
bool trustRootCertificateAsAdmin = false) : this(null, null, userTrustRootCertificate,
machineTrustRootCertificate, trustRootCertificateAsAdmin)
{
}
/// <summary>
/// Initializes a new instance of ProxyServer class with provided parameters.
/// </summary>
/// <param name="rootCertificateName">Name of the root certificate.</param>
/// <param name="rootCertificateIssuerName">Name of the root certificate issuer.</param>
/// <param name="userTrustRootCertificate">
/// Should fake HTTPS certificate be trusted by this machine's user certificate
/// store?
/// </param>
/// <param name="machineTrustRootCertificate">Should fake HTTPS certificate be trusted by this machine's certificate store?</param>
/// <param name="trustRootCertificateAsAdmin">
/// Should we attempt to trust certificates with elevated permissions by
/// prompting for UAC if required?
/// </param>
public ProxyServer(string rootCertificateName, string rootCertificateIssuerName,
bool userTrustRootCertificate = true, bool machineTrustRootCertificate = false,
bool trustRootCertificateAsAdmin = false)
{
// default values
ConnectionTimeOutSeconds = 60;
if (BufferPool == null)
{
BufferPool = new DefaultBufferPool();
}
ProxyEndPoints = new List<ProxyEndPoint>();
tcpConnectionFactory = new TcpConnectionFactory(this);
if (!RunTime.IsRunningOnMono && RunTime.IsWindows)
{
systemProxySettingsManager = new SystemProxyManager();
}
CertificateManager = new CertificateManager(rootCertificateName, rootCertificateIssuerName,
userTrustRootCertificate, machineTrustRootCertificate, trustRootCertificateAsAdmin, ExceptionFunc);
}
/// <summary>
/// An factory that creates tcp connection to server.
/// </summary>
private TcpConnectionFactory tcpConnectionFactory { get; }
/// <summary>
/// Manage system proxy settings.
/// </summary>
private SystemProxyManager systemProxySettingsManager { get; }
//Number of exception retries when connection pool is enabled.
private int retries => EnableConnectionPool ? MaxCachedConnections : 0;
/// <summary>
/// Is the proxy currently running?
/// </summary>
public bool ProxyRunning { get; private set; }
/// <summary>
/// Gets or sets a value indicating whether requests will be chained to upstream gateway.
/// Defaults to false.
/// </summary>
public bool ForwardToUpstreamGateway { get; set; }
/// <summary>
/// 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.
/// </summary>
public bool EnableWinAuth { get; set; }
/// <summary>
/// Should we check for certificare revocation during SSL authentication to servers
/// Note: If enabled can reduce performance. Defaults to false.
/// </summary>
public X509RevocationMode CheckCertificateRevocation { get; set; }
/// <summary>
/// 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.
/// </summary>
public bool Enable100ContinueBehaviour { get; set; }
/// <summary>
/// Should we enable experimental server connection pool?
/// Defaults to disable.
/// </summary>
public bool EnableConnectionPool { get; set; }
/// <summary>
/// Buffer size in bytes used throughout this proxy.
/// Default value is 8192 bytes.
/// </summary>
public int BufferSize { get; set; } = 8192;
/// <summary>
/// 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.
/// </summary>
public int ConnectionTimeOutSeconds { get; set; }
/// <summary>
/// Maximum number of concurrent connections per remote host in cache.
/// Only valid when connection pooling is enabled.
/// Default value is 2.
/// </summary>
public int MaxCachedConnections { get; set; } = 2;
/// <summary>
/// Number of seconds to linger when Tcp connection is in TIME_WAIT state.
/// Default value is 30.
/// </summary>
public int TcpTimeWaitSeconds { get; set; } = 30;
/// <summary>
/// Should we reuse client/server tcp sockets.
/// Default is true (disabled for linux/macOS due to bug in .Net core).
/// </summary>
public bool ReuseSocket { get; set; } = true;
/// <summary>
/// Total number of active client connections.
/// </summary>
public int ClientConnectionCount => clientConnectionCount;
/// <summary>
/// Total number of active server connections.
/// </summary>
public int ServerConnectionCount => serverConnectionCount;
/// <summary>
/// Realm used during Proxy Basic Authentication.
/// </summary>
public string ProxyAuthenticationRealm { get; set; } = "TitaniumProxy";
/// <summary>
/// List of supported Ssl versions.
/// </summary>
public SslProtocols SupportedSslProtocols { get; set; } =
#if NET45
SslProtocols.Ssl3 |
#endif
SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12;
/// <summary>
/// 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.
/// </summary>
public IBufferPool BufferPool { get; set; }
/// <summary>
/// Manages certificates used by this proxy.
/// </summary>
public CertificateManager CertificateManager { get; }
/// <summary>
/// External proxy used for Http requests.
/// </summary>
public ExternalProxy UpStreamHttpProxy { get; set; }
/// <summary>
/// External proxy used for Https requests.
/// </summary>
public ExternalProxy UpStreamHttpsProxy { get; set; }
/// <summary>
/// Local adapter/NIC endpoint where proxy makes request via.
/// Defaults via any IP addresses of this machine.
/// </summary>
public IPEndPoint UpStreamEndPoint { get; set; } = new IPEndPoint(IPAddress.Any, 0);
/// <summary>
/// A list of IpAddress and port this proxy is listening to.
/// </summary>
public List<ProxyEndPoint> ProxyEndPoints { get; set; }
/// <summary>
/// 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.
/// </summary>
public Func<SessionEventArgsBase, Task<ExternalProxy>> GetCustomUpStreamProxyFunc { get; set; }
/// <summary>
/// Callback for error events in this proxy instance.
/// </summary>
public ExceptionHandler ExceptionFunc
{
get => exceptionFunc ?? defaultExceptionFunc;
set
{
exceptionFunc = value;
CertificateManager.ExceptionFunc = value;
}
}
/// <summary>
/// A callback to authenticate proxy clients via basic authentication.
/// Parameters are username and password as provided by client.
/// Should return true for successful authentication.
/// </summary>
public Func<SessionEventArgsBase, string, string, Task<bool>> ProxyBasicAuthenticateFunc { get; set; }
/// <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>
public Func<SessionEventArgsBase, string, string, Task<ProxyAuthenticationContext>> ProxySchemeAuthenticateFunc { get; set; }
/// <summary>
/// A collection of scheme types, e.g. basic, NTLM, Kerberos, Negotiate, to return if scheme authentication is required.
/// Works in relation with ProxySchemeAuthenticateFunc.
/// </summary>
public IEnumerable<string> ProxyAuthenticationSchemes { get; set; } = new string[0];
/// <summary>
/// Event occurs when client connection count changed.
/// </summary>
public event EventHandler ClientConnectionCountChanged;
/// <summary>
/// Event occurs when server connection count changed.
/// </summary>
public event EventHandler ServerConnectionCountChanged;
/// <summary>
/// Event to override the default verification logic of remote SSL certificate received during authentication.
/// </summary>
public event AsyncEventHandler<CertificateValidationEventArgs> ServerCertificateValidationCallback;
/// <summary>
/// Event to override client certificate selection during mutual SSL authentication.
/// </summary>
public event AsyncEventHandler<CertificateSelectionEventArgs> ClientCertificateSelectionCallback;
/// <summary>
/// Intercept request event to server.
/// </summary>
public event AsyncEventHandler<SessionEventArgs> BeforeRequest;
/// <summary>
/// Intercept response event from server.
/// </summary>
public event AsyncEventHandler<SessionEventArgs> BeforeResponse;
/// <summary>
/// Intercept after response event from server.
/// </summary>
public event AsyncEventHandler<SessionEventArgs> AfterResponse;
/// <summary>
/// Customize TcpClient used for client connection upon create.
/// </summary>
public event AsyncEventHandler<TcpClient> OnClientConnectionCreate;
/// <summary>
/// Customize TcpClient used for server connection upon create.
/// </summary>
public event AsyncEventHandler<TcpClient> OnServerConnectionCreate;
/// <summary>
/// Add a proxy end point.
/// </summary>
/// <param name="endPoint">The proxy endpoint.</param>
public void AddEndPoint(ProxyEndPoint 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");
}
ProxyEndPoints.Add(endPoint);
if (ProxyRunning)
{
listen(endPoint);
}
}
/// <summary>
/// Remove a proxy end point.
/// Will throw error if the end point does'nt exist.
/// </summary>
/// <param name="endPoint">The existing endpoint to remove.</param>
public void RemoveEndPoint(ProxyEndPoint endPoint)
{
if (ProxyEndPoints.Contains(endPoint) == false)
{
throw new Exception("Cannot remove endPoints not added to proxy");
}
ProxyEndPoints.Remove(endPoint);
if (ProxyRunning)
{
quitListen(endPoint);
}
}
/// <summary>
/// Set the given explicit end point as the default proxy server for current machine.
/// </summary>
/// <param name="endPoint">The explicit endpoint.</param>
public void SetAsSystemHttpProxy(ExplicitProxyEndPoint endPoint)
{
SetAsSystemProxy(endPoint, ProxyProtocolType.Http);
}
/// <summary>
/// Set the given explicit end point as the default proxy server for current machine.
/// </summary>
/// <param name="endPoint">The explicit endpoint.</param>
public void SetAsSystemHttpsProxy(ExplicitProxyEndPoint endPoint)
{
SetAsSystemProxy(endPoint, ProxyProtocolType.Https);
}
/// <summary>
/// Set the given explicit end point as the default proxy server for current machine.
/// </summary>
/// <param name="endPoint">The explicit endpoint.</param>
/// <param name="protocolType">The proxy protocol type.</param>
public void SetAsSystemProxy(ExplicitProxyEndPoint endPoint, ProxyProtocolType protocolType)
{
if (RunTime.IsRunningOnMono)
{
throw new Exception("Mono Runtime do not support system proxy settings.");
}
validateEndPointAsSystemProxy(endPoint);
bool isHttp = (protocolType & ProxyProtocolType.Http) > 0;
bool isHttps = (protocolType & ProxyProtocolType.Https) > 0;
if (isHttps)
{
CertificateManager.EnsureRootCertificate();
// If certificate was trusted by the machine
if (!CertificateManager.CertValidated)
{
protocolType = protocolType & ~ProxyProtocolType.Https;
isHttps = false;
}
}
// clear any settings previously added
if (isHttp)
{
ProxyEndPoints.OfType<ExplicitProxyEndPoint>().ToList().ForEach(x => x.IsSystemHttpProxy = false);
}
if (isHttps)
{
ProxyEndPoints.OfType<ExplicitProxyEndPoint>().ToList().ForEach(x => x.IsSystemHttpsProxy = false);
}
systemProxySettingsManager.SetProxy(
Equals(endPoint.IpAddress, IPAddress.Any) |
Equals(endPoint.IpAddress, IPAddress.Loopback)
? "localhost"
: endPoint.IpAddress.ToString(),
endPoint.Port,
protocolType);
if (isHttp)
{
endPoint.IsSystemHttpProxy = true;
}
if (isHttps)
{
endPoint.IsSystemHttpsProxy = true;
}
string proxyType = null;
switch (protocolType)
{
case ProxyProtocolType.Http:
proxyType = "HTTP";
break;
case ProxyProtocolType.Https:
proxyType = "HTTPS";
break;
case ProxyProtocolType.AllHttp:
proxyType = "HTTP and HTTPS";
break;
}
if (protocolType != ProxyProtocolType.None)
{
Console.WriteLine("Set endpoint at Ip {0} and port: {1} as System {2} Proxy", endPoint.IpAddress,
endPoint.Port, proxyType);
}
}
/// <summary>
/// Clear HTTP proxy settings of current machine.
/// </summary>
public void DisableSystemHttpProxy()
{
DisableSystemProxy(ProxyProtocolType.Http);
}
/// <summary>
/// Clear HTTPS proxy settings of current machine.
/// </summary>
public void DisableSystemHttpsProxy()
{
DisableSystemProxy(ProxyProtocolType.Https);
}
/// <summary>
/// Clear the specified proxy setting for current machine.
/// </summary>
public void DisableSystemProxy(ProxyProtocolType protocolType)
{
if (RunTime.IsRunningOnMono)
{
throw new Exception("Mono Runtime do not support system proxy settings.");
}
systemProxySettingsManager.RemoveProxy(protocolType);
}
/// <summary>
/// Clear all proxy settings for current machine.
/// </summary>
public void DisableAllSystemProxies()
{
if (RunTime.IsRunningOnMono)
{
throw new Exception("Mono Runtime do not support system proxy settings.");
}
systemProxySettingsManager.DisableAllProxy();
}
/// <summary>
/// Start this proxy server instance.
/// </summary>
public void Start()
{
if (ProxyRunning)
{
throw new Exception("Proxy is already running.");
}
if (ProxyEndPoints.OfType<ExplicitProxyEndPoint>().Any(x => x.GenericCertificate == null))
{
CertificateManager.EnsureRootCertificate();
}
// clear any system proxy settings which is pointing to our own endpoint (causing a cycle)
// due to ungracious proxy shutdown before or something else
if (systemProxySettingsManager != null && RunTime.IsWindows)
{
var proxyInfo = systemProxySettingsManager.GetProxyInfoFromRegistry();
if (proxyInfo.Proxies != null)
{
var protocolToRemove = ProxyProtocolType.None;
foreach (var proxy in proxyInfo.Proxies.Values)
{
if (NetworkHelper.IsLocalIpAddress(proxy.HostName)
&& ProxyEndPoints.Any(x => x.Port == proxy.Port))
{
protocolToRemove |= proxy.ProtocolType;
}
}
if (protocolToRemove != ProxyProtocolType.None)
{
systemProxySettingsManager.RemoveProxy(protocolToRemove, false);
}
}
}
if (ForwardToUpstreamGateway && GetCustomUpStreamProxyFunc == null && systemProxySettingsManager != null)
{
// Use WinHttp to handle PAC/WAPD scripts.
systemProxyResolver = new WinHttpWebProxyFinder();
systemProxyResolver.LoadFromIE();
GetCustomUpStreamProxyFunc = getSystemUpStreamProxy;
}
ProxyRunning = true;
CertificateManager.ClearIdleCertificates();
foreach (var endPoint in ProxyEndPoints)
{
listen(endPoint);
}
}
/// <summary>
/// Stop this proxy server instance.
/// </summary>
public void Stop()
{
if (!ProxyRunning)
{
throw new Exception("Proxy is not running.");
}
if (!RunTime.IsRunningOnMono && RunTime.IsWindows)
{
bool setAsSystemProxy = ProxyEndPoints.OfType<ExplicitProxyEndPoint>()
.Any(x => x.IsSystemHttpProxy || x.IsSystemHttpsProxy);
if (setAsSystemProxy)
{
systemProxySettingsManager.RestoreOriginalSettings();
}
}
foreach (var endPoint in ProxyEndPoints)
{
quitListen(endPoint);
}
ProxyEndPoints.Clear();
CertificateManager?.StopClearIdleCertificates();
tcpConnectionFactory.Dispose();
ProxyRunning = false;
}
/// <summary>
/// Listen on given end point of local machine.
/// </summary>
/// <param name="endPoint">The end point to listen.</param>
private void listen(ProxyEndPoint endPoint)
{
endPoint.Listener = new TcpListener(endPoint.IpAddress, endPoint.Port);
//linux/macOS has a bug with socket reuse in .net core.
if (ReuseSocket && (RunTime.IsWindows || RunTime.IsRunningOnMono))
{
endPoint.Listener.Server.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
}
try
{
endPoint.Listener.Start();
endPoint.Port = ((IPEndPoint)endPoint.Listener.LocalEndpoint).Port;
// accept clients asynchronously
endPoint.Listener.BeginAcceptTcpClient(onAcceptConnection, endPoint);
}
catch (SocketException ex)
{
var pex = new Exception(
$"Endpoint {endPoint} failed to start. Check inner exception and exception data for details.", ex);
pex.Data.Add("ipAddress", endPoint.IpAddress);
pex.Data.Add("port", endPoint.Port);
throw pex;
}
}
/// <summary>
/// Verify if its safe to set this end point as system proxy.
/// </summary>
/// <param name="endPoint">The end point to validate.</param>
private void validateEndPointAsSystemProxy(ExplicitProxyEndPoint endPoint)
{
if (endPoint == null)
{
throw new ArgumentNullException(nameof(endPoint));
}
if (ProxyEndPoints.Contains(endPoint) == false)
{
throw new Exception("Cannot set endPoints not added to proxy as system proxy");
}
if (!ProxyRunning)
{
throw new Exception("Cannot set system proxy settings before proxy has been started.");
}
}
/// <summary>
/// Gets the system up stream proxy.
/// </summary>
/// <param name="sessionEventArgs">The session.</param>
/// <returns>The external proxy as task result.</returns>
private Task<ExternalProxy> getSystemUpStreamProxy(SessionEventArgsBase sessionEventArgs)
{
var proxy = systemProxyResolver.GetProxy(sessionEventArgs.WebSession.Request.RequestUri);
return Task.FromResult(proxy);
}
/// <summary>
/// Act when a connection is received from client.
/// </summary>
private void onAcceptConnection(IAsyncResult asyn)
{
var endPoint = (ProxyEndPoint)asyn.AsyncState;
TcpClient tcpClient = null;
try
{
// based on end point type call appropriate request handlers
tcpClient = endPoint.Listener.EndAcceptTcpClient(asyn);
}
catch (ObjectDisposedException)
{
// The listener was Stop()'d, disposing the underlying socket and
// triggering the completion of the callback. We're already exiting,
// so just return.
return;
}
catch
{
// Other errors are discarded to keep proxy running
}
if (tcpClient != null)
{
Task.Run(async () => { await handleClient(tcpClient, endPoint); });
}
// Get the listener that handles the client request.
endPoint.Listener.BeginAcceptTcpClient(onAcceptConnection, endPoint);
}
/// <summary>
/// Handle the client.
/// </summary>
/// <param name="tcpClient">The client.</param>
/// <param name="endPoint">The proxy endpoint.</param>
/// <returns>The task.</returns>
private async Task handleClient(TcpClient tcpClient, ProxyEndPoint endPoint)
{
tcpClient.ReceiveTimeout = ConnectionTimeOutSeconds * 1000;
tcpClient.SendTimeout = ConnectionTimeOutSeconds * 1000;
tcpClient.SendBufferSize = BufferSize;
tcpClient.ReceiveBufferSize = BufferSize;
tcpClient.LingerState = new LingerOption(true, TcpTimeWaitSeconds);
await InvokeConnectionCreateEvent(tcpClient, true);
using (var clientConnection = new TcpClientConnection(this, tcpClient))
{
if (endPoint is TransparentProxyEndPoint tep)
{
await handleClient(tep, clientConnection);
}
else
{
await handleClient((ExplicitProxyEndPoint)endPoint, clientConnection);
}
}
}
/// <summary>
/// Handle exception.
/// </summary>
/// <param name="clientStream">The client stream.</param>
/// <param name="exception">The exception.</param>
private void onException(CustomBufferedStream clientStream, Exception exception)
{
#if DEBUG
if (clientStream is DebugCustomBufferedStream debugStream)
{
debugStream.LogException(exception);
}
#endif
ExceptionFunc(exception);
}
/// <summary>
/// Quit listening on the given end point.
/// </summary>
private void quitListen(ProxyEndPoint endPoint)
{
endPoint.Listener.Stop();
endPoint.Listener.Server.Dispose();
}
/// <summary>
/// Update client connection count.
/// </summary>
/// <param name="increment">Should we increment/decrement?</param>
internal void UpdateClientConnectionCount(bool increment)
{
if (increment)
{
Interlocked.Increment(ref clientConnectionCount);
}
else
{
Interlocked.Decrement(ref clientConnectionCount);
}
ClientConnectionCountChanged?.Invoke(this, EventArgs.Empty);
}
/// <summary>
/// Update server connection count.
/// </summary>
/// <param name="increment">Should we increment/decrement?</param>
internal void UpdateServerConnectionCount(bool increment)
{
if (increment)
{
Interlocked.Increment(ref serverConnectionCount);
}
else
{
Interlocked.Decrement(ref serverConnectionCount);
}
ServerConnectionCountChanged?.Invoke(this, EventArgs.Empty);
}
/// <summary>
/// Invoke client/server tcp connection events if subscribed by API user.
/// </summary>
/// <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>
/// <returns></returns>
internal async Task InvokeConnectionCreateEvent(TcpClient client, bool isClientConnection)
{
//client connection created
if (isClientConnection && OnClientConnectionCreate != null)
{
await OnClientConnectionCreate.InvokeAsync(this, client, ExceptionFunc);
}
//server connection created
if (!isClientConnection && OnServerConnectionCreate != null)
{
await OnServerConnectionCreate.InvokeAsync(this, client, ExceptionFunc);
}
}
/// <summary>
/// Connection retry policy when using connection pool.
/// </summary>
private RetryPolicy<T> retryPolicy<T>() where T : Exception
{
return new RetryPolicy<T>(retries, tcpConnectionFactory);
}
/// <summary>
/// Dispose the Proxy instance.
/// </summary>
public void Dispose()
{
if (ProxyRunning)
{
Stop();
}
CertificateManager?.Dispose();
BufferPool?.Dispose();
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
using StreamExtended;
using StreamExtended.Network;
using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Helpers.WinHttp;
using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Network;
using Titanium.Web.Proxy.Network.Tcp;
namespace Titanium.Web.Proxy
{
/// <inheritdoc />
/// <summary>
/// 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.
/// </summary>
public partial class ProxyServer : IDisposable
{
/// <summary>
/// HTTP &amp; HTTPS scheme shorthands.
/// </summary>
internal static readonly string UriSchemeHttp = Uri.UriSchemeHttp;
internal static readonly string UriSchemeHttps = Uri.UriSchemeHttps;
/// <summary>
/// A default exception log func.
/// </summary>
private readonly ExceptionHandler defaultExceptionFunc = e => { };
/// <summary>
/// Backing field for exposed public property.
/// </summary>
private int clientConnectionCount;
/// <summary>
/// Backing field for exposed public property.
/// </summary>
private ExceptionHandler exceptionFunc;
/// <summary>
/// Backing field for exposed public property.
/// </summary>
private int serverConnectionCount;
/// <summary>
/// Upstream proxy manager.
/// </summary>
private WinHttpWebProxyFinder systemProxyResolver;
/// <inheritdoc />
/// <summary>
/// Initializes a new instance of ProxyServer class with provided parameters.
/// </summary>
/// <param name="userTrustRootCertificate">
/// Should fake HTTPS certificate be trusted by this machine's user certificate
/// store?
/// </param>
/// <param name="machineTrustRootCertificate">Should fake HTTPS certificate be trusted by this machine's certificate store?</param>
/// <param name="trustRootCertificateAsAdmin">
/// Should we attempt to trust certificates with elevated permissions by
/// prompting for UAC if required?
/// </param>
public ProxyServer(bool userTrustRootCertificate = true, bool machineTrustRootCertificate = false,
bool trustRootCertificateAsAdmin = false) : this(null, null, userTrustRootCertificate,
machineTrustRootCertificate, trustRootCertificateAsAdmin)
{
}
/// <summary>
/// Initializes a new instance of ProxyServer class with provided parameters.
/// </summary>
/// <param name="rootCertificateName">Name of the root certificate.</param>
/// <param name="rootCertificateIssuerName">Name of the root certificate issuer.</param>
/// <param name="userTrustRootCertificate">
/// Should fake HTTPS certificate be trusted by this machine's user certificate
/// store?
/// </param>
/// <param name="machineTrustRootCertificate">Should fake HTTPS certificate be trusted by this machine's certificate store?</param>
/// <param name="trustRootCertificateAsAdmin">
/// Should we attempt to trust certificates with elevated permissions by
/// prompting for UAC if required?
/// </param>
public ProxyServer(string rootCertificateName, string rootCertificateIssuerName,
bool userTrustRootCertificate = true, bool machineTrustRootCertificate = false,
bool trustRootCertificateAsAdmin = false)
{
// default values
ConnectionTimeOutSeconds = 60;
if (BufferPool == null)
{
BufferPool = new DefaultBufferPool();
}
ProxyEndPoints = new List<ProxyEndPoint>();
tcpConnectionFactory = new TcpConnectionFactory(this);
if (!RunTime.IsRunningOnMono && RunTime.IsWindows)
{
systemProxySettingsManager = new SystemProxyManager();
}
CertificateManager = new CertificateManager(rootCertificateName, rootCertificateIssuerName,
userTrustRootCertificate, machineTrustRootCertificate, trustRootCertificateAsAdmin, ExceptionFunc);
}
/// <summary>
/// An factory that creates tcp connection to server.
/// </summary>
private TcpConnectionFactory tcpConnectionFactory { get; }
/// <summary>
/// Manage system proxy settings.
/// </summary>
private SystemProxyManager systemProxySettingsManager { get; }
//Number of exception retries when connection pool is enabled.
private int retries => EnableConnectionPool ? MaxCachedConnections : 0;
/// <summary>
/// Is the proxy currently running?
/// </summary>
public bool ProxyRunning { get; private set; }
/// <summary>
/// Gets or sets a value indicating whether requests will be chained to upstream gateway.
/// Defaults to false.
/// </summary>
public bool ForwardToUpstreamGateway { get; set; }
/// <summary>
/// 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.
/// </summary>
public bool EnableWinAuth { get; set; }
/// <summary>
/// Should we check for certificare revocation during SSL authentication to servers
/// Note: If enabled can reduce performance. Defaults to false.
/// </summary>
public X509RevocationMode CheckCertificateRevocation { get; set; }
/// <summary>
/// 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.
/// </summary>
public bool Enable100ContinueBehaviour { get; set; }
/// <summary>
/// Should we enable experimental server connection pool?
/// Defaults to true.
/// </summary>
public bool EnableConnectionPool { get; set; } = true;
/// <summary>
/// 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.
/// </summary>
public bool EnableTcpServerConnectionPrefetch { get; set; } = true;
/// <summary>
/// Buffer size in bytes used throughout this proxy.
/// Default value is 8192 bytes.
/// </summary>
public int BufferSize { get; set; } = 8192;
/// <summary>
/// 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.
/// </summary>
public int ConnectionTimeOutSeconds { get; set; }
/// <summary>
/// Maximum number of concurrent connections per remote host in cache.
/// Only valid when connection pooling is enabled.
/// Default value is 2.
/// </summary>
public int MaxCachedConnections { get; set; } = 2;
/// <summary>
/// Number of seconds to linger when Tcp connection is in TIME_WAIT state.
/// Default value is 30.
/// </summary>
public int TcpTimeWaitSeconds { get; set; } = 30;
/// <summary>
/// Should we reuse client/server tcp sockets.
/// Default is true (disabled for linux/macOS due to bug in .Net core).
/// </summary>
public bool ReuseSocket { get; set; } = true;
/// <summary>
/// Total number of active client connections.
/// </summary>
public int ClientConnectionCount => clientConnectionCount;
/// <summary>
/// Total number of active server connections.
/// </summary>
public int ServerConnectionCount => serverConnectionCount;
/// <summary>
/// Realm used during Proxy Basic Authentication.
/// </summary>
public string ProxyAuthenticationRealm { get; set; } = "TitaniumProxy";
/// <summary>
/// List of supported Ssl versions.
/// </summary>
public SslProtocols SupportedSslProtocols { get; set; } =
#if NET45
SslProtocols.Ssl3 |
#endif
SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12;
/// <summary>
/// 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.
/// </summary>
public IBufferPool BufferPool { get; set; }
/// <summary>
/// Manages certificates used by this proxy.
/// </summary>
public CertificateManager CertificateManager { get; }
/// <summary>
/// External proxy used for Http requests.
/// </summary>
public ExternalProxy UpStreamHttpProxy { get; set; }
/// <summary>
/// External proxy used for Https requests.
/// </summary>
public ExternalProxy UpStreamHttpsProxy { get; set; }
/// <summary>
/// Local adapter/NIC endpoint where proxy makes request via.
/// Defaults via any IP addresses of this machine.
/// </summary>
public IPEndPoint UpStreamEndPoint { get; set; } = new IPEndPoint(IPAddress.Any, 0);
/// <summary>
/// A list of IpAddress and port this proxy is listening to.
/// </summary>
public List<ProxyEndPoint> ProxyEndPoints { get; set; }
/// <summary>
/// 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.
/// </summary>
public Func<SessionEventArgsBase, Task<ExternalProxy>> GetCustomUpStreamProxyFunc { get; set; }
/// <summary>
/// Callback for error events in this proxy instance.
/// </summary>
public ExceptionHandler ExceptionFunc
{
get => exceptionFunc ?? defaultExceptionFunc;
set
{
exceptionFunc = value;
CertificateManager.ExceptionFunc = value;
}
}
/// <summary>
/// A callback to authenticate proxy clients via basic authentication.
/// Parameters are username and password as provided by client.
/// Should return true for successful authentication.
/// </summary>
public Func<SessionEventArgsBase, string, string, Task<bool>> ProxyBasicAuthenticateFunc { get; set; }
/// <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>
public Func<SessionEventArgsBase, string, string, Task<ProxyAuthenticationContext>> ProxySchemeAuthenticateFunc { get; set; }
/// <summary>
/// A collection of scheme types, e.g. basic, NTLM, Kerberos, Negotiate, to return if scheme authentication is required.
/// Works in relation with ProxySchemeAuthenticateFunc.
/// </summary>
public IEnumerable<string> ProxyAuthenticationSchemes { get; set; } = new string[0];
/// <summary>
/// Event occurs when client connection count changed.
/// </summary>
public event EventHandler ClientConnectionCountChanged;
/// <summary>
/// Event occurs when server connection count changed.
/// </summary>
public event EventHandler ServerConnectionCountChanged;
/// <summary>
/// Event to override the default verification logic of remote SSL certificate received during authentication.
/// </summary>
public event AsyncEventHandler<CertificateValidationEventArgs> ServerCertificateValidationCallback;
/// <summary>
/// Event to override client certificate selection during mutual SSL authentication.
/// </summary>
public event AsyncEventHandler<CertificateSelectionEventArgs> ClientCertificateSelectionCallback;
/// <summary>
/// Intercept request event to server.
/// </summary>
public event AsyncEventHandler<SessionEventArgs> BeforeRequest;
/// <summary>
/// Intercept response event from server.
/// </summary>
public event AsyncEventHandler<SessionEventArgs> BeforeResponse;
/// <summary>
/// Intercept after response event from server.
/// </summary>
public event AsyncEventHandler<SessionEventArgs> AfterResponse;
/// <summary>
/// Customize TcpClient used for client connection upon create.
/// </summary>
public event AsyncEventHandler<TcpClient> OnClientConnectionCreate;
/// <summary>
/// Customize TcpClient used for server connection upon create.
/// </summary>
public event AsyncEventHandler<TcpClient> OnServerConnectionCreate;
/// <summary>
/// Add a proxy end point.
/// </summary>
/// <param name="endPoint">The proxy endpoint.</param>
public void AddEndPoint(ProxyEndPoint 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");
}
ProxyEndPoints.Add(endPoint);
if (ProxyRunning)
{
listen(endPoint);
}
}
/// <summary>
/// Remove a proxy end point.
/// Will throw error if the end point does'nt exist.
/// </summary>
/// <param name="endPoint">The existing endpoint to remove.</param>
public void RemoveEndPoint(ProxyEndPoint endPoint)
{
if (ProxyEndPoints.Contains(endPoint) == false)
{
throw new Exception("Cannot remove endPoints not added to proxy");
}
ProxyEndPoints.Remove(endPoint);
if (ProxyRunning)
{
quitListen(endPoint);
}
}
/// <summary>
/// Set the given explicit end point as the default proxy server for current machine.
/// </summary>
/// <param name="endPoint">The explicit endpoint.</param>
public void SetAsSystemHttpProxy(ExplicitProxyEndPoint endPoint)
{
SetAsSystemProxy(endPoint, ProxyProtocolType.Http);
}
/// <summary>
/// Set the given explicit end point as the default proxy server for current machine.
/// </summary>
/// <param name="endPoint">The explicit endpoint.</param>
public void SetAsSystemHttpsProxy(ExplicitProxyEndPoint endPoint)
{
SetAsSystemProxy(endPoint, ProxyProtocolType.Https);
}
/// <summary>
/// Set the given explicit end point as the default proxy server for current machine.
/// </summary>
/// <param name="endPoint">The explicit endpoint.</param>
/// <param name="protocolType">The proxy protocol type.</param>
public void SetAsSystemProxy(ExplicitProxyEndPoint endPoint, ProxyProtocolType protocolType)
{
if (RunTime.IsRunningOnMono)
{
throw new Exception("Mono Runtime do not support system proxy settings.");
}
validateEndPointAsSystemProxy(endPoint);
bool isHttp = (protocolType & ProxyProtocolType.Http) > 0;
bool isHttps = (protocolType & ProxyProtocolType.Https) > 0;
if (isHttps)
{
CertificateManager.EnsureRootCertificate();
// If certificate was trusted by the machine
if (!CertificateManager.CertValidated)
{
protocolType = protocolType & ~ProxyProtocolType.Https;
isHttps = false;
}
}
// clear any settings previously added
if (isHttp)
{
ProxyEndPoints.OfType<ExplicitProxyEndPoint>().ToList().ForEach(x => x.IsSystemHttpProxy = false);
}
if (isHttps)
{
ProxyEndPoints.OfType<ExplicitProxyEndPoint>().ToList().ForEach(x => x.IsSystemHttpsProxy = false);
}
systemProxySettingsManager.SetProxy(
Equals(endPoint.IpAddress, IPAddress.Any) |
Equals(endPoint.IpAddress, IPAddress.Loopback)
? "localhost"
: endPoint.IpAddress.ToString(),
endPoint.Port,
protocolType);
if (isHttp)
{
endPoint.IsSystemHttpProxy = true;
}
if (isHttps)
{
endPoint.IsSystemHttpsProxy = true;
}
string proxyType = null;
switch (protocolType)
{
case ProxyProtocolType.Http:
proxyType = "HTTP";
break;
case ProxyProtocolType.Https:
proxyType = "HTTPS";
break;
case ProxyProtocolType.AllHttp:
proxyType = "HTTP and HTTPS";
break;
}
if (protocolType != ProxyProtocolType.None)
{
Console.WriteLine("Set endpoint at Ip {0} and port: {1} as System {2} Proxy", endPoint.IpAddress,
endPoint.Port, proxyType);
}
}
/// <summary>
/// Clear HTTP proxy settings of current machine.
/// </summary>
public void DisableSystemHttpProxy()
{
DisableSystemProxy(ProxyProtocolType.Http);
}
/// <summary>
/// Clear HTTPS proxy settings of current machine.
/// </summary>
public void DisableSystemHttpsProxy()
{
DisableSystemProxy(ProxyProtocolType.Https);
}
/// <summary>
/// Clear the specified proxy setting for current machine.
/// </summary>
public void DisableSystemProxy(ProxyProtocolType protocolType)
{
if (RunTime.IsRunningOnMono)
{
throw new Exception("Mono Runtime do not support system proxy settings.");
}
systemProxySettingsManager.RemoveProxy(protocolType);
}
/// <summary>
/// Clear all proxy settings for current machine.
/// </summary>
public void DisableAllSystemProxies()
{
if (RunTime.IsRunningOnMono)
{
throw new Exception("Mono Runtime do not support system proxy settings.");
}
systemProxySettingsManager.DisableAllProxy();
}
/// <summary>
/// Start this proxy server instance.
/// </summary>
public void Start()
{
if (ProxyRunning)
{
throw new Exception("Proxy is already running.");
}
if (ProxyEndPoints.OfType<ExplicitProxyEndPoint>().Any(x => x.GenericCertificate == null))
{
CertificateManager.EnsureRootCertificate();
}
// clear any system proxy settings which is pointing to our own endpoint (causing a cycle)
// due to ungracious proxy shutdown before or something else
if (systemProxySettingsManager != null && RunTime.IsWindows)
{
var proxyInfo = systemProxySettingsManager.GetProxyInfoFromRegistry();
if (proxyInfo.Proxies != null)
{
var protocolToRemove = ProxyProtocolType.None;
foreach (var proxy in proxyInfo.Proxies.Values)
{
if (NetworkHelper.IsLocalIpAddress(proxy.HostName)
&& ProxyEndPoints.Any(x => x.Port == proxy.Port))
{
protocolToRemove |= proxy.ProtocolType;
}
}
if (protocolToRemove != ProxyProtocolType.None)
{
systemProxySettingsManager.RemoveProxy(protocolToRemove, false);
}
}
}
if (ForwardToUpstreamGateway && GetCustomUpStreamProxyFunc == null && systemProxySettingsManager != null)
{
// Use WinHttp to handle PAC/WAPD scripts.
systemProxyResolver = new WinHttpWebProxyFinder();
systemProxyResolver.LoadFromIE();
GetCustomUpStreamProxyFunc = getSystemUpStreamProxy;
}
ProxyRunning = true;
CertificateManager.ClearIdleCertificates();
foreach (var endPoint in ProxyEndPoints)
{
listen(endPoint);
}
}
/// <summary>
/// Stop this proxy server instance.
/// </summary>
public void Stop()
{
if (!ProxyRunning)
{
throw new Exception("Proxy is not running.");
}
if (!RunTime.IsRunningOnMono && RunTime.IsWindows)
{
bool setAsSystemProxy = ProxyEndPoints.OfType<ExplicitProxyEndPoint>()
.Any(x => x.IsSystemHttpProxy || x.IsSystemHttpsProxy);
if (setAsSystemProxy)
{
systemProxySettingsManager.RestoreOriginalSettings();
}
}
foreach (var endPoint in ProxyEndPoints)
{
quitListen(endPoint);
}
ProxyEndPoints.Clear();
CertificateManager?.StopClearIdleCertificates();
tcpConnectionFactory.Dispose();
ProxyRunning = false;
}
/// <summary>
/// Listen on given end point of local machine.
/// </summary>
/// <param name="endPoint">The end point to listen.</param>
private void listen(ProxyEndPoint endPoint)
{
endPoint.Listener = new TcpListener(endPoint.IpAddress, endPoint.Port);
//linux/macOS has a bug with socket reuse in .net core.
if (ReuseSocket && (RunTime.IsWindows || RunTime.IsRunningOnMono))
{
endPoint.Listener.Server.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
}
try
{
endPoint.Listener.Start();
endPoint.Port = ((IPEndPoint)endPoint.Listener.LocalEndpoint).Port;
// accept clients asynchronously
endPoint.Listener.BeginAcceptTcpClient(onAcceptConnection, endPoint);
}
catch (SocketException ex)
{
var pex = new Exception(
$"Endpoint {endPoint} failed to start. Check inner exception and exception data for details.", ex);
pex.Data.Add("ipAddress", endPoint.IpAddress);
pex.Data.Add("port", endPoint.Port);
throw pex;
}
}
/// <summary>
/// Verify if its safe to set this end point as system proxy.
/// </summary>
/// <param name="endPoint">The end point to validate.</param>
private void validateEndPointAsSystemProxy(ExplicitProxyEndPoint endPoint)
{
if (endPoint == null)
{
throw new ArgumentNullException(nameof(endPoint));
}
if (ProxyEndPoints.Contains(endPoint) == false)
{
throw new Exception("Cannot set endPoints not added to proxy as system proxy");
}
if (!ProxyRunning)
{
throw new Exception("Cannot set system proxy settings before proxy has been started.");
}
}
/// <summary>
/// Gets the system up stream proxy.
/// </summary>
/// <param name="sessionEventArgs">The session.</param>
/// <returns>The external proxy as task result.</returns>
private Task<ExternalProxy> getSystemUpStreamProxy(SessionEventArgsBase sessionEventArgs)
{
var proxy = systemProxyResolver.GetProxy(sessionEventArgs.WebSession.Request.RequestUri);
return Task.FromResult(proxy);
}
/// <summary>
/// Act when a connection is received from client.
/// </summary>
private void onAcceptConnection(IAsyncResult asyn)
{
var endPoint = (ProxyEndPoint)asyn.AsyncState;
TcpClient tcpClient = null;
try
{
// based on end point type call appropriate request handlers
tcpClient = endPoint.Listener.EndAcceptTcpClient(asyn);
}
catch (ObjectDisposedException)
{
// The listener was Stop()'d, disposing the underlying socket and
// triggering the completion of the callback. We're already exiting,
// so just return.
return;
}
catch
{
// Other errors are discarded to keep proxy running
}
if (tcpClient != null)
{
Task.Run(async () => { await handleClient(tcpClient, endPoint); });
}
// Get the listener that handles the client request.
endPoint.Listener.BeginAcceptTcpClient(onAcceptConnection, endPoint);
}
/// <summary>
/// Handle the client.
/// </summary>
/// <param name="tcpClient">The client.</param>
/// <param name="endPoint">The proxy endpoint.</param>
/// <returns>The task.</returns>
private async Task handleClient(TcpClient tcpClient, ProxyEndPoint endPoint)
{
tcpClient.ReceiveTimeout = ConnectionTimeOutSeconds * 1000;
tcpClient.SendTimeout = ConnectionTimeOutSeconds * 1000;
tcpClient.SendBufferSize = BufferSize;
tcpClient.ReceiveBufferSize = BufferSize;
tcpClient.LingerState = new LingerOption(true, TcpTimeWaitSeconds);
await InvokeConnectionCreateEvent(tcpClient, true);
using (var clientConnection = new TcpClientConnection(this, tcpClient))
{
if (endPoint is TransparentProxyEndPoint tep)
{
await handleClient(tep, clientConnection);
}
else
{
await handleClient((ExplicitProxyEndPoint)endPoint, clientConnection);
}
}
}
/// <summary>
/// Handle exception.
/// </summary>
/// <param name="clientStream">The client stream.</param>
/// <param name="exception">The exception.</param>
private void onException(CustomBufferedStream clientStream, Exception exception)
{
#if DEBUG
if (clientStream is DebugCustomBufferedStream debugStream)
{
debugStream.LogException(exception);
}
#endif
ExceptionFunc(exception);
}
/// <summary>
/// Quit listening on the given end point.
/// </summary>
private void quitListen(ProxyEndPoint endPoint)
{
endPoint.Listener.Stop();
endPoint.Listener.Server.Dispose();
}
/// <summary>
/// Update client connection count.
/// </summary>
/// <param name="increment">Should we increment/decrement?</param>
internal void UpdateClientConnectionCount(bool increment)
{
if (increment)
{
Interlocked.Increment(ref clientConnectionCount);
}
else
{
Interlocked.Decrement(ref clientConnectionCount);
}
ClientConnectionCountChanged?.Invoke(this, EventArgs.Empty);
}
/// <summary>
/// Update server connection count.
/// </summary>
/// <param name="increment">Should we increment/decrement?</param>
internal void UpdateServerConnectionCount(bool increment)
{
if (increment)
{
Interlocked.Increment(ref serverConnectionCount);
}
else
{
Interlocked.Decrement(ref serverConnectionCount);
}
ServerConnectionCountChanged?.Invoke(this, EventArgs.Empty);
}
/// <summary>
/// Invoke client/server tcp connection events if subscribed by API user.
/// </summary>
/// <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>
/// <returns></returns>
internal async Task InvokeConnectionCreateEvent(TcpClient client, bool isClientConnection)
{
//client connection created
if (isClientConnection && OnClientConnectionCreate != null)
{
await OnClientConnectionCreate.InvokeAsync(this, client, ExceptionFunc);
}
//server connection created
if (!isClientConnection && OnServerConnectionCreate != null)
{
await OnServerConnectionCreate.InvokeAsync(this, client, ExceptionFunc);
}
}
/// <summary>
/// Connection retry policy when using connection pool.
/// </summary>
private RetryPolicy<T> retryPolicy<T>() where T : Exception
{
return new RetryPolicy<T>(retries, tcpConnectionFactory);
}
/// <summary>
/// Dispose the Proxy instance.
/// </summary>
public void Dispose()
{
if (ProxyRunning)
{
Stop();
}
CertificateManager?.Dispose();
BufferPool?.Dispose();
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
#if NETCOREAPP2_1
using System.Net.Security;
#endif
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using StreamExtended.Network;
using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Exceptions;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Network.Tcp;
namespace Titanium.Web.Proxy
{
/// <summary>
/// Handle the request
/// </summary>
public partial class ProxyServer
{
private static readonly Regex uriSchemeRegex =
new Regex("^[a-z]*://", RegexOptions.IgnoreCase | RegexOptions.Compiled);
private static readonly HashSet<string> proxySupportedCompressions =
new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
"gzip",
"deflate"
};
private bool isWindowsAuthenticationEnabledAndSupported =>
EnableWinAuth && RunTime.IsWindows && !RunTime.IsRunningOnMono;
/// <summary>
/// This is the core request handler method for a particular connection from client.
/// Will create new session (request/response) sequence until
/// client/server abruptly terminates connection or by normal HTTP termination.
/// </summary>
/// <param name="endPoint">The proxy endpoint.</param>
/// <param name="clientConnection">The client connection.</param>
/// <param name="clientStream">The client stream.</param>
/// <param name="clientStreamWriter">The client stream writer.</param>
/// <param name="cancellationTokenSource">The cancellation token source for this async task.</param>
/// <param name="httpsConnectHostname">
/// The https hostname as appeared in 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>
private async Task handleHttpSessionRequest(ProxyEndPoint endPoint, TcpClientConnection clientConnection,
CustomBufferedStream clientStream, HttpResponseWriter clientStreamWriter,
CancellationTokenSource cancellationTokenSource, string httpsConnectHostname, ConnectRequest connectRequest,
Task<TcpServerConnection> prefetchConnectionTask = null)
{
var prefetchTask = prefetchConnectionTask;
TcpServerConnection connection = null;
bool closeServerConnection = false;
try
{
var cancellationToken = cancellationTokenSource.Token;
// Loop through each subsequest request on this particular client connection
// (assuming HTTP connection is kept alive by client)
while (true)
{
// read the request line
string httpCmd = await clientStream.ReadLineAsync(cancellationToken);
if (string.IsNullOrEmpty(httpCmd))
{
return;
}
var args = new SessionEventArgs(this, endPoint, cancellationTokenSource)
{
ProxyClient = { ClientConnection = clientConnection },
WebSession = { ConnectRequest = connectRequest }
};
try
{
try
{
Request.ParseRequestLine(httpCmd, out string httpMethod, out string httpUrl,
out var version);
// Read the request headers in to unique and non-unique header collections
await HeaderParser.ReadHeaders(clientStream, args.WebSession.Request.Headers,
cancellationToken);
Uri httpRemoteUri;
if (uriSchemeRegex.IsMatch(httpUrl))
{
try
{
httpRemoteUri = new Uri(httpUrl);
}
catch (Exception ex)
{
throw new Exception($"Invalid URI: '{httpUrl}'", ex);
}
}
else
{
string host = args.WebSession.Request.Host ?? httpsConnectHostname;
string hostAndPath = host;
if (httpUrl.StartsWith("/"))
{
hostAndPath += httpUrl;
}
string url = string.Concat(httpsConnectHostname == null ? "http://" : "https://",
hostAndPath);
try
{
httpRemoteUri = new Uri(url);
}
catch (Exception ex)
{
throw new Exception($"Invalid URI: '{url}'", ex);
}
}
var request = args.WebSession.Request;
request.RequestUri = httpRemoteUri;
request.OriginalUrl = httpUrl;
request.Method = httpMethod;
request.HttpVersion = version;
args.ProxyClient.ClientStream = clientStream;
args.ProxyClient.ClientStreamWriter = clientStreamWriter;
if (!args.IsTransparent)
{
// proxy authorization check
if (httpsConnectHostname == null && await checkAuthorization(args) == false)
{
await invokeBeforeResponse(args);
// send the response
await clientStreamWriter.WriteResponseAsync(args.WebSession.Response,
cancellationToken: cancellationToken);
return;
}
prepareRequestHeaders(request.Headers);
request.Host = request.RequestUri.Authority;
}
// if win auth is enabled
// we need a cache of request body
// so that we can send it after authentication in WinAuthHandler.cs
if (isWindowsAuthenticationEnabledAndSupported && request.HasBody)
{
await args.GetRequestBody(cancellationToken);
}
//we need this to syphon out data from connection if API user changes them.
request.SetOriginalHeaders();
args.TimeLine["Request Received"] = DateTime.Now;
// If user requested interception do it
await invokeBeforeRequest(args);
var response = args.WebSession.Response;
if (request.CancelRequest)
{
// syphon out the request body from client before setting the new body
await args.SyphonOutBodyAsync(true, cancellationToken);
await handleHttpSessionResponse(args);
if (!response.KeepAlive)
{
return;
}
continue;
}
//If prefetch task is available.
if (connection == null && prefetchTask != null)
{
connection = await prefetchTask;
prefetchTask = null;
}
// create a new connection if cache key changes.
// only gets hit when connection pool is disabled.
// or when prefetch task has a unexpectedly different connection.
if (connection != null
&& (await tcpConnectionFactory.GetConnectionCacheKey(this, args,
clientConnection.NegotiatedApplicationProtocol)
!= connection.CacheKey))
{
await tcpConnectionFactory.Release(connection);
connection = null;
}
//a connection generator task with captured parameters via closure.
Func<Task<TcpServerConnection>> generator = () =>
tcpConnectionFactory.GetServerConnection(this, args, isConnect: false,
applicationProtocol:clientConnection.NegotiatedApplicationProtocol,
noCache: false, cancellationToken: cancellationToken);
//for connection pool, retry fails until cache is exhausted.
var result = await retryPolicy<ServerConnectionException>().ExecuteAsync(async (serverConnection) =>
{
args.TimeLine["Server Connection Created"] = DateTime.Now;
// if upgrading to websocket then relay the request without reading the contents
if (request.UpgradeToWebSocket)
{
await handleWebSocketUpgrade(httpCmd, args, request,
response, clientStream, clientStreamWriter,
serverConnection, cancellationTokenSource, cancellationToken);
closeServerConnection = true;
return false;
}
// construct the web request that we are going to issue on behalf of the client.
await handleHttpSessionRequestInternal(serverConnection, args);
return true;
}, generator, connection);
//update connection to latest used
connection = result.LatestConnection;
//throw if exception happened
if(!result.IsSuccess)
{
throw result.Exception;
}
if(!result.Continue)
{
return;
}
//user requested
if (args.WebSession.CloseServerConnection)
{
closeServerConnection = true;
return;
}
// if connection is closing exit
if (!response.KeepAlive)
{
closeServerConnection = true;
return;
}
if (cancellationTokenSource.IsCancellationRequested)
{
throw new Exception("Session was terminated by user.");
}
//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
//between sessions without using it.
//Do not release authenticated connections for performance reasons.
//Otherwise it will keep authenticating per session.
if (EnableConnectionPool && connection!=null
&& !connection.IsWinAuthenticated)
{
await tcpConnectionFactory.Release(connection);
connection = null;
}
}
catch (Exception e) when (!(e is ProxyHttpException))
{
throw new ProxyHttpException("Error occured whilst handling session request", e, args);
}
}
catch (Exception e)
{
args.Exception = e;
closeServerConnection = true;
throw;
}
finally
{
await invokeAfterResponse(args);
args.Dispose();
}
}
}
finally
{
await tcpConnectionFactory.Release(connection,
closeServerConnection);
await tcpConnectionFactory.Release(prefetchTask, closeServerConnection);
}
}
/// <summary>
/// Handle a specific session (request/response sequence)
/// </summary>
/// <param name="serverConnection">The tcp connection.</param>
/// <param name="args">The session event arguments.</param>
/// <returns></returns>
private async Task handleHttpSessionRequestInternal(TcpServerConnection serverConnection, SessionEventArgs args)
{
var cancellationToken = args.CancellationTokenSource.Token;
var request = args.WebSession.Request;
request.Locked = true;
var body = request.CompressBodyAndUpdateContentLength();
// if expect continue is enabled then send the headers first
// and see if server would return 100 conitinue
if (request.ExpectContinue)
{
args.WebSession.SetConnection(serverConnection);
await args.WebSession.SendRequest(Enable100ContinueBehaviour, args.IsTransparent,
cancellationToken);
}
// If 100 continue was the response inform that to the client
if (Enable100ContinueBehaviour)
{
var clientStreamWriter = args.ProxyClient.ClientStreamWriter;
if (request.Is100Continue)
{
await clientStreamWriter.WriteResponseStatusAsync(args.WebSession.Response.HttpVersion,
(int)HttpStatusCode.Continue, "Continue", cancellationToken);
await clientStreamWriter.WriteLineAsync(cancellationToken);
}
else if (request.ExpectationFailed)
{
await clientStreamWriter.WriteResponseStatusAsync(args.WebSession.Response.HttpVersion,
(int)HttpStatusCode.ExpectationFailed, "Expectation Failed", cancellationToken);
await clientStreamWriter.WriteLineAsync(cancellationToken);
}
}
// If expect continue is not enabled then set the connectio and send request headers
if (!request.ExpectContinue)
{
args.WebSession.SetConnection(serverConnection);
await args.WebSession.SendRequest(Enable100ContinueBehaviour, args.IsTransparent,
cancellationToken);
}
// check if content-length is > 0
if (request.ContentLength > 0)
{
if (request.IsBodyRead)
{
var writer = args.WebSession.ServerConnection.StreamWriter;
await writer.WriteBodyAsync(body, request.IsChunked, cancellationToken);
}
else
{
if (!request.ExpectationFailed)
{
if (request.HasBody)
{
HttpWriter writer = args.WebSession.ServerConnection.StreamWriter;
await args.CopyRequestBodyAsync(writer, TransformationMode.None, cancellationToken);
}
}
}
}
// If not expectation failed response was returned by server then parse response
if (!request.ExpectationFailed)
{
await handleHttpSessionResponse(args);
}
args.TimeLine["Response Sent"] = DateTime.Now;
}
/// <summary>
/// Prepare the request headers so that we can avoid encodings not parsable by this proxy
/// </summary>
private void prepareRequestHeaders(HeaderCollection requestHeaders)
{
string acceptEncoding = requestHeaders.GetHeaderValueOrNull(KnownHeaders.AcceptEncoding);
if (acceptEncoding != null)
{
var supportedAcceptEncoding = new List<string>();
//only allow proxy supported compressions
supportedAcceptEncoding.AddRange(acceptEncoding.Split(',')
.Select(x => x.Trim())
.Where(x => proxySupportedCompressions.Contains(x)));
//uncompressed is always supported by proxy
supportedAcceptEncoding.Add("identity");
requestHeaders.SetOrAddHeaderValue(KnownHeaders.AcceptEncoding,
string.Join(",", supportedAcceptEncoding));
}
requestHeaders.FixProxyHeaders();
}
/// <summary>
/// Invoke before request handler if it is set.
/// </summary>
/// <param name="args">The session event arguments.</param>
/// <returns></returns>
private async Task invokeBeforeRequest(SessionEventArgs args)
{
if (BeforeRequest != null)
{
await BeforeRequest.InvokeAsync(this, args, ExceptionFunc);
}
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
#if NETCOREAPP2_1
using System.Net.Security;
#endif
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using StreamExtended.Network;
using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Exceptions;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Network.Tcp;
using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy
{
/// <summary>
/// Handle the request
/// </summary>
public partial class ProxyServer
{
private bool isWindowsAuthenticationEnabledAndSupported =>
EnableWinAuth && RunTime.IsWindows && !RunTime.IsRunningOnMono;
/// <summary>
/// This is the core request handler method for a particular connection from client.
/// Will create new session (request/response) sequence until
/// client/server abruptly terminates connection or by normal HTTP termination.
/// </summary>
/// <param name="endPoint">The proxy endpoint.</param>
/// <param name="clientConnection">The client connection.</param>
/// <param name="clientStream">The client stream.</param>
/// <param name="clientStreamWriter">The client stream writer.</param>
/// <param name="cancellationTokenSource">The cancellation token source for this async task.</param>
/// <param name="httpsConnectHostname">
/// The https hostname as appeared in 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>
private async Task handleHttpSessionRequest(ProxyEndPoint endPoint, TcpClientConnection clientConnection,
CustomBufferedStream clientStream, HttpResponseWriter clientStreamWriter,
CancellationTokenSource cancellationTokenSource, string httpsConnectHostname, ConnectRequest connectRequest,
Task<TcpServerConnection> prefetchConnectionTask = null)
{
var prefetchTask = prefetchConnectionTask;
TcpServerConnection connection = null;
bool closeServerConnection = false;
try
{
var cancellationToken = cancellationTokenSource.Token;
// Loop through each subsequest request on this particular client connection
// (assuming HTTP connection is kept alive by client)
while (true)
{
// read the request line
string httpCmd = await clientStream.ReadLineAsync(cancellationToken);
if (string.IsNullOrEmpty(httpCmd))
{
return;
}
var args = new SessionEventArgs(this, endPoint, cancellationTokenSource)
{
ProxyClient = { ClientConnection = clientConnection },
WebSession = { ConnectRequest = connectRequest }
};
try
{
try
{
Request.ParseRequestLine(httpCmd, out string httpMethod, out string httpUrl,
out var version);
// Read the request headers in to unique and non-unique header collections
await HeaderParser.ReadHeaders(clientStream, args.WebSession.Request.Headers,
cancellationToken);
Uri httpRemoteUri;
if (ProxyConstants.UriSchemeRegex.IsMatch(httpUrl))
{
try
{
httpRemoteUri = new Uri(httpUrl);
}
catch (Exception ex)
{
throw new Exception($"Invalid URI: '{httpUrl}'", ex);
}
}
else
{
string host = args.WebSession.Request.Host ?? httpsConnectHostname;
string hostAndPath = host;
if (httpUrl.StartsWith("/"))
{
hostAndPath += httpUrl;
}
string url = string.Concat(httpsConnectHostname == null ? "http://" : "https://",
hostAndPath);
try
{
httpRemoteUri = new Uri(url);
}
catch (Exception ex)
{
throw new Exception($"Invalid URI: '{url}'", ex);
}
}
var request = args.WebSession.Request;
request.RequestUri = httpRemoteUri;
request.OriginalUrl = httpUrl;
request.Method = httpMethod;
request.HttpVersion = version;
args.ProxyClient.ClientStream = clientStream;
args.ProxyClient.ClientStreamWriter = clientStreamWriter;
if (!args.IsTransparent)
{
// proxy authorization check
if (httpsConnectHostname == null && await checkAuthorization(args) == false)
{
await invokeBeforeResponse(args);
// send the response
await clientStreamWriter.WriteResponseAsync(args.WebSession.Response,
cancellationToken: cancellationToken);
return;
}
prepareRequestHeaders(request.Headers);
request.Host = request.RequestUri.Authority;
}
// if win auth is enabled
// we need a cache of request body
// so that we can send it after authentication in WinAuthHandler.cs
if (isWindowsAuthenticationEnabledAndSupported && request.HasBody)
{
await args.GetRequestBody(cancellationToken);
}
//we need this to syphon out data from connection if API user changes them.
request.SetOriginalHeaders();
args.TimeLine["Request Received"] = DateTime.Now;
// If user requested interception do it
await invokeBeforeRequest(args);
var response = args.WebSession.Response;
if (request.CancelRequest)
{
// syphon out the request body from client before setting the new body
await args.SyphonOutBodyAsync(true, cancellationToken);
await handleHttpSessionResponse(args);
if (!response.KeepAlive)
{
return;
}
continue;
}
//If prefetch task is available.
if (connection == null && prefetchTask != null)
{
connection = await prefetchTask;
prefetchTask = null;
}
// create a new connection if cache key changes.
// only gets hit when connection pool is disabled.
// or when prefetch task has a unexpectedly different connection.
if (connection != null
&& (await tcpConnectionFactory.GetConnectionCacheKey(this, args,
clientConnection.NegotiatedApplicationProtocol)
!= connection.CacheKey))
{
await tcpConnectionFactory.Release(connection);
connection = null;
}
//a connection generator task with captured parameters via closure.
Func<Task<TcpServerConnection>> generator = () =>
tcpConnectionFactory.GetServerConnection(this, args, isConnect: false,
applicationProtocol:clientConnection.NegotiatedApplicationProtocol,
noCache: false, cancellationToken: cancellationToken);
//for connection pool, retry fails until cache is exhausted.
var result = await retryPolicy<ServerConnectionException>().ExecuteAsync(async (serverConnection) =>
{
args.TimeLine["Server Connection Created"] = DateTime.Now;
// if upgrading to websocket then relay the request without reading the contents
if (request.UpgradeToWebSocket)
{
await handleWebSocketUpgrade(httpCmd, args, request,
response, clientStream, clientStreamWriter,
serverConnection, cancellationTokenSource, cancellationToken);
closeServerConnection = true;
return false;
}
// construct the web request that we are going to issue on behalf of the client.
await handleHttpSessionRequest(serverConnection, args);
return true;
}, generator, connection);
//update connection to latest used
connection = result.LatestConnection;
//throw if exception happened
if(!result.IsSuccess)
{
throw result.Exception;
}
if(!result.Continue)
{
return;
}
//user requested
if (args.WebSession.CloseServerConnection)
{
closeServerConnection = true;
return;
}
// if connection is closing exit
if (!response.KeepAlive)
{
closeServerConnection = true;
return;
}
if (cancellationTokenSource.IsCancellationRequested)
{
throw new Exception("Session was terminated by user.");
}
//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
//between sessions without using it.
//Do not release authenticated connections for performance reasons.
//Otherwise it will keep authenticating per session.
if (EnableConnectionPool && connection!=null
&& !connection.IsWinAuthenticated)
{
await tcpConnectionFactory.Release(connection);
connection = null;
}
}
catch (Exception e) when (!(e is ProxyHttpException))
{
throw new ProxyHttpException("Error occured whilst handling session request", e, args);
}
}
catch (Exception e)
{
args.Exception = e;
closeServerConnection = true;
throw;
}
finally
{
await invokeAfterResponse(args);
args.Dispose();
}
}
}
finally
{
await tcpConnectionFactory.Release(connection,
closeServerConnection);
await tcpConnectionFactory.Release(prefetchTask, closeServerConnection);
}
}
/// <summary>
/// Handle a specific session (request/response sequence)
/// </summary>
/// <param name="serverConnection">The tcp connection.</param>
/// <param name="args">The session event arguments.</param>
/// <returns></returns>
private async Task handleHttpSessionRequest(TcpServerConnection serverConnection, SessionEventArgs args)
{
var cancellationToken = args.CancellationTokenSource.Token;
var request = args.WebSession.Request;
request.Locked = true;
var body = request.CompressBodyAndUpdateContentLength();
// if expect continue is enabled then send the headers first
// and see if server would return 100 conitinue
if (request.ExpectContinue)
{
args.WebSession.SetConnection(serverConnection);
await args.WebSession.SendRequest(Enable100ContinueBehaviour, args.IsTransparent,
cancellationToken);
}
// If 100 continue was the response inform that to the client
if (Enable100ContinueBehaviour)
{
var clientStreamWriter = args.ProxyClient.ClientStreamWriter;
if (request.Is100Continue)
{
await clientStreamWriter.WriteResponseStatusAsync(args.WebSession.Response.HttpVersion,
(int)HttpStatusCode.Continue, "Continue", cancellationToken);
await clientStreamWriter.WriteLineAsync(cancellationToken);
}
else if (request.ExpectationFailed)
{
await clientStreamWriter.WriteResponseStatusAsync(args.WebSession.Response.HttpVersion,
(int)HttpStatusCode.ExpectationFailed, "Expectation Failed", cancellationToken);
await clientStreamWriter.WriteLineAsync(cancellationToken);
}
}
// If expect continue is not enabled then set the connectio and send request headers
if (!request.ExpectContinue)
{
args.WebSession.SetConnection(serverConnection);
await args.WebSession.SendRequest(Enable100ContinueBehaviour, args.IsTransparent,
cancellationToken);
}
// check if content-length is > 0
if (request.ContentLength > 0)
{
if (request.IsBodyRead)
{
var writer = args.WebSession.ServerConnection.StreamWriter;
await writer.WriteBodyAsync(body, request.IsChunked, cancellationToken);
}
else
{
if (!request.ExpectationFailed)
{
if (request.HasBody)
{
HttpWriter writer = args.WebSession.ServerConnection.StreamWriter;
await args.CopyRequestBodyAsync(writer, TransformationMode.None, cancellationToken);
}
}
}
}
// If not expectation failed response was returned by server then parse response
if (!request.ExpectationFailed)
{
await handleHttpSessionResponse(args);
}
args.TimeLine["Response Sent"] = DateTime.Now;
}
/// <summary>
/// Prepare the request headers so that we can avoid encodings not parsable by this proxy
/// </summary>
private void prepareRequestHeaders(HeaderCollection requestHeaders)
{
string acceptEncoding = requestHeaders.GetHeaderValueOrNull(KnownHeaders.AcceptEncoding);
if (acceptEncoding != null)
{
var supportedAcceptEncoding = new List<string>();
//only allow proxy supported compressions
supportedAcceptEncoding.AddRange(acceptEncoding.Split(',')
.Select(x => x.Trim())
.Where(x => ProxyConstants.ProxySupportedCompressions.Contains(x)));
//uncompressed is always supported by proxy
supportedAcceptEncoding.Add("identity");
requestHeaders.SetOrAddHeaderValue(KnownHeaders.AcceptEncoding,
string.Join(",", supportedAcceptEncoding));
}
requestHeaders.FixProxyHeaders();
}
/// <summary>
/// Invoke before request handler if it is set.
/// </summary>
/// <param name="args">The session event arguments.</param>
/// <returns></returns>
private async Task invokeBeforeRequest(SessionEventArgs args)
{
if (BeforeRequest != null)
{
await BeforeRequest.InvokeAsync(this, args, ExceptionFunc);
}
}
}
}
using System;
using System.Net;
using System.Threading.Tasks;
using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Network.WinAuth.Security;
namespace Titanium.Web.Proxy
{
/// <summary>
/// Handle the response from server.
/// </summary>
public partial class ProxyServer
{
/// <summary>
/// Called asynchronously when a request was successfull and we received the response.
/// </summary>
/// <param name="args">The session event arguments.</param>
/// <returns> The task.</returns>
private async Task handleHttpSessionResponse(SessionEventArgs args)
{
var cancellationToken = args.CancellationTokenSource.Token;
// read response & headers from server
await args.WebSession.ReceiveResponse(cancellationToken);
args.TimeLine["Response Received"] = DateTime.Now;
var response = args.WebSession.Response;
args.ReRequest = false;
// check for windows authentication
if (isWindowsAuthenticationEnabledAndSupported)
{
if (response.StatusCode == (int)HttpStatusCode.Unauthorized)
{
await handle401UnAuthorized(args);
}
else
{
WinAuthEndPoint.AuthenticatedResponse(args.WebSession.Data);
}
}
//save original values so that if user changes them
//we can still use original values when syphoning out data from attached tcp connection.
response.SetOriginalHeaders();
// if user requested call back then do it
if (!response.Locked)
{
await invokeBeforeResponse(args);
}
// it may changed in the user event
response = args.WebSession.Response;
var clientStreamWriter = args.ProxyClient.ClientStreamWriter;
//user set custom response by ignoring original response from server.
if (response.Locked)
{
//write custom user response with body and return.
await clientStreamWriter.WriteResponseAsync(response, cancellationToken: cancellationToken);
if(args.WebSession.ServerConnection != null
&& !args.WebSession.CloseServerConnection)
{
// syphon out the original response body from server connection
// so that connection will be good to be reused.
await args.SyphonOutBodyAsync(false, cancellationToken);
}
return;
}
// if user requested to send request again
// likely after making modifications from User Response Handler
if (args.ReRequest)
{
// clear current response
await args.ClearResponse(cancellationToken);
await handleHttpSessionRequestInternal(args.WebSession.ServerConnection, args);
return;
}
response.Locked = true;
// Write back to client 100-conitinue response if that's what server returned
if (response.Is100Continue)
{
await clientStreamWriter.WriteResponseStatusAsync(response.HttpVersion,
(int)HttpStatusCode.Continue, "Continue", cancellationToken);
await clientStreamWriter.WriteLineAsync(cancellationToken);
}
else if (response.ExpectationFailed)
{
await clientStreamWriter.WriteResponseStatusAsync(response.HttpVersion,
(int)HttpStatusCode.ExpectationFailed, "Expectation Failed", cancellationToken);
await clientStreamWriter.WriteLineAsync(cancellationToken);
}
if (!args.IsTransparent)
{
response.Headers.FixProxyHeaders();
}
if (response.IsBodyRead)
{
await clientStreamWriter.WriteResponseAsync(response, cancellationToken: cancellationToken);
}
else
{
// Write back response status to client
await clientStreamWriter.WriteResponseStatusAsync(response.HttpVersion, response.StatusCode,
response.StatusDescription, cancellationToken);
await clientStreamWriter.WriteHeadersAsync(response.Headers, cancellationToken: cancellationToken);
// Write body if exists
if (response.HasBody)
{
await args.CopyResponseBodyAsync(clientStreamWriter, TransformationMode.None,
cancellationToken);
}
}
}
/// <summary>
/// Invoke before response if it is set.
/// </summary>
/// <param name="args"></param>
/// <returns></returns>
private async Task invokeBeforeResponse(SessionEventArgs args)
{
if (BeforeResponse != null)
{
await BeforeResponse.InvokeAsync(this, args, ExceptionFunc);
}
}
/// <summary>
/// Invoke after response if it is set.
/// </summary>
/// <param name="args"></param>
/// <returns></returns>
private async Task invokeAfterResponse(SessionEventArgs args)
{
if (AfterResponse != null)
{
await AfterResponse.InvokeAsync(this, args, ExceptionFunc);
}
}
}
}
using System;
using System.Net;
using System.Threading.Tasks;
using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Network.WinAuth.Security;
namespace Titanium.Web.Proxy
{
/// <summary>
/// Handle the response from server.
/// </summary>
public partial class ProxyServer
{
/// <summary>
/// Called asynchronously when a request was successfull and we received the response.
/// </summary>
/// <param name="args">The session event arguments.</param>
/// <returns> The task.</returns>
private async Task handleHttpSessionResponse(SessionEventArgs args)
{
var cancellationToken = args.CancellationTokenSource.Token;
// read response & headers from server
await args.WebSession.ReceiveResponse(cancellationToken);
args.TimeLine["Response Received"] = DateTime.Now;
var response = args.WebSession.Response;
args.ReRequest = false;
// check for windows authentication
if (isWindowsAuthenticationEnabledAndSupported)
{
if (response.StatusCode == (int)HttpStatusCode.Unauthorized)
{
await handle401UnAuthorized(args);
}
else
{
WinAuthEndPoint.AuthenticatedResponse(args.WebSession.Data);
}
}
//save original values so that if user changes them
//we can still use original values when syphoning out data from attached tcp connection.
response.SetOriginalHeaders();
// if user requested call back then do it
if (!response.Locked)
{
await invokeBeforeResponse(args);
}
// it may changed in the user event
response = args.WebSession.Response;
var clientStreamWriter = args.ProxyClient.ClientStreamWriter;
//user set custom response by ignoring original response from server.
if (response.Locked)
{
//write custom user response with body and return.
await clientStreamWriter.WriteResponseAsync(response, cancellationToken: cancellationToken);
if(args.WebSession.ServerConnection != null
&& !args.WebSession.CloseServerConnection)
{
// syphon out the original response body from server connection
// so that connection will be good to be reused.
await args.SyphonOutBodyAsync(false, cancellationToken);
}
return;
}
// if user requested to send request again
// likely after making modifications from User Response Handler
if (args.ReRequest)
{
// clear current response
await args.ClearResponse(cancellationToken);
await handleHttpSessionRequest(args.WebSession.ServerConnection, args);
return;
}
response.Locked = true;
// Write back to client 100-conitinue response if that's what server returned
if (response.Is100Continue)
{
await clientStreamWriter.WriteResponseStatusAsync(response.HttpVersion,
(int)HttpStatusCode.Continue, "Continue", cancellationToken);
await clientStreamWriter.WriteLineAsync(cancellationToken);
}
else if (response.ExpectationFailed)
{
await clientStreamWriter.WriteResponseStatusAsync(response.HttpVersion,
(int)HttpStatusCode.ExpectationFailed, "Expectation Failed", cancellationToken);
await clientStreamWriter.WriteLineAsync(cancellationToken);
}
if (!args.IsTransparent)
{
response.Headers.FixProxyHeaders();
}
if (response.IsBodyRead)
{
await clientStreamWriter.WriteResponseAsync(response, cancellationToken: cancellationToken);
}
else
{
// Write back response status to client
await clientStreamWriter.WriteResponseStatusAsync(response.HttpVersion, response.StatusCode,
response.StatusDescription, cancellationToken);
await clientStreamWriter.WriteHeadersAsync(response.Headers, cancellationToken: cancellationToken);
// Write body if exists
if (response.HasBody)
{
await args.CopyResponseBodyAsync(clientStreamWriter, TransformationMode.None,
cancellationToken);
}
}
}
/// <summary>
/// Invoke before response if it is set.
/// </summary>
/// <param name="args"></param>
/// <returns></returns>
private async Task invokeBeforeResponse(SessionEventArgs args)
{
if (BeforeResponse != null)
{
await BeforeResponse.InvokeAsync(this, args, ExceptionFunc);
}
}
/// <summary>
/// Invoke after response if it is set.
/// </summary>
/// <param name="args"></param>
/// <returns></returns>
private async Task invokeAfterResponse(SessionEventArgs args)
{
if (AfterResponse != null)
{
await AfterResponse.InvokeAsync(this, args, ExceptionFunc);
}
}
}
}
using System.Text.RegularExpressions;
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
namespace Titanium.Web.Proxy.Shared
{
......@@ -14,9 +16,20 @@ namespace Titanium.Web.Proxy.Shared
internal static readonly char[] SemiColonSplit = { ';' };
internal static readonly char[] EqualSplit = { '=' };
internal static readonly byte[] NewLine = { (byte)'\r', (byte)'\n' };
internal static readonly string NewLine = "\r\n";
internal static readonly byte[] NewLineBytes = { (byte)'\r', (byte)'\n' };
public static readonly Regex CNRemoverRegex =
internal static readonly Regex UriSchemeRegex =
new Regex("^[a-z]*://", RegexOptions.IgnoreCase | RegexOptions.Compiled);
internal static readonly HashSet<string> ProxySupportedCompressions =
new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
"gzip",
"deflate"
};
internal static readonly Regex CNRemoverRegex =
new Regex(@"^CN\s*=\s*", RegexOptions.IgnoreCase | RegexOptions.Compiled);
}
}
<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">
<PropertyGroup>
<TargetFrameworks>net45;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 Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net45;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>
<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)' == '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
......@@ -63,13 +63,16 @@ namespace Titanium.Web.Proxy
if (endPoint.DecryptSsl && args.DecryptSsl)
{
//don't pass cancellation token here
//it could cause floating server connections when client exits
prefetchConnectionTask = tcpConnectionFactory.GetServerConnection(httpsHostName, endPoint.Port,
httpVersion: null, isHttps: true, applicationProtocols: null, isConnect: false,
proxyServer: this, upStreamEndPoint: UpStreamEndPoint, externalProxy: UpStreamHttpsProxy,
noCache: false, cancellationToken: CancellationToken.None);
if(EnableTcpServerConnectionPrefetch)
{
//don't pass cancellation token here
//it could cause floating server connections when client exits
prefetchConnectionTask = tcpConnectionFactory.GetServerConnection(httpsHostName, endPoint.Port,
httpVersion: null, isHttps: true, applicationProtocols: null, isConnect: false,
proxyServer: this, upStreamEndPoint: UpStreamEndPoint, externalProxy: UpStreamHttpsProxy,
noCache: false, cancellationToken: CancellationToken.None);
}
SslStream sslStream = null;
//do client authentication using fake certificate
......
......@@ -35,6 +35,24 @@
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</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>
<Reference Include="System" />
<Reference Include="System.Net.Http" />
......@@ -56,8 +74,8 @@
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Titanium.Web.Proxy\Titanium.Web.Proxy.csproj">
<Project>{8d73a1be-868c-42d2-9ece-f32cc1a02906}</Project>
<ProjectReference Include="..\..\src\Titanium.Web.Proxy\Titanium.Web.Proxy.csproj">
<Project>{91018b6d-a7a9-45be-9cb3-79cbb8b169a6}</Project>
<Name>Titanium.Web.Proxy</Name>
</ProjectReference>
</ItemGroup>
......
......@@ -42,6 +42,25 @@
<PropertyGroup>
<AssemblyOriginatorKeyFile>StrongNameKey.snk</AssemblyOriginatorKeyFile>
</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>
<Reference Include="System" />
</ItemGroup>
......@@ -65,13 +84,13 @@
<Compile Include="WinAuthTests.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Titanium.Web.Proxy\Titanium.Web.Proxy.csproj">
<Project>{8d73a1be-868c-42d2-9ece-f32cc1a02906}</Project>
<Name>Titanium.Web.Proxy</Name>
</ProjectReference>
<None Include="StrongNameKey.snk" />
</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>
<Choose>
<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